├── .gitignore ├── JupyterCON2017_Title.pptx ├── LICENSE ├── README.rst ├── data ├── eclipses_21C.csv ├── macro.csv └── mri.nc ├── datasets.yml ├── download_sample_data.py ├── environment.yml ├── images ├── .DS_Store ├── dask.svg ├── datashader.png ├── holoviews.png ├── numba.jpg └── xarray.png ├── notebooks ├── 00-welcome.ipynb ├── 01-workflow-introduction.ipynb ├── 02-annotating-data.ipynb ├── 03-customizing-visual-appearance.ipynb ├── 04-exploration-with-containers.ipynb ├── 05-working-with-tabular-data.ipynb ├── 06-working-with-gridded-data.ipynb ├── 07-custom-interactivity.ipynb ├── 08-operations-and-pipelines.ipynb ├── 09-working-with-large-datasets.ipynb ├── 10-parameters-and-widgets.ipynb ├── 11-deploying-bokeh-apps.ipynb ├── README.md ├── apps │ ├── nyc_taxi │ │ ├── main.py │ │ └── templates │ │ │ ├── index.html │ │ │ └── styles.css │ ├── periodic_app.py │ ├── player_app.py │ └── server_app.py ├── assets │ ├── bokeh.png │ ├── bokeh_logo.svg │ ├── dask.png │ ├── dask.svg │ ├── datashader.png │ ├── datashader_pipeline.png │ ├── geoviews.png │ ├── header_logo.png │ ├── holoviews.png │ ├── hv+bk.png │ ├── matplotlib_logo.png │ ├── numba.png │ ├── tutorial_app.gif │ └── xarray.png └── nyc_taxi_makeparq.ipynb └── solutions ├── 00-welcome-with-solutions.ipynb ├── 01-introduction-to-elements-with-solutions.ipynb ├── 02-customizing-visual-appearance-with-solutions.ipynb ├── 03-exploration-with-containers-with-solutions.ipynb ├── 04-working-with-tabular-data-with-solutions.ipynb ├── 05-working-with-gridded-data-with-solutions.ipynb ├── 06-custom-interactivity-with-solutions.ipynb ├── 07-working-with-large-datasets-with-solutions.ipynb ├── 08-deploying-bokeh-apps-with-solutions.ipynb ├── README.md ├── apps ├── periodic_app.py ├── player_app.py ├── player_app_with_solutions.py ├── server_app.py └── server_app_with_solutions.py └── assets ├── bokeh_logo.svg ├── dask.png ├── dask.svg ├── datashader.png ├── datashader_pipeline.png ├── header_logo.png ├── holoviews.png ├── hv+bk.png ├── matplotlib_logo.png ├── numba.png ├── tutorial_app.gif └── xarray.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /JupyterCON2017_Title.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/JupyterCON2017_Title.pptx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, IOAM 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | jupytercon2017-holoviews-tutorial 2 | ============================= 3 | 4 | HoloViews+Bokeh Viz to Dashboards Tutorial at Jupytercon 2017 5 | 6 | This document explains how to get your computer set up for the 7 | tutorial, including how to install the software libraries and data 8 | files that we will be working with. Because some of the data files 9 | are large, it's best to run through these steps *before* you leave on 10 | your trip, using a good internet connection. 11 | 12 | 13 | Step 1: Clone the `jupytercon-2017-holoviews-tutorial `_ repository 14 | ----------------------------------------------------------------- 15 | 16 | - Any Linux, Mac OS X, or Windows computer with a web browser should work. We recommend Chrome, but typically also test Firefox and Safari. 17 | - 16GB of RAM is required for some of the examples, but most will run fine in 4GB. 18 | - Clone this repository, e.g. using ``git clone https://github.com/ioam/jupytercon2017-holoviews-tutorial.git`` 19 | - Open a terminal window inside the repository. 20 | 21 | 22 | 23 | You should plan to do a "git pull" on your clone of this repository 24 | sometime in the 48 hours before the tutorial, in case we need to make any fixes or 25 | improvements in the meantime. 26 | 27 | 28 | Step 2: Create a conda environment from ``environment.yml`` 29 | ----------------------------------------------------------- 30 | 31 | The easiest way to get an environment set up for the tutorial is 32 | installing it using the ``environment.yml`` we have provided. If you 33 | don't already have it, install `conda `_, 34 | and then create the ``hvtutorial`` environment by executing:: 35 | 36 | > conda env create --force -f environment.yml 37 | 38 | When installation is complete you may activate the environment by writing:: 39 | 40 | > activate hvtutorial 41 | 42 | (for Windows) or:: 43 | 44 | $ source activate hvtutorial 45 | 46 | (for Linux and Mac). 47 | 48 | Later, when you are ready to exit the environment after the tutorial, you can type:: 49 | 50 | > deactivate 51 | 52 | If for some reason you want to remove the environment entirely, you can do so by writing:: 53 | 54 | > conda env remove --name hvtutorial 55 | 56 | 57 | Step 3: Downloading the sample data 58 | --------------------------- 59 | 60 | In this tutorial we will be showing you how to work with some fairly 61 | large datasets. Unfortunately, that also means that you have to 62 | download this data. To make this as easy as possible we have provided 63 | a script that will download the data for you. Simply execute in the 64 | root of your clone of this repository:: 65 | 66 | > python download_sample_data.py 67 | 68 | 69 | Step 4: Launch Jupyter Notebook 70 | ------------------------------- 71 | 72 | You can then launch the notebook server and client:: 73 | 74 | (hvtutorial)> cd notebooks 75 | (hvtutorial)> jupyter notebook --NotebookApp.iopub_data_rate_limit=100000000 76 | 77 | A browser window with a Jupyter Notebook instance should now open, letting 78 | you select and execute each notebook. (Increasing the rate limit in 79 | this way is required for the current 5.0 Jupyter version, but should 80 | not be needed in earlier or later Jupyter releases.) 81 | 82 | If you don't see the notebook appear (e.g. on some OS X versions), 83 | you'll need to cut and paste the URL from the console output manually. 84 | 85 | 86 | Step 5: Test that everything is working 87 | --------------------------------------- 88 | 89 | You can see if everything has installed correctly by selecting the 90 | ``00-welcome.ipynb`` notebook and doing "Cell/Run All" in the menus. 91 | There may be warnings on some platforms, but you'll know it is working 92 | if you see the HoloViews logo after it runs ``hv.extension()``. 93 | 94 | 95 | 96 | Preparing for the Tutorial 97 | -------------------------- 98 | 99 | If you want to get familiar with HoloViews before the tutorial (which 100 | is not a requirement), you can have a look at our new website at 101 | `holoviews.org `_, browsing through the getting 102 | started and user guides. If you want to run these examples yourself, 103 | you can get ahold of them by typing this command inside your conda 104 | environment:: 105 | 106 | (hvtutorial)> holoviews --install-examples 107 | (hvtutorial)> cd holoviews-examples 108 | 109 | You should then be inside a new folder named "holoviews-examples" in 110 | your current directory. Now launch a Jupyter notebook server and dive 111 | into the examples:: 112 | 113 | (hvtutorial)> jupyter notebook --NotebookApp.iopub_data_rate_limit=100000000 114 | -------------------------------------------------------------------------------- /data/macro.csv: -------------------------------------------------------------------------------- 1 | country,year,growth,unem,capmob,trade 2 | United States,1966,5.1111407,3.8,0,9.622906 3 | United States,1967,2.2772829,3.8,0,9.983546 4 | United States,1968,4.7,3.6,0,10.08912 5 | United States,1969,2.8,3.5,0,10.43593 6 | United States,1970,-0.2,4.9,0,10.49535 7 | United States,1971,3.1,5.9,0,11.27827 8 | United States,1972,5.4,5.6,0,11.21771 9 | United States,1973,5.7,4.9,0,11.76705 10 | United States,1974,-0.9,5.6,0,13.77255 11 | United States,1975,-0.8,8.5,0,17.42326 12 | United States,1976,4.7,7.7,0,16.52211 13 | United States,1977,5.5,7.1,0,17.234920000000002 14 | United States,1978,4.7,6.1,0,17.54099 15 | United States,1979,2.6,5.8,0,18.17591 16 | United States,1980,-0.4,7.1,0,19.73285 17 | United States,1981,3.4,7.5,0,21.51057 18 | United States,1982,-3.0,9.5,0,20.53895 19 | United States,1983,2.9,9.5,0,18.56972 20 | United States,1984,7.2,7.5,0,17.81588 21 | United States,1985,3.8,7.1,0,18.02899 22 | United States,1986,2.8,7.0,0,17.20371 23 | United States,1987,3.7,6.2,0,17.23095 24 | United States,1988,4.6,5.5,0,18.29418 25 | United States,1989,2.8,5.2700837,0,19.413526 26 | United States,1990,0.9,5.4145596,0,20.638364000000006 27 | Canada,1966,6.802167599999999,3.6,0,38.45467 28 | Canada,1967,2.9236458,4.1,0,40.16167 29 | Canada,1968,5.6,4.8,0,41.06574000000001 30 | Canada,1969,5.2,4.7,0,42.76849 31 | Canada,1970,2.6,5.9,0,44.16533 32 | Canada,1971,7.0,6.4,0,43.8499 33 | Canada,1972,5.8,6.3,0,42.67813 34 | Canada,1973,7.5,5.6,0,43.88472 35 | Canada,1974,3.5,5.4,0,46.41543 36 | Canada,1975,1.1,6.9,0,50.49331 37 | Canada,1976,6.1,7.1,0,48.16162 38 | Canada,1977,2.2,8.1,0,46.12166 39 | Canada,1978,3.9,8.4,0,48.00311 40 | Canada,1979,3.4,7.5,0,51.22656 41 | Canada,1980,1.0,7.5,0,54.93203000000001 42 | Canada,1981,4.0,7.6,0,55.77161 43 | Canada,1982,-4.3,11.0,0,54.325 44 | Canada,1983,2.8,11.9,0,48.58355 45 | Canada,1984,5.4,11.3,0,48.40208 46 | Canada,1985,4.7,10.5,0,53.52345 47 | Canada,1986,3.1,9.6,0,54.3082 48 | Canada,1987,4.5,8.9,0,53.4611 49 | Canada,1988,5.0,7.8,0,52.15683 50 | Canada,1989,2.4,7.4952143,0,52.077395 51 | Canada,1990,0.4,8.059593,0,49.932476 52 | United Kingdom,1966,1.8770983,1.5,-1,37.9319 53 | United Kingdom,1967,2.2550651000000004,2.3,-1,37.83032 54 | United Kingdom,1968,4.1,2.5,-1,37.76296 55 | United Kingdom,1969,1.5,2.4,-2,41.92546 56 | United Kingdom,1970,2.2,2.6,-2,42.79789 57 | United Kingdom,1971,2.7,3.5,-1,44.08434000000001 58 | United Kingdom,1972,2.3,3.8,-1,43.51069 59 | United Kingdom,1973,7.6,2.7,-1,42.86721 60 | United Kingdom,1974,-1.0,2.6,-1,48.8228 61 | United Kingdom,1975,-0.7,4.0,-1,59.62837 62 | United Kingdom,1976,3.8,5.5,-1,52.51037 63 | United Kingdom,1977,1.0,5.8,-1,56.87488000000001 64 | United Kingdom,1978,3.6,5.7,-1,59.06499 65 | United Kingdom,1979,2.2,5.3,-1,55.53134 66 | United Kingdom,1980,-2.3,7.0,0,55.78893000000001 67 | United Kingdom,1981,-1.4,10.5,0,52.5135 68 | United Kingdom,1982,1.5,11.2,0,50.54586 69 | United Kingdom,1983,3.4,11.7,0,51.03159 70 | United Kingdom,1984,1.8,11.7,0,52.77322 71 | United Kingdom,1985,3.7,11.2,0,58.07664000000001 72 | United Kingdom,1986,3.5,11.2,0,57.58285 73 | United Kingdom,1987,4.7,10.2,0,53.52335 74 | United Kingdom,1988,4.1,8.3,0,52.65015 75 | United Kingdom,1989,2.3,6.1140732,0,50.248182 76 | United Kingdom,1990,0.8,5.4717446,0,52.35597 77 | Netherlands,1966,1.8770983,1.0,-2,87.05895 78 | Netherlands,1967,2.2550651000000004,2.0,-2,85.44972 79 | Netherlands,1968,2.3,1.9,-3,82.56051 80 | Netherlands,1969,6.4,1.4,-2,82.63105999999998 81 | Netherlands,1970,6.7,1.1,-2,86.03879 82 | Netherlands,1971,4.2,1.6,-2,91.87628 83 | Netherlands,1972,3.3,2.8,-1,91.26911 84 | Netherlands,1973,4.7,2.8,-1,87.19008000000002 85 | Netherlands,1974,4.0,3.5,-1,91.15795 86 | Netherlands,1975,-0.1,5.3,-1,104.9757 87 | Netherlands,1976,5.1,5.6,-1,96.28062 88 | Netherlands,1977,2.3,5.5,-1,98.3404 89 | Netherlands,1978,2.5,5.5,0,93.89299 90 | Netherlands,1979,2.4,5.6,0,89.75119000000002 91 | Netherlands,1980,0.9,6.0,0,98.66754 92 | Netherlands,1981,-0.7,8.9,0,105.5502 93 | Netherlands,1982,-1.4,10.3,0,112.4727 94 | Netherlands,1983,0.9,12.3,0,111.3337 95 | Netherlands,1984,1.7,12.3,0,112.8105 96 | Netherlands,1985,2.6,10.2,0,119.0031 97 | Netherlands,1986,2.0,10.0,0,122.6721 98 | Netherlands,1987,1.1,10.0,0,104.1273 99 | Netherlands,1988,2.7,10.1,0,102.312 100 | Netherlands,1989,4.0,8.31223,0,105.057288 101 | Netherlands,1990,3.9,7.5087311,0,111.762226 102 | Belgium,1966,3.1622589,2.7,-2,73.61844 103 | Belgium,1967,3.8753976,3.7,-2,74.54204 104 | Belgium,1968,4.2,4.5,-2,73.58559 105 | Belgium,1969,6.6,3.6,-1,78.44779 106 | Belgium,1970,6.4,2.9,-1,84.37925 107 | Belgium,1971,3.7,2.9,-1,86.75947 108 | Belgium,1972,5.3,3.4,0,86.10609000000002 109 | Belgium,1973,5.9,3.6,0,85.15666 110 | Belgium,1974,4.1,4.0,0,94.41487 111 | Belgium,1975,-1.5,6.7,0,108.1774 112 | Belgium,1976,5.3,6.8,0,93.60921 113 | Belgium,1977,0.4,7.8,0,98.26746 114 | Belgium,1978,3.0,8.4,0,107.1681 115 | Belgium,1979,2.1,8.7,0,104.3904 116 | Belgium,1980,4.0,9.4,0,115.2146 117 | Belgium,1981,-1.5,10.0,0,121.3896 118 | Belgium,1982,1.5,11.7,0,130.5714 119 | Belgium,1983,-0.1,12.9,0,138.4857 120 | Belgium,1984,1.4,13.0,0,137.1896 121 | Belgium,1985,0.9,12.0,0,146.0202 122 | Belgium,1986,1.6,11.3,0,141.2755 123 | Belgium,1987,1.9,11.1,0,127.2102 124 | Belgium,1988,4.3,10.0,0,126.9076 125 | Belgium,1989,3.6,9.2664093,0,133.281122 126 | Belgium,1990,3.8,8.734146899999999,0,142.68608400000005 127 | France,1966,5.2141824,0.7,0,24.30256 128 | France,1967,4.6882107,0.9,0,25.00878 129 | France,1968,4.3,1.2,0,24.85014 130 | France,1969,7.0,1.0,-1,25.40206 131 | France,1970,5.7,1.2,-1,27.82975 132 | France,1971,5.4,1.6,-1,31.51196 133 | France,1972,5.9,2.0,-1,32.18585 134 | France,1973,5.4,2.6,-1,32.61595 135 | France,1974,3.2,2.8,-1,34.76036 136 | France,1975,0.2,4.1,-1,43.20575 137 | France,1976,5.2,4.4,-1,37.32651 138 | France,1977,3.1,4.7,-1,40.42973 139 | France,1978,3.8,5.2,-1,41.56883 140 | France,1979,3.3,5.9,-1,40.2554 141 | France,1980,1.1,6.3,-1,42.54165 142 | France,1981,0.5,7.3,-1,44.89193 143 | France,1982,1.8,8.1,-1,46.89539 144 | France,1983,0.7,8.3,-1,46.22432 145 | France,1984,1.3,9.9,-1,45.69321 146 | France,1985,1.9,10.2,-1,47.64652 147 | France,1986,2.3,10.4,-1,47.21023 148 | France,1987,1.9,10.5,-1,41.62719 149 | France,1988,3.5,9.9,-1,41.46514000000001 150 | France,1989,3.6,9.3791118,-1,42.861233 151 | France,1990,2.6,8.970954699999998,-1,49.667118 152 | West Germany,1966,2.9414163,0.7,0,37.89446 153 | West Germany,1967,-0.1188883,2.1,0,38.81367 154 | West Germany,1968,6.3,1.5,0,39.50642 155 | West Germany,1969,7.8,0.9,0,41.40414000000001 156 | West Germany,1970,6.0,0.7,0,43.06893 157 | West Germany,1971,2.9,0.8,0,43.21042 158 | West Germany,1972,4.2,1.1,0,43.2477 159 | West Germany,1973,4.7,1.2,0,42.76834 160 | West Germany,1974,0.3,2.6,0,44.19636 161 | West Germany,1975,-1.6,4.7,0,52.19484 162 | West Germany,1976,5.4,4.6,0,49.9167 163 | West Germany,1977,3.0,4.5,0,52.59491 164 | West Germany,1978,2.9,4.3,0,52.03534000000001 165 | West Germany,1979,4.2,3.8,0,50.73377 166 | West Germany,1980,1.4,3.8,0,53.40189 167 | West Germany,1981,0.2,5.5,0,57.20418000000001 168 | West Germany,1982,-0.6,7.5,0,61.45247 169 | West Germany,1983,1.2,9.1,0,62.33753000000001 170 | West Germany,1984,2.6,9.1,0,60.67839 171 | West Germany,1985,2.0,9.3,0,64.16871 172 | West Germany,1986,2.3,9.0,0,66.31546999999999 173 | West Germany,1987,1.8,8.9,0,59.98089 174 | West Germany,1988,3.7,8.7,0,58.09554 175 | West Germany,1989,3.2,6.843749000000001,0,59.254416000000006 176 | West Germany,1990,4.7,6.2089887,0,64.47238 177 | Austria,1966,5.6429989,1.7,-2,50.82818 178 | Austria,1967,3.0076867000000003,1.8,-2,51.53986 179 | Austria,1968,4.5,2.0,-2,50.87713 180 | Austria,1969,6.3,1.8,-2,51.62636 181 | Austria,1970,7.1,1.4,-2,55.52404 182 | Austria,1971,5.1,1.2,-2,61.16313 183 | Austria,1972,6.2,1.0,-2,60.62628 184 | Austria,1973,4.9,1.0,-3,60.49547 185 | Austria,1974,3.9,1.1,-1,60.68523 186 | Austria,1975,-0.4,1.7,-1,66.36381 187 | Austria,1976,4.6,1.7,-1,63.06676 188 | Austria,1977,4.4,1.5,-1,66.72645 189 | Austria,1978,0.5,1.8,-1,67.23354 190 | Austria,1979,4.7,1.7,-1,66.62471 191 | Austria,1980,3.0,1.6,-1,71.77694 192 | Austria,1981,-0.1,2.1,-1,75.59992 193 | Austria,1982,1.2,3.1,-1,77.91716 194 | Austria,1983,2.1,3.8,-1,74.13298 195 | Austria,1984,2.0,3.8,-1,73.28623 196 | Austria,1985,2.5,3.6,-1,77.68645 197 | Austria,1986,1.1,3.1,-1,80.93437 198 | Austria,1987,1.9,3.8,-1,72.72447 199 | Austria,1988,4.2,3.6,-1,70.36277 200 | Austria,1989,3.7,3.1304348,-1,74.421892 201 | Austria,1990,4.9,3.2331254,-1,79.47292900000002 202 | Italy,1966,5.9849301,5.9,-1,26.93998 203 | Italy,1967,7.1776258,5.4,-1,28.23712 204 | Italy,1968,6.5,5.7,-1,28.7611 205 | Italy,1969,6.1,5.7,-1,29.41873 206 | Italy,1970,5.3,5.4,-1,31.44462 207 | Italy,1971,1.6,5.4,-1,32.473 208 | Italy,1972,3.2,6.4,-1,32.66676 209 | Italy,1973,7.0,6.4,-1,34.19679 210 | Italy,1974,4.1,5.4,-1,37.14928 211 | Italy,1975,-3.6,5.9,-2,46.89348 212 | Italy,1976,5.9,6.7,-1,43.18141 213 | Italy,1977,1.9,7.2,-2,48.34128 214 | Italy,1978,2.7,7.2,-1,48.47882 215 | Italy,1979,4.9,7.7,-1,47.99013 216 | Italy,1980,3.9,7.6,-1,51.30311 217 | Italy,1981,0.2,8.4,-1,50.45122 218 | Italy,1982,-0.5,9.1,-2,46.008 219 | Italy,1983,-0.2,9.9,-1,44.53123 220 | Italy,1984,2.8,10.1,0,41.13525 221 | Italy,1985,2.6,10.1,0,43.64893 222 | Italy,1986,2.5,11.1,0,43.84961 223 | Italy,1987,3.0,12.0,-1,37.01633 224 | Italy,1988,3.9,12.0,-1,36.8253 225 | Italy,1989,3.0,11.8115106,-1,38.838481 226 | Italy,1990,2.0,10.7891162,-1,38.649988 227 | Finland,1966,2.3721416,1.5,-3,42.16415 228 | Finland,1967,2.1688613,2.9,-3,41.39525 229 | Finland,1968,2.5,3.8,-2,40.35631 230 | Finland,1969,9.6,2.8,-2,43.75069000000001 231 | Finland,1970,7.9,1.9,-2,47.38203 232 | Finland,1971,2.1,2.2,-2,52.59821 233 | Finland,1972,7.6,2.5,-2,50.48052 234 | Finland,1973,6.7,2.3,-3,50.74626 235 | Finland,1974,3.0,1.7,-3,51.49655 236 | Finland,1975,1.2,2.2,-3,58.73076999999999 237 | Finland,1976,0.3,3.8,-4,53.37948000000001 238 | Finland,1977,0.2,5.8,-3,52.09085 239 | Finland,1978,2.6,7.2,-3,55.15342 240 | Finland,1979,7.4,5.9,-3,55.9532 241 | Finland,1980,5.6,4.6,-3,61.35039 242 | Finland,1981,1.8,5.1,-3,66.6819 243 | Finland,1982,3.0,5.4,-3,65.71604 244 | Finland,1983,2.9,5.4,-3,61.49152 245 | Finland,1984,3.0,5.2,-3,60.64693000000001 246 | Finland,1985,3.3,5.0,-2,58.25233000000001 247 | Finland,1986,2.1,5.4,-2,57.31776 248 | Finland,1987,4.0,5.1,-3,51.93765 249 | Finland,1988,5.2,4.6,-3,50.27617 250 | Finland,1989,5.4,3.4456059000000003,-3,49.786669 251 | Finland,1990,0.4,3.5541195,-3,48.981816 252 | Sweden,1966,2.0909698,1.4,-2,44.78557 253 | Sweden,1967,3.3656264,1.7,-2,43.72852 254 | Sweden,1968,3.6,2.0,-2,42.46237 255 | Sweden,1969,5.0,1.7,-2,43.51757 256 | Sweden,1970,6.5,1.4,-2,46.27839 257 | Sweden,1971,0.9,2.0,-2,48.77312 258 | Sweden,1972,2.3,2.0,-2,47.52033 259 | Sweden,1973,4.0,1.9,-2,46.86441 260 | Sweden,1974,3.2,1.5,-1,52.04547 261 | Sweden,1975,2.6,1.4,-1,65.1786 262 | Sweden,1976,1.1,1.2,-1,56.49882 263 | Sweden,1977,-1.6,1.2,-1,56.97875 264 | Sweden,1978,1.8,1.6,-1,56.4408 265 | Sweden,1979,3.8,1.5,-1,55.41761999999999 266 | Sweden,1980,1.7,1.4,-1,61.8139 267 | Sweden,1981,-0.3,2.5,-1,61.51982 268 | Sweden,1982,0.8,3.1,-1,60.16333 269 | Sweden,1983,2.4,3.5,-1,64.76091 270 | Sweden,1984,3.4,3.1,-1,68.5121 271 | Sweden,1985,2.2,2.8,-1,68.25501 272 | Sweden,1986,2.3,2.6,-1,68.22753 273 | Sweden,1987,2.9,1.9,-1,62.7877 274 | Sweden,1988,2.3,1.6,-1,61.83354 275 | Sweden,1989,2.4,0.6847802,-1,63.454899 276 | Sweden,1990,0.6,1.5075377,-1,63.819325 277 | Norway,1966,3.7861464,0.8,-2,82.25381999999998 278 | Norway,1967,6.2568582,0.8,-2,83.08898 279 | Norway,1968,2.3,1.1,-2,85.66164 280 | Norway,1969,6.4,1.0,-2,84.23661 281 | Norway,1970,6.7,0.8,-2,82.44259 282 | Norway,1971,4.6,0.8,-2,84.91699 283 | Norway,1972,5.2,1.7,-2,83.66345 284 | Norway,1973,4.1,1.5,-2,80.62762 285 | Norway,1974,5.2,1.5,-1,87.61421 286 | Norway,1975,4.2,2.3,-1,95.4297 287 | Norway,1976,6.8,1.8,-1,90.33502 288 | Norway,1977,3.6,1.5,-1,91.72334 289 | Norway,1978,4.5,1.7,-1,90.33905 290 | Norway,1979,5.1,2.0,-1,82.75804000000002 291 | Norway,1980,4.2,1.7,-1,85.70865 292 | Norway,1981,0.9,2.0,-1,88.46642 293 | Norway,1982,0.3,2.6,-1,87.5138 294 | Norway,1983,3.9,3.3,-1,85.11059 295 | Norway,1984,3.8,3.1,-1,83.61769 296 | Norway,1985,5.3,2.6,-1,85.50693000000003 297 | Norway,1986,4.2,2.0,-1,85.75997 298 | Norway,1987,3.4,2.1,-1,79.00841 299 | Norway,1988,1.1,3.2,-1,73.38462 300 | Norway,1989,0.4,4.9187935,-1,73.86370500000002 301 | Norway,1990,1.8,5.2287582000000015,-1,79.656174 302 | Denmark,1966,2.7412402,2.3,-3,62.28668 303 | Denmark,1967,3.4212093999999995,2.7,-3,58.78236999999999 304 | Denmark,1968,3.8,5.0,-2,56.86628 305 | Denmark,1969,6.5,3.9,-2,56.77313 306 | Denmark,1970,2.3,2.9,-2,57.00761 307 | Denmark,1971,2.7,3.7,-2,58.80617 308 | Denmark,1972,5.3,3.6,-2,57.03129000000001 309 | Denmark,1973,3.6,2.4,-2,53.59322 310 | Denmark,1974,-0.9,5.2,-1,58.96134 311 | Denmark,1975,-0.7,6.0,-1,66.43666999999999 312 | Denmark,1976,6.5,5.3,-1,61.09859 313 | Denmark,1977,1.6,6.4,-1,62.33729 314 | Denmark,1978,1.5,7.3,-1,61.26526 315 | Denmark,1979,3.5,6.1,-1,57.73387 316 | Denmark,1980,-0.4,6.9,-1,61.31303000000001 317 | Denmark,1981,-0.9,10.3,-1,66.47386 318 | Denmark,1982,3.0,11.0,-1,72.36322 319 | Denmark,1983,2.1,11.4,-1,71.87047 320 | Denmark,1984,3.5,8.5,-1,70.32415 321 | Denmark,1985,4.3,7.3,-1,72.59833 322 | Denmark,1986,3.6,5.5,-1,72.94829 323 | Denmark,1987,-0.6,6.9,-1,64.36702 324 | Denmark,1988,-0.2,7.2,-1,61.44821 325 | Denmark,1989,0.8,8.1278222,0,62.47343100000001 326 | Denmark,1990,1.7,8.310439599999999,0,61.443299 327 | Japan,1966,10.6382757,0.9,-2,20.64122 328 | Japan,1967,11.0823408,1.3,-2,20.5948 329 | Japan,1968,12.8,1.2,-2,20.05519 330 | Japan,1969,12.3,1.1,-2,20.08211 331 | Japan,1970,9.8,1.2,-2,20.59826 332 | Japan,1971,4.2,1.2,-1,21.5081 333 | Japan,1972,8.4,1.4,-1,21.95309 334 | Japan,1973,7.9,1.3,-1,20.1664 335 | Japan,1974,-1.2,1.4,-1,21.53925 336 | Japan,1975,2.6,1.9,-1,29.91749 337 | Japan,1976,4.8,2.0,-1,27.42852 338 | Japan,1977,5.3,2.0,-1,28.03661 339 | Japan,1978,5.1,2.2,-1,26.11608 340 | Japan,1979,5.2,2.1,-1,22.00218 341 | Japan,1980,4.4,2.0,0,26.31612 342 | Japan,1981,3.9,2.2,0,31.21532 343 | Japan,1982,2.8,2.4,0,32.51882 344 | Japan,1983,3.2,2.6,0,32.80608 345 | Japan,1984,5.0,2.7,0,29.7358 346 | Japan,1985,4.7,2.6,0,30.6541 347 | Japan,1986,2.5,2.8,0,29.161590000000004 348 | Japan,1987,4.4,2.9,0,21.90987 349 | Japan,1988,5.7,2.5,0,21.71235 350 | Japan,1989,4.7,2.2647528,0,23.128797 351 | Japan,1990,5.2,2.0989975,0,20.000304 352 | -------------------------------------------------------------------------------- /data/mri.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/data/mri.nc -------------------------------------------------------------------------------- /datasets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | data: 4 | - url: https://s3.amazonaws.com/datashader-data/nyc_taxi_hours.zip 5 | title: 'NYC Taxi Data' 6 | files: 7 | - nyc_taxi_hours.parq 8 | -------------------------------------------------------------------------------- /download_sample_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, absolute_import, division 4 | 5 | # -*- coding: utf-8 -*- 6 | """ 7 | Copyright (c) 2011, Kenneth Reitz 8 | 9 | Permission to use, copy, modify, and/or distribute this software for any 10 | purpose with or without fee is hereby granted, provided that the above 11 | copyright notice and this permission notice appear in all copies. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | 21 | clint.textui.progress 22 | ~~~~~~~~~~~~~~~~~ 23 | 24 | This module provides the progressbar functionality. 25 | 26 | """ 27 | from collections import OrderedDict 28 | from os import path 29 | import glob 30 | import os 31 | import subprocess 32 | import sys 33 | import tarfile 34 | import time 35 | import zipfile 36 | 37 | import yaml 38 | try: 39 | import requests 40 | except ImportError: 41 | print('this download script requires the requests module: conda install requests') 42 | sys.exit(1) 43 | 44 | STREAM = sys.stderr 45 | 46 | BAR_TEMPLATE = '%s[%s%s] %i/%i - %s\r' 47 | MILL_TEMPLATE = '%s %s %i/%i\r' 48 | 49 | DOTS_CHAR = '.' 50 | BAR_FILLED_CHAR = '#' 51 | BAR_EMPTY_CHAR = ' ' 52 | MILL_CHARS = ['|', '/', '-', '\\'] 53 | 54 | # How long to wait before recalculating the ETA 55 | ETA_INTERVAL = 1 56 | # How many intervals (excluding the current one) to calculate the simple moving 57 | # average 58 | ETA_SMA_WINDOW = 9 59 | 60 | 61 | class Bar(object): 62 | def __enter__(self): 63 | return self 64 | 65 | def __exit__(self, exc_type, exc_val, exc_tb): 66 | self.done() 67 | return False # we're not suppressing exceptions 68 | 69 | def __init__(self, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR, 70 | filled_char=BAR_FILLED_CHAR, expected_size=None, every=1): 71 | '''Bar is a class for printing the status of downloads''' 72 | self.label = label 73 | self.width = width 74 | self.hide = hide 75 | # Only show bar in terminals by default (better for piping, logging etc.) 76 | if hide is None: 77 | try: 78 | self.hide = not STREAM.isatty() 79 | except AttributeError: # output does not support isatty() 80 | self.hide = True 81 | self.empty_char = empty_char 82 | self.filled_char = filled_char 83 | self.expected_size = expected_size 84 | self.every = every 85 | self.start = time.time() 86 | self.ittimes = [] 87 | self.eta = 0 88 | self.etadelta = time.time() 89 | self.etadisp = self.format_time(self.eta) 90 | self.last_progress = 0 91 | if (self.expected_size): 92 | self.show(0) 93 | 94 | def show(self, progress, count=None): 95 | if count is not None: 96 | self.expected_size = count 97 | if self.expected_size is None: 98 | raise Exception("expected_size not initialized") 99 | self.last_progress = progress 100 | if (time.time() - self.etadelta) > ETA_INTERVAL: 101 | self.etadelta = time.time() 102 | self.ittimes = \ 103 | self.ittimes[-ETA_SMA_WINDOW:] + \ 104 | [-(self.start - time.time()) / (progress+1)] 105 | self.eta = \ 106 | sum(self.ittimes) / float(len(self.ittimes)) * \ 107 | (self.expected_size - progress) 108 | self.etadisp = self.format_time(self.eta) 109 | x = int(self.width * progress / self.expected_size) 110 | if not self.hide: 111 | if ((progress % self.every) == 0 or # True every "every" updates 112 | (progress == self.expected_size)): # And when we're done 113 | STREAM.write(BAR_TEMPLATE % ( 114 | self.label, self.filled_char * x, 115 | self.empty_char * (self.width - x), progress, 116 | self.expected_size, self.etadisp)) 117 | STREAM.flush() 118 | 119 | def done(self): 120 | self.elapsed = time.time() - self.start 121 | elapsed_disp = self.format_time(self.elapsed) 122 | if not self.hide: 123 | # Print completed bar with elapsed time 124 | STREAM.write(BAR_TEMPLATE % ( 125 | self.label, self.filled_char * self.width, 126 | self.empty_char * 0, self.last_progress, 127 | self.expected_size, elapsed_disp)) 128 | STREAM.write('\n') 129 | STREAM.flush() 130 | 131 | def format_time(self, seconds): 132 | return time.strftime('%H:%M:%S', time.gmtime(seconds)) 133 | 134 | 135 | def bar(it, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR, 136 | filled_char=BAR_FILLED_CHAR, expected_size=None, every=1): 137 | """Progress iterator. Wrap your iterables with it.""" 138 | 139 | count = len(it) if expected_size is None else expected_size 140 | 141 | with Bar(label=label, width=width, hide=hide, empty_char=BAR_EMPTY_CHAR, 142 | filled_char=BAR_FILLED_CHAR, expected_size=count, every=every) \ 143 | as bar: 144 | for i, item in enumerate(it): 145 | yield item 146 | bar.show(i + 1) 147 | 148 | 149 | def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict): 150 | class OrderedLoader(Loader): 151 | pass 152 | def construct_mapping(loader, node): 153 | loader.flatten_mapping(node) 154 | return object_pairs_hook(loader.construct_pairs(node)) 155 | OrderedLoader.add_constructor( 156 | yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, 157 | construct_mapping) 158 | return yaml.load(stream, OrderedLoader) 159 | 160 | 161 | class DirectoryContext(object): 162 | """ 163 | Context Manager for changing directories 164 | """ 165 | def __init__(self, path): 166 | self.old_dir = os.getcwd() 167 | self.new_dir = path 168 | 169 | def __enter__(self): 170 | os.chdir(self.new_dir) 171 | 172 | def __exit__(self, *args): 173 | os.chdir(self.old_dir) 174 | 175 | 176 | def _url_to_binary_write(url, output_path, title): 177 | '''Given a url, output_path and title, 178 | write the contents of a requests get operation to 179 | the url in binary mode and print the title of operation''' 180 | print('Downloading {0}'.format(title)) 181 | resp = requests.get(url, stream=True) 182 | try: 183 | with open(output_path, 'wb') as f: 184 | total_length = int(resp.headers.get('content-length')) 185 | for chunk in bar(resp.iter_content(chunk_size=1024), expected_size=(total_length/1024) + 1, every=1000): 186 | if chunk: 187 | f.write(chunk) 188 | f.flush() 189 | except: 190 | # Don't leave a half-written zip file 191 | if path.exists(output_path): 192 | os.remove(output_path) 193 | raise 194 | 195 | 196 | def _extract_downloaded_archive(output_path): 197 | '''Extract a local archive, e.g. zip or tar, then 198 | delete the archive''' 199 | if output_path.endswith("tar.gz"): 200 | with tarfile.open(output_path, "r:gz") as tar: 201 | tar.extractall() 202 | os.remove(output_path) 203 | elif output_path.endswith("tar"): 204 | with tarfile.open(output_path, "r:") as tar: 205 | tar.extractall() 206 | os.remove(output_path) 207 | elif output_path.endswith("tar.bz2"): 208 | with tarfile.open(output_path, "r:bz2") as tar: 209 | tar.extractall() 210 | os.remove(output_path) 211 | elif output_path.endswith("zip"): 212 | with zipfile.ZipFile(output_path, 'r') as zipf: 213 | zipf.extractall() 214 | os.remove(output_path) 215 | 216 | 217 | def _process_dataset(dataset, output_dir, here): 218 | '''Process each download spec in datasets.yml 219 | 220 | Typically each dataset list entry in the yml has 221 | "files" and "url" and "title" keys/values to show 222 | local files that must be present / extracted from 223 | a decompression of contents downloaded from the url. 224 | 225 | If a url endswith '/', then all files given 226 | are assumed to be added to the url pattern at the 227 | end 228 | ''' 229 | if not path.exists(output_dir): 230 | os.makedirs(output_dir) 231 | 232 | with DirectoryContext(output_dir) as d: 233 | requires_download = False 234 | for f in dataset.get('files', []): 235 | if not path.exists(f): 236 | requires_download = True 237 | break 238 | 239 | if not requires_download: 240 | print('Skipping {0}'.format(dataset['title'])) 241 | return 242 | url = dataset['url'] 243 | title_fmt = dataset['title'] + ' {} of {}' 244 | if url.endswith('/'): 245 | urls = [url + f for f in dataset['files']] 246 | output_paths = [os.path.join(here, 'data', fname) 247 | for fname in dataset['files']] 248 | 249 | unpacked = ['.'.join(output_path.split('.')[:(-2 if output_path.endswith('gz') else -1)]) + '*' 250 | for output_path in output_paths] 251 | else: 252 | urls = [url] 253 | output_paths = [path.split(url)[1]] 254 | unpacked = dataset['files'] 255 | if not isinstance(unpacked, (tuple, list)): 256 | unpacked = [unpacked] 257 | zipped = zip(urls, output_paths, unpacked) 258 | for idx, (url, output_path, unpack) in enumerate(zipped): 259 | running_title = title_fmt.format(idx + 1, len(urls)) 260 | if glob.glob(unpack) or os.path.exists(unpack.replace('*','')): 261 | # Skip a file if a similar one is downloaded: 262 | # i.e. one that has same name but dif't extension 263 | print('Skipping {0}'.format(running_title)) 264 | continue 265 | _url_to_binary_write(url, output_path, running_title) 266 | _extract_downloaded_archive(output_path) 267 | 268 | 269 | def main(): 270 | '''Download each dataset specified by datasets.yml in this directory''' 271 | here = contrib_dir = path.abspath(path.join(path.split(__file__)[0])) 272 | info_file = path.join(here, 'datasets.yml') 273 | with open(info_file) as f: 274 | info = ordered_load(f.read()) 275 | for topic, downloads in info.items(): 276 | output_dir = path.join(here, topic) 277 | for d in downloads: 278 | _process_dataset(d, output_dir, here) 279 | 280 | if __name__ == '__main__': 281 | main() 282 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: hvtutorial 2 | channels: 3 | - ioam 4 | - bokeh 5 | - conda-forge 6 | - defaults 7 | dependencies: 8 | - python=3 9 | - notebook 10 | - holoviews 11 | - geoviews 12 | - pandas 13 | - xarray 14 | - datashader 15 | - selenium 16 | - phantomjs 17 | - fastparquet 18 | - python-snappy 19 | - cffi 20 | - paramnb 21 | - bokeh=0.12.6 22 | - parambokeh 23 | - ipywidgets<7 24 | -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/images/.DS_Store -------------------------------------------------------------------------------- /images/dask.svg: -------------------------------------------------------------------------------- 1 | dask -------------------------------------------------------------------------------- /images/datashader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/images/datashader.png -------------------------------------------------------------------------------- /images/holoviews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/images/holoviews.png -------------------------------------------------------------------------------- /images/numba.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/images/numba.jpg -------------------------------------------------------------------------------- /images/xarray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/images/xarray.png -------------------------------------------------------------------------------- /notebooks/00-welcome.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

