Repository: holoviz/hvplot

Issue:
Derived datetime accessor (time.dayofyear)
Something like xarray
![image](https://user-images.githubusercontent.com/15331990/42415645-47d2f010-820a-11e8-81d7-f641ec4fee5d.png)

So that instead of
```
df['day'] = df.index.day
df.dropna(subset=['tmpf']).hvplot('valid', 'tmpf', groupby='day')
```

one can...
`df.dropna(subset=['tmpf']).hvplot('valid', 'tmpf', groupby='valid.day')`


Discussion:
Maybe we could add a `.hvplot` method on `groupby` objects themselves so that we can support any type of grouped output. 

Resolving patch:
diff --git a/hvplot/converter.py b/hvplot/converter.py
--- a/hvplot/converter.py
+++ b/hvplot/converter.py
@@ -29,7 +29,8 @@
 
 from .util import (
     is_series, is_dask, is_intake, is_streamz, is_xarray, process_crs,
-    process_intake, process_xarray, check_library, is_geopandas
+    process_intake, process_xarray, check_library, is_geopandas,
+    process_derived_datetime,
 )
 
 renderer = hv.renderer('bokeh')
@@ -574,6 +575,7 @@ def _process_data(self, kind, data, x, y, by, groupby, row, col,
 
         if gridded_data:
             not_found = [g for g in groupby if g not in data.coords]
+            not_found, *_ = process_derived_datetime(data, not_found)
             data_vars = list(data.data_vars) if isinstance(data, xr.Dataset) else [data.name]
             indexes = list(data.coords)
             self.variables = list(data.coords) + data_vars
diff --git a/hvplot/util.py b/hvplot/util.py
--- a/hvplot/util.py
+++ b/hvplot/util.py
@@ -226,6 +226,12 @@ def is_xarray(data):
     from xarray import DataArray, Dataset
     return isinstance(data, (DataArray, Dataset))
 
+def is_xarray_groupby(data):
+    if not check_library(data, 'xarray'):
+        return False
+    from xarray.core.groupby import DataArrayGroupBy, DatasetGroupBy
+    return isinstance(data, (DataArrayGroupBy, DatasetGroupBy))
+
 
 def process_intake(data, use_dask):
     if data.container not in ('dataframe', 'xarray'):
@@ -251,6 +257,19 @@ def process_xarray(data, x, y, by, groupby, use_dask, persist, gridded, label, v
         name = data.name or label or value_label
         dataset = data.to_dataset(name=name)
 
+    all_vars = list(other_dims) if other_dims else []
+    for var in [x, y, by, groupby]:
+        if isinstance(var, list):
+            all_vars.extend(var)
+        elif isinstance(var, str):
+            all_vars.append(var)
+
+    if not gridded:
+        not_found = [var for var in all_vars if var not in list(dataset.data_vars) + list(dataset.coords)]
+        _, extra_vars, extra_coords = process_derived_datetime(dataset, not_found)
+        dataset = dataset.assign_coords(**{var: dataset[var] for var in extra_coords})
+        dataset = dataset.assign(**{var: dataset[var] for var in extra_vars})
+
     data_vars = list(dataset.data_vars)
     ignore = (by or []) + (groupby or [])
     dims = [c for c in dataset.coords if dataset[c].shape != () and c not in ignore][::-1]
@@ -293,9 +312,36 @@ def process_xarray(data, x, y, by, groupby, use_dask, persist, gridded, label, v
         elif not x and not y:
             x, y = dims[0], data_vars
 
+        for var in [x, y]:
+            if isinstance(var, list):
+                all_vars.extend(var)
+            elif isinstance(var, str):
+                all_vars.append(var)
+
+        covered_dims = []
+        for var in all_vars:
+            if var in dataset.coords:
+                covered_dims.extend(dataset[var].dims)
+        leftover_dims = [dim for dim in dims if dim not in covered_dims + all_vars]
+
         if by is None:
-            by = [c for c in dims if c not in (x, y)]
-            if len(by) > 1: by = []
+            by = leftover_dims if len(leftover_dims) == 1 else []
         if groupby is None:
-            groupby = [c for c in dims if c not in by+[x, y]]
+            groupby = [c for c in leftover_dims if c not in by]
     return data, x, y, by, groupby
+
+
+def process_derived_datetime(data, not_found):
+    from pandas.api.types import is_datetime64_any_dtype as isdate
+    extra_vars = []
+    extra_coords = []
+    for var in not_found:
+        if '.' in var:
+            derived_from = var.split('.')[0]
+            if isdate(data[derived_from]):
+                if derived_from in data.coords:
+                    extra_coords.append(var)
+                else:
+                    extra_vars.append(var)
+    not_found = [var for var in not_found if var not in extra_vars + extra_coords]
+    return not_found, extra_vars, extra_coords


Test patch:
diff --git a/hvplot/tests/testutil.py b/hvplot/tests/testutil.py
--- a/hvplot/tests/testutil.py
+++ b/hvplot/tests/testutil.py
@@ -41,8 +41,8 @@ def test_process_1d_xarray_dataarray_with_no_coords(self):
         assert isinstance(data, pd.DataFrame)
         assert x == 'index'
         assert y == ['value']
-        assert by == []
-        assert groupby == []
+        assert not by
+        assert not groupby
 
     def test_process_1d_xarray_dataarray_with_coords(self):
         import xarray as xr
@@ -57,8 +57,8 @@ def test_process_1d_xarray_dataarray_with_coords(self):
         assert isinstance(data, pd.DataFrame)
         assert x == 'day'
         assert y == ['value']
-        assert by == []
-        assert groupby == []
+        assert not by
+        assert not groupby
 
     def test_process_1d_xarray_dataarray_with_coords_and_name(self):
         import xarray as xr
@@ -74,8 +74,8 @@ def test_process_1d_xarray_dataarray_with_coords_and_name(self):
         assert isinstance(data, pd.DataFrame)
         assert x == 'day'
         assert y == ['temp']
-        assert by == []
-        assert groupby == []
+        assert not by
+        assert not groupby
 
     def test_process_2d_xarray_dataarray_with_no_coords(self):
         import xarray as xr
@@ -87,8 +87,8 @@ def test_process_2d_xarray_dataarray_with_no_coords(self):
         assert isinstance(data, pd.DataFrame)
         assert x == 'index'
         assert y == ['value']
-        assert by == []
-        assert groupby == []
+        assert not by
+        assert not groupby
 
     def test_process_2d_xarray_dataarray_with_no_coords_as_gridded(self):
         import xarray as xr
@@ -103,8 +103,8 @@ def test_process_2d_xarray_dataarray_with_no_coords_as_gridded(self):
         assert list(data.data_vars.keys()) == ['value']
         assert x == 'dim_1'
         assert y == 'dim_0'
-        assert by is None
-        assert groupby is None
+        assert not by
+        assert not groupby
 
     def test_process_2d_xarray_dataarray_with_coords_as_gridded(self):
         import xarray as xr
@@ -122,8 +122,8 @@ def test_process_2d_xarray_dataarray_with_coords_as_gridded(self):
         assert list(data.data_vars.keys()) == ['value']
         assert x == 'y'
         assert y == 'x'
-        assert by is None
-        assert groupby is None
+        assert not by
+        assert not groupby
 
     def test_process_3d_xarray_dataset_with_coords(self):
         import pandas as pd
@@ -132,7 +132,7 @@ def test_process_3d_xarray_dataset_with_coords(self):
         assert isinstance(data, pd.DataFrame)
         assert x == 'time'
         assert y == ['air']
-        assert by == []
+        assert not by
         assert groupby == ['lon', 'lat']
 
     def test_process_3d_xarray_dataset_with_coords_as_gridded(self):
@@ -160,5 +160,33 @@ def test_process_3d_xarray_dataset_with_coords_as_gridded_uses_axis_to_get_defau
         assert list(data.data_vars.keys()) == ['air']
         assert x == 'lon'
         assert y == 'lat'
-        assert by is None
+        assert not by
         assert groupby == ['time']
+
+    def test_process_xarray_dataset_with_by_as_derived_datetime(self):
+        import pandas as pd
+
+        data = self.ds.mean(dim=['lat', 'lon'])
+        kwargs = self.default_kwargs
+        kwargs.update(gridded=False, y='air', by=['time.hour'])
+
+        data, x, y, by, groupby = process_xarray(data=data, **kwargs)
+        assert isinstance(data, pd.DataFrame)
+        assert x == 'time'
+        assert y == 'air'
+        assert by == ['time.hour']
+        assert not groupby
+
+    def test_process_xarray_dataset_with_x_as_derived_datetime(self):
+        import pandas as pd
+
+        data = self.ds.mean(dim=['lat', 'lon'])
+        kwargs = self.default_kwargs
+        kwargs.update(gridded=False, y='air', x='time.dayofyear')
+
+        data, x, y, by, groupby = process_xarray(data=data, **kwargs)
+        assert isinstance(data, pd.DataFrame)
+        assert x == 'time.dayofyear'
+        assert y == 'air'
+        assert not by
+        assert not groupby