Skip to content

Commit

Permalink
Merge e8864b1 into 62440ed
Browse files Browse the repository at this point in the history
  • Loading branch information
jbednar committed Nov 17, 2020
2 parents 62440ed + e8864b1 commit cf17ed6
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 96 deletions.
151 changes: 83 additions & 68 deletions examples/user_guide/Interactive.ipynb
Expand Up @@ -10,16 +10,18 @@
"import hvplot.xarray # noqa\n",
"import hvplot.pandas # noqa\n",
"import panel as pn\n",
"import panel.widgets as pnw\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",
"Interactive command-line or notebook interfaces are incredibly powerful tools for quickly doing exploratory analysis, letting you supply arguments to Python methods and functions and see the results immediately. However, this process of exploration can be slow and awkward for large parameter spaces because it requires manually typing each argument value. To further ease exploratory workflows, hvPlot ships with a convenient `.interactive` API, which mirrors the regular API of your favorite data analysis libraries like Pandas, Dask, and xarray but makes it possible to pass in _widgets_ for each argument value, not just a constant. When the widgets are used, the output will dynamically update the full pipeline of method calls so that it works just as if that particular value had been specified in the call.\n",
"being wrapped.\n",
"\n",
"In this user guide we will explore how to use the interactive API on xarray and pandas objects:"
"In this user guide we will explore how to use the .interactive API on xarray and pandas objects:"
]
},
{
Expand All @@ -43,7 +45,7 @@
"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:"
"We can supply both regular values, widgets and parameters as arguments to methods on the `.interactive` accessor. Here, we'll use widgets from the [Panel](https://panel.holoviz.org) library. The repr of the resulting object will contain a layout of the widget and a view of the resulting output:"
]
},
{
Expand All @@ -52,7 +54,7 @@
"metadata": {},
"outputs": [],
"source": [
"slider = pn.widgets.IntSlider(name='time', start=0, end=10)\n",
"slider = pnw.IntSlider(name='time', start=0, end=10)\n",
"\n",
"ds.air.interactive(width=800).isel(time=slider)"
]
Expand All @@ -61,7 +63,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"ipywidgets are also supported as dynamic arguments:"
"You can also use widgets from the [ipywidgets](https://ipywidgets.readthedocs.io) library:"
]
},
{
Expand All @@ -79,7 +81,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also let interactive automatically populate a widget, which is particularly useful when working with `DiscreteSlider` widgets:"
"For Panel widgets, we can let .interactive automatically configure the widget, which is particularly convenient when working with `DiscreteSlider` widgets:"
]
},
{
Expand All @@ -88,7 +90,7 @@
"metadata": {},
"outputs": [],
"source": [
"ds.air.interactive(width=800).sel(time=pn.widgets.DiscreteSlider)"
"ds.air.interactive(width=800).sel(time=pnw.DiscreteSlider)"
]
},
{
Expand All @@ -97,7 +99,7 @@
"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:"
"When accessing a method on the `.interactive` accessor it will transparently mirror the docstring of the equivalent method in the underlying library being wrapped:"
]
},
{
Expand All @@ -115,7 +117,7 @@
"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."
"One of the most useful aspects of the .interactive API is to feed the output of chained method calls into a plot."
]
},
{
Expand All @@ -129,7 +131,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The output can be almost anything, the HTML repr or a matplotlib plot:"
"The output can be almost anything, such as the HTML repr (above) or a matplotlib plot:"
]
},
{
Expand All @@ -138,14 +140,14 @@
"metadata": {},
"outputs": [],
"source": [
"ds.air.interactive.sel(time=pn.widgets.DiscreteSlider).plot()"
"ds.air.interactive.sel(time=pnw.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:"
"If we like, we can animate the output with a `Player` widget, and customize the location of the widget using the `loc` keyword argument to `.interactive`:"
]
},
{
Expand All @@ -154,7 +156,7 @@
"metadata": {},
"outputs": [],
"source": [
"time = pn.widgets.Player(name='time', start=0, end=10, loop_policy='loop', interval=100)\n",
"time = pnw.Player(name='time', start=0, end=10, loop_policy='loop', interval=100)\n",
"\n",
"ds.air.interactive(loc='bottom').isel(time=time).plot()"
]
Expand All @@ -170,7 +172,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also make use of the `.hvplot` method to get interactive plots:"
"We can also make use of the `.hvplot` method to get fully interactive Bokeh-based plots:"
]
},
{
Expand All @@ -179,7 +181,7 @@
"metadata": {},
"outputs": [],
"source": [
"slider = pn.widgets.FloatSlider(name='quantile', start=0, end=1)\n",
"slider = pnw.FloatSlider(name='quantile', start=0, end=1)\n",
"\n",
"ds.air.interactive.quantile(slider, dim='time').hvplot(data_aspect=1)"
]
Expand All @@ -197,22 +199,19 @@
"metadata": {},
"outputs": [],
"source": [
"q = pn.widgets.FloatSlider(name='quantile', start=0, end=1)\n",
"q = pnw.FloatSlider(name='quantile', start=0, end=1)\n",
"\n",
"(ds\n",
" .air\n",
" .interactive(loc='left')\n",
" .sel(time=pn.widgets.DiscreteSlider)\n",
"(ds.air.interactive(loc='left')\n",
" .sel(time=pnw.DiscreteSlider)\n",
" .quantile(q=q, dim='lon')\n",
" .hvplot(aspect=1)\n",
")"
" .hvplot(aspect=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also use a `RangeSlider` to select a slice and compute the mean:"
"We can also use a `RangeSlider` to select a slice and compute the mean over that range instead of selecting a specific time:"
]
},
{
Expand All @@ -221,22 +220,19 @@
"metadata": {},
"outputs": [],
"source": [
"range_slider = pn.widgets.RangeSlider(start=0, end=len(ds.time), step=1)\n",
"range_slider = pnw.IntRangeSlider\n",
"\n",
"(ds\n",
" .air\n",
" .interactive\n",
"(ds.air.interactive\n",
" .isel(time=range_slider)\n",
" .mean('time')\n",
" .hvplot()\n",
")"
" .hvplot())"
]
},
{
"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:"
"`.interactive` supports arbitrary chains of method calls, including anything that is supported by your data object. For instance, you can even convert your xarray object into a dataframe using `.to_dataframe`, then call pandas methods:"
]
},
{
Expand All @@ -245,9 +241,7 @@
"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')"
"ds.air.interactive.sel(lat=pnw.DiscreteSlider).to_dataframe().groupby('time').mean().hvplot('time', 'air')"
]
},
{
Expand All @@ -256,7 +250,7 @@
"source": [
"## Operators\n",
"\n",
"You can apply math operators on the interactive object:"
"You can further transform your output, if desired, by applying math operators on the interactive object:"
]
},
{
Expand All @@ -265,9 +259,18 @@
"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"
"slider = pnw.IntSlider(name='time', start=0, end=10)\n",
"baseline = ds.air.mean().item()\n",
"baseline"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ds.air.interactive(width=800).isel(time=slider).mean().item() - baseline"
]
},
{
Expand All @@ -283,22 +286,36 @@
"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",
"slider = pnw.IntSlider(name='time', start=0, end=10)\n",
"offset = pnw.IntSlider(name='offset', start=0, end=500)\n",
"\n",
"ds.air.interactive.isel(time=slider).mean().item() + offset"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Math operators work with array data as well, such as the time-averaged value of each array value:"
]
},
{
"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",
")"
"diff = ds.air.interactive.sel(time=pnw.DiscreteSlider) - ds.air.mean('time')\n",
"kind = pnw.Select(options=['contour', 'contourf', 'image'])\n",
"\n",
"diff.hvplot(cmap='RdBu_r', clim=(-20, 20), kind=kind)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you want more control over the layout, you can use any of the features from [Panel](https://panel.holoviz.org):"
]
},
{
Expand All @@ -307,38 +324,36 @@
"metadata": {},
"outputs": [],
"source": [
"kind = pn.widgets.Select(options=['contour', 'image', 'contourf'])\n",
"diff = ds.air.interactive.sel(time=pnw.DiscreteSlider) - ds.air.mean('time')\n",
"kind = pnw.Select(options=['contourf', 'contour', 'image'], value='image')\n",
"interactive = diff.hvplot(cmap='RdBu_r', clim=(-20, 20), kind=kind)\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",
")"
"pn.Column(\n",
" pn.Row(\n",
" pn.panel(\"https://hvplot.holoviz.org/assets/hvplot-wm.png\", width=100), \n",
" pn.Spacer(width=20),\n",
" pn.Column(\n",
" pn.panel(\"## Select a time and type of plot\", width=400),\n",
" interactive.widgets()\n",
" ),\n",
" pn.panel(\"https://panel.holoviz.org/_static/logo_stacked.png\", width=100)\n",
" ),\n",
" interactive.panel()\n",
").servable()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, the `.interactive` functionality makes it simple to work interactively with your data, letting you use widgets about as easily as any other method argument! See the Panel or ipwidgets docs for the various widgets and other functionality available."
]
}
],
"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
}
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
Expand Down
8 changes: 4 additions & 4 deletions hvplot/cudf.py
Expand Up @@ -2,7 +2,7 @@

