holoviz / holoviz/hvplot

Support `pandas.Interval` arrays in `step` plots

Open
#957 0 comments 0 reactions 0 assignees View on GitHub
interface: xarray
Dominant language
Python
Stars
1.4k
Forks
124
Avg merge
1d 18h
Merged PRs (30d)
1

Description

xarray support generating `step` plots from dimensions that consist of array containing [pandas Interval](https://pandas.pydata.org/docs/reference/api/pandas.Interval.html) objects.

```python
import hvplot.xarray
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import xarray as xr

airtemps = xr.tutorial.open_dataset("air_temperature")
air = airtemps.air - 273.15
air.attrs = airtemps.air.attrs
air.attrs["units"] = "deg C"
air_grp = air.mean(["time", "lon"]).groupby_bins("lat", [0, 23.5, 66.5, 90])
air_mean = air_grp.mean()
air_mean.plot.step()
```

This is what `air_mean.lat_bins.values` looks like:

```
array([Interval(0.0, 23.5, closed='right'),
Interval(23.5, 66.5, closed='right'),
Interval(66.5, 90.0, closed='right')], dtype=object)
```

`air_mean.hvplot.step()` fails with a `TypeError` in HoloViews:

```
TypeError Traceback (most recent call last)
File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/IPython/core/formatters.py:973, in MimeBundleFormatter.__call__(self, obj, include, exclude)
970 method = get_real_method(obj, self.print_method)
972 if method is not None:
--> 973 return method(include=include, exclude=exclude)
974 return None
975 else:

File ~/work/dev/holoviews/holoviews/core/dimension.py:1294, in Dimensioned._repr_mimebundle_(self, include, exclude)
1287 def _repr_mimebundle_(self, include=None, exclude=None):
1288 """
1289 Resolves the class hierarchy for the class rendering the
1290 object using any display hooks registered on Store.display
1291 hooks. The output of all registered display_hooks is then
1292 combined and returned.
1293 """
-> 1294 return Store.render(self)

File ~/work/dev/holoviews/holoviews/core/options.py:1426, in Store.render(cls, obj)
1424 data, metadata = {}, {}
1425 for hook in hooks:
-> 1426 ret = hook(obj)
1427 if ret is None:
1428 continue

File ~/work/dev/holoviews/holoviews/ipython/display_hooks.py:277, in pprint_display(obj)
275 if not ip.display_formatter.formatters['text/plain'].pprint:
276 return None
--> 277 return display(obj, raw_output=True)

File ~/work/dev/holoviews/holoviews/ipython/display_hooks.py:247, in display(obj, raw_output, **kwargs)
245 elif isinstance(obj, (CompositeOverlay, ViewableElement)):
246 with option_state(obj):
--> 247 output = element_display(obj)
248 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
249 with option_state(obj):

File ~/work/dev/holoviews/holoviews/ipython/display_hooks.py:141, in display_hook..wrapped(element)
139 try:
140 max_frames = OutputSettings.options['max_frames']
--> 141 mimebundle = fn(element, max_frames=max_frames)
142 if mimebundle is None:
143 return {}, {}

File ~/work/dev/holoviews/holoviews/ipython/display_hooks.py:187, in element_display(element, max_frames)
184 if type(element) not in Store.registry[backend]:
185 return None
--> 187 return render(element)

File ~/work/dev/holoviews/holoviews/ipython/display_hooks.py:68, in render(obj, **kwargs)
65 if renderer.fig == 'pdf':
66 renderer = renderer.instance(fig='png')
---> 68 return renderer.components(obj, **kwargs)

File ~/work/dev/holoviews/holoviews/plotting/renderer.py:398, in Renderer.components(self, obj, fmt, comm, **kwargs)
395 embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
397 if embed or config.comms == 'default':
--> 398 return self._render_panel(plot, embed, comm)
399 return self._render_ipywidget(plot)

File ~/work/dev/holoviews/holoviews/plotting/renderer.py:405, in Renderer._render_panel(self, plot, embed, comm)
403 doc = Document()
404 with config.set(embed=embed):
--> 405 model = plot.layout._render_model(doc, comm)
406 if embed:
407 return render_model(model, comm)

File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/panel/viewable.py:505, in Renderable._render_model(self, doc, comm)
503 if comm is None:
504 comm = state._comm_manager.get_server_comm()
--> 505 model = self.get_root(doc, comm)
507 if config.embed:
508 embed_state(self, model, doc,
509 json=config.embed_json,
510 json_prefix=config.embed_json_prefix,
511 save_path=config.embed_save_path,
512 load_path=config.embed_load_path,
513 progress=False)

File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/panel/viewable.py:556, in Renderable.get_root(self, doc, comm, preprocess)
539 """
540 Returns the root model and applies pre-processing hooks
541
(...)
553 Returns the bokeh model corresponding to this panel object
554 """
555 doc = init_doc(doc)
--> 556 root = self._get_model(doc, comm=comm)
557 if preprocess:
558 self._preprocess(root)

File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/panel/layout/base.py:146, in Panel._get_model(self, doc, root, parent, comm)
144 if root is None:
145 root = model
--> 146 objects = self._get_objects(model, [], doc, root, comm)
147 props = dict(self._init_params(), objects=objects)
148 model.update(**self._process_param_change(props))

File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/panel/layout/base.py:131, in Panel._get_objects(self, model, old_objects, doc, root, comm)
129 else:
130 try:
--> 131 child = pane._get_model(doc, root, model, comm)
132 except RerenderError:
133 return self._get_objects(model, current_objects[:i], doc, root, comm)

File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/panel/pane/holoviews.py:265, in HoloViews._get_model(self, doc, root, parent, comm)
263 plot = self.object
264 else:
--> 265 plot = self._render(doc, comm, root)
267 plot.pane = self
268 backend = plot.renderer.backend

File ~/miniconda3/envs/hv-dev38/lib/python3.8/site-packages/panel/pane/holoviews.py:342, in HoloViews._render(self, doc, comm, root)
339 if comm:
340 kwargs['comm'] = comm
--> 342 return renderer.get_plot(self.object, **kwargs)

File ~/work/dev/holoviews/holoviews/plotting/bokeh/renderer.py:70, in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
63 @bothmethod
64 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
65 """
66 Given a HoloViews Viewable return a corresponding plot instance.
67 Allows supplying a document attach the plot to, useful when
68 combining the bokeh model with another plot.
69 """
---> 70 plot = super().get_plot(obj, doc, renderer, **kwargs)
71 if plot.document is None:
72 plot.document = Document() if self_or_cls.notebook_context else curdoc()

File ~/work/dev/holoviews/holoviews/plotting/renderer.py:240, in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
237 defaults = [kd.default for kd in plot.dimensions]
238 init_key = tuple(v if d is None else d for v, d in
239 zip(plot.keys[0], defaults))
--> 240 plot.update(init_key)
241 else:
242 plot = obj

File ~/work/dev/holoviews/holoviews/plotting/plot.py:948, in DimensionedPlot.update(self, key)
946 def update(self, key):
947 if len(self) == 1 and ((key == 0) or (key == self.keys[0])) and not self.drawn:
--> 948 return self.initialize_plot()
949 item = self.__getitem__(key)
950 self.traverse(lambda x: setattr(x, '_updated', True))

File ~/work/dev/holoviews/holoviews/plotting/bokeh/element.py:1382, in ElementPlot.initialize_plot(self, ranges, plot, plots, source)
1380 element = self.hmap.last
1381 key = util.wrap_tuple(self.hmap.last_key)
-> 1382 ranges = self.compute_ranges(self.hmap, key, ranges)
1383 self.current_ranges = ranges
1384 self.current_frame = element

File ~/work/dev/holoviews/holoviews/plotting/plot.py:607, in DimensionedPlot.compute_ranges(self, obj, key, ranges)
603 # Only compute ranges if not axiswise on a composite plot
604 # or not framewise on a Overlay or ElementPlot
605 if (not (axiswise and not isinstance(obj, HoloMap)) or
606 (not framewise and isinstance(obj, HoloMap))):
--> 607 self._compute_group_range(group, elements, ranges, framewise,
608 axiswise, robust, self.top_level,
609 prev_frame)
610 self.ranges.update(ranges)
611 return ranges

File ~/work/dev/holoviews/holoviews/plotting/plot.py:824, in DimensionedPlot._compute_group_range(cls, group, elements, ranges, framewise, axiswise, robust, top_level, prev_frame)
821 if t in ('factors', 'id'):
822 continue
823 matching &= (
--> 824 len({'date' if isinstance(v, util.datetime_types) else 'number'
825 for rng in rs for v in rng if util.isfinite(v)}) < 2
826 )
827 if matching:
828 group_dim_ranges[gdim] = values

File ~/work/dev/holoviews/holoviews/plotting/plot.py:825, in (.0)
821 if t in ('factors', 'id'):
822 continue
823 matching &= (
824 len({'date' if isinstance(v, util.datetime_types) else 'number'
--> 825 for rng in rs for v in rng if util.isfinite(v)}) < 2
826 )
827 if matching:
828 group_dim_ranges[gdim] = values

File ~/work/dev/holoviews/holoviews/core/util.py:886, in isfinite(val)
884 elif isinstance(val, (str, bytes)):
885 return True
--> 886 finite = np.isfinite(val)
887 if pd and pandas_version >= LooseVersion('1.0.0'):
888 if finite is pd.NA:

TypeError: ufunc 'isfinite' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
```

I'm not sure whether it's up to hvPlot or HoloViews to add support to this data type. Any opinion @jlstevens ?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.