diff --git a/doc/source/api.rst b/doc/source/api.rst index fff944651588e..9faac4c616477 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -444,6 +444,7 @@ Reindexing / Selection / Label manipulation Series.align Series.drop + Series.droplevel Series.drop_duplicates Series.duplicated Series.equals @@ -1063,6 +1064,7 @@ Reshaping, sorting, transposing .. autosummary:: :toctree: generated/ + DataFrame.droplevel DataFrame.pivot DataFrame.pivot_table DataFrame.reorder_levels diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 034a56b2ac0cb..d300c2b273906 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -74,6 +74,7 @@ Other Enhancements - :func:`Series.mode` and :func:`DataFrame.mode` now support the ``dropna`` parameter which can be used to specify whether NaN/NaT values should be considered (:issue:`17534`) - :func:`to_csv` now supports ``compression`` keyword when a file handle is passed. (:issue:`21227`) - :meth:`Index.droplevel` is now implemented also for flat indexes, for compatibility with :class:`MultiIndex` (:issue:`21115`) +- :meth:`Series.droplevel` and :meth:`DataFrame.droplevel` are now implemented (:issue:`20342`) - Added support for reading from Google Cloud Storage via the ``gcsfs`` library (:issue:`19454`) - :func:`to_gbq` and :func:`read_gbq` signature and documentation updated to reflect changes from the `Pandas-GBQ library version 0.5.0 diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8da678e0adec0..608eebd079eef 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -716,6 +716,66 @@ def swapaxes(self, axis1, axis2, copy=True): return self._constructor(new_values, *new_axes).__finalize__(self) + def droplevel(self, level, axis=0): + """Return DataFrame with requested index / column level(s) removed. + + .. versionadded:: 0.24.0 + + Parameters + ---------- + level : int, str, or list-like + If a string is given, must be the name of a level + If list-like, elements must be names or positional indexes + of levels. + + axis : {0 or 'index', 1 or 'columns'}, default 0 + + + Returns + ------- + DataFrame.droplevel() + + Examples + -------- + >>> df = pd.DataFrame([ + ...: [1, 2, 3, 4], + ...: [5, 6, 7, 8], + ...: [9, 10, 11, 12] + ...: ]).set_index([0, 1]).rename_axis(['a', 'b']) + + >>> df.columns = pd.MultiIndex.from_tuples([ + ...: ('c', 'e'), ('d', 'f') + ...:], names=['level_1', 'level_2']) + + >>> df + level_1 c d + level_2 e f + a b + 1 2 3 4 + 5 6 7 8 + 9 10 11 12 + + >>> df.droplevel('a') + level_1 c d + level_2 e f + b + 2 3 4 + 6 7 8 + 10 11 12 + + >>> df.droplevel('level2', axis=1) + level_1 c d + a b + 1 2 3 4 + 5 6 7 8 + 9 10 11 12 + + """ + labels = self._get_axis(axis) + new_labels = labels.droplevel(level) + result = self.set_axis(new_labels, axis=axis, inplace=False) + return result + def pop(self, item): """ Return item and drop from frame. Raise KeyError if not found. diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 21961906c39bb..4f95eb3fe7b47 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1056,6 +1056,28 @@ def test_reindex_signature(self): "limit", "copy", "level", "method", "fill_value", "tolerance"} + def test_droplevel(self): + # GH20342 + df = pd.DataFrame([ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12] + ]) + df = df.set_index([0, 1]).rename_axis(['a', 'b']) + df.columns = pd.MultiIndex.from_tuples([('c', 'e'), ('d', 'f')], + names=['level_1', 'level_2']) + + # test that dropping of a level in index works + expected = df.reset_index('a', drop=True) + result = df.droplevel('a', axis='index') + assert_frame_equal(result, expected) + + # test that dropping of a level in columns works + expected = df.copy() + expected.columns = pd.Index(['c', 'd'], name='level_1') + result = df.droplevel('level_2', axis='columns') + assert_frame_equal(result, expected) + class TestIntervalIndex(object): diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 859082a7e722d..840c80d6775a5 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -295,3 +295,15 @@ def test_reset_index_drop_errors(self): s = pd.Series(range(4), index=pd.MultiIndex.from_product([[1, 2]] * 2)) with tm.assert_raises_regex(KeyError, 'not found'): s.reset_index('wrong', drop=True) + + def test_droplevel(self): + # GH20342 + ser = pd.Series([1, 2, 3, 4]) + ser.index = pd.MultiIndex.from_arrays([(1, 2, 3, 4), (5, 6, 7, 8)], + names=['a', 'b']) + expected = ser.reset_index('b', drop=True) + result = ser.droplevel('b', axis='index') + assert_series_equal(result, expected) + # test that droplevel raises ValueError on axis != 0 + with pytest.raises(ValueError): + ser.droplevel(1, axis='columns')