Repository: dask/dask

Issue:
Series.groupby
We currently lack a `Series.groupby` method.  Fixing this probably involves a slight refactoring of the current groupby code.

``` python
In [1]: import pandas as pd
In [2]: s = pd.Series([1, 2, 2, 1, 1])
In [3]: s.groupby(s).count()
Out[3]: 
1    3
2    2
dtype: int64

In [4]: import dask.dataframe as dd
In [5]: ss = dd.from_pandas(s, npartitions=2)
In [6]: ss.groupby(ss).count()
AttributeError: 'Series' object has no attribute 'groupby'
```



Discussion:

Resolving patch:
diff --git a/dask/dataframe/core.py b/dask/dataframe/core.py
--- a/dask/dataframe/core.py
+++ b/dask/dataframe/core.py
@@ -811,6 +811,10 @@ def _aca_agg(self, token, func, aggfunc=None):
                    aggregate=lambda x: aggfunc(pd.Series(x)),
                    columns=return_scalar, token=self._token_prefix + token)
 
+    @wraps(pd.Series.groupby)
+    def groupby(self, index, **kwargs):
+        return SeriesGroupBy(self, index, **kwargs)
+
     @wraps(pd.Series.sum)
     def sum(self, axis=None):
         return super(Series, self).sum(axis=axis)
@@ -1500,7 +1504,10 @@ def _aca_agg(self, token, func, aggfunc=None):
         if isinstance(self.index, Series):
 
             def chunk(df, index, func=func, key=self.key):
-                return func(df.groupby(index)[key])
+                if isinstance(df, pd.Series):
+                    return func(df.groupby(index))
+                else:
+                    return func(df.groupby(index)[key])
 
             agg = lambda df: aggfunc(df.groupby(level=0))
             token = self._token_prefix + token
@@ -1619,12 +1626,20 @@ class SeriesGroupBy(_GroupBy):
 
     _token_prefix = 'series-groupby-'
 
-    def __init__(self, df, index, key, **kwargs):
+    def __init__(self, df, index, key=None, **kwargs):
         self.df = df
         self.index = index
         self.key = key
         self.kwargs = kwargs
 
+        if isinstance(df, Series):
+            if not isinstance(index, Series):
+                raise TypeError("A dask Series must be used as the index for a"
+                                " Series groupby.")
+            if not df.divisions == index.divisions:
+                raise NotImplementedError("The Series and index of the groupby"
+                                          " must have the same divisions.")
+
     def apply(self, func, columns=None):
         # df = set_index(self.df, self.index, **self.kwargs)
         if self.index._name == self.df.index._name:


Test patch:
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -1881,3 +1881,24 @@ def test_series_groupby_propagates_names():
     expected.name = 'y'
 
     tm.assert_series_equal(result.compute(), expected)
+
+
+def test_series_groupby():
+    s = pd.Series([1, 2, 2, 1, 1])
+    pd_group = s.groupby(s)
+
+    ss = dd.from_pandas(s, npartitions=2)
+    dask_group = ss.groupby(ss)
+
+    pd_group2 = s.groupby(s + 1)
+    dask_group2 = ss.groupby(ss + 1)
+
+    for dg, pdg in [(dask_group, pd_group), (pd_group2, dask_group2)]:
+        assert eq(dg.count(), pdg.count())
+        assert eq(dg.sum(), pdg.sum())
+        assert eq(dg.min(), pdg.min())
+        assert eq(dg.max(), pdg.max())
+
+    assert raises(TypeError, lambda: ss.groupby([1, 2]))
+    sss = dd.from_pandas(s, npartitions=3)
+    assert raises(NotImplementedError, lambda: ss.groupby(sss))