Repository: pandas-dev/pandas
Pull request: Fixed Issue Preventing Agg on RollingGroupBy Objects
- [X] closes #15072
- [X] tests added / passed
- [X] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

AFAICT the fact that RollingGroupBy could not use `agg` with a list of functions is simply due to the fact that the GroupByMixing it inherits from could not handle the reduction in dimensions that occurs via the normal aggregation functions.

Problem statement:
BUG/ENH: groupby.rolling.agg / Column(s) low already selected
#### GroupBy.rolling.agg failed with Column(s) low already selected

```python
data = pd.DataFrame({"stock": [1, 1, 1, 2, 2, 2, 2], "low": [10, 20, 30, 10, 30, 40, 80]})
data.set_index("stock", inplace=True)
# failed
data.groupby(level="stock").rolling(2).agg({"low": {"mean": "mean", "max": "max"}})
# works
data.rolling(2).agg({"low": {"mean": "mean", "max": "max"}})
```

This also hits `groupby().resample.agg`


```python
In [13]: df = pd.DataFrame({"A": pd.to_datetime(['2015', '2017']), "B": [1, 1]})

In [14]: df
Out[14]:
           A  B
0 2015-01-01  1
1 2017-01-01  1

In [15]: df.set_index("A").groupby([0, 0]).resample("AS")
Out[15]: DatetimeIndexResamplerGroupby [freq=<YearBegin: month=1>, axis=0, closed=left, label=left, convention=e, base=0]

In [16]: df.set_index("A").groupby([0, 0]).resample("AS").agg(['sum', 'count'])
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-16-5f1c18a8d4ac> in <module>()
----> 1 df.set_index("A").groupby([0, 0]).resample("AS").agg(['sum', 'count'])

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/resample.py in aggregate(self, arg, *args, **kwargs)
    339
    340         self._set_binner()
--> 341         result, how = self._aggregate(arg, *args, **kwargs)
    342         if result is None:
    343             result = self._groupby_and_aggregate(arg,

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/base.py in _aggregate(self, arg, *args, **kwargs)
    538             return self._aggregate_multiple_funcs(arg,
    539                                                   _level=_level,
--> 540                                                   _axis=_axis), None
    541         else:
    542             result = None

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/base.py in _aggregate_multiple_funcs(self, arg, _level, _axis)
    583                 try:
    584                     colg = self._gotitem(col, ndim=1, subset=obj[col])
--> 585                     results.append(colg.aggregate(arg))
    586                     keys.append(col)
    587                 except (TypeError, DataError):

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/resample.py in aggregate(self, arg, *args, **kwargs)
    339
    340         self._set_binner()
--> 341         result, how = self._aggregate(arg, *args, **kwargs)
    342         if result is None:
    343             result = self._groupby_and_aggregate(arg,

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/base.py in _aggregate(self, arg, *args, **kwargs)
    538             return self._aggregate_multiple_funcs(arg,
    539                                                   _level=_level,
--> 540                                                   _axis=_axis), None
    541         else:
    542             result = None

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/base.py in _aggregate_multiple_funcs(self, arg, _level, _axis)
    582             for col in obj:
    583                 try:
--> 584                     colg = self._gotitem(col, ndim=1, subset=obj[col])
    585                     results.append(colg.aggregate(arg))
    586                     keys.append(col)

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/base.py in _gotitem(self, key, ndim, subset)
    675                        for attr in self._attributes])
    676         self = self.__class__(subset,
--> 677                               groupby=self._groupby[key],
    678                               parent=self,
    679                               **kwargs)

~/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/base.py in __getitem__(self, key)
    241         if self._selection is not None:
    242             raise Exception('Column(s) {selection} already selected'
--> 243                             .format(selection=self._selection))
    244
    245         if isinstance(key, (list, tuple, ABCSeries, ABCIndexClass,

Exception: Column(s) B already selected
```

#### Problem description

