Skip to content

Commit cc6c957

Browse files
authored
REF: Use * syntax to make reindex kwargs keyword only (pandas-dev#50853)
* Use newer positional only syntax * Push logic into generic, improve docs * Clarify whatsnew * Fix shared_doc * Make axis more generic * Series has different default * Add some type ignores * Fix docstring validation * doc validation
1 parent d0221cb commit cc6c957

File tree

9 files changed

+122
-174
lines changed

9 files changed

+122
-174
lines changed

doc/source/whatsnew/v2.0.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ Removal of prior version deprecations/changes
761761
- Disallow passing non-keyword arguments to :meth:`DataFrame.replace`, :meth:`Series.replace` except for ``to_replace`` and ``value`` (:issue:`47587`)
762762
- Disallow passing non-keyword arguments to :meth:`DataFrame.sort_values` except for ``by`` (:issue:`41505`)
763763
- Disallow passing non-keyword arguments to :meth:`Series.sort_values` (:issue:`41505`)
764-
- Disallow passing 2 non-keyword arguments to :meth:`DataFrame.reindex` (:issue:`17966`)
764+
- Disallow passing non-keyword arguments to :meth:`DataFrame.reindex` except for ``labels`` (:issue:`17966`)
765765
- Disallow :meth:`Index.reindex` with non-unique :class:`Index` objects (:issue:`42568`)
766766
- Disallowed constructing :class:`Categorical` with scalar ``data`` (:issue:`38433`)
767767
- Disallowed constructing :class:`CategoricalIndex` without passing ``data`` (:issue:`38944`)

pandas/conftest.py

-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,6 @@ def pytest_collection_modifyitems(items, config) -> None:
149149
ignored_doctest_warnings = [
150150
# Docstring divides by zero to show behavior difference
151151
("missing.mask_zero_div_zero", "divide by zero encountered"),
152-
# Docstring demonstrates the call raises a warning
153-
("_validators.validate_axis_style_args", "Use named arguments"),
154152
]
155153

156154
for item in items:

pandas/core/frame.py

+42-26
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,10 @@
107107
Appender,
108108
Substitution,
109109
doc,
110-
rewrite_axis_style_signature,
111110
)
112111
from pandas.util._exceptions import find_stack_level
113112
from pandas.util._validators import (
114113
validate_ascending,
115-
validate_axis_style_args,
116114
validate_bool_kwarg,
117115
validate_percentile,
118116
)
@@ -266,11 +264,18 @@
266264
levels and/or column labels.
267265
- if `axis` is 1 or `'columns'` then `by` may contain column
268266
levels and/or index labels.""",
269-
"optional_labels": """labels : array-like, optional
270-
New labels / index to conform the axis specified by 'axis' to.""",
271-
"optional_axis": """axis : int or str, optional
272-
Axis to target. Can be either the axis name ('index', 'columns')
273-
or number (0, 1).""",
267+
"optional_reindex": """
268+
labels : array-like, optional
269+
New labels / index to conform the axis specified by 'axis' to.
270+
index : array-like, optional
271+
New labels for the index. Preferably an Index object to avoid
272+
duplicating data.
273+
columns : array-like, optional
274+
New labels for the columns. Preferably an Index object to avoid
275+
duplicating data.
276+
axis : int or str, optional
277+
Axis to target. Can be either the axis name ('index', 'columns')
278+
or number (0, 1).""",
274279
"replace_iloc": """
275280
This differs from updating with ``.loc`` or ``.iloc``, which require
276281
you to specify a location to update with some value.""",
@@ -5000,26 +5005,37 @@ def set_axis(
50005005
) -> DataFrame:
50015006
return super().set_axis(labels, axis=axis, copy=copy)
50025007

5003-
@Substitution(**_shared_doc_kwargs)
5004-
@Appender(NDFrame.reindex.__doc__)
5005-
@rewrite_axis_style_signature(
5006-
"labels",
5007-
[
5008-
("method", None),
5009-
("copy", None),
5010-
("level", None),
5011-
("fill_value", np.nan),
5012-
("limit", None),
5013-
("tolerance", None),
5014-
],
5008+
@doc(
5009+
NDFrame.reindex,
5010+
klass=_shared_doc_kwargs["klass"],
5011+
optional_reindex=_shared_doc_kwargs["optional_reindex"],
50155012
)
5016-
def reindex(self, *args, **kwargs) -> DataFrame:
5017-
axes = validate_axis_style_args(self, args, kwargs, "labels", "reindex")
5018-
kwargs.update(axes)
5019-
# Pop these, since the values are in `kwargs` under different names
5020-
kwargs.pop("axis", None)
5021-
kwargs.pop("labels", None)
5022-
return super().reindex(**kwargs)
5013+
def reindex( # type: ignore[override]
5014+
self,
5015+
labels=None,
5016+
*,
5017+
index=None,
5018+
columns=None,
5019+
axis: Axis | None = None,
5020+
method: str | None = None,
5021+
copy: bool | None = None,
5022+
level: Level | None = None,
5023+
fill_value: Scalar | None = np.nan,
5024+
limit: int | None = None,
5025+
tolerance=None,
5026+
) -> DataFrame:
5027+
return super().reindex(
5028+
labels=labels,
5029+
index=index,
5030+
columns=columns,
5031+
axis=axis,
5032+
method=method,
5033+
copy=copy,
5034+
level=level,
5035+
fill_value=fill_value,
5036+
limit=limit,
5037+
tolerance=tolerance,
5038+
)
50235039

50245040
@overload
50255041
def drop(

pandas/core/generic.py

+41-31
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
NDFrameT,
7070
RandomState,
7171
Renamer,
72+
Scalar,
7273
SortKind,
7374
StorageOptions,
7475
Suffixes,
@@ -5104,11 +5105,21 @@ def sort_index(
51045105

51055106
@doc(
51065107
klass=_shared_doc_kwargs["klass"],
5107-
axes=_shared_doc_kwargs["axes"],
5108-
optional_labels="",
5109-
optional_axis="",
5108+
optional_reindex="",
51105109
)
5111-
def reindex(self: NDFrameT, *args, **kwargs) -> NDFrameT:
5110+
def reindex(
5111+
self: NDFrameT,
5112+
labels=None,
5113+
index=None,
5114+
columns=None,
5115+
axis: Axis | None = None,
5116+
method: str | None = None,
5117+
copy: bool_t | None = None,
5118+
level: Level | None = None,
5119+
fill_value: Scalar | None = np.nan,
5120+
limit: int | None = None,
5121+
tolerance=None,
5122+
) -> NDFrameT:
51125123
"""
51135124
Conform {klass} to new index with optional filling logic.
51145125
@@ -5118,11 +5129,7 @@ def reindex(self: NDFrameT, *args, **kwargs) -> NDFrameT:
51185129
51195130
Parameters
51205131
----------
5121-
{optional_labels}
5122-
{axes} : array-like, optional
5123-
New labels / index to conform to, should be specified using
5124-
keywords. Preferably an Index object to avoid duplicating data.
5125-
{optional_axis}
5132+
{optional_reindex}
51265133
method : {{None, 'backfill'/'bfill', 'pad'/'ffill', 'nearest'}}
51275134
Method to use for filling holes in reindexed DataFrame.
51285135
Please note: this is only applicable to DataFrames/Series with a
@@ -5311,31 +5318,34 @@ def reindex(self: NDFrameT, *args, **kwargs) -> NDFrameT:
53115318
# TODO: Decide if we care about having different examples for different
53125319
# kinds
53135320

