Skip to content

Commit 895e68a

Browse files
committed
API: Accept 'axis' keyword argument for reindex
1 parent ac7b59e commit 895e68a

File tree

7 files changed

+137
-16
lines changed

7 files changed

+137
-16
lines changed

doc/source/whatsnew/v0.21.0.txt

+17-5
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,32 @@ For example:
109109
# the following is now equivalent
110110
df.drop(columns=['B', 'C'])
111111

112-
``rename`` now also accepts axis keyword
113-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
112+
``rename``, ``reindex`` now also accepts axis keyword
113+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114114

115-
The :meth:`~DataFrame.rename` method has gained the ``axis`` keyword as an
116-
alternative to specify the ``axis`` to target (:issue:`12392`).
115+
The :meth:`DataFrame.rename` and :meth:`DataFrame.reindex` methods have gained
116+
the ``axis`` keyword to specify the axis to target with the operation. This
117+
harmonizes the method signature of these methods with most of the rest of the
118+
library (:issue:`12392`).
119+
120+
Here's ``rename``:
117121

118122
.. ipython::
119123

120124
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
121125
df.rename(str.lower, axis='columns')
122126
df.rename(id, axis='index')
123127

128+
And ``reindex``:
129+
130+
.. ipython::
131+
132+
df.reindex(['A', 'B', 'C'], axis='columns')
133+
df.reindex([0, 1, 3], axis='index')
134+
124135
The ``.rename(index=id, columns=str.lower)`` style continues to work as before.
125-
We *highly* encourage using named arguments to avoid confusion.
136+
We *highly* encourage using named arguments to avoid confusion when using either
137+
style.
126138

127139
.. _whatsnew_0210.enhancements.categorical_dtype:
128140

pandas/core/frame.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114
versionadded_to_excel='',
115115
optional_mapper="""mapper : dict-like or function
116116
Applied to the axis specified by `axis`""",
117+
optional_labels="""labels : array-like
118+
New labels / index to conform the axis specified by 'axis' to.""",
117119
optional_axis="""axis : int or str, optional
118120
Axis to target. Can be either the axis name ('rows', 'columns')
119121
or number (0, 1).""",
@@ -2805,7 +2807,7 @@ def _validate_axis_style_args(self, arg, arg_name, index, columns,
28052807
"'.{method_name}(index=a, columns=b)'. "
28062808
"Use keyword arguments to remove any ambiguity."
28072809
).format(method_name=method_name)
2808-
warnings.warn(msg)
2810+
warnings.warn(msg, stacklevel=3)
28092811
index, columns = arg, index
28102812
elif index is None and columns is None:
28112813
index = arg
@@ -2937,7 +2939,11 @@ def align(self, other, join='outer', axis=None, level=None, copy=True,
29372939
broadcast_axis=broadcast_axis)
29382940

29392941
@Appender(_shared_docs['reindex'] % _shared_doc_kwargs)
2940-
def reindex(self, index=None, columns=None, **kwargs):
2942+
def reindex(self, labels=None, index=None, columns=None, axis=None,
2943+
**kwargs):
2944+
index, columns = self._validate_axis_style_args(labels, 'labels',
2945+
index, columns,
2946+
axis, 'reindex')
29412947
return super(DataFrame, self).reindex(index=index, columns=columns,
29422948
**kwargs)
29432949

