Skip to content

Commit 0e668bf

Browse files
committed
API: Accept 'axis' keyword argument for reindex
1 parent b06e726 commit 0e668bf

File tree

9 files changed

+385
-61
lines changed

9 files changed

+385
-61
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

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

114-
``rename`` now also accepts axis keyword
115-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114+
.. _whatsnew_0210.enhancements.rename_reindex_axis:
116115

117-
The :meth:`~DataFrame.rename` method has gained the ``axis`` keyword as an
118-
alternative to specify the ``axis`` to target (:issue:`12392`).
116+
``rename``, ``reindex`` now also accept axis keyword
117+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
119118

120-
.. ipython::
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
121141

122-
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
123-
df.rename(str.lower, axis='columns')
124-
df.rename(id, axis='index')
142+
df.rename(index=id, columns=str.lower)
143+
df.reindex(index=[0, 1, 3], columns=['A', 'B', 'C'])
125144

126-
The ``.rename(index=id, columns=str.lower)`` style continues to work as before.
127-
We *highly* encourage using named arguments to avoid confusion.
145+
We *highly* encourage using named arguments to avoid confusion when using either
146+
style.
128147

129148
.. _whatsnew_0210.enhancements.categorical_dtype:
130149

pandas/core/frame.py

+87-9
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,
@@ -112,10 +113,10 @@
112113
by : str or list of str
113114
Name or list of names which refer to the axis items.""",
114115
versionadded_to_excel='',
115-
optional_mapper="""mapper : dict-like or function
116-
Applied to the axis specified by `axis`""",
116+
optional_labels="""labels : array-like, optional
117+
New labels / index to conform the axis specified by 'axis' to.""",
117118
optional_axis="""axis : int or str, optional
118-
Axis to target. Can be either the axis name ('rows', 'columns')
119+
Axis to target. Can be either the axis name ('index', 'columns')
119120
or number (0, 1).""",
120121
)
121122

@@ -2801,24 +2802,25 @@ def _validate_axis_style_args(self, arg, arg_name, index, columns,
28012802
elif axis == 'columns':
28022803
columns = arg
28032804

2804-
elif all(x is not None for x in (arg, index, columns)):
2805+
elif _all_not_none(arg, index, columns):
28052806
msg = (
28062807
"Cannot specify all of '{arg_name}', 'index', and 'columns'. "
28072808
"Specify either {arg_name} and 'axis', or 'index' and "
28082809
"'columns'."
28092810
).format(arg_name=arg_name)
28102811
raise TypeError(msg)
28112812

2812-
elif axis is None and (arg is not None and index is not None):
2813+
elif _all_not_none(arg, index):
28132814
# This is the "ambiguous" case, so emit a warning
28142815
msg = (
28152816
"Interpreting call to '.{method_name}(a, b)' as "
28162817
"'.{method_name}(index=a, columns=b)'. "
28172818
"Use keyword arguments to remove any ambiguity."
28182819
).format(method_name=method_name)
2819-
warnings.warn(msg)
2820+
warnings.warn(msg, stacklevel=3)
28202821
index, columns = arg, index
2821-
elif index is None and columns is None:
2822+
elif index is None:
2823+
# This is for the default axis, like reindex([0, 1])
28222824
index = arg
28232825
return index, columns
28242826

@@ -2948,7 +2950,11 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,
29482950
broadcast_axis=broadcast_axis)
29492951

29502952
@Appender(_shared_docs['reindex'] % _shared_doc_kwargs)
2951-
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')
29522958
return super(DataFrame, self).reindex(index=index, columns=columns,
29532959
**kwargs)
29542960

@@ -2960,9 +2966,81 @@ def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True,
29602966
method=method, level=level, copy=copy,
29612967
limit=limit, fill_value=fill_value)
29622968

2963-
@Appender(_shared_docs['rename'] % _shared_doc_kwargs)
29642969
def rename(self, mapper=None, index=None, columns=None, axis=None,
29652970
**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+
"""
29663044
index, columns = self._validate_axis_style_args(mapper, 'mapper',
29673045
index, columns,
29683046
axis, 'rename')

pandas/core/generic.py

