{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Introduction: IPython Widgets\n",
"\n",
"In this notebook, we will get an introduction to IPython widgets. These are tools that allow us to build interactivity into our notebooks often with a single line of code. These widgets are very useful for data exploration and analysis, for example, selecting certain data or updating charts. In effect, Widgets allow you to make Jupyter Notebooks into an interactive dashboard instead of a static document."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Run the below cell if needed. You can also do this from the command line. If in Jupyter lab, [check out the instructions for that environment](https://ipywidgets.readthedocs.io/en/stable/user_install.html). "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:24.136509Z",
"start_time": "2019-01-27T15:06:21.746246Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Enabling notebook extension jupyter-js-widgets/extension...\r\n",
" - Validating: \u001b[32mOK\u001b[0m\r\n"
]
}
],
"source": [
"!pip install -U -q ipywidgets\n",
"!jupyter nbextension enable --py widgetsnbextension"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These are the other imports will use. "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:25.377151Z",
"start_time": "2019-01-27T15:06:24.138977Z"
}
},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Standard Data Science Helpers\n",
"import numpy as np\n",
"import pandas as pd\n",
"import scipy\n",
"\n",
"import plotly.plotly as py\n",
"import plotly.graph_objs as go\n",
"from plotly.offline import iplot, init_notebook_mode\n",
"init_notebook_mode(connected=True)\n",
"\n",
"import cufflinks as cf\n",
"cf.go_offline(connected=True)\n",
"cf.set_config_file(colorscale='plotly', world_readable=True)\n",
"\n",
"# Extra options\n",
"pd.options.display.max_rows = 30\n",
"pd.options.display.max_columns = 25\n",
"\n",
"# Show all code cells outputs\n",
"from IPython.core.interactiveshell import InteractiveShell\n",
"InteractiveShell.ast_node_interactivity = 'all'"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:25.382899Z",
"start_time": "2019-01-27T15:06:25.379269Z"
}
},
"outputs": [],
"source": [
"import os\n",
"from IPython.display import Image, display, HTML"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data\n",
"\n",
"For this project, we'll work with my medium stats data. You can grab your own data or just use mine! "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.073141Z",
"start_time": "2019-01-27T15:06:25.385158Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" claps | \n",
" days_since_publication | \n",
" fans | \n",
" link | \n",
" num_responses | \n",
" publication | \n",
" published_date | \n",
" read_ratio | \n",
" read_time | \n",
" reads | \n",
" started_date | \n",
" tags | \n",
" text | \n",
" title | \n",
" title_word_count | \n",
" type | \n",
" views | \n",
" word_count | \n",
" claps_per_word | \n",
" editing_days | \n",
" <tag>Education | \n",
" <tag>Data Science | \n",
" <tag>Towards Data Science | \n",
" <tag>Machine Learning | \n",
" <tag>Python | \n",
"
\n",
" \n",
" \n",
" \n",
" 130 | \n",
" 2 | \n",
" 594.840722 | \n",
" 2 | \n",
" https://medium.com/p/screw-the-environment-but... | \n",
" 0 | \n",
" None | \n",
" 2017-06-10 14:25:00 | \n",
" 42.17 | \n",
" 7 | \n",
" 70 | \n",
" 2017-06-10 14:24:00 | \n",
" [Climate Change, Economics] | \n",
" Screw the Environment, but Consider Your Walle... | \n",
" Screw the Environment, but Consider Your Wallet | \n",
" 8 | \n",
" published | \n",
" 166 | \n",
" 1859 | \n",
" 0.001076 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
"
\n",
" \n",
" 129 | \n",
" 18 | \n",
" 587.522764 | \n",
" 3 | \n",
" https://medium.com/p/the-vanquishing-of-war-pl... | \n",
" 0 | \n",
" None | \n",
" 2017-06-17 22:02:00 | \n",
" 30.51 | \n",
" 14 | \n",
" 54 | \n",
" 2017-06-17 22:02:00 | \n",
" [Climate Change, Humanity, Optimism, History] | \n",
" The Vanquishing of War, Plague and Famine Part... | \n",
" The Vanquishing of War, Plague and Famine | \n",
" 8 | \n",
" published | \n",
" 177 | \n",
" 3891 | \n",
" 0.004626 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
"
\n",
" \n",
" 126 | \n",
" 51 | \n",
" 574.902877 | \n",
" 20 | \n",
" https://medium.com/p/capstone-project-mercedes... | \n",
" 0 | \n",
" None | \n",
" 2017-06-30 12:55:00 | \n",
" 20.04 | \n",
" 42 | \n",
" 222 | \n",
" 2017-06-30 12:00:00 | \n",
" [Machine Learning, Python, Udacity, Kaggle] | \n",
" Capstone Project: Mercedes-Benz Greener Manufa... | \n",
" Capstone Project: Mercedes-Benz Greener Manufa... | \n",
" 7 | \n",
" published | \n",
" 1108 | \n",
" 12025 | \n",
" 0.004241 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 1 | \n",
" 1 | \n",
"
\n",
" \n",
" 120 | \n",
" 0 | \n",
" 574.060273 | \n",
" 0 | \n",
" https://medium.com/p/home-of-the-scared-5af0fe... | \n",
" 0 | \n",
" None | \n",
" 2017-07-01 09:08:00 | \n",
" 35.85 | \n",
" 9 | \n",
" 19 | \n",
" 2017-06-30 18:21:00 | \n",
" [Politics, Books, News, Media Criticism] | \n",
" Home of the Scared A review of A Culture of Fe... | \n",
" Home of the Scared | \n",
" 4 | \n",
" published | \n",
" 53 | \n",
" 2533 | \n",
" 0.000000 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
"
\n",
" \n",
" 121 | \n",
" 0 | \n",
" 570.072623 | \n",
" 0 | \n",
" https://medium.com/p/the-triumph-of-peace-f485... | \n",
" 0 | \n",
" None | \n",
" 2017-07-05 08:51:00 | \n",
" 8.77 | \n",
" 14 | \n",
" 5 | \n",
" 2017-07-03 20:18:00 | \n",
" [Books, Psychology, History, Humanism] | \n",
" The Triumph of Peace A review of The Better An... | \n",
" The Triumph of Peace | \n",
" 4 | \n",
" published | \n",
" 57 | \n",
" 3892 | \n",
" 0.000000 | \n",
" 1 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
" 0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" claps days_since_publication fans \\\n",
"130 2 594.840722 2 \n",
"129 18 587.522764 3 \n",
"126 51 574.902877 20 \n",
"120 0 574.060273 0 \n",
"121 0 570.072623 0 \n",
"\n",
" link num_responses \\\n",
"130 https://medium.com/p/screw-the-environment-but... 0 \n",
"129 https://medium.com/p/the-vanquishing-of-war-pl... 0 \n",
"126 https://medium.com/p/capstone-project-mercedes... 0 \n",
"120 https://medium.com/p/home-of-the-scared-5af0fe... 0 \n",
"121 https://medium.com/p/the-triumph-of-peace-f485... 0 \n",
"\n",
" publication published_date read_ratio read_time reads \\\n",
"130 None 2017-06-10 14:25:00 42.17 7 70 \n",
"129 None 2017-06-17 22:02:00 30.51 14 54 \n",
"126 None 2017-06-30 12:55:00 20.04 42 222 \n",
"120 None 2017-07-01 09:08:00 35.85 9 19 \n",
"121 None 2017-07-05 08:51:00 8.77 14 5 \n",
"\n",
" started_date tags \\\n",
"130 2017-06-10 14:24:00 [Climate Change, Economics] \n",
"129 2017-06-17 22:02:00 [Climate Change, Humanity, Optimism, History] \n",
"126 2017-06-30 12:00:00 [Machine Learning, Python, Udacity, Kaggle] \n",
"120 2017-06-30 18:21:00 [Politics, Books, News, Media Criticism] \n",
"121 2017-07-03 20:18:00 [Books, Psychology, History, Humanism] \n",
"\n",
" text \\\n",
"130 Screw the Environment, but Consider Your Walle... \n",
"129 The Vanquishing of War, Plague and Famine Part... \n",
"126 Capstone Project: Mercedes-Benz Greener Manufa... \n",
"120 Home of the Scared A review of A Culture of Fe... \n",
"121 The Triumph of Peace A review of The Better An... \n",
"\n",
" title title_word_count \\\n",
"130 Screw the Environment, but Consider Your Wallet 8 \n",
"129 The Vanquishing of War, Plague and Famine 8 \n",
"126 Capstone Project: Mercedes-Benz Greener Manufa... 7 \n",
"120 Home of the Scared 4 \n",
"121 The Triumph of Peace 4 \n",
"\n",
" type views word_count claps_per_word editing_days \\\n",
"130 published 166 1859 0.001076 0 \n",
"129 published 177 3891 0.004626 0 \n",
"126 published 1108 12025 0.004241 0 \n",
"120 published 53 2533 0.000000 0 \n",
"121 published 57 3892 0.000000 1 \n",
"\n",
" Education Data Science Towards Data Science \\\n",
"130 0 0 0 \n",
"129 0 0 0 \n",
"126 0 0 0 \n",
"120 0 0 0 \n",
"121 0 0 0 \n",
"\n",
" Machine Learning Python \n",
"130 0 0 \n",
"129 0 0 \n",
"126 1 1 \n",
"120 0 0 \n",
"121 0 0 "
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.read_parquet('https://github.com/WillKoehrsen/Data-Analysis/blob/master/medium/data/2019-01-26_stats?raw=true')\n",
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.132559Z",
"start_time": "2019-01-27T15:06:26.075532Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" claps | \n",
" days_since_publication | \n",
" fans | \n",
" num_responses | \n",
" read_ratio | \n",
" read_time | \n",
" reads | \n",
" title_word_count | \n",
" views | \n",
" word_count | \n",
" claps_per_word | \n",
" editing_days | \n",
" <tag>Education | \n",
" <tag>Data Science | \n",
" <tag>Towards Data Science | \n",
" <tag>Machine Learning | \n",
" <tag>Python | \n",
"
\n",
" \n",
" \n",
" \n",
" count | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
" 132.000000 | \n",
"
\n",
" \n",
" mean | \n",
" 1809.734848 | \n",
" 247.819529 | \n",
" 352.575758 | \n",
" 7.037879 | \n",
" 29.093864 | \n",
" 12.969697 | \n",
" 6350.022727 | \n",
" 7.143939 | \n",
" 23456.295455 | \n",
" 3042.969697 | \n",
" 0.953826 | \n",
" 20.484848 | \n",
" 0.727273 | \n",
" 0.606061 | \n",
" 0.431818 | \n",
" 0.386364 | \n",
" 0.318182 | \n",
"
\n",
" \n",
" std | \n",
" 2442.104242 | \n",
" 178.744357 | \n",
" 478.385853 | \n",
" 9.024483 | \n",
" 12.451970 | \n",
" 9.527733 | \n",
" 8990.325218 | \n",
" 3.165010 | \n",
" 33946.990419 | \n",
" 2397.107643 | \n",
" 1.848885 | \n",
" 74.372539 | \n",
" 0.447058 | \n",
" 0.490483 | \n",
" 0.497216 | \n",
" 0.488770 | \n",
" 0.467545 | \n",
"
\n",
" \n",
" min | \n",
" 0.000000 | \n",
" 1.086660 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 8.110000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 2.000000 | \n",
" 3.000000 | \n",
" 163.000000 | \n",
" 0.000000 | \n",
" -13.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
"
\n",
" \n",
" 25% | \n",
" 120.750000 | \n",
" 74.320420 | \n",
" 22.500000 | \n",
" 0.000000 | \n",
" 20.027500 | \n",
" 8.000000 | \n",
" 363.500000 | \n",
" 5.000000 | \n",
" 1419.250000 | \n",
" 1686.750000 | \n",
" 0.050312 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
"
\n",
" \n",
" 50% | \n",
" 805.500000 | \n",
" 246.401210 | \n",
" 137.000000 | \n",
" 4.000000 | \n",
" 27.075000 | \n",
" 10.500000 | \n",
" 2025.000000 | \n",
" 7.000000 | \n",
" 7271.000000 | \n",
" 2468.000000 | \n",
" 0.419159 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
" 0.000000 | \n",
"
\n",
" \n",
" 75% | \n",
" 2725.000000 | \n",
" 373.665050 | \n",
" 527.250000 | \n",
" 12.000000 | \n",
" 35.030000 | \n",
" 14.250000 | \n",
" 7835.250000 | \n",
" 8.000000 | \n",
" 29916.000000 | \n",
" 3557.000000 | \n",
" 1.105806 | \n",
" 5.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
"
\n",
" \n",
" max | \n",
" 13500.000000 | \n",
" 594.840722 | \n",
" 2582.000000 | \n",
" 59.000000 | \n",
" 74.320000 | \n",
" 54.000000 | \n",
" 41861.000000 | \n",
" 16.000000 | \n",
" 173069.000000 | \n",
" 15063.000000 | \n",
" 17.891817 | \n",
" 349.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
" 1.000000 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" claps days_since_publication fans num_responses \\\n",
"count 132.000000 132.000000 132.000000 132.000000 \n",
"mean 1809.734848 247.819529 352.575758 7.037879 \n",
"std 2442.104242 178.744357 478.385853 9.024483 \n",
"min 0.000000 1.086660 0.000000 0.000000 \n",
"25% 120.750000 74.320420 22.500000 0.000000 \n",
"50% 805.500000 246.401210 137.000000 4.000000 \n",
"75% 2725.000000 373.665050 527.250000 12.000000 \n",
"max 13500.000000 594.840722 2582.000000 59.000000 \n",
"\n",
" read_ratio read_time reads title_word_count views \\\n",
"count 132.000000 132.000000 132.000000 132.000000 132.000000 \n",
"mean 29.093864 12.969697 6350.022727 7.143939 23456.295455 \n",
"std 12.451970 9.527733 8990.325218 3.165010 33946.990419 \n",
"min 8.110000 1.000000 1.000000 2.000000 3.000000 \n",
"25% 20.027500 8.000000 363.500000 5.000000 1419.250000 \n",
"50% 27.075000 10.500000 2025.000000 7.000000 7271.000000 \n",
"75% 35.030000 14.250000 7835.250000 8.000000 29916.000000 \n",
"max 74.320000 54.000000 41861.000000 16.000000 173069.000000 \n",
"\n",
" word_count claps_per_word editing_days Education \\\n",
"count 132.000000 132.000000 132.000000 132.000000 \n",
"mean 3042.969697 0.953826 20.484848 0.727273 \n",
"std 2397.107643 1.848885 74.372539 0.447058 \n",
"min 163.000000 0.000000 -13.000000 0.000000 \n",
"25% 1686.750000 0.050312 0.000000 0.000000 \n",
"50% 2468.000000 0.419159 1.000000 1.000000 \n",
"75% 3557.000000 1.105806 5.000000 1.000000 \n",
"max 15063.000000 17.891817 349.000000 1.000000 \n",
"\n",
" Data Science Towards Data Science Machine Learning \\\n",
"count 132.000000 132.000000 132.000000 \n",
"mean 0.606061 0.431818 0.386364 \n",
"std 0.490483 0.497216 0.488770 \n",
"min 0.000000 0.000000 0.000000 \n",
"25% 0.000000 0.000000 0.000000 \n",
"50% 1.000000 0.000000 0.000000 \n",
"75% 1.000000 1.000000 1.000000 \n",
"max 1.000000 1.000000 1.000000 \n",
"\n",
" Python \n",
"count 132.000000 \n",
"mean 0.318182 \n",
"std 0.467545 \n",
"min 0.000000 \n",
"25% 0.000000 \n",
"50% 0.000000 \n",
"75% 1.000000 \n",
"max 1.000000 "
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.describe()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Simple Widgets\n",
"\n",
"Let's get started using some widgets! We'll start off pretty simple just to see how the interface works."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.136150Z",
"start_time": "2019-01-27T15:06:26.133977Z"
}
},
"outputs": [],
"source": [
"import ipywidgets as widgets\n",
"from ipywidgets import interact, interact_manual"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To make a function interactive, all we have to do is use the `interact` decorator. This will automatically infer the input types for us! "
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.372108Z",
"start_time": "2019-01-27T15:06:26.137708Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "ef4cd4d479464f4ca782787fecc02e6a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Text(value='claps', description='column'), IntSlider(value=5000, description='x', max=15…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def show_articles_more_than(column='claps', x=5000):\n",
" display(HTML(f'Showing articles with more than {x} {column}'))\n",
" display(df.loc[df[column] > x, ['title', 'published_date', 'read_time', 'tags', 'views', 'reads']])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `interact` decorator automatically inferred we want a `text` box for the `column` and an `int` slider for `x`! This makes it incredibly simple to add interactivity. We can also set the options how we want."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T22:00:31.196061Z",
"start_time": "2019-01-27T22:00:31.148769Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "0d609788ff3c4aaa88f0891b4c86f7eb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(IntSlider(value=3000, description='x', max=5000, min=1000, step=100), Dropdown(descripti…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def show_titles_more_than(x=(1000, 5000, 100),\n",
" column=list(df.select_dtypes('number').columns), \n",
" ):\n",
" # display(HTML(f'Showing articles with more than {x} {column}'))\n",
" display(df.loc[df[column] > x, ['title', 'published_date', 'read_time', 'tags', 'views', 'reads']])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This now gives us a `dropdown` for the `column` selection and still an `int` slider for `x`, but with limits. This can be useful when we need to enforce certains constraints on the interaction."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Image Explorer\n",
"\n",
"Let's see another quick example of creating an interactive function. This one allows us to display images from a folder."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.505629Z",
"start_time": "2019-01-27T15:06:26.458861Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "5c59d405637847fc9ba7a5d46f290178",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='file', options=('1080_1189866210-spanish-sunset.jpg', '1080_201401…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fdir = 'nature/'\n",
"\n",
"@interact\n",
"def show_images(file=os.listdir(fdir)):\n",
" display(Image(fdir+file))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You could use this for example if you have a training set of images that you'd quickly like to run through."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# File Browser\n",
"\n",
"We can do a similar operation to create a very basic file browser. Instead of having to manually run the command every time, we just can use this function to look through our files."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.652564Z",
"start_time": "2019-01-27T15:06:26.508685Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"total 23368\r\n",
"drwxr-xr-x 26 williamkoehrsen staff 832 Jan 26 10:09 \u001b[34mnature\u001b[m\u001b[m\r\n",
"drwxr-xr-x 42 williamkoehrsen staff 1344 Jan 26 10:35 \u001b[34mimages\u001b[m\u001b[m\r\n",
"drwxr-xr-x 37 williamkoehrsen staff 1184 Jan 27 08:13 \u001b[34m..\u001b[m\u001b[m\r\n",
"-rw-r--r-- 1 williamkoehrsen staff 5978847 Jan 27 09:38 Widgets Overview-Copy1.ipynb\r\n",
"drwxr-xr-x 4 williamkoehrsen staff 128 Jan 27 09:44 \u001b[34m.ipynb_checkpoints\u001b[m\u001b[m\r\n",
"-rw-r--r--@ 1 williamkoehrsen staff 8196 Jan 27 09:54 .DS_Store\r\n",
"drwxr-xr-x 18 williamkoehrsen staff 576 Jan 27 09:55 \u001b[34massorted\u001b[m\u001b[m\r\n",
"-rw-r--r-- 1 williamkoehrsen staff 5968258 Jan 27 10:06 Widgets-Overview.ipynb\r\n",
"drwxr-xr-x 9 williamkoehrsen staff 288 Jan 27 10:06 \u001b[34m.\u001b[m\u001b[m\r\n"
]
}
],
"source": [
"!ls -a -t -r -l"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.711965Z",
"start_time": "2019-01-27T15:06:26.655632Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "09a98b6d65f04203a24483eb452d5806",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='dir', options=('additive_models', 'statistics', 'recall_precision'…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import subprocess\n",
"import pprint\n",
"\n",
"root_dir = '../'\n",
"dirs = [d for d in os.listdir(root_dir) if not '.' in d]\n",
"\n",
"@interact\n",
"def show_dir(dir=dirs):\n",
" x = subprocess.check_output(f\"cd {root_dir}{dir} && ls -a -t -r -l -h\", shell=True).decode()\n",
" print(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Dataframe Explorer\n",
"\n",
"Let's look at a few more examples of using widgets to explore data. Here we create a widget that quickly lets us find correlations between columns."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.777457Z",
"start_time": "2019-01-27T15:06:26.715739Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "af1610b8f65648768fb6b7f2048fa635",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='column1', options=('claps', 'days_since_publication', 'fans', 'num…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def correlations(column1=list(df.select_dtypes('number').columns), \n",
" column2=list(df.select_dtypes('number').columns)):\n",
" print(f\"Correlation: {df[column1].corr(df[column2])}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here's one to describe a specific column."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:26.819781Z",
"start_time": "2019-01-27T15:06:26.780137Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "670826b81d1b44928b3032ba19ef0ae7",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='column', options=('claps', 'days_since_publication', 'fans', 'link…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def describe(column=list(df.columns)):\n",
" print(df[column].describe())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Interactive Widgets for Plots\n",
"\n",
"We can use the same basic approach to create interactive widgets for plots. This expands the capabilities of the already powerful plotly visualization library."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:27.753045Z",
"start_time": "2019-01-27T15:06:26.822371Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9b72da9bb036433185e131570ecdc1eb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='x', options=('claps', 'days_since_publication', 'fans', 'num_respo…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def scatter_plot(x=list(df.select_dtypes('number').columns), \n",
" y=list(df.select_dtypes('number').columns)[1:]):\n",
" df.iplot(kind='scatter', x=x, y=y, mode='markers', \n",
" xTitle=x.title(), yTitle=y.title(), title=f'{y.title()} vs {x.title()}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's add some options to control the column scheme."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:27.912776Z",
"start_time": "2019-01-27T15:06:27.754692Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3d96d1cd812045f587ff4ea468941202",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='x', options=('claps', 'days_since_publication', 'fans', 'num_respo…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def scatter_plot(x=list(df.select_dtypes('number').columns), \n",
" y=list(df.select_dtypes('number').columns)[1:],\n",
" theme=list(cf.themes.THEMES.keys()), \n",
" colorscale=list(cf.colors._scales_names.keys())):\n",
" \n",
" df.iplot(kind='scatter', x=x, y=y, mode='markers', \n",
" xTitle=x.title(), yTitle=y.title(), \n",
" text='title',\n",
" title=f'{y.title()} vs {x.title()}',\n",
" theme=theme, colorscale=colorscale)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The next plot lets us choose the grouping category for the plot. "
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.350540Z",
"start_time": "2019-01-27T15:06:27.914088Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "cffb8e62738e415e858bfb32f22a65fe",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='x', options=('claps', 'days_since_publication', 'fans', 'num_respo…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"df['binned_read_time'] = pd.cut(df['read_time'], bins=range(0, 56, 5))\n",
"df['binned_read_time'] = df['binned_read_time'].astype(str)\n",
"\n",
"df['binned_word_count'] = pd.cut(df['word_count'], bins=range(0, 100001, 1000))\n",
"df['binned_word_count'] = df['binned_word_count'].astype(str)\n",
"\n",
"@interact\n",
"def scatter_plot(x=list(df.select_dtypes('number').columns), \n",
" y=list(df.select_dtypes('number').columns)[1:],\n",
" categories=['binned_read_time', 'binned_word_count', 'publication', 'type'],\n",
" theme=list(cf.themes.THEMES.keys()), \n",
" colorscale=list(cf.colors._scales_names.keys())):\n",
" \n",
" df.iplot(kind='scatter', x=x, y=y, mode='markers', \n",
" categories=categories, \n",
" xTitle=x.title(), yTitle=y.title(), \n",
" text='title',\n",
" title=f'{y.title()} vs {x.title()}',\n",
" theme=theme, colorscale=colorscale)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You may have noticed this plot was a little slow to update. When that is the case, we can use `interact_manual` which only updates the function when the button is pressed."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.354112Z",
"start_time": "2019-01-27T15:06:28.351925Z"
}
},
"outputs": [],
"source": [
"from ipywidgets import interact_manual"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.413162Z",
"start_time": "2019-01-27T15:06:28.355500Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "0f206f9eee5645aab3749fb28dd9802b",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='x', options=('claps', 'days_since_publication', 'fans', 'num_respo…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact_manual\n",
"def scatter_plot(x=list(df.select_dtypes('number').columns), \n",
" y=list(df.select_dtypes('number').columns)[1:],\n",
" categories=['binned_read_time', 'binned_word_count', 'publication', 'type'],\n",
" theme=list(cf.themes.THEMES.keys()), \n",
" colorscale=list(cf.colors._scales_names.keys())):\n",
" \n",
" df.iplot(kind='scatter', x=x, y=y, mode='markers', \n",
" categories=categories, \n",
" xTitle=x.title(), yTitle=y.title(), \n",
" text='title',\n",
" title=f'{y.title()} vs {x.title()}',\n",
" theme=theme, colorscale=colorscale)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Making Our Own Widgets\n",
"\n",
"The decorator `interact` (or `interact_manual`) is not the only way to use widgets. We can also explicity create our own. One of the most useful I've found is the `DataPicker`."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.418038Z",
"start_time": "2019-01-27T15:06:28.414813Z"
}
},
"outputs": [],
"source": [
"df.set_index('published_date', inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.464287Z",
"start_time": "2019-01-27T15:06:28.420095Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a67c0338e2954c4b911f6871a824cb3a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(DatePicker(value=Timestamp('2018-01-01 00:00:00'), description='start_date'), DatePicker…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def print_articles_published(start_date, end_date):\n",
" start_date = pd.Timestamp(start_date)\n",
" end_date = pd.Timestamp(end_date)\n",
" stat_df = df.loc[(df.index >= start_date) & (df.index <= end_date)].copy()\n",
" total_words = stat_df['word_count'].sum()\n",
" total_read_time = stat_df['read_time'].sum()\n",
" num_articles = len(stat_df)\n",
" print(f'You published {num_articles} articles between {start_date.date()} and {end_date.date()}.')\n",
" print(f'These articles totalled {total_words:,} words and {total_read_time/60:.2f} hours to read.')\n",
" \n",
"_ = interact(print_articles_published,\n",
" start_date=widgets.DatePicker(value=pd.to_datetime('2018-01-01')),\n",
" end_date=widgets.DatePicker(value=pd.to_datetime('2019-01-01')))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For this function, we use a `Dropdown` and a `DatePicker` to plot one column cumulatively up to a certain time. Instead of having to write this ourselves, we can just let `ipywidgets` do all the work!"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.599020Z",
"start_time": "2019-01-27T15:06:28.465747Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3bc6f894e49147a181addcb676f9775c",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='column', options=('claps', 'days_since_publication', 'fans', 'num_…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plot_up_to(column, date):\n",
" date = pd.Timestamp(date)\n",
" plot_df = df.loc[df.index <= date].copy()\n",
" plot_df[column].cumsum().iplot(mode='markers+lines', \n",
" xTitle='published date',\n",
" yTitle=column, \n",
" title=f'Cumulative {column.title()} Until {date.date()}')\n",
" \n",
"_ = interact(plot_up_to, column=widgets.Dropdown(options=list(df.select_dtypes('number').columns)), \n",
" date = widgets.DatePicker(value=pd.to_datetime('2019-01-01')))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Dependent Widgets\n",
"\n",
"How do we get a value of a widget to depend on that of another? Using the `observe` method.\n",
"\n",
"Going back to the Image Browser earlier, let's make a function that allows us to change the directory for the images to list."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.635335Z",
"start_time": "2019-01-27T15:06:28.601166Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "2799ecdd7f59411cb8e17b9a6ff1efd9",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='fdir', options=('images', 'nature', 'assorted'), value='images'), …"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"directory = widgets.Dropdown(options=['images', 'nature', 'assorted'])\n",
"images = widgets.Dropdown(options=os.listdir(directory.value))\n",
"\n",
"def update_images(*args):\n",
" images.options = os.listdir(directory.value)\n",
"\n",
"directory.observe(update_images, 'value')\n",
"\n",
"def show_images(fdir, file):\n",
" display(Image(f'{fdir}/{file}'))\n",
"\n",
"_ = interact(show_images, fdir=directory, file=images)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also assign to the `interact` call and then reuse the widget. This has unintended affects though! "
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.783700Z",
"start_time": "2019-01-27T15:06:28.638932Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "435d1d1c3c6b47fd997354ab8fd9f6de",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='tag', options=('Towards Data Science', 'Education', 'Machine Learn…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def show_stats_by_tag(tag):\n",
" display(df.groupby(f'{tag}').describe()[['views', 'reads', 'claps', 'read_ratio']])\n",
" \n",
"stats = interact(show_stats_by_tag,\n",
" tag=widgets.Dropdown(options=['Towards Data Science', 'Education', 'Machine Learning', 'Python', 'Data Science']))"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.909262Z",
"start_time": "2019-01-27T15:06:28.787263Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "435d1d1c3c6b47fd997354ab8fd9f6de",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='tag', options=('Towards Data Science', 'Education', 'Machine Learn…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"stats.widget"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now changing the value in one location changes it in both places! This can be a slight inconvenience, but on the plus side, now we can reuse the interactive element."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Linked Values\n",
"\n",
"We can link the value of two widgets to each other using the `jslink` function. This ties the values to be the same."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.949982Z",
"start_time": "2019-01-27T15:06:28.910772Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f20abb716c0449b2882472011a2f8cc6",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(IntText(value=100, description='column1_value'), IntSlider(value=100, description='colum…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def show_less_than(column1_value, column2_value):\n",
" display(df.loc[(df['views'] < column1_value) & \n",
" (df['reads'] < column2_value), \n",
" ['title', 'read_time', 'tags', 'views', 'reads']])\n",
" \n",
"column1_value=widgets.IntText(value=100, label='First')\n",
"column2_value=widgets.IntSlider(value=100, label='Second')\n",
"\n",
"linked = widgets.jslink((column1_value, 'value'),\n",
" (column2_value, 'value'))\n",
"\n",
"less_than = interact(show_less_than, column1_value=column1_value,\n",
" column2_value=column2_value)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'm not exactly sure why you would want to link two widgets, but there you go! We can unlink them using the `unlink` command (sometimes syntax does make sense)."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.954530Z",
"start_time": "2019-01-27T15:06:28.951608Z"
}
},
"outputs": [],
"source": [
"linked.unlink()"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.974262Z",
"start_time": "2019-01-27T15:06:28.956187Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f20abb716c0449b2882472011a2f8cc6",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(IntText(value=100, description='column1_value'), IntSlider(value=100, description='colum…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"less_than.widget"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Conclusions\n",
"\n",
"These widgets are not going to change your life, but they do make notebooks closer to interactive dashboards. I've only shown you some of the capabilities so be sure to look at the [documentation for the full details]. The Jupyter Notebook is useful by itself, but with additional tools, it can be an even better data exploration and analysis technology. Thanks to the efforts of many developers and contributors to open-source, we have these great technologies, so we might as well get the most from these libraries! "
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:28.979018Z",
"start_time": "2019-01-27T15:06:28.975825Z"
}
},
"outputs": [],
"source": [
"cscales = ['Greys', 'YlGnBu', 'Greens', 'YlOrRd', 'Bluered', 'RdBu',\n",
" 'Reds', 'Blues', 'Picnic', 'Rainbow', 'Portland', 'Jet',\n",
" 'Hot', 'Blackbody', 'Earth', 'Electric', 'Viridis', 'Cividis']"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:29.011477Z",
"start_time": "2019-01-27T15:06:28.981102Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "af59c8efebec4e81826cc47683086c21",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='colorscale', options=('Greys', 'YlGnBu', 'Greens', 'YlOrRd', 'Blue…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import plotly.figure_factory as ff\n",
"\n",
"corrs = df.corr()\n",
"\n",
"@interact_manual\n",
"def plot_corrs(colorscale=cscales):\n",
" figure = ff.create_annotated_heatmap(z = corrs.round(2).values, \n",
" x =list(corrs.columns), \n",
" y=list(corrs.index), \n",
" colorscale=colorscale,\n",
" annotation_text=corrs.round(2).values)\n",
" iplot(figure)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"ExecuteTime": {
"end_time": "2019-01-27T15:06:29.167490Z",
"start_time": "2019-01-27T15:06:29.013187Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "82acb6c1dfc24a959a5d273ee5a3e9c5",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"interactive(children=(Dropdown(description='column1', options=('claps', 'views', 'read', 'word_count'), value=…"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"@interact\n",
"def plot_spread(column1=['claps', 'views', 'read', 'word_count'], \n",
" column2=['views', 'claps', 'read', 'word_count']):\n",
" df.iplot(kind='ratio',\n",
" y=column1,\n",
" secondary_y=column2,\n",
" title=f'{column1.title()} and {column2.title()} Spread Plot',\n",
" xTitle='Published Date')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"hide_input": false,
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}