from .interactive import Interactive

def patch(name='hvplot', extension='bokeh', logo=False):
def patch(name='hvplot', interactive='interactive', extension='bokeh', logo=False):
from . import hvPlotTabular, post_patch

try:
Expand All @@ -18,9 +18,9 @@ def patch(name='hvplot', extension='bokeh', logo=False):

_patch_interactive = lambda self: Interactive(self)
_patch_interactive.__doc__ = Interactive.__call__.__doc__
interactive_prop = property(_patch_plot)
setattr(cudf.DataFrame, name, interactive_prop)
setattr(cudf.Series, name, interactive_prop)
interactive_prop = property(_patch_interactive)
setattr(cudf.DataFrame, interactive, interactive_prop)
setattr(cudf.Series, interactive, interactive_prop)

post_patch(extension, logo)

Expand Down
19 changes: 13 additions & 6 deletions hvplot/dask.py
Expand Up @@ -2,7 +2,14 @@

from .interactive import Interactive

def patch(name='hvplot', extension='bokeh', logo=False):
class DaskInteractive(Interactive):

def compute(self):
self._method = 'compute'
return self.__call__()


def patch(name='hvplot', interactive='interactive', extension='bokeh', logo=False):
from . import hvPlotTabular, post_patch

try:
Expand All @@ -16,11 +23,11 @@ def patch(name='hvplot', extension='bokeh', logo=False):
setattr(dd.DataFrame, name, plot_prop)
setattr(dd.Series, name, plot_prop)

_patch_interactive = lambda self: Interactive(self)
_patch_interactive.__doc__ = Interactive.__call__.__doc__
interactive_prop = property(_patch_plot)
setattr(dd.DataFrame, name, interactive_prop)
setattr(dd.Series, name, interactive_prop)
_patch_interactive = lambda self: DaskInteractive(self)
_patch_interactive.__doc__ = DaskInteractive.__call__.__doc__
interactive_prop = property(_patch_interactive)
setattr(dd.DataFrame, interactive, interactive_prop)
setattr(dd.Series, interactive, interactive_prop)

post_patch(extension, logo)

Expand Down

0 comments on commit cf17ed6

Please sign in to comment.