holoviz / hvplot

A high-level plotting API for pandas, dask, xarray, and networkx built on HoloViews
https://hvplot.holoviz.org
BSD 3-Clause "New" or "Revised" License
1.09k stars 105 forks source link

hvplot.labels: TypeError: '<' not supported between instances of 'str' and 'int' #994

Open MarcSkovMadsen opened 1 year ago

MarcSkovMadsen commented 1 year ago

hvplot==0.8.1

A colleague of mine was trying to add labels to her plot. She got an exception that was hard to find the cause of. The problem was that she was using heterogenous data as text labels.

Here is a minimum, reproducible example.

import hvplot.pandas
import pandas as pd

df = pd.DataFrame({
    "x": [1,2],
    "y": [3,4],
    "text": [0, "+1"],
})
df.hvplot.labels(x="x", y="y", text="text")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /opt/conda/lib/python3.10/site-packages/IPython/core/formatters.py:972, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    969     method = get_real_method(obj, self.print_method)
    971     if method is not None:
--> 972         return method(include=include, exclude=exclude)
    973     return None
    974 else:

File /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:141, in display_hook.<locals>.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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/panel/viewable.py:507, in Renderable._render_model(self, doc, comm)
    505 if comm is None:
    506     comm = state._comm_manager.get_server_comm()
--> 507 model = self.get_root(doc, comm)
    509 if config.embed:
    510     embed_state(self, model, doc,
    511                 json=config.embed_json,
    512                 json_prefix=config.embed_json_prefix,
    513                 save_path=config.embed_save_path,
    514                 load_path=config.embed_load_path,
    515                 progress=False)

File /opt/conda/lib/python3.10/site-packages/panel/viewable.py:558, in Renderable.get_root(self, doc, comm, preprocess)
    541 """
    542 Returns the root model and applies pre-processing hooks
    543 
   (...)
    555 Returns the bokeh model corresponding to this panel object
    556 """
    557 doc = init_doc(doc)
--> 558 root = self._get_model(doc, comm=comm)
    559 if preprocess:
    560     self._preprocess(root)

File /opt/conda/lib/python3.10/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 /opt/conda/lib/python3.10/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 /opt/conda/lib/python3.10/site-packages/panel/pane/holoviews.py:248, in HoloViews._get_model(self, doc, root, parent, comm)
    246     plot = self.object
    247 else:
--> 248     plot = self._render(doc, comm, root)
    250 plot.pane = self
    251 backend = plot.renderer.backend

File /opt/conda/lib/python3.10/site-packages/panel/pane/holoviews.py:324, in HoloViews._render(self, doc, comm, root)
    321     if comm:
    322         kwargs['comm'] = comm
--> 324 return renderer.get_plot(self.object, **kwargs)

File /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/holoviews/plotting/bokeh/element.py:1380, in ElementPlot.initialize_plot(self, ranges, plot, plots, source)
   1378     element = self.hmap.last
   1379 key = util.wrap_tuple(self.hmap.last_key)
-> 1380 ranges = self.compute_ranges(self.hmap, key, ranges)
   1381 self.current_ranges = ranges
   1382 self.current_frame = element

File /opt/conda/lib/python3.10/site-packages/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 /opt/conda/lib/python3.10/site-packages/holoviews/plotting/plot.py:717, in DimensionedPlot._compute_group_range(cls, group, elements, ranges, framewise, axiswise, robust, top_level, prev_frame)
    715     data_range = ds.range(el_dim, dimension_range=False)
    716 else:
--> 717     data_range = el.range(el_dim, dimension_range=False)
    719 data_ranges[(el, el_dim)] = data_range
    720 if dtype is not None and dtype.kind == 'uif' and robust:

File /opt/conda/lib/python3.10/site-packages/holoviews/core/data/__init__.py:204, in PipelineMeta.pipelined.<locals>.pipelined_fn(*args, **kwargs)
    201     inst._in_method = True
    203 try:
