Last active
June 26, 2019 17:32
-
-
Save mpkocher/83ac8f99a7b6a4ed87f1f2014ef74c2b to your computer and use it in GitHub Desktop.
Pyviz Panel. Kicking the Tires
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencies: | |
- panel>=0.6.0 | |
- holoviews | |
- hvplot | |
- param | |
- matplotlib | |
- scipy | |
- conda-forge::altair | |
- conda-forge::plotly | |
- conda-forge::vtk=8.1.1 | |
- conda-forge::jupyter |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Python Dashboards with Panel: Kicking the Tires\n", | |
"\n", | |
"This explore creating dashboard components using Pyviz's [Panel](http://panel.pyviz.org). The [release post](https://medium.com/@philipp.jfr/panel-announcement-2107c2b15f52) has a good overview of the features and overall design.\n", | |
"\n", | |
"This notebook provides an example of creating generic Panel components to create Panel driven dashboard. \n", | |
"\n", | |
"This dashboard will require a server, hence **the output cells of the notebook are not rendered** (this also keeps the `.ipynb` file very small).\n", | |
"\n", | |
"The raw notebook can be launched from [mybinder.org](https://mybinder.org/v2/gist/mpkocher/83ac8f99a7b6a4ed87f1f2014ef74c2b/master?filepath=panel-dashboard.ipynb). Note, this is complete notebook with the embedded dashboard, not the dashboard only \"view\" that is obtained via `panel serve /path/to/notebook.ipynb`).\n", | |
"\n", | |
"\n", | |
"## High-Level Summary of Panel\n", | |
"\n", | |
"- Tame the entropy in the Python visualization ecosystem (specifically in the dashboard space)\n", | |
"- Support several different libs (e.g., bokeh, matplotlib, holoviews, altair, etc...)\n", | |
"- Provide an iterative model to develop components directly within Jupyter notebook\n", | |
"- Deploy dashboard directly from a Jupyter (`.ipynb`) or python (`.py`) format using `panel serve /path/to/notebook.ipynb`\n", | |
"\n", | |
"## Outline/Goals\n", | |
"\n", | |
"- Creating components using bokeh figures\n", | |
"- Create component with Tabs\n", | |
"- Create an generic interactive XY scatter and Histogram components\n", | |
"- Create linked components to update multiple downstream figures\n", | |
"- Generate a component using holoview\n", | |
"- Construct an example dashboard using the components defined above\n", | |
"- Tips and Suggestions " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Imports and Setup\n", | |
"\n", | |
"import math\n", | |
"import datetime\n", | |
"\n", | |
"from typing import List, Optional, Any, Callable\n", | |
"\n", | |
"import matplotlib.pyplot as plt\n", | |
"plt.style.use('ggplot')\n", | |
"\n", | |
"#import seaborn as sns\n", | |
"#sns.set(rc={'figure.figsize': (16, 9.)})\n", | |
"\n", | |
"%config InlineBackend.figure_format = 'retina'\n", | |
"\n", | |
"import panel as pn\n", | |
"import panel.widgets as pnw\n", | |
"import param\n", | |
"\n", | |
"import bokeh\n", | |
"from bokeh.plotting import figure, show\n", | |
"\n", | |
"import holoviews as hv\n", | |
"import hvplot.pandas\n", | |
"\n", | |
"pn.extension('vega', 'katex', 'plotly', logo=False)\n", | |
"hv.extension('bokeh', logo=False)\n", | |
"\n", | |
"# test data to use in dashboard\n", | |
"from bokeh.sampledata.iris import flowers as iris\n", | |
"from bokeh.sampledata.autompg import autompg\n", | |
"\n", | |
"\n", | |
"def to_info():\n", | |
" # Util for package versions\n", | |
" components = (bokeh, pn, hv)\n", | |
" attrs = ('__name__', '__version__')\n", | |
" \n", | |
" def to_s(c):\n", | |
" return \" = \".join([getattr(c, a) for a in attrs])\n", | |
" \n", | |
" return \"\\n\".join(map(to_s, components))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Creating a Figure and wrapping it in Panel" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_example_figure(func):\n", | |
" p1 = figure(width=600, height=400)\n", | |
" xs = list(range(0, 100))\n", | |
" ys = [func(x) for x in xs]\n", | |
" p1.line(xs, ys)\n", | |
" return p1\n", | |
"\n", | |
"def to_pow(y):\n", | |
" def f(x):\n", | |
" return math.pow(x, y)\n", | |
" return f" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"f0 = to_example_figure(to_pow(1))\n", | |
"f1 = to_example_figure(to_pow(3))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"pn.Column(f0, f1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Define some util funcs to generate $x^n$ datasets" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_name(i):\n", | |
" return f\"x^{i}\"\n", | |
"\n", | |
"def to_f(i):\n", | |
" return to_name(i), to_pow(i)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"funcs = map(to_f, range(1, 9))\n", | |
"\n", | |
"t0 = pn.Tabs()\n", | |
"t0.extend([(name, to_example_figure(f)) for name, f in funcs])\n", | |
"\n", | |
"\n", | |
"c0 = pn.Column(pn.Row(pn.pane.Markdown(\"##Example Dashboard Component using Tabs\")), t0)\n", | |
"c0" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Create Interactive Plots\n", | |
"\n", | |
"There are [four different models/styles](https://panel.pyviz.org/user_guide/APIs.html) to create components.\n", | |
"\n", | |
"1. Interact functions: Auto-generates a full UI (including widgets) given a function\n", | |
"2. Reactive functions: Linking functions or methods to widgets using the pn.depends decorator, declaring that the function should be re-run when those widget values change\n", | |
"3. Parameterized class: Declare parameters and their ranges in Parameterized classes, then get GUIs (and value checking!) for free\n", | |
"4. Callbacks: Generate a UI by manually declaring callbacks that update panels or panes\n", | |
"\n", | |
"In the examples below use style #2 and #3. " | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Defining Components using Parameterized Class Style" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_interactive_xy_class_style(columns, func, default_color = '#058805', name=\"XY-Explorer\"):\n", | |
" \n", | |
" class XYExplorer(param.Parameterized):\n", | |
" x = param.Selector(default=columns[0], objects=columns)\n", | |
" y = param.Selector(default=columns[1], objects=columns)\n", | |
" color = param.Color(default=default_color)\n", | |
" \n", | |
" @param.depends('x', 'y', 'color') # optional in this case\n", | |
" def plot(self):\n", | |
" return func(self.x, self.y, self.color)\n", | |
" \n", | |
" def panel(self):\n", | |
" return pn.Row(self.param, self.plot)\n", | |
" \n", | |
" return XYExplorer(name=name)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Defining Components using Reactive Functions Style" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_interactive_xy_deco_style(columns, func, default_color = '#058805', name=\"XY-Explorer\"):\n", | |
" x = pnw.Select(name=\"X\", value=columns[0], options=columns)\n", | |
" y = pnw.Select(name=\"Y\", value=columns[1], options=columns)\n", | |
" color = pnw.ColorPicker(name=\"Color\", value=default_color)\n", | |
" \n", | |
" @pn.depends(x.param.value, y.param.value, color.param.value)\n", | |
" def reactive_xy(x, y, color):\n", | |
" return func(x, y, color)\n", | |
" \n", | |
" return pn.Row(pn.Column(pn.pane.Markdown(name), x, y, color), reactive_xy)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Create a figure using AutoMPG dataset" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def autompg_plot(x='mpg', y='hp', color='#058805'):\n", | |
" return autompg.hvplot.scatter(x, y, c=color, padding=0.1)\n", | |
"\n", | |
"auto_mpg_columns = list(autompg.columns[:-2]) " | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"auto_mpg_exp = to_interactive_xy_class_style(auto_mpg_columns, autompg_plot)\n", | |
"auto_mpg_exp.panel()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"c_autompg = to_interactive_xy_deco_style(auto_mpg_columns, autompg_plot)\n", | |
"c_autompg" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def iris_xy_plot(x, y, color):\n", | |
" return iris.hvplot.scatter(x, y, c=color, padding=0.1)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"iris_columns = list(iris.columns[:-1])\n", | |
"\n", | |
"c_iris_xy = to_interactive_xy_deco_style(iris_columns, iris_xy_plot, default_color=\"#0000FF\", name=\"# Iris XY Explorer\")\n", | |
"\n", | |
"c_iris_xy" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"For dashboard components where the data source isn't configurable, it's trivial to wrap them in a **Panel**." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"pn.Row(iris.hvplot.hist(y='sepal_length'))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Defining a Histogram using Reactive Style" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_hist_explorer(columns: List[str], runner_func:Callable[[str, str], Any], \n", | |
" default_y:Optional[str]=None, \n", | |
" default_color='#0000ff', name=\"Histogram Explorer\"):\n", | |
" \n", | |
" ys = columns[0] if default_y is None else default_y\n", | |
" y = pn.widgets.Select(name=\"Y\", value=ys, options=columns)\n", | |
" color = pn.widgets.ColorPicker(name=\"Color\", value=default_color)\n", | |
" \n", | |
" @pn.depends(y.param.value, color.param.value)\n", | |
" def to_hist(y, color):\n", | |
" return runner_func(y, color)\n", | |
" \n", | |
" return pn.Row(pn.pane.Markdown(name), pn.Row(pn.Column(y, color), to_hist))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def iris_hist_plot(y, color):\n", | |
" return iris.hvplot.hist(y=y, color=color)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"iris_hist_exp = to_hist_explorer(iris_columns, iris_hist_plot, default_y=iris_columns[0])\n", | |
"\n", | |
"iris_hist_exp" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Creating Linked Dashboard Components" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_linked_component(columns, xy_plotter_func, hist_plotter_func, \n", | |
" default_color='#0000ff', name=\"Linked XY/Histogram Component\"):\n", | |
" \n", | |
" x = pn.widgets.Select(name=\"X\", value=columns[0], options=columns)\n", | |
" y = pn.widgets.Select(name=\"Y\", value=columns[1], options=columns)\n", | |
" color = pn.widgets.ColorPicker(name=\"Color\", value=default_color)\n", | |
" \n", | |
" @pn.depends(x.param.value, y.param.value, color.param.value)\n", | |
" def reactive_xy(x, y, color):\n", | |
" return xy_plotter_func(x, y, color)\n", | |
" \n", | |
" @pn.depends(y.param.value, color.param.value)\n", | |
" def reactive_hist_y(y, color):\n", | |
" return hist_plotter_func(y, color)\n", | |
" \n", | |
" @pn.depends(x.param.value, color.param.value)\n", | |
" def reactive_hist_x(x, color):\n", | |
" return hist_plotter_func(x, color)\n", | |
" \n", | |
" input_params = pn.Column(y, x, color)\n", | |
" \n", | |
" return pn.Column(pn.pane.Markdown(name), pn.Row(input_params), reactive_xy, reactive_hist_y, reactive_hist_x) " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"c_iris_xy = to_linked_component(iris_columns, iris_xy_plot, iris_hist_plot, name=\"Iris Linked XY/Histogram Component\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"c_iris_xy" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"autompg.describe()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"autompg_ds = hv.Dataset(autompg[['mpg', 'cyl', 'hp', 'weight', 'accel', 'yr']])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"autompg_ds" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from holoviews import opts\n", | |
"from holoviews.operation import gridmatrix" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def to_grid(df):\n", | |
" density_grid = gridmatrix(df, diagonal_type=hv.Distribution)\n", | |
" point_grid = gridmatrix(df, chart_type=hv.Points)\n", | |
"\n", | |
" p = (density_grid * point_grid).opts(opts.Bivariate(bandwidth=0.5, cmap='Blues'), \n", | |
" opts.Points(size=2, tools=['box_select']))\n", | |
" return pn.Row(p)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"rg = to_grid(autompg_ds)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"c_autompg_joint = pn.Row(pn.pane.Markdown(\"## AutoMPG Plots\"), rg)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"#c_autompg_joint" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Dashboard \n", | |
"\n", | |
"Any component can be exported/registered by calling the `.servable()` on the component that are defined above. It's easier to" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"header = \"\"\"# Example Dashboard\"\"\"\n", | |
"\n", | |
"desc = \"\"\"\n", | |
"Example dashboard using Pyviz's [Panel](https://panel.pyviz.org/) (version 0.6.0)\n", | |
"\"\"\"\n", | |
"\n", | |
"img_src = \"https://vignette.wikia.nocookie.net/simpsons/images/b/b3/Homerswebpage.jpg\"\n", | |
"\n", | |
"\n", | |
"dashboard = pn.Column(\n", | |
" pn.pane.Markdown(header), \n", | |
" pn.panel(img_src, width=200),\n", | |
" pn.pane.Markdown(desc),\n", | |
" c0,\n", | |
" pn.pane.Markdown(\"## IRIS Components\"),\n", | |
" c_iris_xy,\n", | |
" pn.pane.Markdown(\"# AutoMPG Components\"),\n", | |
" c_autompg,\n", | |
" pn.pane.Markdown(\"## AutoMPG Joint Plot\"),\n", | |
" rg,\n", | |
" pn.pane.Markdown(\"Generated at {} using Components: {}\".format(datetime.datetime.now(), to_info()))\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"dashboard.servable()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Tips and Suggestions\n", | |
"\n", | |
"- Be pragmatic about mixing up layout and Components\n", | |
"- Prefer \"thin\" components that are largely purely wired up calls to your analysis functions (this will decouple the layers in the dashboard)\n", | |
"- Use small factory-ish functions to create generic reusable components. It's trivial to share widgets between components this way.\n", | |
"- Use `panel.widgets.*` instead of `param.*`" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"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.7.3" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment