diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index 7fbf2533428dc4..364b10a616462d 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -109,6 +109,19 @@ For example: # the following is now equivalent df.drop(columns=['B', 'C']) +``rename`` now also accepts axis keyword +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The :meth:`~DataFrame.rename` method has gained the ``axis`` keyword as an +alternative to specify the ``axis`` to target (:issue:`12392`). + +.. ipython:: + + df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}) + df.rename(str.lower, axis='columns') + +The ``.rename(columns=str.lower)`` style continues to work as before. + .. _whatsnew_0210.enhancements.categorical_dtype: ``CategoricalDtype`` for specifying categoricals diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 142ccf1f034bc6..0d7e7ee8d7c758 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2904,7 +2904,28 @@ def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True, limit=limit, fill_value=fill_value) @Appender(_shared_docs['rename'] % _shared_doc_kwargs) - def rename(self, index=None, columns=None, **kwargs): + def rename(self, *args, index=None, columns=None, **kwargs): + nargs = len(args) + if 'axis' in kwargs and (index is not None or columns is not None): + raise TypeError("Cannot specify 'index' or 'columns' and 'axis' at" + " the same time. Specify either\n" + "\t.rename(mapping, axis=axis), or\n" + "\t.rename(index=index, columns=columns)") + if 'axis' in kwargs and nargs > 1: + raise TypeError("Cannot specify 'index', 'axis', and 'columns' at " + "the same time.") + if nargs > 2: + raise TypeError("Too many positional arguments") + + if nargs and index is not None: + raise TypeError("rename() got multiple arguments for argumnet " + "'index'") + + if index is None and nargs: + index = args[0] + if columns is None and nargs > 1: + columns = args[1] + return super(DataFrame, self).rename(index=index, columns=columns, **kwargs) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 942a9ff2790927..d3d796650dfbb9 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -807,11 +807,20 @@ def swaplevel(self, i=-2, j=-1, axis=0): @Appender(_shared_docs['rename'] % dict(axes='axes keywords for this' ' object', klass='NDFrame')) def rename(self, *args, **kwargs): - axes, kwargs = self._construct_axes_from_arguments(args, kwargs) copy = kwargs.pop('copy', True) inplace = kwargs.pop('inplace', False) level = kwargs.pop('level', None) + axis = kwargs.pop("axis", None) + + if axis: + # At this point, we know the call was + # rename(fn, axis=axis), so axes is always + # {index: op, columns: None} + axis = self._get_axis_name(axis) + op = axes['index'] + axes['index'] = None + axes[axis] = op if kwargs: raise TypeError('rename() got an unexpected keyword ' diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 27906838abb2de..3fe97ddea2e023 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -837,6 +837,66 @@ def test_rename_objects(self): assert 'FOO' in renamed assert 'foo' not in renamed + def test_rename_columns(self): + df = pd.DataFrame({"A": [1, 2], "B": [1, 2]}, index=['X', 'Y']) + expected = pd.DataFrame({"a": [1, 2], "b": [1, 2]}, index=['X', 'Y']) + + result = df.rename(str.lower, axis=1) + assert_frame_equal(result, expected) + + result = df.rename(str.lower, axis='columns') + assert_frame_equal(result, expected) + + result = df.rename({"A": 'a', 'B': 'b'}, axis=1) + assert_frame_equal(result, expected) + + result = df.rename({"A": 'a', 'B': 'b'}, axis='columns') + assert_frame_equal(result, expected) + + # Index + expected = pd.DataFrame({"A": [1, 2], "B": [1, 2]}, index=['x', 'y']) + result = df.rename(str.lower, axis=0) + assert_frame_equal(result, expected) + + result = df.rename(str.lower, axis='index') + assert_frame_equal(result, expected) + + result = df.rename({'X': 'x', 'Y': 'y'}, axis=0) + assert_frame_equal(result, expected) + + result = df.rename({'X': 'x', 'Y': 'y'}, axis='index') + assert_frame_equal(result, expected) + + def test_rename_raises(self): + df = pd.DataFrame({"A": [1, 2], "B": [1, 2]}, index=['0', '1']) + + # Named target and axis + with tm.assert_raises_regex(TypeError, None): + df.rename(index=str.lower, axis=1) + + with tm.assert_raises_regex(TypeError, None): + df.rename(index=str.lower, axis='columns') + + with tm.assert_raises_regex(TypeError, None): + df.rename(index=str.lower, axis=0) + + with tm.assert_raises_regex(TypeError, None): + df.rename(index=str.lower, axis='columns') + + with tm.assert_raises_regex(TypeError, None): + df.rename(columns=str.lower, axis='columns') + + # Multiple targets and axis + with tm.assert_raises_regex(TypeError, None): + df.rename(str.lower, str.lower, axis='columns') + + # Too many targets + with tm.assert_raises_regex(TypeError, None): + df.rename(str.lower, str.lower, str.lower) + + with tm.assert_raises_regex(TypeError, None): + df.rename(str.lower, index=str.lower) + def test_assign_columns(self): self.frame['hi'] = 'there'