00. Welcome, Demos and Setup

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "\n", 16 | "\n", 17 | "Welcome to the HoloViews+Bokeh Viz to Dashboards JupyterCon 2017 tutorial!\n", 18 | "\n", 19 | "This notebook serves as the homepage of the tutorial, including a table of contents listing each tutorial section, a general overview, links to demos illustrating the range of topics covered, and instructions to check that everything is installed properly." 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "## Index and Schedule\n", 27 | "\n", 28 | "This 3.5-hour tutorial is broken down into the following sections. Sections marked \"*(Omit)*\" are available for later self study, but there is not expected to be time to present those in detail.\n", 29 | "\n", 30 | "- *Overview*\n", 31 | "\n", 32 | " *   **5 min**  [0 - Welcome](./00-welcome.ipynb): (This notebook!) Welcome, demos, and setup.\n", 33 | " * **20 min**  [1 - Workflow](./01-workflow-introduction.ipynb): Overview of solving a simple but complete data-science task from exploration to deployment.

\n", 34 | "\n", 35 | "- *Making data visualizable*\n", 36 | " * **30 min**  [2 - Annotating your data](./02-annotating-data.ipynb): Using HoloViews Elements to make your data instantly visualizable\n", 37 | " * **20 min**  [3 - Customizing visual appearance](./03-customizing-visual-appearance.ipynb): How to change the appearance and output format of elements.\n", 38 | " * **20 min**  [4 - Exploration with containers](./04-exploration-with-containers.ipynb): Using HoloViews \"containers\" for quick, easy data exploration.\n", 39 | " * *(Omit)*    [5 - Working with tabular data](./05-working-with-tabular-data.ipynb): Exploring a tabular (columnar) dataset.\n", 40 | " * *(Omit)*    [6 - Working with gridded data](./06-working-with-gridded-data.ipynb): Exploring a gridded (n-dimensional) dataset.\n", 41 | " * **30 min**  *Break*\n", 42 | "\n", 43 | "\n", 44 | "- *Interactive apps and dashboards*\n", 45 | "\n", 46 | " * **25 min**  [7 - Custom interactivity](./07-custom-interactivity.ipynb): Using HoloViews \"streams\" to add interactivity to your visualizations.\n", 47 | " * **15 min**  [8 - Operations and pipelines](./08-operations-and-pipelines.ipynb): Dynamically transforming your data as needed\n", 48 | " * *(Omit)*    [9 - Working with large data](./09-working-with-large-datasets.ipynb): Using datasets too large to feed directly to your browser.\n", 49 | " * **15 min**  [10 - Parameters and widgets](./10-parameters-and-widgets.ipynb): Declarative custom controls\n", 50 | " * **30 min**  [11 - Deploying Bokeh Apps](./11-deploying-bokeh-apps.ipynb): Deploying your visualizations using Bokeh server." 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "## What is this all about?\n", 58 | "\n", 59 | "Data scientists and analysts very often switch between interactively exploring some data themselves, and then creating apps and dashboards for sharing their analyses with others. The Jupyter notebook environment supports a rich ecosystem of tools for exploratory, interactive visualizations, but these tools are often quite separate from those supporting app deployment. Bridging between these two common activities is thus needlessly difficult.\n", 60 | "\n", 61 | "In this tutorial we will be introducing a set of open-source Python libraries we have been developing to streamline the process of working with small and large datasets (from a few points to billions), whether doing exploratory analysis, making simple widget-based tools, or building full-featured dashboards. The libraries in this ecosystem include:\n", 62 | "\n", 63 | "* [**HoloViews**](http://holoviews.org): Declarative objects for instantly visualizable data\n", 64 | "* [**GeoViews**](http://geo.holoviews.org): Easy mix-and-matching of geographic data with custom plots\n", 65 | "* [**Bokeh**](http://bokeh.pydata.org): Interactive plotting in web browsers, controlled by Python\n", 66 | "* [**Param**](https://github.com/ioam/param): Declaring user-relevant parameters in domain-specific code\n", 67 | "* [**Datashader**](https://github.com/bokeh/datashader): Rasterizing huge datasets quickly using Dask and Numba\n", 68 | "* [**Pandas**](http://pandas.pydata.org): Convenient computation on columnar datasets\n", 69 | "* [**Xarray**](http://xarray): Convenient computations on multidimensional array datasets\n", 70 | "* [**Dask**](http://dask.pydata.org): Efficient out-of-core/distributed computation on massive datasets\n", 71 | "* [**Numba**](http://numba.pydata.org): Accelerated machine code for inner loops\n", 72 | "* [**Fastparquet**](https://fastparquet.readthedocs.io): Efficient storage for columnar data\n", 73 | "\n", 74 | "This tutorial will guide you through the process of using these tools together to build rich, high-performance, scalable, and deployable visualizations, apps, and dashboards.\n", 75 | "\n", 76 | "Although this is a long (and yet still incomplete) list, we will show how to coordinate most of them using the high-level declarative interface provided by HoloViews, giving you convenient, concise access to high-performance interactive visualizations from within Jupyter notebooks and deployed apps." 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "## Demos\n", 84 | "\n", 85 | "To give you an idea what sort of functionality is possible with these tools, check out some of these links:\n", 86 | "\n", 87 | "\n", 88 | "* [Selection stream](http://holoviews.org/reference/apps/bokeh/selection_stream.html)\n", 89 | "* [Bounds stream](http://holoviews.org/reference/streams/bokeh/BoundsX.html)\n", 90 | "* [Mandelbrot](http://holoviews.org/gallery/apps/bokeh/mandelbrot.html)\n", 91 | "* [DynamicMap](http://holoviews.org/reference/containers/bokeh/DynamicMap.html)\n", 92 | "* [CrossFilter](http://holoviews.org/gallery/apps/bokeh/crossfilter.html)\n", 93 | "* [Game of Life](http://holoviews.org/gallery/apps/bokeh/game_of_life.html)\n", 94 | "* [Dragon curve](http://holoviews.org/gallery/demos/bokeh/dragon_curve.html)\n", 95 | "* [Datashader NYC Taxi](https://anaconda.org/jbednar/nyc_taxi)\n", 96 | "* [Datashader Graphs](https://anaconda.org/jbednar/edge_bundling)\n", 97 | "* [Datashader OpenSky](https://anaconda.org/jbednar/opensky)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Related links\n", 105 | "\n", 106 | "You will find extensive support material on our website [holoviews.org](http://www.holoviews.org). In particular, you may find these links useful during the tutorial:\n", 107 | "\n", 108 | "* [Reference gallery](http://holoviews.org/reference/index.html): Visual reference of all elements and containers, along with some other components\n", 109 | "* [Getting started guide](http://holoviews.org/getting_started/index.html): Covers some of the same topics as this tutorial, but without exercises\n", 110 | "\n", 111 | "## Getting set up\n", 112 | "\n", 113 | "Please consult the tutorial repository [README](https://github.com/ioam/jupytercon2017-holoviews-tutorial/blob/master/README.rst) for instructions on setting up your environment. Here is the condensed version of these instructions for unix-based systems (Linux or Mac OS X):\n", 114 | "\n", 115 | "```bash\n", 116 | "$ conda env create -f environment.yml\n", 117 | "$ source activate hvtutorial\n", 118 | "$ cd notebooks\n", 119 | "```\n", 120 | "\n", 121 | "If you have any problems with running these instructions, you can conveniently view the full instructions within this notebook by running the following cell:" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "from IPython.core import page\n", 131 | "with open('../README.rst', 'r') as f:\n", 132 | " page.page(f.read())" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "If you created the environment last week, make sure to ``git pull``, activate the ``hvtutorial`` environment in the ``notebooks`` directory and run:\n", 140 | "\n", 141 | "```\n", 142 | "git pull\n", 143 | "conda env update -f ../environment.yml\n", 144 | "```\n", 145 | "\n", 146 | "Now you can launch the notebook server:\n", 147 | "\n", 148 | "```bash\n", 149 | "$ jupyter notebook --NotebookApp.iopub_data_rate_limit=100000000\n", 150 | "```" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "Once the environment is set up, the following cell should print '1.8.3':" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "import holoviews as hv\n", 167 | "hv.__version__" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "And you should see the HoloViews, Bokeh, and Matplotlib logos after running the following cell:" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "hv.extension('bokeh', 'matplotlib')" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "The next cell tests the key imports needed for this tutorial, and if it completes without errors your environment should be ready to go:" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [ 199 | "import bokeh\n", 200 | "import matplotlib\n", 201 | "import pandas\n", 202 | "import datashader\n", 203 | "import dask\n", 204 | "import geoviews" 205 | ] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "metadata": {}, 210 | "source": [ 211 | "Lastly, let's make sure the large taxi dataset is available, which can be obtained as described in the [README](https://github.com/ioam/jupytercon2017-holoviews-tutorial/blob/master/README.rst):" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [ 220 | "import os\n", 221 | "if not os.path.isdir('../data/nyc_taxi_hours.parq/'):\n", 222 | " print('Taxi dataset not found; please run \"python download_sample_data.py\".')" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "metadata": {}, 228 | "source": [ 229 | "## Recommended configuration" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "The following configuration options are recommended additions to your '~/.holoviews.rc' file as they improve the tutorial experience and will be the default behaviour in future:" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": null, 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "lines = \"\"\"\\\n", 246 | "import holoviews as hv\n", 247 | "hv.extension.case_sensitive_completion=True\n", 248 | "hv.Dataset.datatype = ['dataframe']+hv.Dataset.datatype\n", 249 | "\"\"\"" 250 | ] 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "metadata": {}, 255 | "source": [ 256 | "If you do not have a holoviews.rc already, simply run the following cell to generate one containing the above lines:" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": null, 262 | "metadata": {}, 263 | "outputs": [], 264 | "source": [ 265 | "rcpath = os.path.join(os.path.expanduser('~'), '.holoviews.rc')\n", 266 | "if not os.path.isfile(rcpath):\n", 267 | " with open(rcpath, 'w') as f:\n", 268 | " f.write(lines)" 269 | ] 270 | } 271 | ], 272 | "metadata": { 273 | "language_info": { 274 | "name": "python", 275 | "pygments_lexer": "ipython3" 276 | } 277 | }, 278 | "nbformat": 4, 279 | "nbformat_minor": 2 280 | } 281 | -------------------------------------------------------------------------------- /notebooks/04-exploration-with-containers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

04. Exploration with Containers

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "In the first two sections of this tutorial we discovered how to declare static elements and compose them one by one into composite objects, allowing us to quickly visualize data as we explore it. However, many datasets contain numerous additional dimensions of data, such as the same measurement repeated across a large number of different settings or parameter values. To address these common situations, HoloViews provides ontainers that allow you to explore extra dimensions of your data using widgets, as animations, or by \"faceting\" it (splitting it into [\"small multiples\"](https://en.wikipedia.org/wiki/Small_multiple)) in various ways.\n", 16 | "\n", 17 | "To begin with we will discover how we can quickly explore the parameters of a function by having it return an element and then evaluating the function over the parameter space." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import numpy as np\n", 27 | "import holoviews as hv\n", 28 | "hv.extension('bokeh')\n", 29 | "%opts Curve Area [width=600]" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "# Declaring elements in a function" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "If we write a function that accepts one or more parameters and constructs an element, we can build plots that do things like:\n", 44 | "\n", 45 | "* Loading data from disk as needed\n", 46 | "* Querying data from an API\n", 47 | "* Calculating data from a mathematical function\n", 48 | "* Generating data from a simulation\n", 49 | "\n", 50 | "As a basic example, let's declare a function that generates a frequency-modulated signal and returns a [``Curve``](http://holoviews.org/reference/elements/bokeh/Curve.html) element:" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": { 57 | "collapsed": true 58 | }, 59 | "outputs": [], 60 | "source": [ 61 | "def fm_modulation(f_carrier=110, f_mod=110, mod_index=1, length=0.1, sampleRate=3000):\n", 62 | " x = np.arange(0, length, 1.0/sampleRate)\n", 63 | " y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))\n", 64 | " return hv.Curve((x, y), kdims=['Time'], vdims=['Amplitude'])" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "The function defines a number of parameters that will change the signal, but using the default parameters the function outputs a ``Curve`` like this:" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "fm_modulation()" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "## HoloMaps\n", 88 | "\n", 89 | "The ``HoloMap`` is the first container type we will start working with, because it is often the starting point of a parameter exploration. HoloMaps allow exploring a parameter space sampled at specific, discrete values, and can easily be created using a dictionary comprehension. When declaring a [``HoloMap``](http://holoviews.org/reference/containers/bokeh/HoloMap.html), just ensure the length and ordering of the key tuple matches the key dimensions:" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "carrier_frequencies = [10, 20, 110, 220, 330]\n", 99 | "modulation_frequencies = [110, 220, 330]\n", 100 | "\n", 101 | "hmap = hv.HoloMap({(fc, fm): fm_modulation(fc, fm) for fc in carrier_frequencies\n", 102 | " for fm in modulation_frequencies}, kdims=['fc', 'fm'])\n", 103 | "hmap" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "Note how the keys in our ``HoloMap`` map on to two automatically generated sliders. HoloViews can generate two types of widgets: sliders for numeric values, or a dropdown selection menu for all other types. These sliders appear because a HoloMap can display only a single Element at one time, and the user must thus select which of the available elements to show at any one time." 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": { 117 | "collapsed": true 118 | }, 119 | "outputs": [], 120 | "source": [ 121 | "# Exercise: Try changing the function below to return an ``Area`` or ``Scatter`` element,\n", 122 | "# in the same way `fm_modulation` returned a ``Curve`` element.\n", 123 | "def fm_modulation2(f_carrier=220, f_mod=110, mod_index=1, length=0.1, sampleRate=3000):\n", 124 | " x = np.arange(0,length, 1.0/sampleRate)\n", 125 | " y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": { 132 | "collapsed": true 133 | }, 134 | "outputs": [], 135 | "source": [ 136 | "# Then declare a HoloMap like above and assign it to a ``exercise_hmap`` variable and display that\n" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "Apart from their simplicity and generality, one of the key features of HoloMaps is that they can be exported to a static HTML file, GIF, or video, because every combination of the sliders (parameter values) has been pre-computed already. Of course, this very convenient feature of pre-computation becomes a liability for very large or densely sampled parameter spaces, leading to the DynamicMap type discussed next.\n", 144 | "\n", 145 | "\n", 146 | "#### Summary\n", 147 | "\n", 148 | "* HoloMaps allow declaring a parameter space\n", 149 | "* The default widgets provide a slider for numeric types and a dropdown menu for non-numeric types.\n", 150 | "* HoloMap works well for small or sparsely sampled parameter spaces, exporting to static files" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "## DynamicMap\n", 158 | "\n", 159 | "A [``DynamicMap``]((holoviews.org/reference/containers/bokeh/DynamicMap.html) is very similar to a ``HoloMap`` except that it evaluates the function lazily. This property makes DynamicMap require a live, running Python server, not just an HTML-serving web site or email, and it may be slow if each frame is slower to compute than it is to display. However, because of these properties, DynamicMap allows exploring arbitrarily large parameter spaces, dynamically generating each element as needed to satisfy a request from the user. The key dimensions ``kdims`` must match the arguments of the function:" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "%%opts Curve (color='red')\n", 169 | "dmap = hv.DynamicMap(fm_modulation, kdims=['f_carrier', 'f_mod', 'mod_index'])\n", 170 | "dmap = dmap.redim.range(f_carrier=((10, 110)), f_mod=(10, 110), mod_index=(0.1, 2))\n", 171 | "dmap" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": { 178 | "collapsed": true 179 | }, 180 | "outputs": [], 181 | "source": [ 182 | "# Exercise: Declare a DynamicMap using the function from the previous exercise and name it ``exercise_dmap``\n" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "metadata": { 189 | "collapsed": true 190 | }, 191 | "outputs": [], 192 | "source": [ 193 | "# Exercise (Optional): Use the ``.redim.step`` method and a floating point range to modify the slider step\n" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "## Faceting parameter spaces" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "### Casting" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "HoloMaps and DynamicMaps let you explore a multidimensional parameter space by looking at one point in that space at a time, which is often but not always sufficient. If you want to see more data at once, you can facet the HoloMap to put some data points side by side or overlaid to facilitate comparison. One easy way to do that is to cast your HoloMap into a [``GridSpace``](http://holoviews.org/reference/elements/bokeh/GridSpace.html), [``NdLayout``](http://holoviews.org/reference/elements/bokeh/NdLayout.html), or [``NdOverlay``](http://holoviews.org/reference/elements/bokeh/NdOverlay.html) container:" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "metadata": {}, 221 | "outputs": [], 222 | "source": [ 223 | "%%opts Curve [width=150]\n", 224 | "hv.GridSpace(hmap).opts()" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": { 231 | "collapsed": true 232 | }, 233 | "outputs": [], 234 | "source": [ 235 | "# Exercise: Try casting your ``exercise_hmap`` HoloMap from the first exercise to an ``NdLayout`` or \n", 236 | "# ``NdOverlay``, guessing from the name what the resulting organization will be before testing it.\n" 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "metadata": {}, 242 | "source": [ 243 | "### Faceting with methods" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "Using the ``.overlay``, ``.grid`` and ``.layout`` methods we can facet multi-dimensional data by a specific dimension:" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "hmap.overlay('fm')" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "Using these methods with a DynamicMap requires special attention, because a dynamic map can return an infinite number of different values along its dimensions, unlike a HoloMap. Obviously, HoloViews could not comply with such a request, but these methods are perfectly legal with ``DynamicMap`` if you also define which specific dimension ``values`` you need, using the ``.redim.values`` method:" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": null, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "%%opts Curve [width=150]\n", 276 | "dmap.redim.values(f_mod=[10, 20, 30], f_carrier=[10, 20, 30]).overlay('f_mod').grid('f_carrier').opts()" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": { 283 | "collapsed": true 284 | }, 285 | "outputs": [], 286 | "source": [ 287 | "# Exercise: Facet the ``exercise_dmap`` DynamicMap using ``.overlay`` and ``.grid``\n", 288 | "# Hint: Use the .redim.values method to set discrete values for ``f_mod`` and ``f_carrier`` dimensions\n" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "## Optional\n", 296 | "\n", 297 | "### Slicing and indexing" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "HoloMaps and other containers also allow you to easily index or select by key, allowing you to:\n", 305 | "\n", 306 | "* select a specific key: ``obj[10, 110]``\n", 307 | "* select a slice: ``obj[10, 200:]``\n", 308 | "* select multiple values: ``obj[[10, 110], 110]``" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": null, 314 | "metadata": {}, 315 | "outputs": [], 316 | "source": [ 317 | "%%opts Curve [width=300]\n", 318 | "hmap[10, 110] + hmap[10, 200:].overlay() + hmap[[10, 110], 110].overlay()" 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "metadata": {}, 324 | "source": [ 325 | "You can do the same using the select method:" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": null, 331 | "metadata": {}, 332 | "outputs": [], 333 | "source": [ 334 | "(hmap.select(fc=10, fm=110) +\n", 335 | " hmap.select(fc=10, fm=(200, None)).overlay() +\n", 336 | " hmap.select(fc=[10, 110], fm=110).overlay())" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": null, 342 | "metadata": { 343 | "collapsed": true 344 | }, 345 | "outputs": [], 346 | "source": [ 347 | "# Exercise: Try selecting two carrier frequencies and two modulation frequencies on the ``exercise_hmap``\n" 348 | ] 349 | }, 350 | { 351 | "cell_type": "markdown", 352 | "metadata": {}, 353 | "source": [ 354 | "# Onwards\n", 355 | "\n", 356 | "* Learn more about using HoloMaps and other containers in the [Dimensioned Containers](http://holoviews.org/user_guide/Dimensioned_Containers.html) user guide\n", 357 | "* Learn more about working with DynamicMap in the [Live Data](http://holoviews.org/user_guide/Live_Data.html) user guide.\n", 358 | "\n", 359 | "The following section will talk about building containers from data stored in tabular (spreadsheet-like) formats, which is a very common situation given special support." 360 | ] 361 | } 362 | ], 363 | "metadata": { 364 | "kernelspec": { 365 | "display_name": "Python 3", 366 | "language": "python", 367 | "name": "python3" 368 | }, 369 | "language_info": { 370 | "codemirror_mode": { 371 | "name": "ipython", 372 | "version": 3 373 | }, 374 | "file_extension": ".py", 375 | "mimetype": "text/x-python", 376 | "name": "python", 377 | "nbconvert_exporter": "python", 378 | "pygments_lexer": "ipython3", 379 | "version": "3.6.2" 380 | } 381 | }, 382 | "nbformat": 4, 383 | "nbformat_minor": 2 384 | } 385 | -------------------------------------------------------------------------------- /notebooks/05-working-with-tabular-data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

05. Working with Tabular Datasets

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "As we have already discovered, elements are simple wrappers around your data that provide a semantically meaningful representation. Tabular data (also called columnar data) is one of the most common, general, and versatile data formats, corresponding to how data is laid out in a spreadsheet. There are many different ways to put data into a tabular format, but for interactive analysis having [**tidy data**](http://www.jeannicholashould.com/tidy-data-in-python.html) provides flexibility and simplicity.\n", 16 | "\n", 17 | "In this tutorial all the information you have learned in the previous sections will finally really pay off. We will discover how to facet data and use different element types to explore and visualize the data contained in a real dataset." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import numpy as np\n", 27 | "import scipy.stats as ss\n", 28 | "import pandas as pd\n", 29 | "import holoviews as hv\n", 30 | "hv.extension('bokeh')\n", 31 | "%opts Curve Scatter [tools=['hover']]" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "## What is tabular, tidy data?" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "macro_df = pd.read_csv('../data/macro.csv')\n", 48 | "macro_df.head()" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "For tidy data, the **columns** of the table represent **variables** or **dimensions** and the **rows** represent **observations**." 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "## Declaring dimensions" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Mathematical variables can usually be described as **dependent** or **independent**. In HoloViews these correspond to value dimensions and key dimensions (respectively).\n", 70 | "\n", 71 | "In this dataset ``'country'`` and ``'year'`` are independent variables or key dimensions, while the remainder are automatically inferred as value dimensions:" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "macro = hv.Dataset(macro_df, kdims=['country', 'year'])\n", 81 | "macro" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "We will also give the dimensions more sensible labels using ``redim.label``:" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "macro = macro.redim.label(growth='GDP Growth', unem='Unemployment', year='Year', country='Country')" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Mapping dimensions to elements" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "Once we have a ``Dataset`` with multiple dimensions we can map these dimensions onto elements onto the ``.to`` method. The method takes four main arguments:\n", 112 | "\n", 113 | "1. The element you want to convert to\n", 114 | "2. The key dimensions (or independent variables to display)\n", 115 | "3. The dependent variables to display\n", 116 | "4. The dimensions to group by" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "As a first simple example let's go through such a declaration:\n", 124 | "\n", 125 | "1. We will use a ``Curve``\n", 126 | "2. Our independent variable will be the 'year'\n", 127 | "3. Our dependent variable will be 'unem'\n", 128 | "4. We will ``groupby`` the 'country'." 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "curves = macro.to(hv.Curve, kdims='year', vdims='unem', groupby='country')\n", 138 | "print(curves)\n", 139 | "curves" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "If you look at the printed output you will see that instead of a simple ``Curve`` we got a ``HoloMap`` of ``Curve`` Elements for each country.\n", 147 | "\n", 148 | "Alternatively we could also group by the year and view the unemployment rate by country as Bars instead. If we simply want to groupby all remaining key dimensions (in this case just the year) we can leave out the groupby argument:" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "metadata": {}, 155 | "outputs": [], 156 | "source": [ 157 | "%%opts Bars [width=600 xrotation=45]\n", 158 | "bars = macro.sort('country').to(hv.Bars, kdims='country', vdims='unem')\n", 159 | "bars" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "# Exercise: Create a HeatMap using ``macro.to``, declaring vdims 'year' and 'country', and kdims 'growth'\n", 169 | "# You'll need to declare ``width`` and ``xrotation`` plot options for HeatMap to make the plot readable\n", 170 | "# You can also add ``tools=['hover']`` to get more info" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "metadata": {}, 183 | "source": [ 184 | "## Displaying distributions\n", 185 | "\n", 186 | "Often we want to summarize the distribution of values, e.g. to reveal the distribution of unemployment rates for each OECD country across time. This means we want to ignore the 'year' dimension in our dataset, letting it be summarized instead. To stop HoloViews from grouping by the extra variable, we pass an empty list to the groupby argument. In this case we can easily declare the ``BoxWhisker`` directly, but ommitting a key dimension from the ``groupby`` can be useful in cases when there are more dimensions:" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "%%opts BoxWhisker [width=800 xrotation=30] (box_fill_color=Palette('Category20'))\n", 196 | "macro.to(hv.BoxWhisker, 'country', 'growth', groupby=[])\n", 197 | "# Is equivalent to:\n", 198 | "hv.BoxWhisker(macro, kdims=['country'], vdims=['growth'])" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "# Exercise: Display the distribution of GDP growth by year using the BoxWhisker element" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "## Faceting dimensions\n", 215 | "\n", 216 | "In the previous section we discovered how to facet our data using the ``.overlay``, ``.grid`` and ``.layout`` methods. Instead of working with more abstract FM modulation signals, we now have concrete variables to group by, namely the 'country' and 'year':" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": null, 222 | "metadata": {}, 223 | "outputs": [], 224 | "source": [ 225 | "%%opts Scatter [width=800 height=400 size_index='growth'] (color=Palette('Category20') size=5)\n", 226 | "%%opts NdOverlay [legend_position='left']\n", 227 | "macro.to(hv.Scatter, 'year', ['unem', 'growth']).overlay().relabel('OECD Unemployment 1960 - 1990')" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": null, 233 | "metadata": {}, 234 | "outputs": [], 235 | "source": [ 236 | "# Exercise: Instead of faceting using an .overlay() of Scatter elements, facet the data using a .grid() \n", 237 | "# of Curve or Area elements" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "metadata": {}, 244 | "outputs": [], 245 | "source": [ 246 | "# Exercise: You'll notice that you get quite a lot of countries in the grid. \n", 247 | "# You can try supplying a short list of countries to the 'macro.select` method to get a more-practical subset.\n", 248 | "# Hint: You may want to pass the shared_yaxis=True plot option to GridSpace, to get a numeric axis" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [] 257 | }, 258 | { 259 | "cell_type": "markdown", 260 | "metadata": {}, 261 | "source": [ 262 | "## Aggregating" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "Another common operation is computing aggregates. We can also compute and visualize these easily using the ``aggregate`` method. The aggregate method lets you declare the dimension(s) to aggregate by and a function to aggregate with (optionally a secondary function can be supplied to compute the spread). Once we have computed the aggregate we can simply pass it to the [``Curve``](http://holoviews.org/reference/elements/bokeh/Curve.html) and [``ErrorBars``](http://holoviews.org/reference/elements/bokeh/ErrorBars.html):" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": null, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "%%opts Curve [width=600]\n", 279 | "agg = macro.aggregate('year', function=np.mean, spreadfn=np.std)\n", 280 | "(hv.Curve(agg) * hv.ErrorBars(agg, kdims=['year'], vdims=['growth', 'growth_std']))" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "# Exercise: Display aggregate GDP growth by country, building it up in a series of steps\n", 290 | "# Step 1. First, aggregate the data by country rather than by year, using\n", 291 | "# np.mean and ss.sem as the function and spreadfn, respectively, then \n", 292 | "# make a `Bars` element from the resulting ``agg``" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": null, 298 | "metadata": {}, 299 | "outputs": [], 300 | "source": [ 301 | "%%opts Bars [width=600 xrotation=45]\n", 302 | "agg = macro.aggregate('country', function=np.mean, spreadfn=ss.sem)\n", 303 | "hv.Bars(agg)" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": null, 309 | "metadata": {}, 310 | "outputs": [], 311 | "source": [ 312 | "# Step 2: You should now have a bars plot, but with no error bars. To add the error bars,\n", 313 | "# print the 'agg' as text to see which vdims are available (which will be different for \n", 314 | "# different spreadfns), then overlay ErrorBars as above but for the new kdims and\n", 315 | "# the appropriate vdims\n", 316 | "# Hint: You'll want to make the plot wider and use an xrotation to see the labels clearly" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": null, 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "## Onward\n", 331 | "\n", 332 | "* Go through the Tabular Data [getting started](http://build.holoviews.org/getting_started/Tabular_Datasets.html) and [user guide](http://build.holoviews.org/user_guide/Tabular_Datasets.html).\n", 333 | "* Learn about slicing, indexing and sampling in the [Indexing and Selecting Data](http://holoviews.org/user_guide/Indexing_and_Selecting_Data.html) user guide.\n", 334 | "\n", 335 | "The next section shows a similar approach, but for working with gridded data, in multidimensional array formats." 336 | ] 337 | } 338 | ], 339 | "metadata": { 340 | "language_info": { 341 | "name": "python", 342 | "pygments_lexer": "ipython3" 343 | } 344 | }, 345 | "nbformat": 4, 346 | "nbformat_minor": 2 347 | } 348 | -------------------------------------------------------------------------------- /notebooks/06-working-with-gridded-data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

06. Working with Gridded Datasets

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "Many datasets in science and engineering consist of n-dimensional data. Gridded datasets usually represent observations of some continuous variable across multiple dimensions---a monochrome image representing luminance values across a 2D surface, volumetric 3D data, an RGB image sequence over time, or any other multi-dimensional parameter space. This type of data is particularly common in research areas that make use of spatial imaging or modeling, such as climatology, biology, and astronomy, but can also be used to represent any arbitrary data that varies over multiple dimensions.\n", 16 | "\n", 17 | "xarray is a convenient way of working with and representing labelled n-dimensional arrays, like pandas for labelled n-D arrays:\n", 18 | "
\n", 19 | "" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": { 26 | "collapsed": true 27 | }, 28 | "outputs": [], 29 | "source": [ 30 | "import numpy as np\n", 31 | "import pandas as pd\n", 32 | "import holoviews as hv\n", 33 | "hv.extension('bokeh', 'matplotlib')" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Load the data" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": { 47 | "collapsed": true 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "import xarray as xr\n", 52 | "mri_xr = xr.open_dataset('../data/mri.nc')\n", 53 | "mri_xr" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "The data here represents volumetric data from an [MRI scan](https://graphics.stanford.edu/data/voldata/), with three coordinate dimensions 'x', 'y' and 'z'. In this simple example these coordinates are integers, but they are not required to be. Instead of volumetric data, we could imagine the data could be 2D spatial data that evolves over time, as is common in climatology and many other fields." 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "## Declaring the dataset\n", 68 | "\n", 69 | "In a gridded dataset the dimensions are typically alreay declared unambiguously, with **coordinates** (i.e. key dimensions) and **data variables** (i.e. value dimensions) that HoloViews can determine automatically:" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": { 76 | "collapsed": true 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "mri = hv.Dataset(mri_xr)\n", 81 | "mri" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "## Displaying the data" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "Just as we saw in the previous tutorial, we can group the data by one or more dimensions. Since we are dealing with volumetric data but have only a 2D display device, we can take slices along each axis. Here we will slice along the sagittal plane corresponding to the z-dimension:" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "metadata": { 102 | "collapsed": true 103 | }, 104 | "outputs": [], 105 | "source": [ 106 | "mri.to(hv.Image, groupby='z', dynamic=True)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": { 113 | "collapsed": true 114 | }, 115 | "outputs": [], 116 | "source": [ 117 | "# Exercise: Display transverse or frontal sections of the data by declaring the kdims in the .to method\n" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "## Slice and dice across n-dimensions" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "We can use ``.to`` to slice the cube along all three axes separately:" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": { 138 | "collapsed": true 139 | }, 140 | "outputs": [], 141 | "source": [ 142 | "%%opts Image {+axiswise} [xaxis=None yaxis=None width=225 height=225]\n", 143 | "(mri.to(hv.Image, ['z', 'y'], dynamic=True) +\n", 144 | " mri.to(hv.Image, ['z', 'x'], dynamic=True) +\n", 145 | " mri.to(hv.Image, ['x', 'y'], dynamic=True)).redim.range(MR=(0, 255))" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "## Aggregation\n", 153 | "\n", 154 | "We can also easily compute aggregates across one or more dimensions. Previously we used the ``aggregate`` method for this purpose, but when working with gridded datasets it often makes more sense to think of aggregation as a ``reduce`` operation. We can for example reduce the ``z`` dimension using ``np.mean`` and display the resulting averaged 2D array as an [``Image``](http://holoviews.org/reference/elements/bokeh/Image.html):" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": { 161 | "collapsed": true 162 | }, 163 | "outputs": [], 164 | "source": [ 165 | "hv.Image(mri.reduce(z=np.mean))" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": { 172 | "collapsed": true 173 | }, 174 | "outputs": [], 175 | "source": [ 176 | "# Exercise: Recreate the plot above using the aggregate method\n", 177 | "# Hint: The aggregate and reduce methods are inverses of each other\n", 178 | "# Try typing \"hv.Image(mri.aggregate(\" to see the signature of `aggregate`.\n" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "As you can see, it is straightforward to work with the additional dimensions of data available in gridded datasets, as explained in more detail in the [user guide](http://holoviews.org/user_guide/Gridded_Datasets.html).\n", 186 | "\n", 187 | "# Onwards\n", 188 | "\n", 189 | "The previous sections focused on displaying plots that provide certain standard types of interactivity, whether widget-based (to select values along a dimension) or within each plot (for panning, zooming, etc.). A wide range of additional types of interactivity can also be defined by the user for working with specific types of data, as outlined in the next section." 190 | ] 191 | } 192 | ], 193 | "metadata": { 194 | "kernelspec": { 195 | "display_name": "Python 3", 196 | "language": "python", 197 | "name": "python3" 198 | }, 199 | "language_info": { 200 | "codemirror_mode": { 201 | "name": "ipython", 202 | "version": 3 203 | }, 204 | "file_extension": ".py", 205 | "mimetype": "text/x-python", 206 | "name": "python", 207 | "nbconvert_exporter": "python", 208 | "pygments_lexer": "ipython3", 209 | "version": "3.6.2" 210 | } 211 | }, 212 | "nbformat": 4, 213 | "nbformat_minor": 2 214 | } 215 | -------------------------------------------------------------------------------- /notebooks/08-operations-and-pipelines.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

08. Operations and Pipelines

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "When interactively exploring a dataset you often end up interleaving visualization and analysis code. Since in HoloViews your visualization and your data are one and the same, analysis and data transformations can be applied directly to the visualizable data. For that purpose HoloViews provides operations, which can be used to implement any analysis or data transformation you might want to do. Operations take a HoloViews Element and return another Element of either the same type or a new type, depending on the operation.\n", 16 | "\n", 17 | "Since Operations know about HoloViews you can apply them to large collections of data collected in HoloMap and DynamicMap containers. Since operations work on both of these containers that means they can also be applied lazily. This feature allows us to chain multiple operations in a data analysis, processing, and visualization pipeline to drive the operation of a dashboard.\n", 18 | "\n", 19 | "Pipelines built using DynamicMap and HoloViews operations are also useful for caching intermediate results and just-in-time computations, because they lazily (re)compute just the part of the pipeline that has changed." 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import time\n", 29 | "import param\n", 30 | "import numpy as np\n", 31 | "import pandas as pd\n", 32 | "import holoviews as hv\n", 33 | "import datashader as ds\n", 34 | "\n", 35 | "from bokeh.sampledata import stocks\n", 36 | "from holoviews.operation import decimate\n", 37 | "from holoviews.operation.timeseries import rolling, rolling_outlier_std\n", 38 | "from holoviews.operation.datashader import datashade, dynspread, aggregate\n", 39 | "\n", 40 | "hv.extension('bokeh')" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "# Declare some data" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "In this example we'll work with a timeseries that stands in for stock-price data. We'll define a small function to generate a random, noisy timeseries, then define a ``DynamicMap`` that will generate a timeseries for each stock symbol:" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "def time_series(T=1, N=100, mu=0.1, sigma=0.1, S0=20): \n", 64 | " \"\"\"Parameterized noisy time series\"\"\"\n", 65 | " dt = float(T)/N\n", 66 | " t = np.linspace(0, T, N)\n", 67 | " W = np.random.standard_normal(size = N) \n", 68 | " W = np.cumsum(W)*np.sqrt(dt) # standard brownian motion\n", 69 | " X = (mu-0.5*sigma**2)*t + sigma*W \n", 70 | " S = S0*np.exp(X) # geometric brownian motion\n", 71 | " return S\n", 72 | "\n", 73 | "def load_symbol(symbol, **kwargs):\n", 74 | " return hv.Curve(time_series(N=10000), kdims=[('time', 'Time')],\n", 75 | " vdims=[('adj_close', 'Adjusted Close')])\n", 76 | "\n", 77 | "dmap = hv.DynamicMap(load_symbol, kdims=['Symbol']).redim.values(Symbol=stocks.stocks)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "We will start by visualizing this data as-is:" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "%opts Curve [width=600] {+framewise}\n", 94 | "dmap" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "## Applying an operation" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "Now let's start applying some operations to this data. HoloViews ships with two ready-to-use timeseries operations: the ``rolling`` operation, which applies a function over a rolling window, and a ``rolling_outlier_std`` operation that computes outlier points in a timeseries. Specifically, ``rolling_outlier_std`` excludes points less than one sigma (standard deviation) away from the rolling mean, which is just one example; you can trivially write your own operations that do whatever you like." 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "%opts Scatter (color='indianred')\n", 118 | "smoothed = rolling(dmap, rolling_window=30)\n", 119 | "outliers = rolling_outlier_std(dmap, rolling_window=30)\n", 120 | "smoothed * outliers" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "As you can see, the operations transform the ``Curve`` element into a smoothed version and a set of ``Scatter`` points containing the outliers both with a ``rolling_window`` of 30. Since we applied the operation to a ``DynamicMap``, the operation is lazy and only computes the result when it is requested. " 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "# Exercise: Apply the rolling and rolling_outlier_std operations changing the rolling_window and sigma parameters" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "## Linking operations to streams" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "Instead of supplying the parameter values for each operation explicitly as a scalar value, we can also define a ``Stream`` that will let us update our visualization dynamically. By supplying a ``Stream`` with a ``rolling_window`` parameter to both operations, we can now generate our own events on the stream and watch our visualization update each time." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "rolling_stream = hv.streams.Stream.define('rolling', rolling_window=5)\n", 160 | "stream = rolling_stream()\n", 161 | "\n", 162 | "rolled_dmap = rolling(dmap, streams=[stream])\n", 163 | "outlier_dmap = rolling_outlier_std(dmap, streams=[stream])\n", 164 | "rolled_dmap * outlier_dmap" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "for i in range(20, 200, 20):\n", 174 | " time.sleep(0.2)\n", 175 | " stream.event(rolling_window=i)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "# Exercise: Create a stream to control the sigma value and add it to the outlier operation,\n", 185 | "# then vary the sigma value and observe the effect" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "## Defining operations\n", 193 | "\n", 194 | "Defining custom Operations is also very straightforward. For instance, let's define an ``Operation`` to compute the residual between two overlaid ``Curve`` Elements. All we need to do is subclass from the ``Operation`` baseclass and define a ``_process`` method, which takes the ``Element`` or ``Overlay`` as input and returns a new ``Element``. The residual operation can then be used to subtract the y-values of the second Curve from those of the first Curve." 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "import param\n", 204 | "from holoviews.operation import Operation\n", 205 | "\n", 206 | "class residual(Operation):\n", 207 | " \"\"\"\n", 208 | " Subtracts two curves from one another.\n", 209 | " \"\"\"\n", 210 | " \n", 211 | " label = param.String(default='Residual', doc=\"\"\"\n", 212 | " Defines the label of the returned Element.\"\"\")\n", 213 | " \n", 214 | " def _process(self, element, key=None):\n", 215 | " # Get first and second Element in overlay\n", 216 | " el1, el2 = element.get(0), element.get(1)\n", 217 | " \n", 218 | " # Get x-values and y-values of curves\n", 219 | " xvals = el1.dimension_values(0)\n", 220 | " yvals1 = el1.dimension_values(1)\n", 221 | " yvals2 = el2.dimension_values(1)\n", 222 | " \n", 223 | " # Return new Element with subtracted y-values\n", 224 | " # and new label\n", 225 | " return el1.clone((xvals, yvals1-yvals2),\n", 226 | " vdims=[self.p.label])" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "To see what that looks like in action let's try it out by comparing the smoothed and original Curve." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "residual_dmap = residual(rolled_dmap * dmap)\n", 243 | "residual_dmap" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "Since the stream we created is linked to one of the inputs of ``residual_dmap``, changing the stream values triggers updates both in the plot above and in our new residual plot." 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "for i in range(20, 200, 20):\n", 260 | " time.sleep(0.2)\n", 261 | " stream.event(rolling_window=i)" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "metadata": {}, 267 | "source": [ 268 | "## Chaining operations" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": {}, 274 | "source": [ 275 | "Of course, since operations simply transform an Element in some way, operations can easily be chained. As a simple example, we will take the ``rolled_dmap`` and apply the ``datashading`` and ``dynspread`` operation to it to construct a datashaded version of the plot. As you'll be able to see, this concise specification defines a complex analysis pipeline that gets reapplied whenever you change the Symbol or interact with the plot -- whenever the data needs to be updated." 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "%%opts RGB [width=600 height=400] {+framewise}\n", 285 | "overlay = dynspread(datashade(rolled_dmap)) * outlier_dmap\n", 286 | "(overlay + residual_dmap).cols(1)" 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "## Visualizing the pipeline" 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "To understand what is going on we will write a small utility that traverses the output we just displayed above and visualizes each processing step leading up to it." 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": null, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "%%opts RGB Curve [width=250 height=200]\n", 310 | "\n", 311 | "def traverse(obj, key, items=None):\n", 312 | " items = [] if items is None else items\n", 313 | " for inp in obj.callback.inputs[:1]:\n", 314 | " label = inp.callback.operation.name if isinstance(inp.callback, hv.core.OperationCallable) else 'price'\n", 315 | " if inp.last: items.append(inp[key].relabel(label))\n", 316 | " if isinstance(inp, hv.DynamicMap): traverse(inp, key, items)\n", 317 | " return list(hv.core.util.unique_iterator(items))[:-1]\n", 318 | "\n", 319 | "hv.Layout(traverse(overlay, 'AAPL')).cols(4)" 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "metadata": {}, 325 | "source": [ 326 | "Reading from right to left, the original price timeseries is first smoothed with a rolling window, then datashaded, then each pixel is spread to cover a larger area. As you can see, arbitrarily many standard or custom operations can be defined to capture even very complex workflows so that they can be replayed dynamically as needed interactively." 327 | ] 328 | } 329 | ], 330 | "metadata": { 331 | "language_info": { 332 | "name": "python", 333 | "pygments_lexer": "ipython3" 334 | } 335 | }, 336 | "nbformat": 4, 337 | "nbformat_minor": 2 338 | } 339 | -------------------------------------------------------------------------------- /notebooks/09-working-with-large-datasets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

09. Working with large datasets

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "HoloViews supports even high-dimensional datasets easily, and the standard mechanisms discussed already work well as long as you select a small enough subset of the data to display at any one time. However, some datasets are just inherently large, even for a single frame of data, and cannot safely be transferred for display in any standard web browser. Luckily, HoloViews makes it simple for you to use the separate [``datashader``](http://datashader.readthedocs.io) library together with any of the plotting extension libraries, including Bokeh and Matplotlib. Datashader is designed to complement standard plotting libraries by providing faithful visualizations for very large datasets, focusing on revealing the overall distribution, not just individual data points.\n", 16 | "\n", 17 | "Datashader uses computations accelerated using [Numba](http://numba.pydata.org), making it fast to work with datasets of millions or billions of datapoints stored in [``dask``](http://dask.pydata.org/en/latest/) dataframes. Dask dataframes provide an API that is functionally equivalent to pandas, but allows working with data out of core while scaling out to many processors across compute clusters. Here we will use Dask to load a large Parquet-format file of taxi coordinates.\n", 18 | "\n", 19 | "
\n", 20 | "\n", 21 | "\n", 22 | "\n", 23 | "
" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "### How does datashader work?\n", 31 | "\n", 32 | "\n", 33 | "\n", 34 | "* Tools like Bokeh map **Data** (left) directly into an HTML/JavaScript **Plot** (right)\n", 35 | "* datashader instead renders **Data** into a plot-sized **Aggregate** array, from which an **Image** can be constructed then embedded into a Bokeh **Plot**\n", 36 | "* Only the fixed-sized **Image** needs to be sent to the browser, allowing millions or billions of datapoints to be used\n", 37 | "* Every step automatically adjusts to the data, but can be customized" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "#### When not to use datashader\n", 45 | "\n", 46 | "* Plotting less than 1e5 or 1e6 data points\n", 47 | "* When every datapoint matters; standard Bokeh will render all of them\n", 48 | "* For full interactivity (hover tools) with every datapoint\n", 49 | "\n", 50 | "#### When to use datashader\n", 51 | "\n", 52 | "* Actual big data; when Bokeh/Matplotlib have trouble\n", 53 | "* When the distribution matters more than individual points\n", 54 | "* When you find yourself sampling, decimating, or binning to better understand the distribution" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "import pandas as pd\n", 64 | "import holoviews as hv\n", 65 | "import dask.dataframe as dd\n", 66 | "import datashader as ds\n", 67 | "import geoviews as gv\n", 68 | "\n", 69 | "from holoviews.operation.datashader import datashade, aggregate\n", 70 | "hv.extension('bokeh')" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "## Load the data" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "As a first step we will load a large dataset using Dask. If you have followed the setup instructions you will have downloaded a large Parquet-format file containing 12 million taxi trips. Let's load this data using dask to create a dataframe ``ddf``:" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "
\n", 92 | " Warning! If you are low on memory (less than 8 GB) load only a subset of the data by changing the line below to:\n", 93 | "
\n", 94 | " df = dd.read_parquet('../data/nyc_taxi_hours.parq/')[:10000].persist()\n", 95 | "
" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "ddf = dd.read_parquet('../data/nyc_taxi_hours.parq/').persist()\n", 105 | "\n", 106 | "print('%s Rows' % len(ddf))\n", 107 | "print('Columns:', list(ddf.columns))" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "## Create a dataset\n", 115 | "\n", 116 | "In previous sections we have already seen how to declare a set of [``Points``](http://holoviews.org/reference/elements/bokeh/Points.html) from a pandas dataframe. Here we do the same for a Dask dataframe passed in with the desired key dimensions:" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": { 123 | "collapsed": true 124 | }, 125 | "outputs": [], 126 | "source": [ 127 | "points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y'])" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "We could now simply type ``points``, and Bokeh will attempt to display this data as a standard Bokeh plot. Before doing that, however, remember that we have 12 million rows of data, and no current plotting program will handle this well! Instead of letting Bokeh see this data, let's convert it to something far more tractable using the ``datashade`` operation. This operation will aggregate the data on a 2D grid, apply shading to assign pixel colors to each bin in this grid, and build an ``RGB`` Element (just a fixed-sized image) we can safely display:" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "%opts RGB [width=600 height=500 bgcolor=\"black\"]\n", 144 | "datashade(points)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "If you zoom in you will note that the plot rerenders depending on the zoom level, which allows the full dataset to be explored interactively even though only an image of it is ever sent to the browser. The way this works is that ``datashade`` is a dynamic operation that also declares some linked streams. These linked streams are automatically instantiated and dynamically supply the plot size, ``x_range``, and ``y_range`` from the Bokeh plot to the operation based on your current viewport as you zoom or pan:" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "datashade.streams" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": { 167 | "collapsed": true 168 | }, 169 | "outputs": [], 170 | "source": [ 171 | "# Exercise: Plot the taxi pickup locations ('pickup_x' and 'pickup_y' columns)\n", 172 | "# Warning: Don't try to display hv.Points() directly; it's too big! Use datashade() for any display\n", 173 | "# Optional: Change the cmap on the datashade operation to inferno\n", 174 | "\n", 175 | "from datashader.colors import inferno\n" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "## Adding a tile source" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "Using the GeoViews (geographic) extension for HoloViews, we can display a map in the background. Just declare a Bokeh WMTSTileSource and pass it to the gv.WMTS Element, then we can overlay it:" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "%opts RGB [xaxis=None yaxis=None]\n", 199 | "import geoviews as gv\n", 200 | "from bokeh.models import WMTSTileSource\n", 201 | "url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg'\n", 202 | "wmts = WMTSTileSource(url=url)\n", 203 | "gv.WMTS(wmts) * datashade(points)" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": { 210 | "collapsed": true 211 | }, 212 | "outputs": [], 213 | "source": [ 214 | "# Exercise: Overlay the taxi pickup data on top of the Wikipedia tile source\n", 215 | "\n", 216 | "wiki_url = 'https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png'\n" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": {}, 222 | "source": [ 223 | "## Aggregating with a variable" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "So far we have simply been counting taxi dropoffs, but our dataset is much richer than that. We have information about a number of variables including the base cost of a taxi ride, the ``fare_amount``. Datashader provides a number of ``aggregator`` functions, which you can supply to the datashade operation. Here use the ``ds.mean`` aggregator to compute the average cost of a trip at a dropoff location:" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "selected = points.select(fare_amount=(None, 1000))\n", 240 | "selected.data = selected.data.persist()\n", 241 | "gv.WMTS(wmts) * datashade(selected, aggregator=ds.mean('fare_amount'))" 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": null, 247 | "metadata": { 248 | "collapsed": true 249 | }, 250 | "outputs": [], 251 | "source": [ 252 | "# Exercise: Use the ds.min or ds.max aggregator to visualize ``tip_amount`` by dropoff location\n", 253 | "# Optional: Eliminate outliers by using select\n" 254 | ] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "metadata": {}, 259 | "source": [ 260 | "## Grouping by a variable\n", 261 | "\n", 262 | "Because datashading happens only just before visualization, you can use any of the techniques shown in previous sections to select, filter, or group your data before visualizing it, such as grouping it by the hour of day:" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "%opts Image [width=600 height=500 logz=True xaxis=None yaxis=None]\n", 272 | "taxi_ds = hv.Dataset(ddf)\n", 273 | "grouped = taxi_ds.to(hv.Points, ['dropoff_x', 'dropoff_y'], groupby=['dropoff_hour'], dynamic=True)\n", 274 | "aggregate(grouped).redim.values(dropoff_hour=range(24))" 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": null, 280 | "metadata": { 281 | "collapsed": true 282 | }, 283 | "outputs": [], 284 | "source": [ 285 | "# Exercise: Facet the trips in the morning hours as an NdLayout using aggregate(grouped.layout())\n", 286 | "# Hint: You can reuse the existing grouped variable or select a subset before using the .to method\n" 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "## Additional features" 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "The actual points are never given directly to Bokeh, and so the normal Bokeh hover (and other) tools will not normally be useful with Datashader output. However, we can easily verlay an invisible ``QuadMesh`` to reveal information on hover, providing information about values in a local area while still only ever sending a fixed-size array to the browser to avoid issues with large data." 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": null, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "%%opts QuadMesh [width=800 height=400 tools=['hover']] (alpha=0 hover_line_alpha=1 hover_fill_alpha=0)\n", 310 | "hover_info = aggregate(points, width=40, height=20, streams=[hv.streams.RangeXY]).map(hv.QuadMesh, hv.Image)\n", 311 | "gv.WMTS(wmts) * datashade(points) * hover_info" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "As you can see, datashader requires taking some extra steps into consideration, but it makes it practical to work with even quite large datasets on an ordinary laptop. On a 16GB machine, datasets 10X or 100X the one used here should be very practical, as illustrated at the [datashader web site](https://github.com/bokeh/datashader).\n", 319 | "\n", 320 | "\n", 321 | "# Onwards\n", 322 | "\n", 323 | "* The [user guide](http://holoviews.org/user_guide/Large_Data.html) explains in more detail how to work with large datasets using datashader.\n", 324 | "* There is a sample [bokeh app](http://holoviews.org/gallery/apps/bokeh/nytaxi_hover.html) using this dataset and an additional linked stream that works well as a starting point.\n", 325 | "\n", 326 | "The final sections of this tutorial will show how to bring all of the concepts covered so far into an interactive web app for working with large or small datasets." 327 | ] 328 | } 329 | ], 330 | "metadata": { 331 | "kernelspec": { 332 | "display_name": "Python 3", 333 | "language": "python", 334 | "name": "python3" 335 | }, 336 | "language_info": { 337 | "codemirror_mode": { 338 | "name": "ipython", 339 | "version": 3 340 | }, 341 | "file_extension": ".py", 342 | "mimetype": "text/x-python", 343 | "name": "python", 344 | "nbconvert_exporter": "python", 345 | "pygments_lexer": "ipython3", 346 | "version": "3.6.2" 347 | } 348 | }, 349 | "nbformat": 4, 350 | "nbformat_minor": 2 351 | } 352 | -------------------------------------------------------------------------------- /notebooks/10-parameters-and-widgets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

10. Parameters and Widgets

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "In previous sections we have mostly been learning about using HoloViews to build visualizations. There is only so much information that can be packed on to the screen at once, and in practice it's often necessary to supply interactive widgets so the user can further select what is shown. HoloViews can provide widgets automatically for dimensions that have been declared on the data, but often you will want to express other types of user-settable parameters to control your plots. Here we will discover how to leverage the ``param``, ``paramnb`` and ``parambokeh`` libraries to declare your own custom parameters and how to link widgets to your visualization." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import param\n", 25 | "import paramnb\n", 26 | "import parambokeh\n", 27 | "\n", 28 | "import numpy as np\n", 29 | "import holoviews as hv\n", 30 | "from bokeh.document import Document\n", 31 | "\n", 32 | "hv.extension('bokeh')" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "# Declaring parameters" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "As a first step we need to declare a so called ``Parameterized`` class with a number of parameters, which will control our visualization. In this example we will stick with something simple and we will simply expose some styling options for a Curve plot, declaring ``alpha``, ``color``, and ``line_width`` parameters. We'll use a special type of ``Parameterized`` called a ``Stream`` that defines an ``event`` method that we will use later." 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "class StyleOptions(hv.streams.Stream):\n", 56 | "\n", 57 | " alpha = param.Magnitude(default=0.8)\n", 58 | " \n", 59 | " color = param.ObjectSelector(default='red', objects=['red', 'green', 'blue'])\n", 60 | " \n", 61 | " line_width = param.Number(default=1, bounds=(0, 10)) " 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "## Displaying parameters" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "We can automatically get a UI to control these parameters using the ``parambokeh`` and ``paramnb`` libraries. Simply call the ``Widgets`` function with a ``Parameterized`` instance as the first argument and it will display a set of widgets based on ``bokeh`` or ``ipywidgets``:\n", 76 | "\n", 77 | "#### ParamBokeh" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "opts = StyleOptions()\n", 87 | "parambokeh.Widgets(opts)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "#### ParamNB" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "opts = StyleOptions()\n", 104 | "paramnb.Widgets(opts)" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "## Linking parameters as streams" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "As a final step we will link the Parameterized class and the widgets to an actual plot. To do this, we will declare a ``DynamicMap``, which sets the style options on an existing ``Curve``. The only thing we need to declare is that the ``Widgets`` should call the ``event`` method on our stream whenever the widget is updated. This will immediately link the widgets to the actual plot." 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "opts = StyleOptions()\n", 128 | "curve = hv.Curve(np.sin(np.linspace(0, np.pi*3, 100)))\n", 129 | "dmap = hv.DynamicMap(lambda **kwargs: curve.opts(style=kwargs), streams=[opts])\n", 130 | "paramnb.Widgets(opts, callback=opts.event, continuous_update=True)\n", 131 | "dmap" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "metadata": {}, 137 | "source": [ 138 | "## Laying out plots and widgets" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "In a Jupyter notebook, the widgets and the plot they control can be in any cell, because the notebook ensures that all of them are laid out on to the screen. When deploying the dashboard as a standalone bokeh app, we have to combine the plot and widgets into a single layout, because there is no default linear ordering of \"cells\" at the Bokeh server level. The easiest way to build such a layout is to use the BokehRenderer to convert our plot to a Bokeh model, then pass that to the ``parambokeh.Widgets`` call. We can also supply a ``view_position`` telling it how to lay out the widgets and plots." 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "viewer = StyleOptions()\n", 155 | "curve = hv.Curve(np.sin(np.linspace(0, np.pi*3, 100)))\n", 156 | "dmap = hv.DynamicMap(lambda **kwargs: curve.opts(style=kwargs), streams=[viewer])\n", 157 | "\n", 158 | "# Get bokeh model for the plot\n", 159 | "plot = hv.renderer('bokeh').get_plot(dmap, doc=Document()).state\n", 160 | "\n", 161 | "parambokeh.Widgets(viewer, callback=viewer.event, view_position='right',\n", 162 | " continuous_update=True, plots=[plot])" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "## Exercise: Subclass the StyleOptions class and add a parameter to control the line_dash style option\n", 172 | "\n", 173 | "# Hint: The valid options for line_dash are: 'solid', 'dashed', 'dotted', 'dotdash', 'dashdot'" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "At present, only these relatively simple options for layout are supported, but these objects can be used in an arbitrary Jinja2 template (not shown), and additional Python layout options are expected to be added over time." 181 | ] 182 | } 183 | ], 184 | "metadata": { 185 | "language_info": { 186 | "name": "python", 187 | "pygments_lexer": "ipython3" 188 | } 189 | }, 190 | "nbformat": 4, 191 | "nbformat_minor": 2 192 | } 193 | -------------------------------------------------------------------------------- /notebooks/11-deploying-bokeh-apps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

11. Deploying Bokeh Apps

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "In the previous sections we discovered how to use a ``HoloMap`` to build a Jupyter notebook with interactive visualizations that can be exported to a standalone HTML file, as well as how to use ``DynamicMap`` and ``Streams`` to set up dynamic interactivity backed by the Jupyter Python kernel. However, frequently we want to package our visualization or dashboard for wider distribution, backed by Python but run outside of the notebook environment. Bokeh Server provides a flexible and scalable architecture to deploy complex interactive visualizations and dashboards, integrating seamlessly with Bokeh and with HoloViews.\n", 16 | "\n", 17 | "For a detailed background on Bokeh Server see [the Bokeh user guide](http://bokeh.pydata.org/en/latest/docs/user_guide/server.html). In this tutorial we will discover how to deploy the visualizations we have created so far as a standalone Bokeh Server app, and how to flexibly combine HoloViews and ParamBokeh to build complex apps. We will also reuse a lot of what we have learned so far---loading large, tabular datasets, applying Datashader operations to them, and adding linked Streams to our app.\n", 18 | "\n", 19 | "## A simple Bokeh app\n", 20 | "\n", 21 | "The preceding sections of this tutorial focused solely on the Jupyter notebook, but now let's look at a bare Python script that can be deployed using Bokeh Server:" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "with open('./apps/server_app.py', 'r') as f:\n", 31 | " print(f.read())" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "Step 1 of this app should be very familiar by now -- declare that we are using Bokeh to render plots, load some taxi dropoff locations, declare a Points object, Datashade them, and set some plot options.\n", 39 | "\n", 40 | "At this point, if we were working with this code in a notebook, we would simply type ``shaded`` and let Jupyter's rich display support take over, rendering the object into a Bokeh plot and displaying it inline. Here, step 2 adds the code necessary to do those steps explicitly:\n", 41 | "\n", 42 | "- get a handle on the Bokeh renderer object using ``hv.renderer``\n", 43 | "- create a Bokeh document from ``shaded`` by passing it to the renderer's ``server_doc`` method\n", 44 | "- optionally, change some properties of the Bokeh document like the title.\n", 45 | "\n", 46 | "This simple chunk of boilerplate code can be added to turn any HoloViews object into a fully functional, deployable Bokeh app!" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "## Deploying the app\n", 54 | "\n", 55 | "Assuming that you have a terminal window open with the ``hvtutorial`` environment activated, in the ``notebooks/`` directory, you can launch this app using Bokeh Server:\n", 56 | "\n", 57 | "```\n", 58 | "bokeh serve --show apps/server_app.py\n", 59 | "```\n", 60 | "\n", 61 | "If you don't already have a favorite way to get a terminal, one way is to [open it from within Jupyter](../terminals/1), then make sure you are in the ``notebooks`` directory, and activate the environment using ``source activate hvtutorial`` (or ``activate tutorial`` on Windows). You can also [open the app script file](../edit/apps/server_app.py) in the inbuilt text editor, or you can use your own preferred editor." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "# Exercise: Modify the app to display the pickup locations and add a tilesource, then run the app with bokeh serve\n", 71 | "# Tip: Refer to the previous notebook\n" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "## Building an app with custom widgets\n", 79 | "\n", 80 | "The above app script can be built entirely without using Jupyter, though we displayed it here using Jupyter for convenience in the tutorial. Jupyter notebooks are also often helpful when initially developing such apps, allowing you to quickly iterate over visualizations in the notebook, deploying it as a standalone app only once we are happy with it. In this section we will combine everything we have learned so far including declaring of various parameters to control our visualization using a set of widgets.\n", 81 | "\n", 82 | "We begin as usual with a set of imports:" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "import holoviews as hv, geoviews as gv, param, parambokeh, dask.dataframe as dd\n", 92 | "\n", 93 | "from colorcet import cm\n", 94 | "from bokeh.models import WMTSTileSource\n", 95 | "from holoviews.operation.datashader import datashade\n", 96 | "from holoviews.streams import RangeXY\n", 97 | "\n", 98 | "hv.extension('bokeh', logo=False)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "Next we once again load the Taxi dataset and define a tile source:" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "usecols = ['dropoff_x', 'dropoff_y', 'pickup_x', 'pickup_y', 'dropoff_hour']\n", 115 | "df = dd.read_parquet('../data/nyc_taxi_hours.parq/')\n", 116 | "df = df[usecols].persist()\n", 117 | "\n", 118 | "url='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg'\n", 119 | "tiles = gv.WMTS(WMTSTileSource(url=url))\n", 120 | "tile_options = dict(width=600,height=400,xaxis=None,yaxis=None,bgcolor='black',show_grid=False)" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "Finally we will put together a complete dashboard with a number of parameters controlling our visualization, including controls over the alpha level of the tiles and the colormap as well as the hour of day and whether to plot dropoff or pickup location." 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "class NYCTaxiExplorer(hv.streams.Stream):\n", 137 | " alpha = param.Magnitude(default=0.75, doc=\"Alpha value for the map opacity\")\n", 138 | " colormap = param.ObjectSelector(default=cm[\"fire\"], objects=[cm[k] for k in cm.keys() if not '_' in k])\n", 139 | " hour = param.Range(default=(0, 24), bounds=(0, 24))\n", 140 | " location = param.ObjectSelector(default='dropoff', objects=['dropoff', 'pickup'])\n", 141 | "\n", 142 | " def make_view(self, x_range, y_range, **kwargs):\n", 143 | " map_tiles = tiles.opts(style=dict(alpha=self.alpha), plot=tile_options)\n", 144 | " points = hv.Points(df, kdims=[self.location+'_x', self.location+'_y'], vdims=['dropoff_hour'])\n", 145 | " if self.hour != (0, 24): points = points.select(dropoff_hour=self.hour)\n", 146 | " taxi_trips = datashade(points, x_sampling=1, y_sampling=1, cmap=self.colormap,\n", 147 | " dynamic=False, x_range=x_range, y_range=y_range, width=600, height=400)\n", 148 | " return map_tiles * taxi_trips\n", 149 | "\n", 150 | "explorer = NYCTaxiExplorer(name=\"NYC Taxi Trips\")\n", 151 | "dmap = hv.DynamicMap(explorer.make_view, streams=[explorer, RangeXY()])\n", 152 | "plot = hv.renderer('bokeh').get_plot(dmap)\n", 153 | "parambokeh.Widgets(explorer, view_position='right', callback=explorer.event, plots=[plot.state])" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "Now let's open the [text editor](../edit/apps/nyc_taxi/main.py) again and make this edit to a separate app, which we can then launch using Bokeh Server from the [terminal](../terminals/1)." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "# Exercise: Note the differences between the server app and the app defined above\n", 170 | "# then add an additional parameter and plot" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [ 179 | "# Exercise: Click the link below and edit the Jinja2 template to customize the app " 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "[Edit the template](../edit/apps/nyc_taxi/templates/index.html)" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": {}, 192 | "source": [ 193 | "## Combining HoloViews with bokeh models" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "Now for a last hurrah let's put everything we have learned to good use and create a bokeh app with it. This time we will go straight to a [Python script containing the app](../edit/apps/player_app.py). If you run the app with ``bokeh serve --show ./apps/player_app.py`` from [your terminal](../terminals/1) you should see something like this:\n", 201 | "\n", 202 | "" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": {}, 208 | "source": [ 209 | "This more complex app consists of several components:\n", 210 | "\n", 211 | "1. A datashaded plot of points for the indicated hour of the daty (in the slider widget)\n", 212 | "2. A linked ``PointerX`` stream, to compute a cross-section\n", 213 | "3. A set of custom Bokeh widgets linked to the hour-of-day stream\n", 214 | "\n", 215 | "We have already covered 1. and 2. so we will focus on 3., which shows how easily we can combine a HoloViews plot with custom Bokeh models. We will not look at the precise widgets in too much detail, instead let's have a quick look at the callback defined for slider widget updates:\n", 216 | "\n", 217 | "```python\n", 218 | "def slider_update(attrname, old, new):\n", 219 | " stream.event(hour=new)\n", 220 | "```\n", 221 | "\n", 222 | "Whenever the slider value changes this will trigger a stream event updating our plots. The second part is how we combine HoloViews objects and Bokeh models into a single layout we can display. Once again we can use the renderer to convert the HoloViews object into something we can display with Bokeh:\n", 223 | "\n", 224 | "```python\n", 225 | "renderer = hv.renderer('bokeh')\n", 226 | "plot = renderer.get_plot(hvobj, doc=curdoc())\n", 227 | "```\n", 228 | "\n", 229 | "The ``plot`` instance here has a ``state`` attribute that represents the actual Bokeh model, which means we can combine it into a Bokeh layout just like any other Bokeh model:\n", 230 | "\n", 231 | "```python\n", 232 | "layout = layout([[plot.state], [slider, button]], sizing_mode='fixed')\n", 233 | "curdoc().add_root(layout)\n", 234 | "```" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "# Advanced Exercise: Add a histogram to the bokeh layout next to the datashaded plot\n", 244 | "# Hint: Declare the histogram like this: hv.operation.histogram(aggregated, bin_range=(0, 20))\n", 245 | "# then use renderer.get_plot and hist_plot.state and add it to the layout\n" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "# Onwards\n", 253 | "\n", 254 | "Although the code above is more complex than in previous sections, it's providing a huge range of custom types of interactivity, which if implemented in Bokeh alone would have required far more than a notebook cell of code. Hopefully it is clear that arbitrarily complex collections of visualizations and interactive controls can be built from the components provided by HoloViews, allowing you to make simple analyses very easily and making it practical to make even quite complex apps when needed. The [user guide](http://holoviews.org/user_guide), [gallery](http://holoviews.org/gallery/index.html), and [reference gallery](http://holoviews.org/reference) should have all the information you need to get started with all this power on your own datasets and tasks. Good luck!" 255 | ] 256 | } 257 | ], 258 | "metadata": { 259 | "language_info": { 260 | "name": "python", 261 | "pygments_lexer": "ipython3" 262 | } 263 | }, 264 | "nbformat": 4, 265 | "nbformat_minor": 2 266 | } 267 | -------------------------------------------------------------------------------- /notebooks/README.md: -------------------------------------------------------------------------------- 1 | 2 | The notebooks in this directory are also available for viewing online via [nbviewer](https://nbviewer.jupyter.org). 3 | Note that some output requires a live server which means you won't see the intended behavior without running a notebook server, as 4 | detailed in the [README](https://github.com/ioam/jupytercon2017-holoviews-tutorial/blob/master/README.rst). 5 | 6 | [00-welcome](https://nbviewer.jupyter.org/gist/jlstevens/62d27808a3d4beb6f537a83f6e2837a0): Get started by creating a variety of different HoloViews "elements".
7 | [01-introduction-to-elements](https://nbviewer.jupyter.org/gist/jlstevens/e1f9ee0ef93df371b4ae178f406a034c): How to change the appearance and output format of elements.
8 | [02-customizing-visual-appearance](https://nbviewer.jupyter.org/gist/jlstevens/aaa9e1ae6cd5ca4e731c0287a1b296df): Customizing visual appearance
9 | [03-exploration-with-containers](https://nbviewer.jupyter.org/gist/jlstevens/9b4cb4c627534480c00b4a425843e971): Using HoloViews "containers" for quick, easy data exploration.
10 | [04-working-with-tabular-data](https://nbviewer.jupyter.org/gist/jlstevens/a97da19a90706c21189d04796df2d907): Exploring a tabular (columnar) dataset.
11 | [05-working-with-gridded-data](https://nbviewer.jupyter.org/gist/jlstevens/96d5cf4d1404105f6a16ee247a938063): Exploring a gridded (n-dimensional) dataset.
12 | [06-custom-interactivity](https://nbviewer.jupyter.org/gist/jlstevens/b0ae8f72243b1959d05b6eb8d8e6e2f6): Using HoloViews "streams" to add interactivity to your visualizations.
13 | [07-working-with-large-datasets](https://nbviewer.jupyter.org/gist/jlstevens/a8460b5ada31d77d9ce1ec7fda2bb096): Working with large data: Using datasets too large to feed directly to your browser.
14 | [08-deploying-bokeh-apps](https://nbviewer.jupyter.org/gist/jlstevens/6a51efdd79f0945502821e23740893f7): Deploying Bokeh Apps: Deploying your visualizations using Bokeh server.
15 | -------------------------------------------------------------------------------- /notebooks/apps/nyc_taxi/main.py: -------------------------------------------------------------------------------- 1 | import holoviews as hv, geoviews as gv, param, parambokeh, dask.dataframe as dd 2 | 3 | from colorcet import cm 4 | from bokeh.models import WMTSTileSource 5 | from holoviews.operation.datashader import datashade 6 | from holoviews.streams import RangeXY 7 | 8 | hv.extension('bokeh', logo=False) 9 | 10 | usecols = ['dropoff_x','dropoff_y','pickup_x','pickup_y','dropoff_hour','pickup_hour','passenger_count'] 11 | df = dd.read_parquet('../../data/nyc_taxi_hours.parq/')[usecols].persist() 12 | 13 | url='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg' 14 | tiles = gv.WMTS(WMTSTileSource(url=url)) 15 | options = dict(width=1000,height=600,xaxis=None,yaxis=None,bgcolor='black',show_grid=False) 16 | max_pass = int(df.passenger_count.max().compute()+1) 17 | 18 | class NYCTaxiExplorer(hv.streams.Stream): 19 | alpha = param.Magnitude(default=0.75, doc="Alpha value for the map opacity") 20 | colormap = param.ObjectSelector(default=cm["fire"], objects=[cm[k] for k in cm.keys() if not '_' in k]) 21 | hour = param.Integer(default=None, bounds=(0, 23), doc="All hours by default; drag to select one hour") 22 | passengers = param.Range(default=(0,max_pass), bounds=(0,max_pass)) 23 | location = param.ObjectSelector(default='dropoff', objects=['dropoff', 'pickup']) 24 | 25 | def make_view(self, x_range, y_range, **kwargs): 26 | map_tiles = tiles.opts(style=dict(alpha=self.alpha), plot=options) 27 | points = hv.Points(df, kdims=[self.location+'_x', self.location+'_y'], vdims=[self.location+'_hour']) 28 | selection = {self.location+"_hour":self.hour if self.hour else (0,24), "passenger_count":self.passengers} 29 | taxi_trips = datashade(points.select(**selection), x_sampling=1, y_sampling=1, cmap=self.colormap, 30 | dynamic=False, x_range=x_range, y_range=y_range, width=1000, height=600) 31 | return map_tiles * taxi_trips 32 | 33 | explorer = NYCTaxiExplorer(name="NYC Taxi Trips") 34 | dmap = hv.DynamicMap(explorer.make_view, streams=[explorer, RangeXY()]) 35 | 36 | plot = hv.renderer('bokeh').instance(mode='server').get_plot(dmap) 37 | parambokeh.Widgets(explorer, view_position='right', callback=explorer.event, plots=[plot.state], 38 | mode='server') 39 | -------------------------------------------------------------------------------- /notebooks/apps/nyc_taxi/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NYC Taxi Dataset Example 6 | {{ bokeh_css }} 7 | {{ bokeh_js }} 8 | 11 | 12 | 13 |
14 |

NYC Taxi Dataset Dashboard

15 | 16 | {{ plot_div|indent(8) }} 17 | 18 |
19 | {{ plot_script|indent(8) }} 20 | 21 | 22 | -------------------------------------------------------------------------------- /notebooks/apps/nyc_taxi/templates/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | width: 100%; 3 | } 4 | body { 5 | min-width: 600px; 6 | width: 90%; 7 | margin: auto; 8 | } 9 | .content { 10 | margin: 1em; 11 | } 12 | h1 { 13 | margin: 2em 0 0 0; 14 | color: #2e484c; 15 | font-family: 'Julius Sans One', sans-serif; 16 | font-size: 1.8em; 17 | text-transform: uppercase; 18 | } 19 | a:link { 20 | font-weight: bold; 21 | text-decoration: none; 22 | color: #0d8ba1; 23 | } 24 | a:visited { 25 | font-weight: bold; 26 | text-decoration: none; 27 | color: #1a5952; 28 | } 29 | a:hover, a:focus, a:active { 30 | text-decoration: underline; 31 | color: #9685BA; 32 | } 33 | p { 34 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 35 | font-size: 1.2em; 36 | text-align: justify; 37 | text-justify: inter-word; 38 | } -------------------------------------------------------------------------------- /notebooks/apps/periodic_app.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/apps/periodic_app.py -------------------------------------------------------------------------------- /notebooks/apps/player_app.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import holoviews as hv 3 | import geoviews as gv 4 | import parambokeh 5 | import param 6 | 7 | from colorcet import cm 8 | 9 | from bokeh.models import Slider, Button 10 | from bokeh.layouts import layout 11 | from bokeh.io import curdoc 12 | from bokeh.models import WMTSTileSource 13 | 14 | from holoviews.operation.datashader import datashade, aggregate, shade 15 | from holoviews.plotting.util import fire 16 | shade.cmap = fire 17 | 18 | hv.extension('bokeh') 19 | renderer = hv.renderer('bokeh').instance(mode='server') 20 | 21 | # Load data 22 | ddf = dd.read_parquet('../data/nyc_taxi_hours.parq/').persist() 23 | 24 | from bokeh.models import WMTSTileSource 25 | url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg' 26 | wmts = gv.WMTS(WMTSTileSource(url=url)) 27 | 28 | stream = hv.streams.Stream.define('HourSelect', hour=0)() 29 | points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y']) 30 | dmap = hv.util.Dynamic(points, operation=lambda obj, hour: obj.select(dropoff_hour=hour).relabel('Hour of Day: %d' % hour), 31 | streams=[stream]) 32 | 33 | # Apply aggregation 34 | aggregated = aggregate(dmap, link_inputs=True, streams=[hv.streams.RangeXY], width=1200, height=600) 35 | 36 | # Shade the data 37 | class ColormapPicker(hv.streams.Stream): 38 | colormap = param.ObjectSelector(default=cm["fire"], 39 | objects=[cm[k] for k in cm.keys() if not '_' in k]) 40 | 41 | cmap_picker = ColormapPicker(rename={'colormap': 'cmap'}, name='') 42 | shaded = shade(aggregated, link_inputs=True, streams=[cmap_picker]) 43 | 44 | # Define PointerX stream, attach to points and declare DynamicMap for cross-section and VLine 45 | pointer = hv.streams.PointerX(x=ddf.dropoff_x.loc[0].compute().iloc[0], source=points) 46 | section = hv.util.Dynamic(aggregated, operation=lambda obj, x: obj.sample(dropoff_x=x), 47 | streams=[pointer], link_inputs=False).relabel('') 48 | vline = hv.DynamicMap(lambda x: hv.VLine(x), streams=[pointer]) 49 | 50 | # Define options 51 | hv.opts("RGB [width=1200 height=600 xaxis=None yaxis=None fontsize={'title': '14pt'}] VLine (color='white' line_width=2)") 52 | hv.opts("Curve [width=150 yaxis=None show_frame=False] (color='black') {+framewise} Layout [shared_axes=False]") 53 | 54 | # Combine it all into a complex layout 55 | hvobj = (wmts * shaded * vline) << section 56 | 57 | ### Pass the HoloViews object to the renderer 58 | plot = renderer.get_plot(hvobj, doc=curdoc()) 59 | 60 | # Define a slider and button 61 | start, end = 0, 23 62 | 63 | def slider_update(attrname, old, new): 64 | stream.event(hour=new) 65 | 66 | slider = Slider(start=start, end=end, value=0, step=1, title="Hour") 67 | slider.on_change('value', slider_update) 68 | 69 | def animate_update(): 70 | year = slider.value + 1 71 | if year > end: 72 | year = start 73 | slider.value = year 74 | 75 | def animate(): 76 | if button.label == '► Play': 77 | button.label = '❚❚ Pause' 78 | curdoc().add_periodic_callback(animate_update, 500) 79 | else: 80 | button.label = '► Play' 81 | curdoc().remove_periodic_callback(animate_update) 82 | 83 | button = Button(label='► Play', width=60) 84 | button.on_click(animate) 85 | 86 | widget = parambokeh.Widgets(cmap_picker, mode='raw') 87 | 88 | # Combine the bokeh plot on plot.state with the widgets 89 | layout = layout([ 90 | [widget], 91 | [plot.state], 92 | [slider, button], 93 | ], sizing_mode='fixed') 94 | 95 | curdoc().add_root(layout) 96 | -------------------------------------------------------------------------------- /notebooks/apps/server_app.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import holoviews as hv 3 | from holoviews.operation.datashader import datashade 4 | 5 | hv.extension('bokeh') 6 | 7 | # 1. Load data and Datashade it 8 | ddf = dd.read_parquet('../data/nyc_taxi_hours.parq/')[['dropoff_x', 'dropoff_y']].persist() 9 | points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y']) 10 | shaded = datashade(points).opts(plot=dict(width=800, height=600)) 11 | 12 | # 2. Instead of Jupyter's automatic rich display, render the object as a bokeh document 13 | doc = hv.renderer('bokeh').server_doc(shaded) 14 | doc.title = 'HoloViews Bokeh App' 15 | -------------------------------------------------------------------------------- /notebooks/assets/bokeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/bokeh.png -------------------------------------------------------------------------------- /notebooks/assets/bokeh_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 63 | 68 | 74 | 80 | 86 | 92 | 98 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /notebooks/assets/dask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/dask.png -------------------------------------------------------------------------------- /notebooks/assets/dask.svg: -------------------------------------------------------------------------------- 1 | dask -------------------------------------------------------------------------------- /notebooks/assets/datashader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/datashader.png -------------------------------------------------------------------------------- /notebooks/assets/datashader_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/datashader_pipeline.png -------------------------------------------------------------------------------- /notebooks/assets/geoviews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/geoviews.png -------------------------------------------------------------------------------- /notebooks/assets/header_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/header_logo.png -------------------------------------------------------------------------------- /notebooks/assets/holoviews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/holoviews.png -------------------------------------------------------------------------------- /notebooks/assets/hv+bk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/hv+bk.png -------------------------------------------------------------------------------- /notebooks/assets/matplotlib_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/matplotlib_logo.png -------------------------------------------------------------------------------- /notebooks/assets/numba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/numba.png -------------------------------------------------------------------------------- /notebooks/assets/tutorial_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/tutorial_app.gif -------------------------------------------------------------------------------- /notebooks/assets/xarray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/notebooks/assets/xarray.png -------------------------------------------------------------------------------- /notebooks/nyc_taxi_makeparq.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Internal-use notebook for generating a .parq file from a .csv" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import pandas as pd\n", 17 | "import holoviews as hv\n", 18 | "import dask.dataframe as dd\n", 19 | "import datashader as ds\n", 20 | "import geoviews as gv\n", 21 | "import numpy as np\n", 22 | "\n", 23 | "from holoviews.operation.datashader import datashade, aggregate\n", 24 | "hv.extension('bokeh')" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "ddf = dd.read_csv('../data/nyc_taxi.csv', parse_dates=['tpep_dropoff_datetime','tpep_pickup_datetime'])\n", 34 | "ddf['dropoff_hour'] = ddf.tpep_dropoff_datetime.dt.hour\n", 35 | "ddf['pickup_hour'] = ddf.tpep_pickup_datetime.dt.hour\n", 36 | "\n", 37 | "print('%s Rows' % len(ddf))\n", 38 | "print('Columns:', list(ddf.columns))" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "Could keep the full datetimes, but currently only keeps the hours." 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "float_cols = ['pickup_x', 'pickup_y', 'dropoff_x', 'dropoff_y', 'trip_distance', 'fare_amount', 'tip_amount']\n", 55 | "int_cols = ['dropoff_hour','pickup_hour', 'passenger_count']\n", 56 | "\n", 57 | "for col in float_cols:\n", 58 | " ddf[col] = ddf[col].astype(np.float32)\n", 59 | "\n", 60 | "for col in int_cols:\n", 61 | " ddf[col] = ddf[col].astype(np.int8)\n", 62 | "\n", 63 | "df = ddf[float_cols+int_cols]\n", 64 | " \n", 65 | "print(df.tail())\n", 66 | "print(df.dtypes)\n", 67 | "\n", 68 | "dd.to_parquet(\"../data/nyc_taxi_hours.parq\", df, compression=\"SNAPPY\", has_nulls=False, write_index=False)" 69 | ] 70 | } 71 | ], 72 | "metadata": { 73 | "kernelspec": { 74 | "display_name": "Python 3", 75 | "language": "python", 76 | "name": "python3" 77 | }, 78 | "language_info": { 79 | "codemirror_mode": { 80 | "name": "ipython", 81 | "version": 3 82 | }, 83 | "file_extension": ".py", 84 | "mimetype": "text/x-python", 85 | "name": "python", 86 | "nbconvert_exporter": "python", 87 | "pygments_lexer": "ipython3", 88 | "version": "3.6.2" 89 | } 90 | }, 91 | "nbformat": 4, 92 | "nbformat_minor": 2 93 | } 94 | -------------------------------------------------------------------------------- /solutions/00-welcome-with-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

00. Introduction and Setup

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "\n", 16 | "\n", 17 | "Welcome to the HoloViews JupyterCon 2017 tutorial!\n", 18 | "\n", 19 | "This notebook serves as the homepage of the tutorial, including a general overview, instructions to check that everything is installed properly, and a table of contents listing each tutorial section.\n", 20 | "\n", 21 | "\n", 22 | "## What is this all about?\n", 23 | "\n", 24 | "HoloViews is an [open-source](https://github.com/ioam/holoviews) Python library that makes it simpler to explore your data and communicate the results to others. Compared to other tools, the most important feature of HoloViews is that:\n", 25 | "\n", 26 | "**HoloViews lets you work seamlessly with both the data** ***and*** **its graphical representation.**\n", 27 | "\n", 28 | "When using HoloViews, the focus is on bundling your data together with the appropriate metadata to support both analysis and plotting, making your raw data *and* its visualization equally accessible at all times. This tutorial will introduce HoloViews and guide you through the process of building rich, deployable visualizations based on [Bokeh](bokeh.pydata.org), [Datashader](https://github.com/bokeh/datashader), and (briefly) [matplotlib](http://matplotlib.org)." 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "## Index and Schedule\n", 36 | "\n", 37 | "This four-hour tutorial is broken down into the following sections:\n", 38 | "\n", 39 | "* **40 min**  [1 - Introduction](./01-introduction-to-elements.ipynb): Get started by creating a variety of different HoloViews \"elements\".\n", 40 | "* **20 min**  [2 - Customizing visual appearance](./02-customizing-visual-appearance.ipynb): How to change the appearance and output format of elements.\n", 41 | "* **30 min**  [3 - Exploration with containers](./03-exploration-with-containers.ipynb): Using HoloViews \"containers\" for quick, easy data exploration.\n", 42 | "* **15 min**  *Break*\n", 43 | "* **30 min**  [4 - Working with tabular data](./04-working-with-tabular-data.ipynb): Exploring a tabular (columnar) dataset.\n", 44 | "* **20 min**  [5 - Working with gridded data](./05-working-with-gridded-data.ipynb): Exploring a gridded (n-dimensional) dataset.\n", 45 | "* **30 min**  [6 - Custom interactivity](./06-custom-interactivity.ipynb): Using HoloViews \"streams\" to add interactivity to your visualizations.\n", 46 | "*   **5 min**  *Break*\n", 47 | "* **20 min**  [7 - Working with large data](./07-working-with-large-datasets.ipynb): Using datasets too large to feed directly to your browser.\n", 48 | "* **30 min**  [8 - Deploying Bokeh Apps](./08-deploying-bokeh-apps.ipynb): Deploying your visualizations using Bokeh server.\n", 49 | "\n", 50 | "\n", 51 | "## Related links\n", 52 | "\n", 53 | "You will find extensive support material on our website [holoviews.org](http://www.holoviews.org). In particular, you may find these links useful during the tutorial:\n", 54 | "\n", 55 | "* [Reference gallery](http://holoviews.org/reference/index.html): Visual reference of all elements and containers, along with some other components\n", 56 | "* [Getting started guide](http://holoviews.org/getting_started/index.html): Covers some of the same topics as this tutorial, but without exercises\n", 57 | "\n", 58 | "## Getting set up\n", 59 | "\n", 60 | "Please consult the tutorial repository [README](https://github.com/ioam/scipy-2017-holoviews-tutorial/blob/master/README.rst) for instructions on setting up your environment. Here is the condensed version of these instructions for unix-based systems (Linux or Mac OS X):\n", 61 | "\n", 62 | "```bash\n", 63 | "$ conda env create -f environment.yml\n", 64 | "$ source activate hvtutorial\n", 65 | "$ cd notebooks\n", 66 | "```\n", 67 | "\n", 68 | "If you have any problems with running these instructions, you can conveniently view the full instructions within this notebook by running the following cell:" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": { 75 | "collapsed": true 76 | }, 77 | "outputs": [], 78 | "source": [ 79 | "from IPython.core import page\n", 80 | "with open('../README.rst', 'r') as f:\n", 81 | " page.page(f.read())" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "If you created the environment last week, make sure to ``git pull``, activate the ``hvtutorial`` environment in the ``notebooks`` directory and run:\n", 89 | "\n", 90 | "```\n", 91 | "git pull\n", 92 | "conda env update -f ../environment.yml\n", 93 | "```\n", 94 | "\n", 95 | "Now you can launch the notebook server:\n", 96 | "\n", 97 | "```bash\n", 98 | "$ jupyter notebook --NotebookApp.iopub_data_rate_limit=100000000\n", 99 | "```" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "Once the environment is set up, the following cell should print '1.8.1':" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "import holoviews as hv\n", 116 | "hv.__version__" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "And you should see the HoloViews logo after running the following cell:" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "hv.extension('bokeh', 'matplotlib')" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "The next cell tests the key imports needed for this tutorial:" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "import bokeh\n", 149 | "import matplotlib\n", 150 | "import pandas\n", 151 | "import datashader\n", 152 | "import dask\n", 153 | "import geoviews" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "Lastly, let's make sure the large taxi dataset is available - instructions for acquiring this dataset may be found in the [README](https://github.com/ioam/jupytercon2017-holoviews-tutorial/blob/master/README.rst):" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "import os\n", 170 | "if not os.path.isfile('./assets/nyc_taxi.csv'):\n", 171 | " print('Taxi dataset not found.')" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "## Recommended configuration" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "The following configuration options are recommended additions to your '~/.holoviews.rc' file as they improve the tutorial experience and will be the default behaviour in future:" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "lines = ['import holoviews as hv', 'hv.extension.case_sensitive_completion=True',\n", 195 | " \"hv.Dataset.datatype = ['dataframe']+hv.Dataset.datatype\"]\n", 196 | "print('\\n'.join(lines))" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "If you do not have a holoviews.rc already, simply run the following cell to generate one containing the above lines:" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": { 210 | "collapsed": true 211 | }, 212 | "outputs": [], 213 | "source": [ 214 | "rcpath = os.path.join(os.path.expanduser('~'), '.holoviews.rc')\n", 215 | "if not os.path.isfile(rcpath):\n", 216 | " with open(rcpath, 'w') as f:\n", 217 | " f.write('\\n'.join(lines))" 218 | ] 219 | } 220 | ], 221 | "metadata": { 222 | "kernelspec": { 223 | "display_name": "Python 3", 224 | "language": "python", 225 | "name": "python3" 226 | }, 227 | "language_info": { 228 | "codemirror_mode": { 229 | "name": "ipython", 230 | "version": 3 231 | }, 232 | "file_extension": ".py", 233 | "mimetype": "text/x-python", 234 | "name": "python", 235 | "nbconvert_exporter": "python", 236 | "pygments_lexer": "ipython3", 237 | "version": "3.6.1" 238 | } 239 | }, 240 | "nbformat": 4, 241 | "nbformat_minor": 2 242 | } 243 | -------------------------------------------------------------------------------- /solutions/04-working-with-tabular-data-with-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

04. Working with Tabular Datasets

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "As we have already discovered, elements are simple wrappers around your data that provide a semantically meaningful representation. Tabular data (also called columnar data) is one of the most common, general, and versatile data formats, corresponding to how data is laid out in a spreadsheet. There are many different ways to put data into a tabular format, but for interactive analysis having [**tidy data**](http://www.jeannicholashould.com/tidy-data-in-python.html) provides flexibility and simplicity.\n", 16 | "\n", 17 | "In this tutorial all the information you have learned in the previous sections will finally really pay off. We will discover how to facet data and use different element types to explore and visualize the data contained in a real dataset." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": { 24 | "collapsed": true 25 | }, 26 | "outputs": [], 27 | "source": [ 28 | "import numpy as np\n", 29 | "import scipy.stats as ss\n", 30 | "import pandas as pd\n", 31 | "import holoviews as hv\n", 32 | "hv.extension('bokeh')\n", 33 | "%opts Curve Scatter [tools=['hover']]" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## What is tabular, tidy data?" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": { 47 | "collapsed": true 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "macro_df = pd.read_csv('../data/macro.csv')\n", 52 | "macro_df.head()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "For tidy data, the **columns** of the table represent **variables** or **dimensions** and the **rows** represent **observations**." 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## Declaring dimensions" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "Mathematical variables can usually be described as **dependent** or **independent**. In HoloViews these correspond to value dimensions and key dimensions (respectively).\n", 74 | "\n", 75 | "In this dataset ``'country'`` and ``'year'`` are independent variables or key dimensions, while the remainder are automatically inferred as value dimensions:" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": { 82 | "collapsed": true 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "macro = hv.Dataset(macro_df, kdims=['country', 'year'])\n", 87 | "macro" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "We will also give the dimensions more sensible labels using ``redim.label``:" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": { 101 | "collapsed": true 102 | }, 103 | "outputs": [], 104 | "source": [ 105 | "macro = macro.redim.label(growth='GDP Growth', unem='Unemployment', year='Year', country='Country')" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Mapping dimensions to elements" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "Once we have a ``Dataset`` with multiple dimensions we can map these dimensions onto elements onto the ``.to`` method. The method takes four main arguments:\n", 120 | "\n", 121 | "1. The element you want to convert to\n", 122 | "2. The key dimensions (or independent variables to display)\n", 123 | "3. The dependent variables to display\n", 124 | "4. The dimensions to group by" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "As a first simple example let's go through such a declaration:\n", 132 | "\n", 133 | "1. We will use a ``Curve``\n", 134 | "2. Our independent variable will be the 'year'\n", 135 | "3. Our dependent variable will be 'unem'\n", 136 | "4. We will ``groupby`` the 'country'." 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": { 143 | "collapsed": true 144 | }, 145 | "outputs": [], 146 | "source": [ 147 | "curves = macro.to(hv.Curve, kdims='year', vdims='unem', groupby='country')\n", 148 | "curves" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Alternatively we could also group by the year and view the unemployment rate by country as Bars instead:" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": null, 161 | "metadata": { 162 | "collapsed": true 163 | }, 164 | "outputs": [], 165 | "source": [ 166 | "%%opts Bars [width=600 xrotation=45]\n", 167 | "bars = macro.sort('country').to(hv.Bars, kdims='country', vdims='unem', groupby='year')\n", 168 | "bars" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": null, 174 | "metadata": { 175 | "collapsed": true 176 | }, 177 | "outputs": [], 178 | "source": [ 179 | "%%opts HeatMap [width=600 xrotation=90 tools=['hover']]\n", 180 | "# Exercise: Create a HeatMap using ``macro.to``, declaring kdims 'year' and 'country', and vdims 'growth'\n", 181 | "# You'll need to declare ``width`` and ``xrotation`` plot options for HeatMap to make the plot readable\n", 182 | "# You can also add ``tools=['hover']`` to get more info\n", 183 | "macro.to(hv.HeatMap, kdims=['year', 'country'], vdims=['growth'])" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "## Displaying distributions\n", 191 | "\n", 192 | "Often we want to summarize the distribution of values, e.g. to reveal the distribution of unemployment rates for each OECD country across time. This means we want to ignore the 'year' dimension in our dataset, letting it be summarized instead. To stop HoloViews from grouping by the extra variable, we pass an empty list to the groupby argument." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "metadata": { 199 | "collapsed": true 200 | }, 201 | "outputs": [], 202 | "source": [ 203 | "%%opts BoxWhisker [width=800 xrotation=30] (box_fill_color=Palette('Category20'))\n", 204 | "macro.to(hv.BoxWhisker, 'country', 'growth', groupby=[])" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "metadata": { 211 | "collapsed": true 212 | }, 213 | "outputs": [], 214 | "source": [ 215 | "%%opts BoxWhisker [width=800 xrotation=30] (box_fill_color=Palette('Category20'))\n", 216 | "# Exercise: Display the distribution of GDP growth by year using the BoxWhisker element\n", 217 | "macro.to(hv.BoxWhisker, 'year', 'growth', groupby=[])" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "## Faceting dimensions\n", 225 | "\n", 226 | "In the previous section we discovered how to facet our data using the ``.overlay``, ``.grid`` and ``.layout`` methods. Instead of working with more abstract FM modulation signals, we now have concrete variables to group by, namely the 'country' and 'year':" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": { 233 | "collapsed": true 234 | }, 235 | "outputs": [], 236 | "source": [ 237 | "%%opts Scatter [width=800 height=400 size_index='growth'] (color=Palette('Category20') size=5)\n", 238 | "%%opts NdOverlay [legend_position='left']\n", 239 | "macro.to(hv.Scatter, 'year', ['unem', 'growth']).overlay().relabel('OECD Unemployment 1960 - 1990')" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "metadata": { 246 | "collapsed": true 247 | }, 248 | "outputs": [], 249 | "source": [ 250 | "# Exercise: Instead of faceting using an .overlay() of Scatter elements, facet the data using a .grid() \n", 251 | "# of Curve or Area elements\n", 252 | "macro.to(hv.Curve, 'year', ['unem', 'growth']).grid().relabel('OECD Unemployment 1960 - 1990')" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": null, 258 | "metadata": { 259 | "collapsed": true 260 | }, 261 | "outputs": [], 262 | "source": [ 263 | "%%opts GridSpace [shared_yaxis=True]\n", 264 | "# Exercise: You'll notice that you get quite a lot of countries in the grid. \n", 265 | "# You can try supplying a short list of countries to the 'macro.select` method to get a more-practical subset.\n", 266 | "# Hint: You may want to pass the shared_yaxis=True plot option to GridSpace, to get a y-axis\n", 267 | "macro.select(country=['Italy', 'France', 'Sweden', 'Netherlands']).to(hv.Area, 'year', 'unem').grid()" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": {}, 273 | "source": [ 274 | "## Aggregating" 275 | ] 276 | }, 277 | { 278 | "cell_type": "markdown", 279 | "metadata": {}, 280 | "source": [ 281 | "Another common operation is computing aggregates. We can also compute and visualize these easily using the ``aggregate`` method. Simply supply the dimension(s) to aggregate by and supply a function and optionally a secondary function to compute the spread. Once we have computed the aggregate we can simply pass it to the [``Curve``](http://holoviews.org/reference/elements/bokeh/Curve.html) and [``ErrorBars``](http://holoviews.org/reference/elements/bokeh/ErrorBars.html):" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": null, 287 | "metadata": { 288 | "collapsed": true 289 | }, 290 | "outputs": [], 291 | "source": [ 292 | "%%opts Curve [width=600]\n", 293 | "agg = macro.aggregate('year', function=np.mean, spreadfn=np.std)\n", 294 | "(hv.Curve(agg) * hv.ErrorBars(agg, kdims=['year'], vdims=['growth', 'growth_std']))" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": null, 300 | "metadata": { 301 | "collapsed": true 302 | }, 303 | "outputs": [], 304 | "source": [ 305 | "%%opts Bars [width=800 xrotation=90]\n", 306 | "# Exercise: Display aggregate GDP growth by country, building it up in a series of steps\n", 307 | "# Step 1. First, aggregate the data by country rather than by year, using\n", 308 | "# np.mean and ss.sem as the function and spreadfn, respectively, then \n", 309 | "# make a `Bars` element from the resulting ``agg``\n", 310 | "agg = macro.aggregate('country', function=np.mean, spreadfn=ss.sem)\n", 311 | "hv.Bars(agg).sort() " 312 | ] 313 | }, 314 | { 315 | "cell_type": "code", 316 | "execution_count": null, 317 | "metadata": { 318 | "collapsed": true 319 | }, 320 | "outputs": [], 321 | "source": [ 322 | "%%opts Bars [width=800 xrotation=90]\n", 323 | "# Step 2: You should now have a bars plot, but with no error bars. To add the error bars,\n", 324 | "# print the 'agg' as text to see which vdims are available (which will be different for \n", 325 | "# different spreadfns), then overlay ErrorBars as above but for the new kdims and\n", 326 | "# the appropriate vdims\n", 327 | "# Hint: You'll want to make the plot wider and use an xrotation to see the labels clearly\n", 328 | "hv.Bars(agg).sort() * hv.ErrorBars(agg, kdims=['country'], vdims=['growth', 'growth_sem'])" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "metadata": {}, 334 | "source": [ 335 | "## Onward\n", 336 | "\n", 337 | "* Go through the Tabular Data [getting started](http://build.holoviews.org/getting_started/Tabular_Datasets.html) and [user guide](http://build.holoviews.org/user_guide/Tabular_Datasets.html).\n", 338 | "* Learn about slicing, indexing and sampling in the [Indexing and Selecting Data](http://holoviews.org/user_guide/Indexing_and_Selecting_Data.html) user guide.\n", 339 | "\n", 340 | "The next section shows a similar approach, but for working with gridded data, in multidimensional array formats." 341 | ] 342 | } 343 | ], 344 | "metadata": { 345 | "kernelspec": { 346 | "display_name": "Python 3", 347 | "language": "python", 348 | "name": "python3" 349 | }, 350 | "language_info": { 351 | "codemirror_mode": { 352 | "name": "ipython", 353 | "version": 3 354 | }, 355 | "file_extension": ".py", 356 | "mimetype": "text/x-python", 357 | "name": "python", 358 | "nbconvert_exporter": "python", 359 | "pygments_lexer": "ipython3", 360 | "version": "3.6.1" 361 | } 362 | }, 363 | "nbformat": 4, 364 | "nbformat_minor": 2 365 | } 366 | -------------------------------------------------------------------------------- /solutions/05-working-with-gridded-data-with-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

05. Working with Gridded Datasets

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "Many datasets in science and engineering consist of n-dimensional data. Gridded datasets usually represent observations of some continuous variable across multiple dimensions---a monochrome image representing luminance values across a 2D surface, volumetric 3D data, an RGB image sequence over time, or any other multi-dimensional parameter space. This type of data is particularly common in research areas that make use of spatial imaging or modeling, such as climatology, biology, and astronomy, but can also be used to represent any arbitrary data that varies over multiple dimensions.\n", 16 | "\n", 17 | "xarray is a convenient way of working with and representing labelled n-dimensional arrays, like pandas for labelled n-D arrays:\n", 18 | "
\n", 19 | "" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import numpy as np\n", 29 | "import pandas as pd\n", 30 | "import holoviews as hv\n", 31 | "hv.extension('bokeh', 'matplotlib')" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "## Load the data" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "import xarray as xr\n", 48 | "mri_xr = xr.open_dataset('../data/mri.nc')\n", 49 | "mri_xr" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "The data here represents volumetric data from an [MRI scan](https://graphics.stanford.edu/data/voldata/), with three coordinate dimensions 'x', 'y' and 'z'. In this simple example these coordinates are integers, but they are not required to be. Instead of volumetric data, we could imagine the data could be 2D spatial data that evolves over time, as is common in climatology and many other fields." 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "## Declaring the dataset\n", 64 | "\n", 65 | "In a gridded dataset the dimensions are typically alreay declared unambiguously, with **coordinates** (i.e. key dimensions) and **data variables** (i.e. value dimensions) that HoloViews can determine automatically:" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "mri = hv.Dataset(mri_xr)\n", 75 | "mri" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "## Displaying the data" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "Just as we saw in the previous tutorial, we can group the data by one or more dimensions. Since we are dealing with volumetric data but have only a 2D display device, we can take slices along each axis. Here we will slice along the sagittal plane corresponding to the z-dimension:" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "mri.to(hv.Image, groupby='z', dynamic=True)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "# Exercise: Display transverse or frontal sections of the data by declaring the kdims in the .to method\n", 108 | "mri.to(hv.Image, ['z', 'y'], dynamic=True).relabel('Frontal') + \\\n", 109 | "mri.to(hv.Image, ['z', 'x'], dynamic=True).relabel('Transverse')" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "## Slice and dice across n-dimensions" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "We can use ``.to`` to slice the cube along all three axes separately:" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "%%opts Image {+axiswise} [xaxis=None yaxis=None width=225 height=225]\n", 133 | "(mri.to(hv.Image, ['z', 'y'], dynamic=True) +\n", 134 | " mri.to(hv.Image, ['z', 'x'], dynamic=True) +\n", 135 | " mri.to(hv.Image, ['x', 'y'], dynamic=True)).redim.range(MR=(0, 255))" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "## Aggregation\n", 143 | "\n", 144 | "We can also easily compute aggregates across one or more dimensions. Previously we used the ``aggregate`` method for this purpose, but when working with gridded datasets it often makes more sense to think of aggregation as a ``reduce`` operation. We can for example reduce the ``z`` dimension using ``np.mean`` and display the resulting averaged 2D array as an [``Image``](http://holoviews.org/reference/elements/bokeh/Image.html):" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "hv.Image(mri.reduce(z=np.mean))" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "# Exercise: Recreate the plot above using the aggregate method\n", 163 | "# Hint: The aggregate and reduce methods are inverses of each other\n", 164 | "# Try typing \"hv.Image(mri.aggregate(\" to see the signature of `aggregate`.\n", 165 | "hv.Image(mri.aggregate(['x', 'y'], np.mean))" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "As you can see, it is straightforward to work with the additional dimensions of data available in gridded datasets, as explained in more detail in the [user guide](http://holoviews.org/user_guide/Gridded_Datasets.html).\n", 173 | "\n", 174 | "# Onwards\n", 175 | "\n", 176 | "The previous sections focused on displaying plots that provide certain standard types of interactivity, whether widget-based (to select values along a dimension) or within each plot (for panning, zooming, etc.). A wide range of additional types of interactivity can also be defined by the user for working with specific types of data, as outlined in the next section." 177 | ] 178 | } 179 | ], 180 | "metadata": { 181 | "kernelspec": { 182 | "display_name": "Python 3", 183 | "language": "python", 184 | "name": "python3" 185 | }, 186 | "language_info": { 187 | "codemirror_mode": { 188 | "name": "ipython", 189 | "version": 3 190 | }, 191 | "file_extension": ".py", 192 | "mimetype": "text/x-python", 193 | "name": "python", 194 | "nbconvert_exporter": "python", 195 | "pygments_lexer": "ipython3", 196 | "version": "3.6.1" 197 | } 198 | }, 199 | "nbformat": 4, 200 | "nbformat_minor": 2 201 | } 202 | -------------------------------------------------------------------------------- /solutions/08-deploying-bokeh-apps-with-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\"HV+BK\n", 8 | "

08. Deploying Bokeh Apps

" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "In the previous sections we discovered how to use a ``HoloMap`` to build a Jupyter notebook with interactive visualizations that can be exported to a standalone HTML file, as well as how to use ``DynamicMap`` and ``Streams`` to set up dynamic interactivity backed by the Jupyter Python kernel. However, frequently we want to package our visualization or dashboard for wider distribution, backed by Python but run outside of the notebook environment. Bokeh Server provides a flexible and scalable architecture to deploy complex interactive visualizations and dashboards, integrating seamlessly with Bokeh and with HoloViews.\n", 16 | "\n", 17 | "For a detailed background on Bokeh Server see [the bokeh user guide](http://bokeh.pydata.org/en/latest/docs/user_guide/server.html). In this tutorial we will discover how to deploy the visualizations we have created so far as a standalone bokeh server app, and how to flexibly combine HoloViews and Bokeh APIs to build highly customized apps. We will also reuse a lot of what we have learned so far---loading large, tabular datasets, applying datashader operations to them, and adding linked streams to our app." 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "## A simple bokeh app\n", 25 | "\n", 26 | "The preceding sections of this tutorial focused solely on the Jupyter notebook, but now let's look at a bare Python script that can be deployed using Bokeh Server:" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "with open('./apps/server_app.py', 'r') as f:\n", 36 | " print(f.read())" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "Of the three parts of this app, part 2 should be very familiar by now -- load some taxi dropoff locations, declare a Points object, datashade them, and set some plot options.\n", 44 | "\n", 45 | "Step 1 is new: Instead of loading the bokeh extension using ``hv.extension('bokeh')``, we get a direct handle on a bokeh renderer using the ``hv.renderer`` function. This has to be done at the top of the script, to be sure that options declared are passed to the Bokeh renderer. \n", 46 | "\n", 47 | "Step 3 is also new: instead of typing ``app`` to see the visualization as we would in the notebook, here we create a Bokeh document from it by passing the HoloViews object to the ``renderer.server_doc`` method. \n", 48 | "\n", 49 | "Steps 1 and 3 are essentially boilerplate, so you can now use this simple skeleton to turn any HoloViews object into a fully functional, deployable Bokeh app!\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "## Deploying the app\n", 57 | "\n", 58 | "Assuming that you have a terminal window open with the ``hvtutorial`` environment activated, in the ``notebooks/`` directory, you can launch this app using Bokeh Server:\n", 59 | "\n", 60 | "```\n", 61 | "bokeh serve --show apps/server_app.py\n", 62 | "```\n", 63 | "\n", 64 | "If you don't already have a favorite way to get a terminal, one way is to [open it from within Jupyter](../terminals/1), then make sure you are in the ``notebooks`` directory, and activate the environment using ``source activate hvtutorial`` (or ``activate tutorial`` on Windows). You can also [open the app script file](../edit/apps/server_app.py) in the inbuilt text editor, or you can use your own preferred editor." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "# Exercise: Modify the app to display the pickup locations and add a tilesource, then run the app with bokeh serve\n", 74 | "# Tip: Refer to the previous notebook\n", 75 | "with open('./apps/server_app_with_solutions.py', 'r') as f:\n", 76 | " print(f.read())\n", 77 | " \n", 78 | "# Run using: bokeh serve --show apps/server_app_with_solutions.py" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "## Iteratively building a bokeh app in the notebook\n", 86 | "\n", 87 | "The above app script can be built entirely without using Jupyter, though we displayed it here using Jupyter for convenience in the tutorial. Jupyter notebooks are also often helpful when initially developing such apps, allowing you to quickly iterate over visualizations in the notebook, deploying it as a standalone app only once we are happy with it.\n", 88 | "\n", 89 | "To illustrate this process, let's quickly go through such a workflow. As before we will set up our imports, load the extension, and load the taxi dataset:" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": { 96 | "collapsed": true 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "import holoviews as hv\n", 101 | "import geoviews as gv\n", 102 | "import dask.dataframe as dd\n", 103 | "\n", 104 | "from holoviews.operation.datashader import datashade, aggregate, shade\n", 105 | "from bokeh.models import WMTSTileSource\n", 106 | "\n", 107 | "hv.extension('bokeh', logo=False)\n", 108 | "\n", 109 | "usecols = ['tpep_pickup_datetime', 'dropoff_x', 'dropoff_y']\n", 110 | "ddf = dd.read_csv('../data/nyc_taxi.csv', parse_dates=['tpep_pickup_datetime'], usecols=usecols)\n", 111 | "ddf['hour'] = ddf.tpep_pickup_datetime.dt.hour\n", 112 | "ddf = ddf.persist()" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "Next we define a ``Counter`` stream which we will use to select taxi trips by hour." 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": { 126 | "collapsed": true 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "stream = hv.streams.Counter()\n", 131 | "points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y'])\n", 132 | "dmap = hv.DynamicMap(lambda counter: points.select(hour=counter%24).relabel('Hour: %s' % (counter % 24)),\n", 133 | " streams=[stream])\n", 134 | "shaded = datashade(dmap)\n", 135 | "\n", 136 | "hv.opts('RGB [width=800, height=600, xaxis=None, yaxis=None]')\n", 137 | "\n", 138 | "url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg'\n", 139 | "wmts = gv.WMTS(WMTSTileSource(url=url))\n", 140 | "\n", 141 | "overlay = wmts * shaded" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "Up to this point, we have a normal HoloViews notebook that we could display using Jupyter's rich display of ``overlay``, as we would with an any notebook. But having come up with the objects we want interactively in this way, we can now display the result as a Bokeh app, without leaving the notebook. To do that, first edit the following cell to change \"8888\" to whatever port your jupyter session is using, in case your URL bar doesn't say \"localhost:8888/\".\n", 149 | "\n", 150 | "Then run this cell to launch the Bokeh app within this notebook:" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": { 157 | "collapsed": true, 158 | "scrolled": false 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "renderer = hv.renderer('bokeh')\n", 163 | "server = renderer.app(overlay, show=True, websocket_origin='localhost:8888')" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "We could stop here, having launched an app, but so far the app will work just the same as in the normal Jupyter notebook, responding to user inputs as they occur. Having defined a ``Counter`` stream above, let's go one step further and add a series of periodic events that will let the visualization play on its own even without any user input:" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "metadata": { 177 | "collapsed": true 178 | }, 179 | "outputs": [], 180 | "source": [ 181 | "dmap.periodic(1)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": { 187 | "collapsed": true 188 | }, 189 | "source": [ 190 | "You can stop this ongoing process by clearing the cell displaying the app.\n", 191 | "\n", 192 | "Now let's open the [text editor](../edit/apps/periodic_app.py) again and make this edit to a separate app, which we can then launch using Bokeh Server separately from this notebook." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "metadata": { 199 | "scrolled": false 200 | }, 201 | "outputs": [], 202 | "source": [ 203 | "# Exercise: Copy the example above into periodic_app.py and modify it so it can be run with bokeh serve\n", 204 | "# Hint: Use hv.renderer and renderer.server_doc\n", 205 | "# Note that you have to run periodic **after** creating the bokeh document\n", 206 | "with open('./apps/periodic_app.py', 'r') as f:\n", 207 | " print(f.read())\n", 208 | " \n", 209 | "# Run using: bokeh serve --show apps/periodic_app.py" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "## Combining HoloViews with bokeh models" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": {}, 222 | "source": [ 223 | "Now for a last hurrah let's put everything we have learned to good use and create a bokeh app with it. This time we will go straight to a [Python script containing the app](../edit/apps/player_app.py). If you run the app with ``bokeh serve --show ./apps/player_app.py`` from [your terminal](../terminals/1) you should see something like this:\n", 224 | "\n", 225 | "" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "This more complex app consists of several components:\n", 233 | "\n", 234 | "1. A datashaded plot of points for the indicated hour of the daty (in the slider widget)\n", 235 | "2. A linked ``PointerX`` stream, to compute a cross-section\n", 236 | "3. A set of custom bokeh widgets linked to the hour-of-day stream\n", 237 | "\n", 238 | "We have already covered 1. and 2. so we will focus on 3., which shows how easily we can combine a HoloViews plot with custom Bokeh models. We will not look at the precise widgets in too much detail, instead let's have a quick look at the callback defined for slider widget updates:\n", 239 | "\n", 240 | "```python\n", 241 | "def slider_update(attrname, old, new):\n", 242 | " stream.event(hour=new)\n", 243 | "```\n", 244 | "\n", 245 | "Whenever the slider value changes this will trigger a stream event updating our plots. The second part is how we combine HoloViews objects and Bokeh models into a single layout we can display. Once again we can use the renderer to convert the HoloViews object into something we can display with Bokeh:\n", 246 | "\n", 247 | "```python\n", 248 | "renderer = hv.renderer('bokeh')\n", 249 | "plot = renderer.get_plot(hvobj, doc=curdoc())\n", 250 | "```\n", 251 | "\n", 252 | "The ``plot`` instance here has a ``state`` attribute that represents the actual Bokeh model, which means we can combine it into a Bokeh layout just like any other Bokeh model:\n", 253 | "\n", 254 | "```python\n", 255 | "layout = layout([[plot.state], [slider, button]], sizing_mode='fixed')\n", 256 | "curdoc().add_root(layout)\n", 257 | "```" 258 | ] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "metadata": {}, 264 | "outputs": [], 265 | "source": [ 266 | "# Advanced Exercise: Add a histogram to the bokeh layout next to the datashaded plot\n", 267 | "# Hint: Declare the histogram like this: hv.operation.histogram(aggregated, bin_range=(0, 20))\n", 268 | "# then use renderer.get_plot and hist_plot.state and add it to the layout\n", 269 | "with open('./apps/player_app_with_solutions.py', 'r') as f:\n", 270 | " print(f.read())\n", 271 | " \n", 272 | "# Run using: bokeh serve --show apps/player_app_with_solutions.py" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "# Onwards\n", 280 | "\n", 281 | "Although the code above is more complex than in previous sections, it's actually providing a huge range of custom types of interactivity, which if implemented in Bokeh alone would have required far more than a notebook cell of code. Hopefully it is clear that arbitrarily complex collections of visualizations and interactive controls can be built from the components provided by HoloViews, allowing you to make simple analyses very easily and making it practical to make even quite complex apps when needed. The [user guide](http://holoviews.org/user_guide), [gallery](http://holoviews.org/gallery/index.html), and [reference gallery](http://holoviews.org/reference) should have all the information you need to get started with all this power on your own datasets and tasks. Good luck!" 282 | ] 283 | } 284 | ], 285 | "metadata": { 286 | "kernelspec": { 287 | "display_name": "Python 3", 288 | "language": "python", 289 | "name": "python3" 290 | }, 291 | "language_info": { 292 | "codemirror_mode": { 293 | "name": "ipython", 294 | "version": 3 295 | }, 296 | "file_extension": ".py", 297 | "mimetype": "text/x-python", 298 | "name": "python", 299 | "nbconvert_exporter": "python", 300 | "pygments_lexer": "ipython3", 301 | "version": "3.6.1" 302 | } 303 | }, 304 | "nbformat": 4, 305 | "nbformat_minor": 2 306 | } 307 | -------------------------------------------------------------------------------- /solutions/README.md: -------------------------------------------------------------------------------- 1 | 2 | The notebooks in this directory are those presented during the tutorial, 3 | including exercise solutions. The following snapshots of these notebooks 4 | are available online via [nbviewer](https://nbviewer.jupyter.org). 5 | 6 | Note that some of the output in notebooks 3,4,5,6 and 7 requires a live 7 | server to support the interactivity offered by HoloViews widgets and 8 | Bokeh tools. 9 | 10 | 11 | [00-welcome](https://nbviewer.jupyter.org/gist/jlstevens/adb550e7a43fbafafeef7bd4d263e026): Get started by creating a variety of different HoloViews "elements".
12 | [01-introduction-to-elements](https://nbviewer.jupyter.org/gist/jlstevens/93fec6e5c35262fbbb6ecb16b7b4d6ed): How to change the appearance and output format of elements.
13 | [02-customizing-visual-appearance](https://nbviewer.jupyter.org/gist/jlstevens/2fcdb6795801b93ef5bb7fd0301a2cb5): Customizing visual appearance
14 | [03-exploration-with-containers](https://nbviewer.jupyter.org/gist/jlstevens/cefb3a51ec1927613a48cfea73dc25f4): Using HoloViews "containers" for quick, easy data exploration.
15 | [04-working-with-tabular-data](https://nbviewer.jupyter.org/gist/jlstevens/d8c2233b8e9ae6aeddd8bd3673c2504b): Exploring a tabular (columnar) dataset.
16 | [05-working-with-gridded-data](https://nbviewer.jupyter.org/gist/jlstevens/addb349ab03ae1d5181cd9b7013aaeb7): Exploring a gridded (n-dimensional) dataset.
17 | [06-custom-interactivity](https://nbviewer.jupyter.org/gist/jlstevens/3942b51e0e7a75df9a437f151936ef70): Using HoloViews "streams" to add interactivity to your visualizations.
18 | [07-working-with-large-datasets](https://nbviewer.jupyter.org/gist/jlstevens/4f7348b5fa67d51d3b71670fca5c22ae): Working with large data: Using datasets too large to feed directly to your browser.
19 | [08-deploying-bokeh-apps](https://nbviewer.jupyter.org/gist/jlstevens/f13d16581399195a5889d95ecd70b3b8): Deploying Bokeh Apps: Deploying your visualizations using Bokeh server.
-------------------------------------------------------------------------------- /solutions/apps/periodic_app.py: -------------------------------------------------------------------------------- 1 | import holoviews as hv 2 | import geoviews as gv 3 | import dask.dataframe as dd 4 | 5 | from holoviews.operation.datashader import datashade, aggregate, shade 6 | from bokeh.models import WMTSTileSource 7 | 8 | renderer = hv.renderer('bokeh') 9 | 10 | # Load data 11 | usecols = ['tpep_pickup_datetime', 'dropoff_x', 'dropoff_y'] 12 | ddf = dd.read_csv('../data/nyc_taxi.csv', parse_dates=['tpep_pickup_datetime'], usecols=usecols) 13 | ddf['hour'] = ddf.tpep_pickup_datetime.dt.hour 14 | ddf = ddf.persist() 15 | 16 | # Declare objects 17 | stream = hv.streams.Counter() 18 | points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y']) 19 | dmap = hv.DynamicMap(lambda counter: points.select(hour=counter%24).relabel('Hour: %s' % (counter % 24)), 20 | streams=[stream]) 21 | shaded = datashade(dmap) 22 | 23 | hv.opts('RGB [width=800, height=600, xaxis=None, yaxis=None]') 24 | 25 | url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg' 26 | wmts = gv.WMTS(WMTSTileSource(url=url)) 27 | 28 | overlay = wmts * shaded 29 | 30 | # Create server document 31 | doc = renderer.server_doc(overlay) 32 | dmap.periodic(1) 33 | -------------------------------------------------------------------------------- /solutions/apps/player_app.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import holoviews as hv 3 | import geoviews as gv 4 | 5 | from bokeh.models import Slider, Button 6 | from bokeh.layouts import layout 7 | from bokeh.io import curdoc 8 | from bokeh.models import WMTSTileSource 9 | 10 | from holoviews.operation.datashader import datashade, aggregate, shade 11 | from holoviews.plotting.util import fire 12 | shade.cmap = fire 13 | 14 | hv.extension('bokeh') 15 | renderer = hv.renderer('bokeh').instance(mode='server') 16 | 17 | # Load data 18 | usecols = ['tpep_pickup_datetime', 'dropoff_x', 'dropoff_y'] 19 | ddf = dd.read_csv('../data/nyc_taxi.csv', parse_dates=['tpep_pickup_datetime'], usecols=usecols) 20 | ddf['hour'] = ddf.tpep_pickup_datetime.dt.hour 21 | ddf = ddf.persist() 22 | 23 | from bokeh.models import WMTSTileSource 24 | url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg' 25 | wmts = gv.WMTS(WMTSTileSource(url=url)) 26 | 27 | stream = hv.streams.Stream.define('HourSelect', hour=0)() 28 | points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y']) 29 | dmap = hv.util.Dynamic(points, operation=lambda obj, hour: obj.select(hour=hour), 30 | streams=[stream]) 31 | 32 | # Apply aggregation 33 | aggregated = aggregate(dmap, link_inputs=True) 34 | 35 | # Shade the data 36 | shaded = shade(aggregated) 37 | 38 | # Define PointerX stream, attach to points and declare DynamicMap for cross-section and VLine 39 | pointer = hv.streams.PointerX(x=ddf.dropoff_x.loc[0].compute().iloc[0], source=points) 40 | section = hv.util.Dynamic(aggregated, operation=lambda obj, x: obj.sample(dropoff_x=x), 41 | streams=[pointer], link_inputs=False) 42 | vline = hv.DynamicMap(lambda x: hv.VLine(x), streams=[pointer]) 43 | 44 | # Define options 45 | hv.opts("RGB [width=800 height=600 xaxis=None yaxis=None] VLine (color='black' line_width=1)") 46 | hv.opts("Curve [width=100 yaxis=None show_frame=False] (color='black') {+framewise} Layout [shared_axes=False]") 47 | 48 | # Combine it all into a complex layout 49 | hvobj = (wmts * shaded * vline) << section 50 | 51 | ### Pass the HoloViews object to the renderer 52 | plot = renderer.get_plot(hvobj, doc=curdoc()) 53 | 54 | # Define a slider and button 55 | start, end = 0, 23 56 | 57 | def slider_update(attrname, old, new): 58 | stream.event(hour=new) 59 | 60 | slider = Slider(start=start, end=end, value=0, step=1, title="Hour") 61 | slider.on_change('value', slider_update) 62 | 63 | def animate_update(): 64 | year = slider.value + 1 65 | if year > end: 66 | year = start 67 | slider.value = year 68 | 69 | def animate(): 70 | if button.label == '► Play': 71 | button.label = '❚❚ Pause' 72 | curdoc().add_periodic_callback(animate_update, 1000) 73 | else: 74 | button.label = '► Play' 75 | curdoc().remove_periodic_callback(animate_update) 76 | 77 | button = Button(label='► Play', width=60) 78 | button.on_click(animate) 79 | 80 | # Combine the bokeh plot on plot.state with the widgets 81 | layout = layout([ 82 | [plot.state], 83 | [slider, button], 84 | ], sizing_mode='fixed') 85 | 86 | curdoc().add_root(layout) 87 | -------------------------------------------------------------------------------- /solutions/apps/player_app_with_solutions.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import holoviews as hv 3 | import geoviews as gv 4 | 5 | from bokeh.models import Slider, Button 6 | from bokeh.layouts import layout 7 | from bokeh.io import curdoc 8 | from bokeh.models import WMTSTileSource 9 | 10 | from holoviews.operation.datashader import datashade, aggregate, shade 11 | from holoviews.plotting.util import fire 12 | shade.cmap = fire 13 | 14 | hv.extension('bokeh') 15 | renderer = hv.renderer('bokeh').instance(mode='server') 16 | 17 | # Load data 18 | usecols = ['tpep_pickup_datetime', 'dropoff_x', 'dropoff_y'] 19 | ddf = dd.read_csv('../data/nyc_taxi.csv', parse_dates=['tpep_pickup_datetime'], usecols=usecols) 20 | ddf['hour'] = ddf.tpep_pickup_datetime.dt.hour 21 | ddf = ddf.persist() 22 | 23 | from bokeh.models import WMTSTileSource 24 | url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg' 25 | wmts = gv.WMTS(WMTSTileSource(url=url)) 26 | 27 | stream = hv.streams.Stream.define('HourSelect', hour=0)() 28 | points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y']) 29 | dmap = hv.util.Dynamic(points, operation=lambda obj, hour: obj.select(hour=hour), 30 | streams=[stream]) 31 | 32 | # Apply aggregation 33 | aggregated = aggregate(dmap, link_inputs=True) 34 | 35 | # Shade the data 36 | shaded = shade(aggregated) 37 | 38 | # Define PointerX stream, attach to points and declare DynamicMap for cross-section and VLine 39 | pointer = hv.streams.PointerX(x=ddf.dropoff_x.loc[0].compute().iloc[0], source=points) 40 | section = hv.util.Dynamic(aggregated, operation=lambda obj, x: obj.sample(dropoff_x=x), 41 | streams=[pointer], link_inputs=False) 42 | vline = hv.DynamicMap(lambda x: hv.VLine(x), streams=[pointer]) 43 | 44 | # Define options 45 | hv.opts("RGB [width=800 height=600 xaxis=None yaxis=None] VLine (color='black' line_width=1)") 46 | hv.opts("Curve [width=100 yaxis=None show_frame=False] (color='black') {+framewise} Layout [shared_axes=False]") 47 | 48 | # Combine it all into a complex layout 49 | hvobj = (wmts * shaded * vline) << section 50 | 51 | ### Pass the HoloViews object to the renderer 52 | plot = renderer.get_plot(hvobj, doc=curdoc()) 53 | 54 | hist = hv.operation.histogram(aggregated, bin_range=(0, 10)) 55 | hist_plot = renderer.get_plot(hist, doc=curdoc()) 56 | 57 | # Define a slider and button 58 | start, end = 0, 23 59 | 60 | def slider_update(attrname, old, new): 61 | stream.event(hour=new) 62 | 63 | slider = Slider(start=start, end=end, value=0, step=1, title="Hour") 64 | slider.on_change('value', slider_update) 65 | 66 | def animate_update(): 67 | year = slider.value + 1 68 | if year > end: 69 | year = start 70 | slider.value = year 71 | 72 | def animate(): 73 | if button.label == '► Play': 74 | button.label = '❚❚ Pause' 75 | curdoc().add_periodic_callback(animate_update, 1000) 76 | else: 77 | button.label = '► Play' 78 | curdoc().remove_periodic_callback(animate_update) 79 | 80 | button = Button(label='► Play', width=60) 81 | button.on_click(animate) 82 | 83 | # Combine the bokeh plot on plot.state with the widgets 84 | layout = layout([ 85 | [plot.state, hist_plot.state], 86 | [slider, button], 87 | ], sizing_mode='fixed') 88 | 89 | curdoc().add_root(layout) 90 | -------------------------------------------------------------------------------- /solutions/apps/server_app.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import holoviews as hv 3 | from holoviews.operation.datashader import datashade 4 | 5 | # 1. Instead of using hv.extension, declare that we are using Bokeh and grab the renderer 6 | renderer = hv.renderer('bokeh') 7 | 8 | # 2. Load data and datashade it 9 | ddf = dd.read_csv('../data/nyc_taxi.csv', usecols=['dropoff_x', 'dropoff_y']).persist() 10 | points = hv.Points(ddf, kdims=['dropoff_x', 'dropoff_y']) 11 | shaded = datashade(points).opts(plot=dict(width=800, height=600)) 12 | 13 | # 3. Instead of Jupyter's automatic rich display, render the object as a bokeh document 14 | doc = renderer.server_doc(shaded) 15 | doc.title = 'HoloViews Bokeh App' 16 | -------------------------------------------------------------------------------- /solutions/apps/server_app_with_solutions.py: -------------------------------------------------------------------------------- 1 | import dask.dataframe as dd 2 | import holoviews as hv 3 | import geoviews as gv 4 | 5 | from bokeh.models import WMTSTileSource 6 | from holoviews.operation.datashader import datashade 7 | 8 | url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg' 9 | wmts = WMTSTileSource(url=url) 10 | 11 | # 1. Instead of using hv.extension, declare that we are using Bokeh and grab the renderer 12 | renderer = hv.renderer('bokeh') 13 | 14 | 15 | # 2. Declare points and datashade them 16 | ddf = dd.read_csv('../data/nyc_taxi.csv', usecols=['pickup_x', 'pickup_y']).persist() 17 | shaded = datashade(hv.Points(ddf)) 18 | 19 | # Set some plot options 20 | app = gv.WMTS(wmts) * shaded.opts(plot=dict(width=800, height=600)) 21 | 22 | # 3. Instead of Jupyter's automatic rich display, render the object as a bokeh document 23 | doc = renderer.server_doc(app) 24 | doc.title = 'HoloViews Bokeh App' 25 | -------------------------------------------------------------------------------- /solutions/assets/bokeh_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 63 | 68 | 74 | 80 | 86 | 92 | 98 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /solutions/assets/dask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/dask.png -------------------------------------------------------------------------------- /solutions/assets/dask.svg: -------------------------------------------------------------------------------- 1 | dask -------------------------------------------------------------------------------- /solutions/assets/datashader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/datashader.png -------------------------------------------------------------------------------- /solutions/assets/datashader_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/datashader_pipeline.png -------------------------------------------------------------------------------- /solutions/assets/header_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/header_logo.png -------------------------------------------------------------------------------- /solutions/assets/holoviews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/holoviews.png -------------------------------------------------------------------------------- /solutions/assets/hv+bk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/hv+bk.png -------------------------------------------------------------------------------- /solutions/assets/matplotlib_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/matplotlib_logo.png -------------------------------------------------------------------------------- /solutions/assets/numba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/numba.png -------------------------------------------------------------------------------- /solutions/assets/tutorial_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/tutorial_app.gif -------------------------------------------------------------------------------- /solutions/assets/xarray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioam/jupytercon2017-holoviews-tutorial/b88b634cadcf4927fff8e5bdaa5b1941525dbff0/solutions/assets/xarray.png --------------------------------------------------------------------------------