Repository: holoviz/hvplot

Issue:
Missing behavior for zs
As reported in https://stackoverflow.com/questions/58350586/generate-xarray-dataset-image-subplots, passing a list of `zs` appears not to be fully implemented in current hvplot master:

```
import xarray as xr, numpy as np, hvplot.xarray
ds = xr.Dataset({
    'temp': (('lon', 'lat'), 15 + 8 * np.random.randn(2, 2)),
    'precip': (('lon', 'lat'), 10 * np.random.rand(2, 2))},
    coords={'lon': [-99.83, -99.32],'lat': [42.25, 42.21]})
ds.hvplot(x="lat", y="lon", z=['temp', 'precip'])
```

```
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-18-ca3236b8574f> in <module>
      4     'precip': (('lon', 'lat'), 10 * np.random.rand(2, 2))},
      5     coords={'lon': [-99.83, -99.32],'lat': [42.25, 42.21]})
----> 6 ds.hvplot(x="lat", y="lon", z=['temp', 'precip'])

~/hvplot/hvplot/plotting/core.py in __call__(self, x, y, kind, **kwds)
     72                 return pn.panel(plot, **panel_dict)
     73 
---> 74         return self._get_converter(x, y, kind, **kwds)(kind, x, y)
     75 
     76     def _get_converter(self, x=None, y=None, kind=None, **kwds):

~/hvplot/hvplot/converter.py in __call__(self, kind, x, y)
    938 
    939         if not (self.datashade or self.rasterize):
--> 940             return self._apply_layers(obj)
    941 
    942         try:

UnboundLocalError: local variable 'obj' referenced before assignment
```

Plotting works fine if a single item (precip or temp here) is passed for `z`, but a multi-item list of zs fails. It _appears_ to have been intended to be implemented, given the handling for `len(zs) > 1` in 
[`hvplot.converter.HoloViewsConverter.__call__()`](https://github.com/pyviz/hvplot/blob/master/hvplot/converter.py#L913), but that code defines `dataset` without defining `obj`, causing a problem when all the various branches eventually lead to something processing `obj`.  A single-item list of zs also fails, for a different reason, but that might not have been expected to be implemented.  The original poster was hoping to use multiple `zs` as subplots, which seems reasonable given how multiple `ys` work, though without subplots it's not clear how the multiple zs should be handled.  (With `ys`, the default is  to overlay them, but overlaying multiple colormaps isn't particularly meaningful, so a default of getting a widget seems more useful in that case.)  In any case, this case appears to be _partially_ implemented, and it seems like it should either be fully implemented or explicitly made into an error.


Discussion:

Resolving patch:
diff --git a/hvplot/converter.py b/hvplot/converter.py
--- a/hvplot/converter.py
+++ b/hvplot/converter.py
@@ -30,7 +30,7 @@
 from pandas import DatetimeIndex, MultiIndex
 
 from .util import (
-    is_tabular, is_series, is_dask, is_intake, is_streamz, is_xarray,
+    is_tabular, is_series, is_dask, is_intake, is_streamz, is_xarray, is_xarray_dataarray,
     process_crs, process_intake, process_xarray, check_library, is_geopandas,
     process_derived_datetime_xarray, process_derived_datetime_pandas
 )
@@ -498,7 +498,10 @@ def _process_symmetric(self, symmetric, clim, check_symmetric_max):
             if self.data.chunks:
                 return False
             data = self.data[self.z]
-            if data.size > check_symmetric_max:
+            if is_xarray_dataarray(data):
+                if data.size > check_symmetric_max:
+                    return False
+            else:
                 return False
 
         elif self._color_dim:
@@ -920,12 +923,13 @@ def __call__(self, kind, x, y):
                 else:
                     obj = dataset.map(lambda ds: method(x, y, data=ds.data), Dataset)
             elif len(zs) > 1:
+                dimensions = [Dimension(self.group_label, values=zs)]
                 if self.dynamic:
-                    dataset = DynamicMap(lambda z: method(x, y, z, data=dataset.data),
-                                         kdims=[Dimension(self.group_label, values=zs)])
+                    obj = DynamicMap(lambda z: method(x, y, z, data=dataset.data),
+                                     kdims=dimensions)
                 else:
-                    dataset = HoloMap({z: method(x, y, z, data=dataset.data) for z in zs},
-                                      kdims=[self.group_label])
+                    obj = HoloMap({z: method(x, y, z, data=dataset.data) for z in zs},
+                                  kdims=dimensions)
             else:
                 obj = method(x, y, data=dataset.data)
             if self.grid:
diff --git a/hvplot/util.py b/hvplot/util.py
--- a/hvplot/util.py
+++ b/hvplot/util.py
@@ -253,6 +253,12 @@ def is_xarray(data):
     from xarray import DataArray, Dataset
     return isinstance(data, (DataArray, Dataset))
 
+def is_xarray_dataarray(data):
+    if not check_library(data, 'xarray'):
+        return False
+    from xarray import DataArray
+    return isinstance(data, DataArray)
+
 
 def process_intake(data, use_dask):
     if data.container not in ('dataframe', 'xarray'):


Test patch:
diff --git a/hvplot/tests/testgridplots.py b/hvplot/tests/testgridplots.py
--- a/hvplot/tests/testgridplots.py
+++ b/hvplot/tests/testgridplots.py
@@ -38,6 +38,11 @@ def setUp(self):
         self.da_img = xr.DataArray(np.arange(-2, 2).reshape((2, 2)), name='foo')
         self.big_img = xr.DataArray(np.arange(-1e6, 1e6).reshape(1000, 2000))
 
+        self.ds = xr.Dataset({
+            'temp': (('lon', 'lat'), 15 + 8 * np.random.randn(2, 2)),
+            'precip': (('lon', 'lat'), 10 * np.random.rand(2, 2))},
+            coords={'lon': [-99.83, -99.32],'lat': [42.25, 42.21]})
+
     def test_rgb_dataarray_no_args(self):
         rgb = self.da_rgb.hvplot()
         self.assertEqual(rgb, RGB(([0, 1], [0, 1])+tuple(self.da_rgb.values)))
@@ -155,3 +160,10 @@ def test_symmetric_with_big_img_and_check_symmetric_max_calculates_symmetric(sel
         self.assertEqual(plot_opts.kwargs.get('symmetric'), True)
         style_opts = Store.lookup_options('bokeh', plot, 'style')
         self.assertEqual(style_opts.kwargs['cmap'], 'coolwarm')
+
+    def test_multiple_zs(self):
+        plot = self.ds.hvplot(x='lat', y='lon', z=['temp', 'precip'], dynamic=False)
+        assert 'temp' in plot.keys()
+        assert 'precip' in plot.keys()
+        assert plot['temp'].kdims == ['lat', 'lon']
+        assert plot['precip'].kdims == ['lat', 'lon']