diff --git a/doc/source/whatsnew/v0.19.0.txt b/doc/source/whatsnew/v0.19.0.txt index 6933cbedb5d67..ca44c4ef36890 100644 --- a/doc/source/whatsnew/v0.19.0.txt +++ b/doc/source/whatsnew/v0.19.0.txt @@ -515,6 +515,7 @@ Other enhancements - ``astype()`` will now accept a dict of column name to data types mapping as the ``dtype`` argument. (:issue:`12086`) - The ``pd.read_json`` and ``DataFrame.to_json`` has gained support for reading and writing json lines with ``lines`` option see :ref:`Line delimited json ` (:issue:`9180`) - :func:``read_excel`` now supports the true_values and false_values keyword arguments (:issue:`13347`) +- ``groupby()`` will now accept a scalar and a single-element list for specifying ``level`` on a non-``MultiIndex`` grouper. (:issue:`13907`) .. _whatsnew_0190.api: diff --git a/pandas/core/groupby.py b/pandas/core/groupby.py index 7ed84b970d9c3..733fae0c34729 100644 --- a/pandas/core/groupby.py +++ b/pandas/core/groupby.py @@ -23,6 +23,7 @@ is_complex_dtype, is_bool_dtype, is_scalar, + is_list_like, _ensure_float64, _ensure_platform_int, _ensure_int64, @@ -2370,12 +2371,26 @@ def _get_grouper(obj, key=None, axis=0, level=None, sort=True, # axis of the object if level is not None: if not isinstance(group_axis, MultiIndex): + # allow level to be a length-one list-like object + # (e.g., level=[0]) + # GH 13901 + if is_list_like(level): + nlevels = len(level) + if nlevels == 1: + level = level[0] + elif nlevels == 0: + raise ValueError('No group keys passed!') + else: + raise ValueError('multiple levels only valid with ' + 'MultiIndex') + if isinstance(level, compat.string_types): if obj.index.name != level: raise ValueError('level name %s is not the name of the ' 'index' % level) - elif level > 0: - raise ValueError('level > 0 only valid with MultiIndex') + elif level > 0 or level < -1: + raise ValueError('level > 0 or level < -1 only valid with ' + ' MultiIndex') level = None key = group_axis diff --git a/pandas/tests/test_groupby.py b/pandas/tests/test_groupby.py index 492326d0898f0..8588323297f17 100644 --- a/pandas/tests/test_groupby.py +++ b/pandas/tests/test_groupby.py @@ -2029,7 +2029,7 @@ def loop(df): loop(frame) - def test_mulitindex_passthru(self): + def test_multiindex_passthru(self): # GH 7997 # regression from 0.14.1 @@ -2039,6 +2039,24 @@ def test_mulitindex_passthru(self): result = df.groupby(axis=1, level=[0, 1]).first() assert_frame_equal(result, df) + def test_multiindex_negative_level(self): + # GH 13901 + result = self.mframe.groupby(level=-1).sum() + expected = self.mframe.groupby(level='second').sum() + assert_frame_equal(result, expected) + + result = self.mframe.groupby(level=-2).sum() + expected = self.mframe.groupby(level='first').sum() + assert_frame_equal(result, expected) + + result = self.mframe.groupby(level=[-2, -1]).sum() + expected = self.mframe + assert_frame_equal(result, expected) + + result = self.mframe.groupby(level=[-1, 'first']).sum() + expected = self.mframe.groupby(level=['second', 'first']).sum() + assert_frame_equal(result, expected) + def test_multifunc_select_col_integer_cols(self): df = self.df df.columns = np.arange(len(df.columns)) @@ -2566,13 +2584,28 @@ def test_groupby_level_mapper(self): assert_frame_equal(result0, expected0) assert_frame_equal(result1, expected1) - def test_groupby_level_0_nonmulti(self): - # #1313 - a = Series([1, 2, 3, 10, 4, 5, 20, 6], Index([1, 2, 3, 1, - 4, 5, 2, 6], name='foo')) + def test_groupby_level_nonmulti(self): + # GH 1313, GH 13901 + s = Series([1, 2, 3, 10, 4, 5, 20, 6], + Index([1, 2, 3, 1, 4, 5, 2, 6], name='foo')) + expected = Series([11, 22, 3, 4, 5, 6], + Index(range(1, 7), name='foo')) - result = a.groupby(level=0).sum() - self.assertEqual(result.index.name, a.index.name) + result = s.groupby(level=0).sum() + self.assert_series_equal(result, expected) + result = s.groupby(level=[0]).sum() + self.assert_series_equal(result, expected) + result = s.groupby(level=-1).sum() + self.assert_series_equal(result, expected) + result = s.groupby(level=[-1]).sum() + self.assert_series_equal(result, expected) + + tm.assertRaises(ValueError, s.groupby, level=1) + tm.assertRaises(ValueError, s.groupby, level=-2) + tm.assertRaises(ValueError, s.groupby, level=[]) + tm.assertRaises(ValueError, s.groupby, level=[0, 0]) + tm.assertRaises(ValueError, s.groupby, level=[0, 1]) + tm.assertRaises(ValueError, s.groupby, level=[1]) def test_groupby_complex(self): # GH 12902