pandas/core/generic.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -2758,10 +2758,11 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
27582758
27592759
Parameters
27602760
----------
2761-
%(axes)s : array-like, optional (can be specified in order, or as
2762-
keywords)
2761+
%(optional_labels)s
2762+
%(axes)s : array-like, optional (should be specified using keywords)
27632763
New labels / index to conform to. Preferably an Index object to
27642764
avoid duplicating data
2765+
%(optional_axis)s
27652766
method : {None, 'backfill'/'bfill', 'pad'/'ffill', 'nearest'}, optional
27662767
method to use for filling holes in reindexed DataFrame.
27672768
Please note: this is only applicable to DataFrames/Series with a
@@ -2793,6 +2794,11 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
27932794
Examples
27942795
--------
27952796
2797+
Note that ``.reindex`` can be called by specifying either
2798+
2799+
* One or both of ``index`` and ``columns``
2800+
* ``labels`` and ``axis``
2801+
27962802
Create a dataframe with some fictional data.
27972803
27982804
>>> index = ['Firefox', 'Chrome', 'Safari', 'IE10', 'Konqueror']
@@ -2843,6 +2849,26 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
28432849
IE10 404 0.08
28442850
Chrome 200 0.02
28452851
2852+
We can also reindex the columns.
2853+
2854+
>>> df.reindex(columns=['http_status', 'user_agent'])
2855+
http_status user_agent
2856+
Firefox 200 NaN
2857+
Chrome 200 NaN
2858+
Safari 404 NaN
2859+
IE10 404 NaN
2860+
Konqueror 301 NaN
2861+
2862+
Or we can use "axis-style" keyword arguments
2863+
2864+
>>> df.reindex(['http_status', 'user_agent'], axis="columns")
2865+
http_status user_agent
2866+
Firefox 200 NaN
2867+
Chrome 200 NaN
2868+
Safari 404 NaN
2869+
IE10 404 NaN
2870+
Konqueror 301 NaN
2871+
28462872
To further illustrate the filling functionality in
28472873
``reindex``, we will create a dataframe with a
28482874
monotonically increasing index (for example, a sequence
@@ -2913,7 +2939,9 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
29132939
# TODO: Decide if we care about having different examples for different
29142940
# kinds
29152941

2916-
@Appender(_shared_docs['reindex'] % dict(axes="axes", klass="NDFrame"))
2942+
@Appender(_shared_docs['reindex'] % dict(axes="axes", klass="NDFrame",
2943+
optional_labels="",
2944+
optional_axis=""))
29172945
def reindex(self, *args, **kwargs):
29182946

29192947
# 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'])

pandas/core/series.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
inplace="""inplace : boolean, default False
8686
If True, performs operation inplace and returns None.""",
8787
unique='np.ndarray', duplicated='Series',
88-
optional_by='', optional_mapper='', optional_axis='',
88+
optional_by='', optional_mapper='', optional_labels='', optional_axis='',
8989
versionadded_to_excel='\n .. versionadded:: 0.20.0\n')
9090

9191

pandas/core/sparse/series.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535

3636

3737
_shared_doc_kwargs = dict(axes='index', klass='SparseSeries',
38-
axes_single_arg="{0, 'index'}")
38+
axes_single_arg="{0, 'index'}",
39+
optional_labels='', optional_axis='')
3940

4041
# -----------------------------------------------------------------------------
4142
# Wrapper function for Series arithmetic methods

pandas/tests/frame/test_axis_select_reindex.py

+77-3
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,80 @@ def test_reindex_dups(self):
447447
# reindex fails
448448
pytest.raises(ValueError, df.reindex, index=list(range(len(df))))
449449