--> 204     result = method_fn(*args, **kwargs)
    205     if PipelineMeta.disable:
    206         return result

File /opt/conda/lib/python3.10/site-packages/holoviews/core/data/__init__.py:515, in Dataset.range(self, dim, data_range, dimension_range)
    513     return dim.range
    514 elif dim in self.dimensions() and data_range and bool(self):
--> 515     lower, upper = self.interface.range(self, dim)
    516 else:
    517     lower, upper = (np.NaN, np.NaN)

File /opt/conda/lib/python3.10/site-packages/holoviews/core/data/pandas.py:179, in PandasInterface.range(cls, dataset, dimension)
    177     column = column.sort(inplace=False)
    178 else:
--> 179     column = column.sort_values()
    180 try:
    181     column = column[~column.isin([None, pd.NA])]

File /opt/conda/lib/python3.10/site-packages/pandas/util/_decorators.py:331, in deprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper(*args, **kwargs)
    325 if len(args) > num_allow_args:
    326     warnings.warn(
    327         msg.format(arguments=_format_argument_list(allow_args)),
    328         FutureWarning,
    329         stacklevel=find_stack_level(),
    330     )
--> 331 return func(*args, **kwargs)

File /opt/conda/lib/python3.10/site-packages/pandas/core/series.py:3768, in Series.sort_values(self, axis, ascending, inplace, kind, na_position, ignore_index, key)
   3766 # GH 35922. Make sorting stable by leveraging nargsort
   3767 values_to_sort = ensure_key_mapped(self, key)._values if key else self._values
-> 3768 sorted_index = nargsort(values_to_sort, kind, bool(ascending), na_position)
   3770 result = self._constructor(
   3771     self._values[sorted_index], index=self.index[sorted_index]
   3772 )
   3774 if ignore_index:

File /opt/conda/lib/python3.10/site-packages/pandas/core/sorting.py:438, in nargsort(items, kind, ascending, na_position, key, mask)
    436     non_nans = non_nans[::-1]
    437     non_nan_idx = non_nan_idx[::-1]
--> 438 indexer = non_nan_idx[non_nans.argsort(kind=kind)]
    439 if not ascending:
    440     indexer = indexer[::-1]

TypeError: '<' not supported between instances of 'str' and 'int'
maximlt commented 1 year ago

Thanks for the reproducible example!

@MarcSkovMadsen could you please not assign milestones and labels on hvPlot repo? I've been trying to careful curate them and I'm still in the process of doing so. I'll let you know once this is done and will make it clearer how it should be done. Thanks!

matsvandecavey commented 1 year ago

Same problem arises when using heterogeneous data column using hover_cols : df.hvplot(y='numeric',hover_cols=['heterogeneous']) with df:

  | numeric | heterogeneous -- | -- | -- row1 | 0.0 | "string" row2 | 1.0 | 0

quick user side fix using df = df.assign(heterogeneous=df.heterogeneous.astype('str'))

GeoVizNow commented 8 months ago

I just came across a similar situation using holoviews and had troubles debugging. It seem the issue above is not limited to hvplot (but I guess also the traceback indicates that), and not limited to when the hetereeogenous columns is actually used. I am on the following versions:

Your code example @MarcSkovMadsen converted to holoviews:

import pandas as pd
import holoviews as hv
hv.extension('bokeh')

df = pd.DataFrame({
    "x": [1,2],
    "y": [3,4],
    "text": [0, "+1"],
})
hv.Points(df, ["x", "y"])

even though the heterogenous column is not part of the picture.

while if one exclude the column with heterogenous info entirely then there is no error.

hv.Points(df[["x", "y"]], ["x", "y"])
hoxbro commented 8 months ago

hv.Points(df, ["x", "y"]) use the text column as the vdims. You can see this by: print(hv.Points(df, ["x", "y"]))