holoviz / geoviews

Simple, concise geographical visualization in Python
http://geoviews.org
BSD 3-Clause "New" or "Revised" License
595 stars 77 forks source link

geoviews throws TypeError: '<=' not supported between instances of #721

Closed dwr-psandhu closed 4 months ago

dwr-psandhu commented 6 months ago

Versions of relevant libraries (python 3.11 env)

bokeh 3.3.4 pyhd8ed1ab_0 conda-forge geopandas 0.14.3 pyhd8ed1ab_0 conda-forge geopandas-base 0.14.3 pyha770c72_0 conda-forge geoviews 1.11.1 pyhd8ed1ab_0 conda-forge geoviews-core 1.11.1 pyha770c72_0 conda-forge holoviews 1.18.3 pyhd8ed1ab_0 conda-forge

Unncessary calculation of range on columns

geopandas array with many columns some of which of items that are not comparable (i.e. different types or Nans)

Example

# %%
import geopandas as gpd
import pandas as pd
import holoviews as hv
import geoviews as gv

hv.extension("bokeh")
import cartopy.crs as ccrs

# %%
# create a dataframe with some x,y coordinates and a couple of columns
df = pd.DataFrame(
    {
        "x": [1, 2, 3, 4, 5],
        "y": [1, 2, 3, 4, 5],
        "value1": [10, 20, 30, 40, 50],
        "value2": [5, '4', 3, 2, 1],
    }
)
# emulate UTM zone 10n coordinates
df["x"] = df["x"].astype(float) + 625000
df["y"] = df["y"].astype(float) + 4180000
# %%
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.x, df.y))

# %%
gv.Points(gdf)
# %%

Stack traceback and/or browser JavaScript console output