5314-
# construct the args
5315-
axes, kwargs = self._construct_axes_from_arguments(args, kwargs)
5316-
method = clean_reindex_fill_method(kwargs.pop("method", None))
5317-
level = kwargs.pop("level", None)
5318-
copy = kwargs.pop("copy", None)
5319-
limit = kwargs.pop("limit", None)
5320-
tolerance = kwargs.pop("tolerance", None)
5321-
fill_value = kwargs.pop("fill_value", None)
5322-
5323-
# Series.reindex doesn't use / need the axis kwarg
5324-
# We pop and ignore it here, to make writing Series/Frame generic code
5325-
# easier
5326-
kwargs.pop("axis", None)
5327-
5328-
if kwargs:
5329-
raise TypeError(
5330-
"reindex() got an unexpected keyword "
5331-
f'argument "{list(kwargs.keys())[0]}"'
5332-
)
5321+
if index is not None and columns is not None and labels is not None:
5322+
raise TypeError("Cannot specify all of 'labels', 'index', 'columns'.")
5323+
elif index is not None or columns is not None:
5324+
if axis is not None:
5325+
raise TypeError(
5326+
"Cannot specify both 'axis' and any of 'index' or 'columns'"
5327+
)
5328+
if labels is not None:
5329+
if index is not None:
5330+
columns = labels
5331+
else:
5332+
index = labels
5333+
else:
5334+
if axis and self._get_axis_number(axis) == 1:
5335+
columns = labels
5336+
else:
5337+
index = labels
5338+
axes: dict[Literal["index", "columns"], Any] = {
5339+
"index": index,
5340+
"columns": columns,
5341+
}
5342+
method = clean_reindex_fill_method(method)
53335343

