Repository: holoviz/hvplot

Issue:
Plot a time-dependent series of geographic polygons from GeoDataframe using hvplot and groupby
#### ALL software version info
hvplot                    0.8.0              pyh6c4a22f_0    conda-forge
bokeh                     2.4.3              pyhd8ed1ab_3    conda-forge
python                    3.10.5          h582c2e5_0_cpython    conda-forge
jupyter                   1.0.0           py310hff52083_7    conda-forge
jupyterlab                3.4.5              pyhd8ed1ab_0    conda-forge

OS: Rocky Linux 8.5 (Green Obsidian) - remote
Browser: Mozilla Firefox v82.0.2 (on Windows 10 18.09)

#### Description of expected behavior and the observed behavior

I am trying to plot a time series of maps with geometric polygons from a GeoDataframe. The GeoDataframe holds a `reference_time` next to the `geometry` with the polygons. So I am using the `groupby` argument of hvplot on the `reference_time`  variable. 

The plotting work with `geo=False`. However it crashes with an error with `geo=True`.

#### Complete, minimal, self-contained example code that reproduces the issue

```
import geopandas
import hvplot.pandas

json_str = '{"type": "FeatureCollection", "features": [{"id": "292", "type": "Feature", "properties": {"reference_time": "2021-08-03T06:00:00"}, "geometry": {"type": "Polygon", "coordinates": [[[6.734387964482268, 50.096981436429836], [6.715702760798321, 50.08350087669019], [6.7164606556881665, 50.07500090230046], [6.726932700709735, 50.06899131283367], [6.727498990205583, 50.062616788193964], [6.730988279570088, 50.06061342371476], [6.7311767879985, 50.05848865569604], [6.748616494592647, 50.048470246593965], [6.749553435189103, 50.0378472878539], [6.7565245571179045, 50.033839329129954], [6.822494229902199, 50.036222772078176], [6.829457117826622, 50.03220914103082], [6.832755487784024, 50.032326768682196], [6.836236195568452, 50.03031965546154], [6.839534418239325, 50.03043703037027], [6.843014579155103, 50.02842967525436], [6.846312654459997, 50.02854679744998], [6.849792268503478, 50.026539200507266], [6.853090196362978, 50.026656070019335], [6.856569263530571, 50.024648231318345], [6.882952088134839, 50.02557774446169], [6.889189529279116, 50.03005826409537], [6.888113617274682, 50.04280701271642], [6.884634555008016, 50.04481630967492], [6.8747356652759315, 50.04446880452035], [6.8712557178674984, 50.04647759882098], [6.8646562244125935, 50.04624505628612], [6.861175560726028, 50.048253478263526], [6.854575813313595, 50.04802018550571], [6.847612674584511, 50.05203617315432], [6.847430905745282, 50.05416109906186], [6.843948557379587, 50.056168944871104], [6.843766557088876, 50.05829391731779], [6.833316605440537, 50.06431684307392], [6.83001537252634, 50.06419921302388], [6.826531128284592, 50.066206501150646], [6.806723163146417, 50.065497367264776], [6.799751515201596, 50.069510039897715], [6.799566902060483, 50.07163509569316], [6.789105952186076, 50.077653323156746], [6.788550085538567, 50.0840288255632], [6.785061709927709, 50.086034734508154], [6.784876167120653, 50.088159975675374], [6.781387200638471, 50.09016579161379], [6.781201425169829, 50.092291079083346], [6.777711867667758, 50.094296801962315], [6.777525859439406, 50.09642213571793], [6.77403571076884, 50.098427765484495], [6.734387964482268, 50.096981436429836]]]}}, {"id": "329", "type": "Feature", "properties": {"reference_time": "2021-08-03T06:15:00"}, "geometry": {"type": "Polygon", "coordinates": [[[6.53500332673663, 50.17262838814129], [6.525676395793689, 50.165864441490726], [6.509130066483161, 50.165217619771184], [6.493598140164972, 50.15393999616791], [6.490290012794701, 50.15380969662393], [6.487184983165786, 50.151553827645586], [6.483877092942987, 50.151423292112206], [6.477668602593298, 50.1469110968444], [6.477872177168287, 50.14478575952954], [6.474768669293165, 50.142529583182], [6.474972387017253, 50.140404309639585], [6.468766845311061, 50.135891799690036], [6.452234991265688, 50.135234543767545], [6.449133859035186, 50.1329776075923], [6.449954293080332, 50.12447741050391], [6.443754499017571, 50.119963601072335], [6.440449599993195, 50.119831480170184], [6.437350486271928, 50.117574347547794], [6.434045824577542, 50.11744199108877], [6.430947304193949, 50.115184668906], [6.4276428798783325, 50.115052076929956], [6.424544952781937, 50.11279456525802], [6.421240765893827, 50.11266173780486], [6.421447405298261, 50.11053697719101], [6.415253448962403, 50.10602157197326], [6.415460396331883, 50.10389691192589], [6.412364153209042, 50.101639131494146], [6.409061010457461, 50.101505861479545], [6.405965360262447, 50.099247891817576], [6.406172804999734, 50.09712334179774], [6.403077699809985, 50.09486533258377], [6.406587622735568, 50.092874323519545], [6.406794995742371, 50.0907498552632], [6.41030432603753, 50.088758750082896], [6.413606331103893, 50.08889196357895], [6.417115116616857, 50.08690061275905], [6.433624670504715, 50.08756418677074], [6.452208535577216, 50.101104117299926], [6.458815082702433, 50.10136733427171], [6.461914147828928, 50.10362349560114], [6.465217670042701, 50.10375480739412], [6.4683173279463855, 50.10601077904516], [6.468113416590205, 50.108135588575635], [6.480516509589828, 50.1171588550765], [6.483821311675101, 50.11728948785017], [6.48692324762389, 50.11954499954339], [6.506753830987399, 50.120325544782304], [6.516065757468713, 50.12708957980573], [6.519371575991884, 50.127218901978026], [6.522476524780929, 50.129473313114914], [6.529088657857204, 50.129731363130404], [6.535300548774286, 50.13423950078656], [6.5340989899325645, 50.146991888717444], [6.537206508245576, 50.149246060243634], [6.537006334657516, 50.15137159007232], [6.540114400713651, 50.1536257208377], [6.539714318626019, 50.15787693505321], [6.542823099854781, 50.16013106138122], [6.541823326839114, 50.170759755944204], [6.538313303264706, 50.17275704375211], [6.53500332673663, 50.17262838814129]]]}}]}'
small_dataset = geopandas.read_file(json_str)

small_dataset.hvplot(geo=False, groupby="reference_time") # works
small_dataset.hvplot(geo=True, groupby="reference_time") # does not work

```