+53-3
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ def swaplevel(self, i=-2, j=-1, axis=0):
768768
769769
Examples
770770
--------
771+
771772
>>> s = pd.Series([1, 2, 3])
772773
>>> s
773774
0 1
@@ -790,16 +791,29 @@ def swaplevel(self, i=-2, j=-1, axis=0):
790791
5 3
791792
dtype: int64
792793
794+
Since ``DataFrame`` doesn't have a ``.name`` attribute,
795+
only mapping-type arguments are allowed.
796+
793797
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
794798
>>> df.rename(2)
795799
Traceback (most recent call last):
796800
...
797801
TypeError: 'int' object is not callable
802+
803+
``DataFrame.rename`` supports two calling conventions
804+
805+
* ``(index=index_mapper, columns=columns_mapper, ...)
806+
* ``(mapper, axis={'index', 'columns'}, ...)
807+
808+
We *highly* recommend using keyword arguments to clarify your
809+
intent.
810+
798811
>>> df.rename(index=str, columns={"A": "a", "B": "c"})
799812
a c
800813
0 1 4
801814
1 2 5
802815
2 3 6
816+
803817
>>> df.rename(index=str, columns={"A": "a", "C": "c"})
804818
a B
805819
0 1 4
@@ -819,6 +833,8 @@ def swaplevel(self, i=-2, j=-1, axis=0):
819833
0 1 4
820834
2 2 5
821835
4 3 6
836+
837+
See the :ref:`user guide <basics.rename>` for more.
822838
"""
823839

824840
@Appender(_shared_docs['rename'] % dict(axes='axes keywords for this'
@@ -904,6 +920,7 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False):
904920
905921
Examples
906922
--------
923+
907924
>>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
908925
>>> df.rename_axis("foo") # scalar, alters df.index.name
909926
A B
@@ -2764,10 +2781,11 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
27642781
27652782
Parameters
27662783
----------
2767-
%(axes)s : array-like, optional (can be specified in order, or as
2768-
keywords)
2784+
%(optional_labels)s
2785+
%(axes)s : array-like, optional (should be specified using keywords)
27692786
New labels / index to conform to. Preferably an Index object to
27702787
avoid duplicating data
2788+
%(optional_axis)s
27712789
method : {None, 'backfill'/'bfill', 'pad'/'ffill', 'nearest'}, optional
27722790
method to use for filling holes in reindexed DataFrame.
27732791
Please note: this is only applicable to DataFrames/Series with a
@@ -2799,6 +2817,14 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
27992817
Examples
28002818
--------
28012819
2820+
``DataFrame.reindex`` supports two calling conventions
2821+
2822+
* ``(index=index_labels, columns=column_labels, ...)
2823+
* ``(labels, axis={'index', 'columns'}, ...)
2824+
2825+
We *highly* recommend using keyword arguments to clarify your
2826+
intent.
2827+
28022828
Create a dataframe with some fictional data.
28032829
28042830
>>> index = ['Firefox', 'Chrome', 'Safari', 'IE10', 'Konqueror']
@@ -2849,6 +2875,26 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
28492875
IE10 404 0.08
28502876
Chrome 200 0.02
28512877
2878+
We can also reindex the columns.
2879+
2880+
>>> df.reindex(columns=['http_status', 'user_agent'])
2881+
http_status user_agent
2882+
Firefox 200 NaN
2883+
Chrome 200 NaN
2884+
Safari 404 NaN
2885+
IE10 404 NaN
2886+
Konqueror 301 NaN
2887+
2888+
Or we can use "axis-style" keyword arguments
2889+
2890+
>>> df.reindex(['http_status', 'user_agent'], axis="columns")
2891+
http_status user_agent
2892+
Firefox 200 NaN
2893+
Chrome 200 NaN
2894+
Safari 404 NaN
2895+
IE10 404 NaN
2896+
Konqueror 301 NaN
2897+
28522898
To further illustrate the filling functionality in
28532899
``reindex``, we will create a dataframe with a
28542900
monotonically increasing index (for example, a sequence
@@ -2911,6 +2957,8 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
29112957
desired indexes. If you do want to fill in the ``NaN`` values present
29122958
in the original dataframe, use the ``fillna()`` method.
29132959
2960+
See the :ref:`user guide <basics.reindexing>` for more.
2961+
29142962
Returns
29152963
-------
29162964
reindexed : %(klass)s
@@ -2919,7 +2967,9 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
29192967
# TODO: Decide if we care about having different examples for different
29202968
# kinds
29212969

2922-
@Appender(_shared_docs['reindex'] % dict(axes="axes", klass="NDFrame"))
2970+
@Appender(_shared_docs['reindex'] % dict(axes="axes", klass="NDFrame",
2971+
optional_labels="",
2972+
optional_axis=""))
29232973
def reindex(self, *args, **kwargs):
29242974

29252975
# construct the args

pandas/core/panel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
axes='items, major_axis, minor_axis',
4141
klass="Panel",
4242
axes_single_arg="{0, 1, 2, 'items', 'major_axis', 'minor_axis'}",
43-
optional_mapper='', optional_axis='')
43+
optional_mapper='', optional_axis='', optional_labels='')
4444
_shared_doc_kwargs['args_transpose'] = ("three positional arguments: each one"
4545
"of\n%s" %
4646
_shared_doc_kwargs['axes_single_arg'])

0 commit comments

Comments
 (0)