Skip to content

Commit daaaae3

Browse files
TomAugspurgerKrzysztof Chomski
authored and
Krzysztof Chomski
committed
API: Added axis argument to rename, reindex (pandas-dev#17800)
* API: Added axis argument to rename xref: pandas-dev#12392 * API: Accept 'axis' keyword argument for reindex
1 parent bef9bd7 commit daaaae3

File tree

9 files changed

+529
-18
lines changed

9 files changed

+529
-18
lines changed

doc/source/basics.rst

+22-2
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,15 @@ following can be done:
12171217
This means that the reindexed Series's index is the same Python object as the
12181218
DataFrame's index.
12191219

1220+
.. versionadded:: 0.21.0
1221+
1222+
:meth:`DataFrame.reindex` also supports an "axis-style" calling convention,
1223+
where you specify a single ``labels`` argument and the ``axis`` it applies to.
1224+
1225+
.. ipython:: python
1226+
1227+
df.reindex(['c', 'f', 'b'], axis='index')
1228+
df.reindex(['three', 'two', 'one'], axis='columns')
12201229
12211230
.. seealso::
12221231

@@ -1413,12 +1422,23 @@ Series can also be used:
14131422

14141423
.. ipython:: python
14151424
1416-
df.rename(columns={'one' : 'foo', 'two' : 'bar'},
1417-
index={'a' : 'apple', 'b' : 'banana', 'd' : 'durian'})
1425+
df.rename(columns={'one': 'foo', 'two': 'bar'},
1426+
index={'a': 'apple', 'b': 'banana', 'd': 'durian'})
14181427
14191428
If the mapping doesn't include a column/index label, it isn't renamed. Also
14201429
extra labels in the mapping don't throw an error.
14211430

1431+
.. versionadded:: 0.21.0
1432+
1433+
:meth:`DataFrame.rename` also supports an "axis-style" calling convention, where
1434+
you specify a single ``mapper`` and the ``axis`` to apply that mapping to.
1435+
1436+
.. ipython:: python
1437+
1438+
df.rename({'one': 'foo', 'two': 'bar'}, axis='columns'})
1439+
df.rename({'a': 'apple', 'b': 'banana', 'd': 'durian'}, axis='columns'})
1440+
1441+
14221442
The :meth:`~DataFrame.rename` method also provides an ``inplace`` named
14231443
parameter that is by default ``False`` and copies the underlying data. Pass
14241444
``inplace=True`` to rename the data in place.

doc/source/whatsnew/v0.21.0.txt

+34
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,40 @@ For example:
111111
# the following is now equivalent
112112
df.drop(columns=['B', 'C'])
113113

114+
.. _whatsnew_0210.enhancements.rename_reindex_axis:
115+
116+
``rename``, ``reindex`` now also accept axis keyword
117+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
118+
119+
The :meth:`DataFrame.rename` and :meth:`DataFrame.reindex` methods have gained
120+
the ``axis`` keyword to specify the axis to target with the operation
121+
(:issue:`12392`).
122+
123+
Here's ``rename``:
124+
125+
.. ipython:: python
126+
127+
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
128+
df.rename(str.lower, axis='columns')
129+
df.rename(id, axis='index')
130+
131+
And ``reindex``:
132+
133+
.. ipython:: python
134+
135+
df.reindex(['A', 'B', 'C'], axis='columns')
136+
df.reindex([0, 1, 3], axis='index')
137+
138+
The "index, columns" style continues to work as before.
139+
140+
.. ipython:: python
141+
142+
df.rename(index=id, columns=str.lower)
143+
df.reindex(index=[0, 1, 3], columns=['A', 'B', 'C'])
144+
145+
We *highly* encourage using named arguments to avoid confusion when using either
146+
style.
147+
114148
.. _whatsnew_0210.enhancements.categorical_dtype:
115149

116150
``CategoricalDtype`` for specifying categoricals

pandas/core/frame.py

+132-4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
_values_from_object,
6666
_maybe_box_datetimelike,
6767
_dict_compat,
68+
_all_not_none,
6869
standardize_mapping)
6970
from pandas.core.generic import NDFrame, _shared_docs
7071
from pandas.core.index import (Index, MultiIndex, _ensure_index,
@@ -111,7 +112,13 @@
111112
optional_by="""
112113
by : str or list of str
113114
Name or list of names which refer to the axis items.""",
114-
versionadded_to_excel='')
115+
versionadded_to_excel='',
116+
optional_labels="""labels : array-like, optional
117+
New labels / index to conform the axis specified by 'axis' to.""",
118+
optional_axis="""axis : int or str, optional
119+
Axis to target. Can be either the axis name ('index', 'columns')
120+
or number (0, 1).""",
121+
)
115122

