diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 7eb3ea303cc9d1..0e591e180e0781 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -785,6 +785,7 @@ Groupby/Resample/Rolling - Bug in :meth:`Series.resample` when passing ``numpy.timedelta64`` to ``loffset`` kwarg (:issue:`7687`). - Bug in :meth:`Resampler.asfreq` when frequency of ``TimedeltaIndex`` is a subperiod of a new frequency (:issue:`13022`). - Bug in :meth:`SeriesGroupBy.mean` when values were integral but could not fit inside of int64, overflowing instead. (:issue:`22487`) +- :func:`RollingGroupby.agg` and :func:`ExpandingGroupby.agg` now support multiple aggregation functions as parameters (:issue:`15072`) Sparse ^^^^^^ diff --git a/pandas/core/base.py b/pandas/core/base.py index 26fea89b45ae1f..7f14a68503973e 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -245,8 +245,8 @@ def _obj_with_exclusions(self): def __getitem__(self, key): if self._selection is not None: - raise Exception('Column(s) {selection} already selected' - .format(selection=self._selection)) + raise IndexError('Column(s) {selection} already selected' + .format(selection=self._selection)) if isinstance(key, (list, tuple, ABCSeries, ABCIndexClass, np.ndarray)): diff --git a/pandas/core/groupby/base.py b/pandas/core/groupby/base.py index 96c74f7fd4d75a..ac84971de08d8c 100644 --- a/pandas/core/groupby/base.py +++ b/pandas/core/groupby/base.py @@ -44,8 +44,15 @@ def _gotitem(self, key, ndim, subset=None): # we need to make a shallow copy of ourselves # with the same groupby kwargs = {attr: getattr(self, attr) for attr in self._attributes} + + # Try to select from a DataFrame, falling back to a Series + try: + groupby = self._groupby[key] + except IndexError: + groupby = self._groupby + self = self.__class__(subset, - groupby=self._groupby[key], + groupby=groupby, parent=self, **kwargs) self._reset_cache() diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index 483f814bc83835..3cdd0965ccfd0d 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -623,8 +623,14 @@ def test_as_index_series_return_frame(df): assert isinstance(result2, DataFrame) assert_frame_equal(result2, expected2) - # corner case - pytest.raises(Exception, grouped['C'].__getitem__, 'D') + +def test_as_index_series_column_slice_raises(df): + # GH15072 + grouped = df.groupby('A', as_index=False) + msg = r"Column\(s\) C already selected" + + with tm.assert_raises_regex(IndexError, msg): + grouped['C'].__getitem__('D') def test_groupby_as_index_cython(df): diff --git a/pandas/tests/test_window.py b/pandas/tests/test_window.py index 052bfd2b858fb2..cc663fc59cbf16 100644 --- a/pandas/tests/test_window.py +++ b/pandas/tests/test_window.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from itertools import product import pytest import warnings @@ -314,6 +315,53 @@ def test_preserve_metadata(self): assert s2.name == 'foo' assert s3.name == 'foo' + @pytest.mark.parametrize("func,window_size,expected_vals", [ + ('rolling', 2, [[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]]), + ('expanding', None, [[10., 10., 20., 20.], + [15., 20., 25., 20.], + [20., 30., 30., 20.], + [10., 10., 30., 30.], + [20., 30., 35., 30.], + [26.666667, 40., 50., 30.], + [40., 80., 60., 30.]])]) + def test_multiple_agg_funcs(self, func, window_size, expected_vals): + # 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']) + + f = getattr(df.groupby('stock'), func) + if window_size: + window = f(window_size) + else: + window = f() + + 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(expected_vals, index=index, columns=columns) + + result = window.agg(OrderedDict(( + ('low', ['mean', 'max']), + ('high', ['mean', 'min']), + ))) + + tm.assert_frame_equal(result, expected) + @pytest.mark.filterwarnings("ignore:can't resolve package:ImportWarning") class TestWindow(Base):