#### Stack traceback and/or browser JavaScript console output
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/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 /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/core/dimension.py:1293, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1286 def _repr_mimebundle_(self, include=None, exclude=None):
   1287     """
   1288     Resolves the class hierarchy for the class rendering the
   1289     object using any display hooks registered on Store.display
   1290     hooks.  The output of all registered display_hooks is then
   1291     combined and returned.
   1292     """
-> 1293     return Store.render(self)

File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/core/options.py:1418, in Store.render(cls, obj)
   1416 data, metadata = {}, {}
   1417 for hook in hooks:
-> 1418     ret = hook(obj)
   1419     if ret is None:
   1420         continue

File /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:253, in display(obj, raw_output, **kwargs)
    251 elif isinstance(obj, (HoloMap, DynamicMap)):
    252     with option_state(obj):
--> 253         output = map_display(obj)
    254 elif isinstance(obj, Plot):
    255     output = render(obj)

File /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:201, in map_display(vmap, max_frames)
    198     max_frame_warning(max_frames)
    199     return None
--> 201 return render(vmap)

File /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/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 /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/panel/pane/holoviews.py:272, in HoloViews._get_model(self, doc, root, parent, comm)
    269 kwargs = {p: v for p, v in self.param.values().items()
    270           if p in Layoutable.param and p != 'name'}
    271 child_pane = self._get_pane(backend, state, **kwargs)
--> 272 self._update_plot(plot, child_pane)
    273 model = child_pane._get_model(doc, root, parent, comm)
    274 if ref in self._plots:

File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/panel/pane/holoviews.py:208, in HoloViews._update_plot(self, plot, pane)
    206 if plot.comm or state._unblocked(plot.document):
    207     with unlocked():
--> 208         plot.update(key)
    209     if plot.comm and 'embedded' not in plot.root.tags:
    210         plot.push()

File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/plotting/plot.py:949, in DimensionedPlot.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))
    951 return item

File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/plotting/plot.py:435, in DimensionedPlot.__getitem__(self, frame)
    433 if not isinstance(frame, tuple):
    434     frame = self.keys[frame]
--> 435 self.update_frame(frame)
    436 return self.state

File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/holoviews/plotting/bokeh/element.py:1526, in ElementPlot.update_frame(self, key, ranges, plot, element)
   1523     self._updated = True
   1525 if 'hover' in self.handles:
-> 1526     self._update_hover(element)
   1527     if 'cds' in self.handles:
   1528         cds = self.handles['cds']