53345344
# if all axes that are requested to reindex are equal, then only copy
53355345
# if indicated must have index names equal here as well as values
53365346
if all(
5337-
self._get_axis(axis).identical(ax)
5338-
for axis, ax in axes.items()
5347+
self._get_axis(axis_name).identical(ax)
5348+
for axis_name, ax in axes.items()
53395349
if ax is not None
53405350
):
53415351
return self.copy(deep=copy)
@@ -5519,7 +5529,7 @@ def filter(
55195529
name = self._get_axis_name(axis)
55205530
# error: Keywords must be strings
55215531
return self.reindex( # type: ignore[misc]
5522-
**{name: [r for r in items if r in labels]}
5532+
**{name: [r for r in items if r in labels]} # type: ignore[arg-type]
55235533
)
55245534
elif like:
55255535

pandas/core/groupby/groupby.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4096,7 +4096,7 @@ def _reindex_output(
40964096
"copy": False,
40974097
"fill_value": fill_value,
40984098
}
4099-
return output.reindex(**d)
4099+
return output.reindex(**d) # type: ignore[arg-type]
41004100

41014101
# GH 13204
41024102
# Here, the categorical in-axis groupers, which need to be fully

pandas/core/series.py

+29-16
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
NaPosition,
6060
QuantileInterpolation,
6161
Renamer,
62+
Scalar,
6263
SingleManager,
6364
SortKind,
6465
StorageOptions,
@@ -196,8 +197,12 @@
196197
"duplicated": "Series",
197198
"optional_by": "",
198199
"optional_mapper": "",
199-
"optional_labels": "",
200-
"optional_axis": "",
200+
"optional_reindex": """
201+
index : array-like, optional
202+
New labels for the index. Preferably an Index object to avoid
203+
duplicating data.
204+
axis : int or str, optional
205+
Unused.""",
201206
"replace_iloc": """
202207
This differs from updating with ``.loc`` or ``.iloc``, which require
203208
you to specify a location to update with some value.""",
@@ -4874,21 +4879,29 @@ def set_axis(
48744879
@doc(
48754880
NDFrame.reindex, # type: ignore[has-type]
48764881
klass=_shared_doc_kwargs["klass"],
4877-
axes=_shared_doc_kwargs["axes"],
4878-
optional_labels=_shared_doc_kwargs["optional_labels"],
4879-
optional_axis=_shared_doc_kwargs["optional_axis"],
4882+
optional_reindex=_shared_doc_kwargs["optional_reindex"],
48804883
)
4881-
def reindex(self, *args, **kwargs) -> Series:
4882-
if len(args) > 1:
4883-
raise TypeError("Only one positional argument ('index') is allowed")
4884-
if args:
4885-
(index,) = args
4886-
if "index" in kwargs:
4887-
raise TypeError(
4888-
"'index' passed as both positional and keyword argument"
4889-
)
4890-
kwargs.update({"index": index})
4891-
return super().reindex(**kwargs)
4884+
def reindex( # type: ignore[override]
4885+
self,
4886+
index=None,
4887+
*,
4888+
axis: Axis | None = None,
4889+
method: str | None = None,
4890+
copy: bool | None = None,
4891+
level: Level | None = None,
4892+
fill_value: Scalar | None = None,
4893+
limit: int | None = None,
4894+
tolerance=None,
4895+
) -> Series:
4896+
return super().reindex(
4897+
index=index,
4898+
method=method,
4899+
copy=copy,
4900+
level=level,
4901+
fill_value=fill_value,
4902+
limit=limit,
4903+
tolerance=tolerance,
4904+
)
48924905

48934906
@overload
48944907
def drop(

pandas/tests/frame/methods/test_reindex.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -841,17 +841,18 @@ def test_reindex_positional_raises(self):
841841
# https://github.com/pandas-dev/pandas/issues/12392
842842
# Enforced in 2.0
843843
df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
844-
with pytest.raises(TypeError, match=r".* is ambiguous."):
844+
msg = r"reindex\(\) takes from 1 to 2 positional arguments but 3 were given"
845+
with pytest.raises(TypeError, match=msg):
845846
df.reindex([0, 1], ["A", "B", "C"])
846847

847848
def test_reindex_axis_style_raises(self):
848849
# https://github.com/pandas-dev/pandas/issues/12392
849850
df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
850851
with pytest.raises(TypeError, match="Cannot specify both 'axis'"):
851-
df.reindex([0, 1], ["A"], axis=1)
852+
df.reindex([0, 1], columns=["A"], axis=1)
852853

853854
with pytest.raises(TypeError, match="Cannot specify both 'axis'"):
854-
df.reindex([0, 1], ["A"], axis="index")
855+
df.reindex([0, 1], columns=["A"], axis="index")
855856

856857
with pytest.raises(TypeError, match="Cannot specify both 'axis'"):
857858
df.reindex(index=[0, 1], axis="index")
@@ -866,7 +867,7 @@ def test_reindex_axis_style_raises(self):
866867
df.reindex(index=[0, 1], columns=[0, 1], axis="columns")
867868

868869
with pytest.raises(TypeError, match="Cannot specify all"):
869-
df.reindex([0, 1], [0], ["A"])
870+
df.reindex(labels=[0, 1], index=[0], columns=["A"])
870871

871872
# Mixing styles
872873
with pytest.raises(TypeError, match="Cannot specify both 'axis'"):

pandas/tests/series/methods/test_reindex.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,15 @@ def test_reindex_periodindex_with_object(p_values, o_values, values, expected_va
369369
def test_reindex_too_many_args():
370370
# GH 40980
371371
ser = Series([1, 2])
372-
with pytest.raises(
373-
TypeError, match=r"Only one positional argument \('index'\) is allowed"
374-
):
372+
msg = r"reindex\(\) takes from 1 to 2 positional arguments but 3 were given"
373+
with pytest.raises(TypeError, match=msg):
375374
ser.reindex([2, 3], False)
376375

377376

378377
def test_reindex_double_index():
379378
# GH 40980
380379
ser = Series([1, 2])
381-
msg = r"'index' passed as both positional and keyword argument"
380+
msg = r"reindex\(\) got multiple values for argument 'index'"
382381
with pytest.raises(TypeError, match=msg):
383382
ser.reindex([2, 3], index=[3, 4])
384383

0 commit comments

Comments
 (0)