{
    "name": "TypeError",
    "message": "'<=' not supported between instances of 'int' and 'str'",
    "stack": "---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\IPython\\core\\formatters.py:977, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    974     method = get_real_method(obj, self.print_method)
    976     if method is not None:
--> 977         return method(include=include, exclude=exclude)
    978     return None
    979 else:

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\core\\dimension.py:1286, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1279 def _repr_mimebundle_(self, include=None, exclude=None):
   1280     \"\"\"
   1281     Resolves the class hierarchy for the class rendering the
   1282     object using any display hooks registered on Store.display
   1283     hooks.  The output of all registered display_hooks is then
   1284     combined and returned.
   1285     \"\"\"
-> 1286     return Store.render(self)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\core\\options.py:1428, in Store.render(cls, obj)
   1426 data, metadata = {}, {}
   1427 for hook in hooks:
-> 1428     ret = hook(obj)
   1429     if ret is None:
   1430         continue

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\ipython\\display_hooks.py:287, in pprint_display(obj)
    285 if not ip.display_formatter.formatters['text/plain'].pprint:
    286     return None
--> 287 return display(obj, raw_output=True)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\ipython\\display_hooks.py:255, in display(obj, raw_output, **kwargs)
    253 elif isinstance(obj, (CompositeOverlay, ViewableElement)):
    254     with option_state(obj):
--> 255         output = element_display(obj)
    256 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
    257     with option_state(obj):

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\ipython\\display_hooks.py:149, in display_hook.<locals>.wrapped(element)
    147 try:
    148     max_frames = OutputSettings.options['max_frames']
--> 149     mimebundle = fn(element, max_frames=max_frames)
    150     if mimebundle is None:
    151         return {}, {}

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\ipython\\display_hooks.py:195, in element_display(element, max_frames)
    192 if type(element) not in Store.registry[backend]:
    193     return None
--> 195 return render(element)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\ipython\\display_hooks.py:76, in render(obj, **kwargs)
     73 if renderer.fig == 'pdf':
     74     renderer = renderer.instance(fig='png')
---> 76 return renderer.components(obj, **kwargs)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\renderer.py:396, in Renderer.components(self, obj, fmt, comm, **kwargs)
    393 embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
    395 if embed or config.comms == 'default':
--> 396     return self._render_panel(plot, embed, comm)
    397 return self._render_ipywidget(plot)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\renderer.py:403, in Renderer._render_panel(self, plot, embed, comm)
    401 doc = Document()
    402 with config.set(embed=embed):
--> 403     model = plot.layout._render_model(doc, comm)
    404 if embed:
    405     return render_model(model, comm)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\viewable.py:748, in Viewable._render_model(self, doc, comm)
    746 if comm is None:
    747     comm = state._comm_manager.get_server_comm()
--> 748 model = self.get_root(doc, comm)
    750 if self._design and self._design.theme.bokeh_theme:
    751     doc.theme = self._design.theme.bokeh_theme

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\layout\\base.py:311, in Panel.get_root(self, doc, comm, preprocess)
    307 def get_root(
    308     self, doc: Optional[Document] = None, comm: Optional[Comm] = None,
    309     preprocess: bool = True
    310 ) -> Model:
--> 311     root = super().get_root(doc, comm, preprocess)
    312     # ALERT: Find a better way to handle this
    313     if hasattr(root, 'styles') and 'overflow-x' in root.styles:

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\viewable.py:666, in Renderable.get_root(self, doc, comm, preprocess)
    664 wrapper = self._design._wrapper(self)
    665 if wrapper is self:
--> 666     root = self._get_model(doc, comm=comm)
    667     if preprocess:
    668         self._preprocess(root)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\layout\\base.py:177, in Panel._get_model(self, doc, root, parent, comm)
    175 root = root or model
    176 self._models[root.ref['id']] = (model, parent)
--> 177 objects, _ = self._get_objects(model, [], doc, root, comm)
    178 props = self._get_properties(doc)
    179 props[self._property_mapping['objects']] = objects

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\layout\\base.py:159, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    157 else:
    158     try:
--> 159         child = pane._get_model(doc, root, model, comm)
    160     except RerenderError as e:
    161         if e.layout is not None and e.layout is not self:

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\pane\\holoviews.py:423, in HoloViews._get_model(self, doc, root, parent, comm)
    421     plot = self.object
    422 else:
--> 423     plot = self._render(doc, comm, root)
    425 plot.pane = self
    426 backend = plot.renderer.backend

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\panel\\pane\\holoviews.py:518, in HoloViews._render(self, doc, comm, root)
    515     if comm:
    516         kwargs['comm'] = comm
--> 518 return renderer.get_plot(self.object, **kwargs)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\bokeh\\renderer.py:68, in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     61 @bothmethod
     62 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
     63     \"\"\"
     64     Given a HoloViews Viewable return a corresponding plot instance.
     65     Allows supplying a document attach the plot to, useful when
     66     combining the bokeh model with another plot.
     67     \"\"\"
---> 68     plot = super().get_plot(obj, doc, renderer, **kwargs)
     69     if plot.document is None:
     70         plot.document = Document() if self_or_cls.notebook_context else curdoc()

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\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 c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\plot.py:955, in DimensionedPlot.update(self, key)
    953 def update(self, key):
    954     if len(self) == 1 and key in (0, self.keys[0]) and not self.drawn:
--> 955         return self.initialize_plot()
    956     item = self.__getitem__(key)
    957     self.traverse(lambda x: setattr(x, '_updated', True))

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\geoviews\\plotting\\bokeh\\plot.py:108, in GeoPlot.initialize_plot(self, ranges, plot, plots, source)
    106 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
    107     opts = {} if isinstance(self, HvOverlayPlot) else {'source': source}
--> 108     fig = super().initialize_plot(ranges, plot, plots, **opts)
    109     if self.geographic and self.show_bounds and not self.overlaid:
    110         from . import GeoShapePlot

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\bokeh\\element.py:1867, in ElementPlot.initialize_plot(self, ranges, plot, plots, source)
   1865     element = self.hmap.last
   1866 key = util.wrap_tuple(self.hmap.last_key)
-> 1867 ranges = self.compute_ranges(self.hmap, key, ranges)
   1868 self.current_ranges = ranges
   1869 self.current_frame = element

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\plot.py:614, in DimensionedPlot.compute_ranges(self, obj, key, ranges)
    610     # Only compute ranges if not axiswise on a composite plot
    611     # or not framewise on a Overlay or ElementPlot
    612     if (not (axiswise and not isinstance(obj, HoloMap)) or
    613         (not framewise and isinstance(obj, HoloMap))):
--> 614         self._compute_group_range(group, elements, ranges, framewise,
    615                                   axiswise, robust, self.top_level,
    616                                   prev_frame)
    617 self.ranges.update(ranges)
    618 return ranges

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\plotting\\plot.py:724, in DimensionedPlot._compute_group_range(cls, group, elements, ranges, framewise, axiswise, robust, top_level, prev_frame)
    722     data_range = ds.range(el_dim, dimension_range=False)
    723 else:
--> 724     data_range = el.range(el_dim, dimension_range=False)
    726 data_ranges[(el, el_dim)] = data_range
    727 if dtype is not None and dtype.kind in 'uif' and robust:

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\core\\data\\__init__.py:196, in PipelineMeta.pipelined.<locals>.pipelined_fn(*args, **kwargs)
    193     inst._in_method = True
    195 try:
--> 196     result = method_fn(*args, **kwargs)
    197     if PipelineMeta.disable:
    198         return result

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\holoviews\\core\\data\\__init__.py:508, in Dataset.range(self, dim, data_range, dimension_range)
    506     return dim.range
    507 elif dim in self.dimensions() and data_range and bool(self):
--> 508     lower, upper = self.interface.range(self, dim)
    509 else:
    510     lower, upper = (np.nan, np.nan)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\geoviews\\data\\geopandas.py:287, in GeoPandasInterface.range(cls, dataset, dim)
    285 else:
    286     vals = dataset.data[dim.name]
--> 287     return vals.min(), vals.max()

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\\series.py:6498, in Series.min(self, axis, skipna, numeric_only, **kwargs)
   6490 @doc(make_doc(\"min\", ndim=1))
   6491 def min(
   6492     self,
   (...)
   6496     **kwargs,
   6497 ):
-> 6498     return NDFrame.min(self, axis, skipna, numeric_only, **kwargs)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\\generic.py:12385, in NDFrame.min(self, axis, skipna, numeric_only, **kwargs)
  12378 def min(
  12379     self,
  12380     axis: Axis | None = 0,
   (...)
  12383     **kwargs,
  12384 ):
> 12385     return self._stat_function(
  12386         \"min\",
  12387         nanops.nanmin,
  12388         axis,
  12389         skipna,
  12390         numeric_only,
  12391         **kwargs,
  12392     )

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\\generic.py:12374, in NDFrame._stat_function(self, name, func, axis, skipna, numeric_only, **kwargs)
  12370 nv.validate_func(name, (), kwargs)
  12372 validate_bool_kwarg(skipna, \"skipna\", none_allowed=False)
> 12374 return self._reduce(
  12375     func, name=name, axis=axis, skipna=skipna, numeric_only=numeric_only
  12376 )

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\\series.py:6448, in Series._reduce(self, op, name, axis, skipna, numeric_only, filter_type, **kwds)
   6443     # GH#47500 - change to TypeError to match other methods
   6444     raise TypeError(
   6445         f\"Series.{name} does not allow {kwd_name}={numeric_only} \"
   6446         \"with non-numeric dtypes.\"
   6447     )
-> 6448 return op(delegate, skipna=skipna, **kwds)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\
anops.py:147, in bottleneck_switch.__call__.<locals>.f(values, axis, skipna, **kwds)
    145         result = alt(values, axis=axis, skipna=skipna, **kwds)
    146 else:
--> 147     result = alt(values, axis=axis, skipna=skipna, **kwds)
    149 return result

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\
anops.py:404, in _datetimelike_compat.<locals>.new_func(values, axis, skipna, mask, **kwargs)
    401 if datetimelike and mask is None:
    402     mask = isna(values)
--> 404 result = func(values, axis=axis, skipna=skipna, mask=mask, **kwargs)
    406 if datetimelike:
    407     result = _wrap_results(result, orig_values.dtype, fill_value=iNaT)

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\\pandas\\core\
anops.py:1098, in _nanminmax.<locals>.reduction(values, axis, skipna, mask)
   1093     return _na_for_min_count(values, axis)
   1095 values, mask = _get_values(
   1096     values, skipna, fill_value_typ=fill_value_typ, mask=mask
   1097 )
-> 1098 result = getattr(values, meth)(axis)
   1099 result = _maybe_null_out(result, axis, mask, values.shape)
   1100 return result

File c:\\Users\\psandhu\\AppData\\Local\\miniconda3\\envs\\pydelmod\\Lib\\site-packages\
umpy\\core\\_methods.py:45, in _amin(a, axis, out, keepdims, initial, where)
     43 def _amin(a, axis=None, out=None, keepdims=False,
     44           initial=_NoValue, where=True):
---> 45     return umr_minimum(a, axis, None, out, keepdims, initial, where)

TypeError: '<=' not supported between instances of 'int' and 'str'"
}
dwr-psandhu commented 6 months ago

Just remove the string value '4' with the number 4 in the above example and it works just fine. Same issue happens if any value in the columns is NaN, etc...

ahuang11 commented 6 months ago

Thanks for sharing.

I think this is expected behavior since I don't expect geoviews to cast types.

However, GeoViews could raise a more descriptive error if this happens?

dwr-psandhu commented 6 months ago

That’s what I meant. A better error message than the default exception would be great. So should this be moved to holoviews ??

hoxbro commented 6 months ago

What triggers the error is as simple as df.min(). I'm not sure we should improve the error message, the data is bad and therefore the exception is bad. Maybe we could catch the error in geopandas.py:287, and make it nans but that could give weird side effects.

For the np.nan case we do emit a warning:

WARNING:param.project_points: While projecting a Points element from a PlateCarree coordinate reference system (crs) to a Mercator projection none of the projected paths were contained within the bounds specified by the projection. Ensure you have specified the correct coordinate system for your data.
dwr-psandhu commented 6 months ago

@Hoxbro I guess I don't understand why ranges would matter on non-numeric columns. So in the case of df.min there is an option to numeric_only df.min(numeric_only=True) doesn't calculate it for the non-numeric column.

hoxbro commented 6 months ago

It calculates the ranges of the element dimensions, and for gv.Points, all of the columns are used.

image

Also, it seems to set the p.kdims to longitude + latitude, which seems weird to me. When they are not in the data.