116123
_numeric_only_doc = """numeric_only : boolean, default None
117124
Include only float, int, boolean data. If None, will attempt to use
@@ -2776,6 +2783,47 @@ def reindexer(value):
27762783

27772784
return np.atleast_2d(np.asarray(value))
27782785

2786+
def _validate_axis_style_args(self, arg, arg_name, index, columns,
2787+
axis, method_name):
2788+
if axis is not None:
2789+
# Using "axis" style, along with a positional arg
2790+
# Both index and columns should be None then
2791+
axis = self._get_axis_name(axis)
2792+
if index is not None or columns is not None:
2793+
msg = (
2794+
"Can't specify both 'axis' and 'index' or 'columns'. "
2795+
"Specify either\n"
2796+
"\t.{method_name}.rename({arg_name}, axis=axis), or\n"
2797+
"\t.{method_name}.rename(index=index, columns=columns)"
2798+
).format(arg_name=arg_name, method_name=method_name)
2799+
raise TypeError(msg)
2800+
if axis == 'index':
2801+
index = arg
2802+
elif axis == 'columns':
2803+
columns = arg
2804+
2805+
elif _all_not_none(arg, index, columns):
2806+
msg = (
2807+
"Cannot specify all of '{arg_name}', 'index', and 'columns'. "
2808+
"Specify either {arg_name} and 'axis', or 'index' and "
2809+
"'columns'."
2810+
).format(arg_name=arg_name)
2811+
raise TypeError(msg)
2812+
2813+
elif _all_not_none(arg, index):
2814+
# This is the "ambiguous" case, so emit a warning
2815+
msg = (
2816+
"Interpreting call to '.{method_name}(a, b)' as "
2817+
"'.{method_name}(index=a, columns=b)'. "
2818+
"Use keyword arguments to remove any ambiguity."
2819+
).format(method_name=method_name)
2820+
warnings.warn(msg, stacklevel=3)
2821+
index, columns = arg, index
2822+
elif index is None:
2823+
# This is for the default axis, like reindex([0, 1])
2824+
index = arg
2825+
return index, columns
2826+
27792827
@property
27802828
def _series(self):
27812829
result = {}
@@ -2902,7 +2950,11 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,
29022950
broadcast_axis=broadcast_axis)
29032951

29042952
@Appender(_shared_docs['reindex'] % _shared_doc_kwargs)
2905-
def reindex(self, index=None, columns=None, **kwargs):
2953+
def reindex(self, labels=None, index=None, columns=None, axis=None,
2954+
**kwargs):
2955+
index, columns = self._validate_axis_style_args(labels, 'labels',
2956+
index, columns,
2957+
axis, 'reindex')
29062958
return super(DataFrame, self).reindex(index=index, columns=columns,
29072959
**kwargs)
29082960

@@ -2914,8 +2966,84 @@ def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True,
29142966
method=method, level=level, copy=copy,
29152967
limit=limit, fill_value=fill_value)
29162968

2917-
@Appender(_shared_docs['rename'] % _shared_doc_kwargs)
2918-
def rename(self, index=None, columns=None, **kwargs):
2969+
def rename(self, mapper=None, index=None, columns=None, axis=None,
2970+
**kwargs):
2971+
"""Alter axes labels.
2972+
2973+
Function / dict values must be unique (1-to-1). Labels not contained in
2974+
a dict / Series will be left as-is. Extra labels listed don't throw an
2975+
error.
2976+
2977+
See the :ref:`user guide <basics.rename>` for more.
2978+
2979+
Parameters
2980+
----------
2981+
mapper, index, columns : dict-like or function, optional
2982+
dict-like or functions transformations to apply to
2983+
that axis' values. Use either ``mapper`` and ``axis`` to
2984+
specify the axis to target with ``mapper``, or ``index`` and
2985+
``columns``.
2986+
axis : int or str, optional
2987+
Axis to target with ``mapper``. Can be either the axis name
2988+
('index', 'columns') or number (0, 1). The default is 'index'.
2989+
copy : boolean, default True
2990+
Also copy underlying data
2991+
inplace : boolean, default False
2992+
Whether to return a new %(klass)s. If True then value of copy is
2993+
ignored.
2994+
level : int or level name, default None
2995+
In case of a MultiIndex, only rename labels in the specified
2996+
level.
2997+
2998+
Returns
2999+
-------
3000+
renamed : DataFrame
3001+
3002+
See Also
3003+
--------
3004+
pandas.DataFrame.rename_axis
3005+
3006+
Examples
3007+
--------
3008+
3009+
``DataFrame.rename`` supports two calling conventions
3010+
3011+
* ``(index=index_mapper, columns=columns_mapper, ...)
3012+
* ``(mapper, axis={'index', 'columns'}, ...)
3013+
3014+
We *highly* recommend using keyword arguments to clarify your
3015+
intent.
3016+
3017+
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
3018+
>>> df.rename(index=str, columns={"A": "a", "B": "c"})
3019+
a c
3020+
0 1 4
3021+
1 2 5
3022+
2 3 6
3023+
3024+
>>> df.rename(index=str, columns={"A": "a", "C": "c"})
3025+
a B
3026+
0 1 4
3027+
1 2 5
3028+
2 3 6
3029+
3030+
Using axis-style parameters
3031+
3032+
>>> df.rename(str.lower, axis='columns')
3033+
a b
3034+
0 1 4
3035+
1 2 5
3036+
2 3 6
3037+
3038+
>>> df.rename({1: 2, 2: 4}, axis='index')
3039+
A B
3040+
0 1 4
3041+
2 2 5
3042+
4 3 6
3043+
"""
3044+
index, columns = self._validate_axis_style_args(mapper, 'mapper',
3045+
index, columns,
3046+
axis, 'rename')
29193047
return super(DataFrame, self).rename(index=index, columns=columns,
29203048
**kwargs)
29213049

0 commit comments

Comments
 (0)