Skip to content

Commit

Permalink
Implement interactive API for all data backends (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Nov 14, 2020
1 parent 78639c4 commit a4fb538
Show file tree
Hide file tree
Showing 7 changed files with 825 additions and 10 deletions.
11 changes: 11 additions & 0 deletions doc/user_guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ and `timeseries data <Timeseries_Data.html>`_. These sections are not meant
to be read in a particular order; you should take a look at any that seem
relevant to your data.

The `interactive <Interactive.html>`_ user guide introduces you to the
ability to use the APIs of your favorite data analysis libraries
interactively by allowing you to pass in widgets in place of constant
arguments. This will provide you with an invaluable tool to perform
exploratory analyses quickly but also build powerful and complex data
analysis pipelines using APIs you are already familiar with.

Lastly the `statistical plots <Statistical_Plots.html>`_ section will
take you through a number of specialized plot types modelled on the
pandas.plotting module and the `pandas API <Pandas_API.html>`_ section mimics
Expand All @@ -40,6 +47,9 @@ rather than Matplotlib.
* `Customization <Customization.html>`_
Listing of available options to customize plots.

* `Interactive <Interactive.html>`_
Interactive APIs for data exploration.

* `Widgets <Widgets.html>`_
Adding and customizing interactivity using Panel widgets.

Expand Down Expand Up @@ -78,6 +88,7 @@ rather than Matplotlib.
Introduction <Introduction>
Plotting <Plotting>
Customization <Customization>
Interactive <Interactive>
Widgets <Widgets>
Viewing <Viewing>
Subplots <Subplots>
Expand Down
346 changes: 346 additions & 0 deletions examples/user_guide/Interactive.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import xarray as xr\n",
"import hvplot.xarray # noqa\n",
"import hvplot.pandas # noqa\n",
"import panel as pn\n",
"import ipywidgets as ipw"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"An interactive REPL or notebook interfaces are incredibly powerful tool for quickly doing exploratory analysis, however they generally still require manually changing arguments to method and function calls to see their effect. To further ease exploratory workflows hvPlot ships with a so called `interactive` API, which mirrors the regular API of your favorite data analysis libraries like Pandas, Dask, and xarray but makes it possible to replace constant arguments with widgets that dynamically update the output of the method calls and which tranparently chain, behaving just like the object that is being wrapped.\n",
"\n",
"In this user guide we will explore how to use the interactive API on xarray and pandas objects:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ds = xr.tutorial.load_dataset('air_temperature')\n",
"ds"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Interactive widgets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can supply both regular values, widgets and parameters as arguments to methods on the `interactive` accessor. The repr of the resulting object will contain a layout of the widget and a view of the resulting output:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"slider = pn.widgets.IntSlider(name='time', start=0, end=10)\n",
"\n",
"ds.air.interactive(width=800).isel(time=slider)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"ipywidgets are also supported as dynamic arguments:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"slider = ipw.IntSlider(description='time', min=0, max=10)\n",
"\n",
"ds.air.interactive(width=800).isel(time=slider)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also let interactive automatically populate a widget, which is particularly useful when working with `DiscreteSlider` widgets:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ds.air.interactive(width=800).sel(time=pn.widgets.DiscreteSlider)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Docstrings\n",
"\n",
"When accessing a method on the `interactive` accessor it will transparently mirror the docstring of the equivalent method in the underlying library being wrapped:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(ds.air.interactive.isel.__doc__)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plotting\n",
"\n",
"One of the most useful aspects of the interactive API is to feed the output of chained method calls into a plot."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Matplotlib"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The output can be almost anything, the HTML repr or a matplotlib plot:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ds.air.interactive.sel(time=pn.widgets.DiscreteSlider).plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can animate the output with a `Player` widget, and customize the location of the widget using the `loc` keyword argument to the `interactive` accessor:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"time = pn.widgets.Player(name='time', start=0, end=10, loop_policy='loop', interval=100)\n",
"\n",
"ds.air.interactive(loc='bottom').isel(time=time).plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### hvPlot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also make use of the `.hvplot` method to get interactive plots:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"slider = pn.widgets.FloatSlider(name='quantile', start=0, end=1)\n",
"\n",
"ds.air.interactive.quantile(slider, dim='time').hvplot(data_aspect=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can chain any number of methods, with as many widgets controlling steps in this pipeline as you wish:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"q = pn.widgets.FloatSlider(name='quantile', start=0, end=1)\n",
"\n",
"(ds\n",
" .air\n",
" .interactive(loc='left')\n",
" .sel(time=pn.widgets.DiscreteSlider)\n",
" .quantile(q=q, dim='lon')\n",
" .hvplot(aspect=1)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also use a `RangeSlider` to select a slice and compute the mean:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"range_slider = pn.widgets.RangeSlider(start=0, end=len(ds.time), step=1)\n",
"\n",
"(ds\n",
" .air\n",
" .interactive\n",
" .isel(time=range_slider)\n",
" .mean('time')\n",
" .hvplot()\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that `.interactive` can be chained arbitrarily, e.g. we can even convert to a dataframe using `to_dataframe` and then call pandas methods:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"lat = pn.widgets.DiscreteSlider(name='Latitude', options=sorted(ds.lat.values))\n",
"\n",
"ds.air.interactive.sel(lat=lat).to_dataframe().groupby('time').mean().hvplot('time', 'air')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Operators\n",
"\n",
"You can apply math operators on the interactive object:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"slider = pn.widgets.IntSlider(name='time', start=0, end=10)\n",
"\n",
"ds.air.interactive(width=800).isel(time=slider).mean().item() + 10"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can even do math with a widget:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"slider = pn.widgets.IntSlider(name='time', start=0, end=10)\n",
"\n",
"offset = pn.widgets.IntSlider(name='Offset', start=0, end=10)\n",
"\n",
"ds.air.interactive.isel(time=slider).mean().item() + offset"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"((ds.air.interactive.sel(time=pn.widgets.DiscreteSlider) - ds.air.mean('time'))\n",
" .hvplot(cmap='RdBu_r', clim=(-20, 20))\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"kind = pn.widgets.Select(options=['contour', 'image', 'contourf'])\n",
"\n",
"pn.panel((ds.air.interactive.sel(time=pn.widgets.DiscreteSlider) - ds.air.mean('time'))\n",
" .hvplot(cmap='RdBu_r', clim=(-20, 20), kind=kind).layout\n",
")"
]
}
],
"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.5"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading

0 comments on commit a4fb538

Please sign in to comment.