File /media/users/user/opt/miniconda3/envs/holoviz/lib/python3.10/site-packages/geoviews/plotting/bokeh/plot.py:161, in GeoPlot._update_hover(self, element)
    158 if 'hv_created' in hover.tags:
    159     tooltips = [(ttp.pprint_label, '@{%s}' % dimension_sanitizer(ttp.name))
    160                 if isinstance(ttp, Dimension) else ttp for ttp in tooltips]
--> 161     if self.geographic and tooltips[2:] == hover.tooltips[2:]:
    162         return
    163     tooltips = [(l, t+'{custom}' if t in hover.formatters else t) for l, t in tooltips]

TypeError: 'NoneType' object is not subscriptable

:DynamicMap   [reference_time]
   :Polygons   [Longitude,Latitude]
```

#### Screenshots or screencasts of the bug in action



Discussion:
Thanks for reporting this issue, it looks like a bug indeed. I've seen you posted it first on [StackOverflow](https://stackoverflow.com/questions/73449326/plot-a-time-dependent-series-of-geographic-polygons-from-geodataframe-using-hvpl). The HoloViz community is more active on [Discourse](https://discourse.holoviz.org/), this is the recommended place for asking questions :)
As a workaround, if you pass `hover_cols="all"` you can get passed this error. The issue is that hvplot is trying to be too clever about which columns to include in the input for the plot.

```python
small_dataset.hvplot(geo=True, groupby="reference_time", hover_cols=True) # works
```

I have a fix for this. PR coming shortly. Just need to write a test.
Ok I had what I thought was a solution but it resulted in a test failure (yay tests!) so I ended up with a much more niche solution. There will be a geoviews PR as well.

Resolving patch:
diff --git a/hvplot/converter.py b/hvplot/converter.py
--- a/hvplot/converter.py
+++ b/hvplot/converter.py
@@ -2243,9 +2243,12 @@ def _geom_plot(self, x=None, y=None, data=None, kind='polygons'):
         for opts_ in [cur_opts, compat_opts]:
             if 'color' in opts_ and opts_['color'] in vdims:
                 opts_['color'] = hv.dim(opts_['color'])
+            # if there is nothing to put in hover, turn it off
+            if 'tools' in opts_ and kind in ["polygons", "paths"] and not vdims:
+                opts_["tools"] = [t for t in opts_["tools"] if t != "hover"]
         if self.geo: params['crs'] = self.crs
         if self.by:
-            obj = Dataset(data).to(element, kdims, vdims, self.by, **params)
+            obj = Dataset(data, self.by+kdims, vdims).to(element, kdims, vdims, self.by, **params)
             if self.subplots:
                 obj = obj.layout(sort=False)
             else:


Test patch:
diff --git a/hvplot/tests/testgeo.py b/hvplot/tests/testgeo.py
--- a/hvplot/tests/testgeo.py
+++ b/hvplot/tests/testgeo.py
@@ -7,7 +7,6 @@
 import numpy as np
 import pandas as pd
 import holoviews as hv
-import pytest
 
 from hvplot.util import proj_to_cartopy
 
@@ -273,11 +272,14 @@ def test_points_hover_cols_with_c_set_to_name(self):
         opts = hv.Store.lookup_options('bokeh', points, 'style').kwargs
         assert opts['color'] == 'name'
 
-    @pytest.mark.xfail
     def test_points_hover_cols_with_by_set_to_name(self):
         points = self.cities.hvplot(by='name')
-        assert points.kdims == ['x', 'y']
-        assert points.vdims == ['name']
+        assert isinstance(points, hv.core.overlay.NdOverlay)
+        assert points.kdims == ['name']
+        assert points.vdims == []
+        for element in points.values():
+            assert element.kdims == ['x', 'y']
+            assert element.vdims == []
 
     def test_points_project_xlim_and_ylim(self):
         points = self.cities.hvplot(geo=True, xlim=(-10, 10), ylim=(-20, -10))
@@ -285,10 +287,11 @@ def test_points_project_xlim_and_ylim(self):
         assert opts['xlim'] == (-10, 10)
         assert opts['ylim'] == (-20, -10)
 
-    @pytest.mark.xfail(
-        reason='Waiting for upstream fix https://github.com/holoviz/holoviews/pull/5325',
-        raises=KeyError,
-    )
     def test_polygons_by_subplots(self):
         polygons = self.polygons.hvplot(geo=True, by="name", subplots=True)
         assert isinstance(polygons, hv.core.layout.NdLayout)
+
+    def test_polygons_turns_off_hover_when_there_are_no_fields_to_include(self):
+        polygons = self.polygons.hvplot(geo=True)
+        opts = hv.Store.lookup_options('bokeh', polygons, 'plot').kwargs
+        assert 'hover' not in opts.get('tools')