450+
def test_reindex_axis_style(self):
451+
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
452+
expected = pd.DataFrame({"A": [1, 2, np.nan], "B": [4, 5, np.nan]},
453+
index=[0, 1, 3])
454+
result = df.reindex([0, 1, 3])
455+
assert_frame_equal(result, expected)
456+
457+
result = df.reindex([0, 1, 3], axis=0)
458+
assert_frame_equal(result, expected)
459+
460+
result = df.reindex([0, 1, 3], axis='index')
461+
assert_frame_equal(result, expected)
462+
463+
def test_reindex_positional_warns(self):
464+
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
465+
expected = pd.DataFrame({"A": [1., 2], 'B': [4., 5],
466+
"C": [np.nan, np.nan]})
467+
with tm.assert_produces_warning(UserWarning):
468+
result = df.reindex([0, 1], ['A', 'B', 'C'])
469+
470+
assert_frame_equal(result, expected)
471+
472+
def test_reindex_axis_style_raises(self):
473+
df = pd.DataFrame({"A": [1, 2, 3], 'B': [4, 5, 6]})
474+
with tm.assert_raises_regex(TypeError, ''):
475+
df.reindex([0, 1], ['A'], axis=1)
476+
477+
with tm.assert_raises_regex(TypeError, ''):
478+
df.reindex([0, 1], ['A'], axis='index')
479+
480+
with tm.assert_raises_regex(TypeError, ''):
481+
df.reindex(index=[0, 1], axis='index')
482+
483+
with tm.assert_raises_regex(TypeError, ''):
484+
df.reindex(index=[0, 1], axis='columns')
485+
486+
with tm.assert_raises_regex(TypeError, ''):
487+
df.reindex(columns=[0, 1], axis='columns')
488+
489+
with tm.assert_raises_regex(TypeError, ''):
490+
df.reindex(index=[0, 1], columns=[0, 1], axis='columns')
491+
492+
with tm.assert_raises_regex(TypeError, ''):
493+
df.reindex([0, 1], [0], ['A'])
494+
495+
def test_reindex_api_equivalence(self):
496+
# equivalence of the labels/axis and index/columns API's
497+
df = DataFrame([[1, 2, 3], [3, 4, 5], [5, 6, 7]],
498+
index=['a', 'b', 'c'],
499+
columns=['d', 'e', 'f'])
500+
501+
res1 = df.reindex(['b', 'a'])
502+
res2 = df.reindex(index=['b', 'a'])
503+
res3 = df.reindex(labels=['b', 'a'])
504+
res4 = df.reindex(labels=['b', 'a'], axis=0)
505+
res5 = df.reindex(['b', 'a'], axis=0)
506+
for res in [res2, res3, res4, res5]:
507+
tm.assert_frame_equal(res1, res)
508+
509+
res1 = df.reindex(columns=['e', 'd'])
510+
res2 = df.reindex(['e', 'd'], axis=1)
511+
res3 = df.reindex(labels=['e', 'd'], axis=1)
512+
for res in [res2, res3]:
513+
tm.assert_frame_equal(res1, res)
514+
515+
with tm.assert_produces_warning(UserWarning) as m:
516+
res1 = df.reindex(['b', 'a'], ['e', 'd'])
517+
assert 'reindex' in str(m[0].message)
518+
res2 = df.reindex(columns=['e', 'd'], index=['b', 'a'])
519+
res3 = df.reindex(labels=['b', 'a'], axis=0).reindex(labels=['e', 'd'],
520+
axis=1)
521+
for res in [res2, res3]:
522+
tm.assert_frame_equal(res1, res)
523+
450524
def test_align(self):
451525
af, bf = self.frame.align(self.frame)
452526
assert af._data is not self.frame._data
@@ -974,21 +1048,21 @@ def test_reindex_with_nans(self):
9741048
def test_reindex_multi(self):
9751049
df = DataFrame(np.random.randn(3, 3))
9761050

977-
result = df.reindex(lrange(4), lrange(4))
1051+
result = df.reindex(index=lrange(4), columns=lrange(4))
9781052
expected = df.reindex(lrange(4)).reindex(columns=lrange(4))
9791053

9801054
assert_frame_equal(result, expected)
9811055

9821056
df = DataFrame(np.random.randint(0, 10, (3, 3)))
9831057

984-
result = df.reindex(lrange(4), lrange(4))
1058+
result = df.reindex(index=lrange(4), columns=lrange(4))
9851059
expected = df.reindex(lrange(4)).reindex(columns=lrange(4))
9861060

9871061
assert_frame_equal(result, expected)
9881062

9891063
df = DataFrame(np.random.randint(0, 10, (3, 3)))
9901064

991-
result = df.reindex(lrange(2), lrange(2))
1065+
result = df.reindex(index=lrange(2), columns=lrange(2))
9921066
expected = df.reindex(lrange(2)).reindex(columns=lrange(2))
9931067

9941068
assert_frame_equal(result, expected)

0 commit comments

Comments
 (0)