It's not work as expected

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Linux
OS-release: 4.4.0-53-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.19.2+0.g825876c.dirty
nose: 1.3.7
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.24.1
numpy: 1.11.1
scipy: 0.18.1
statsmodels: 0.6.1
xarray: None
IPython: 5.1.0
sphinx: 1.4.6
patsy: 0.4.1
dateutil: 2.5.3
pytz: 2016.6.1
blosc: None
bottleneck: 1.1.0
tables: 3.2.3.1
numexpr: 2.6.1
matplotlib: 1.5.3
openpyxl: 2.3.2
xlrd: 1.0.0
xlwt: 1.1.2
xlsxwriter: 0.9.3
lxml: 3.6.4
bs4: 4.5.1
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: 1.0.13
pymysql: 0.7.9.None
psycopg2: None
jinja2: 2.8
boto: 2.42.0
pandas_datareader: None
</details>

Hints from issue discussion:
sophisticated ``.agg`` was never explicitly implemented on ``.groupby.rolling``, so not surprising this doesn't work. if you'd like to debug please do.

```
In [12]: data.groupby(level="stock").rolling(2).mean()
Out[12]:
              low
stock stock
1     1       NaN
      1      15.0
      1      25.0
2     2       NaN
      2      20.0
      2      35.0
      2      60.0

In [13]: data.groupby(level="stock").rolling(2).max()
Out[13]:
              low
stock stock
1     1       NaN
      1      20.0
      1      30.0
2     2       NaN
      2      30.0
      2      40.0
      2      80.0
```
Hi All!

I've faced with the same error: https://stackoverflow.com/questions/46350611/aggregate-group-by-rolling-window-keyerror-column-not-found-foo

Commit message: Fixed GroupByMixin class constructor with SeriesGroupBy

Patch to review:
diff --git a/pandas/core/base.py b/pandas/core/base.py
index c331ead8d2fef..ec3d6b7ef0adc 100644
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -684,8 +684,14 @@ def _gotitem(self, key, ndim, subset=None):
         # with the same groupby
         kwargs = dict([(attr, getattr(self, attr))
                        for attr in self._attributes])
+
+        if self._groupby.ndim == 1 and self._groupby.obj.name == key:
+            groupby = self._groupby
+        else:
+            groupby = self._groupby[key]
+
         self = self.__class__(subset,
-                              groupby=self._groupby[key],
+                              groupby=groupby,
                               parent=self,
                               **kwargs)
         self._reset_cache()
diff --git a/pandas/tests/test_window.py b/pandas/tests/test_window.py
index 74f2c977e0db2..b72cb4eb5b976 100644
--- a/pandas/tests/test_window.py
+++ b/pandas/tests/test_window.py
@@ -520,6 +520,39 @@ def test_iter_raises(self, klass):
         with pytest.raises(NotImplementedError):
             iter(obj.rolling(2))
 
+    def test_agg(self):
+        # GH 15072
+        df = pd.DataFrame([
+            ['A', 10, 20],
+            ['A', 20, 30],
+            ['A', 30, 40],
+            ['B', 10, 30],
+            ['B', 30, 40],
+            ['B', 40, 80],
+            ['B', 80, 90]], columns=['stock', 'low', 'high'])
+
+        index = pd.MultiIndex.from_tuples([
+            ('A', 0), ('A', 1), ('A', 2),
+            ('B', 3), ('B', 4), ('B', 5), ('B', 6)], names=['stock', None])
+        columns = pd.MultiIndex.from_tuples([
+            ('low', 'mean'), ('low', 'max'), ('high', 'mean'),
+            ('high', 'min')])
+        expected = pd.DataFrame([
+            [np.nan, np.nan, np.nan, np.nan],
+            [15., 20., 25., 20.],
+            [25., 30., 35., 30.],
+            [np.nan, np.nan, np.nan, np.nan],
+            [20., 30., 35., 30.],
+            [35., 40., 60., 40.],
+            [60., 80., 85., 80]], index=index, columns=columns)
+
+        result = df.groupby('stock').rolling(2).agg({
+            'low': ['mean', 'max'],
+            'high': ['mean', 'min']
+        })
+
+        tm.assert_frame_equal(result, expected)
+
 
 class TestExpanding(Base):
 


Code review comments:
diff_hunk: @@ -684,8 +684,14 @@ def _gotitem(self, key, ndim, subset=None):
         # with the same groupby
         kwargs = dict([(attr, getattr(self, attr))
                        for attr in self._attributes])
+
+        if self._groupby.ndim == 1 and self._groupby.obj.name == key:
path: pandas/core/base.py
text: @user1:
I hate checks like this :>

does
``groupby = self._groupby[key]`` not just work here?

@author:
That’s what failed in the original code. Could alternately try and catch the error