Skip to content

Commit 8405a07

Browse files
authored
DEPR: replace method/limit keywords (#58039)
1 parent 192db0d commit 8405a07

File tree

8 files changed

+14
-196
lines changed

8 files changed

+14
-196
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ Removal of prior version deprecations/changes
207207
- All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`)
208208
- All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`)
209209
- Removed "freq" keyword from :class:`PeriodArray` constructor, use "dtype" instead (:issue:`52462`)
210+
- Removed deprecated "method" and "limit" keywords from :meth:`Series.replace` and :meth:`DataFrame.replace` (:issue:`53492`)
210211
- Removed the "closed" and "normalize" keywords in :meth:`DatetimeIndex.__new__` (:issue:`52628`)
211212
- Removed the "closed" and "unit" keywords in :meth:`TimedeltaIndex.__new__` (:issue:`52628`, :issue:`55499`)
212213
- All arguments in :meth:`Index.sort_values` are now keyword only (:issue:`56493`)

pandas/conftest.py

-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ def pytest_collection_modifyitems(items, config) -> None:
150150
("is_categorical_dtype", "is_categorical_dtype is deprecated"),
151151
("is_sparse", "is_sparse is deprecated"),
152152
("DataFrameGroupBy.fillna", "DataFrameGroupBy.fillna is deprecated"),
153-
("NDFrame.replace", "The 'method' keyword"),
154153
("NDFrame.replace", "Series.replace without 'value'"),
155154
("NDFrame.clip", "Downcasting behavior in Series and DataFrame methods"),
156155
("Series.idxmin", "The behavior of Series.idxmin"),

pandas/core/generic.py

+6-42
Original file line numberDiff line numberDiff line change
@@ -7285,9 +7285,7 @@ def replace(
72857285
value=...,
72867286
*,
72877287
inplace: Literal[False] = ...,
7288-
limit: int | None = ...,
72897288
regex: bool = ...,
7290-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
72917289
) -> Self: ...
72927290

72937291
@overload
@@ -7297,9 +7295,7 @@ def replace(
72977295
value=...,
72987296
*,
72997297
inplace: Literal[True],
7300-
limit: int | None = ...,
73017298
regex: bool = ...,
7302-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
73037299
) -> None: ...
73047300

73057301
@overload
@@ -7309,9 +7305,7 @@ def replace(
73097305
value=...,
73107306
*,
73117307
inplace: bool = ...,
7312-
limit: int | None = ...,
73137308
regex: bool = ...,
7314-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ...,
73157309
) -> Self | None: ...
73167310

73177311
@final
@@ -7326,32 +7320,9 @@ def replace(
73267320
value=lib.no_default,
73277321
*,
73287322
inplace: bool = False,
7329-
limit: int | None = None,
73307323
regex: bool = False,
7331-
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = lib.no_default,
73327324
) -> Self | None:
7333-
if method is not lib.no_default:
7334-
warnings.warn(
7335-
# GH#33302
7336-
f"The 'method' keyword in {type(self).__name__}.replace is "
7337-
"deprecated and will be removed in a future version.",
7338-
FutureWarning,
7339-
stacklevel=find_stack_level(),
7340-
)
7341-
elif limit is not None:
7342-
warnings.warn(
7343-
# GH#33302
7344-
f"The 'limit' keyword in {type(self).__name__}.replace is "
7345-
"deprecated and will be removed in a future version.",
7346-
FutureWarning,
7347-
stacklevel=find_stack_level(),
7348-
)
7349-
if (
7350-
value is lib.no_default
7351-
and method is lib.no_default
7352-
and not is_dict_like(to_replace)
7353-
and regex is False
7354-
):
7325+
if value is lib.no_default and not is_dict_like(to_replace) and regex is False:
73557326
# case that goes through _replace_single and defaults to method="pad"
73567327
warnings.warn(
73577328
# GH#33302
@@ -7387,14 +7358,11 @@ def replace(
73877358
if not is_bool(regex) and to_replace is not None:
73887359
raise ValueError("'to_replace' must be 'None' if 'regex' is not a bool")
73897360

7390-
if value is lib.no_default or method is not lib.no_default:
7361+
if value is lib.no_default:
73917362
# GH#36984 if the user explicitly passes value=None we want to
73927363
# respect that. We have the corner case where the user explicitly
73937364
# passes value=None *and* a method, which we interpret as meaning
73947365
# they want the (documented) default behavior.
7395-
if method is lib.no_default:
7396-
# TODO: get this to show up as the default in the docs?
7397-
method = "pad"
73987366

73997367
# passing a single value that is scalar like
74007368
# when value is None (GH5319), for compat
@@ -7408,12 +7376,12 @@ def replace(
74087376

74097377
result = self.apply(
74107378
Series._replace_single,
7411-
args=(to_replace, method, inplace, limit),
7379+
args=(to_replace, inplace),
74127380
)
74137381
if inplace:
74147382
return None
74157383
return result
7416-
return self._replace_single(to_replace, method, inplace, limit)
7384+
return self._replace_single(to_replace, inplace)
74177385

74187386
if not is_dict_like(to_replace):
74197387
if not is_dict_like(regex):
@@ -7458,9 +7426,7 @@ def replace(
74587426
else:
74597427
to_replace, value = keys, values
74607428

7461-
return self.replace(
7462-
to_replace, value, inplace=inplace, limit=limit, regex=regex
7463-
)
7429+
return self.replace(to_replace, value, inplace=inplace, regex=regex)
74647430
else:
74657431
# need a non-zero len on all axes
74667432
if not self.size:
@@ -7524,9 +7490,7 @@ def replace(
75247490
f"or a list or dict of strings or regular expressions, "
75257491
f"you passed a {type(regex).__name__!r}"
75267492
)
7527-
return self.replace(
7528-
regex, value, inplace=inplace, limit=limit, regex=True
7529-
)
7493+
return self.replace(regex, value, inplace=inplace, regex=True)
75307494
else:
75317495
# dest iterable dict-like
75327496
if is_dict_like(value): # NA -> {'A' : 0, 'B' : -1}

pandas/core/series.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -5113,28 +5113,22 @@ def info(
51135113
)
51145114

51155115
@overload
5116-
def _replace_single(
5117-
self, to_replace, method: str, inplace: Literal[False], limit
5118-
) -> Self: ...
5116+
def _replace_single(self, to_replace, inplace: Literal[False]) -> Self: ...
51195117

51205118
@overload
5121-
def _replace_single(
5122-
self, to_replace, method: str, inplace: Literal[True], limit
5123-
) -> None: ...
5119+
def _replace_single(self, to_replace, inplace: Literal[True]) -> None: ...
51245120

51255121
@overload
5126-
def _replace_single(
5127-
self, to_replace, method: str, inplace: bool, limit
5128-
) -> Self | None: ...
5122+
def _replace_single(self, to_replace, inplace: bool) -> Self | None: ...
51295123

51305124
# TODO(3.0): this can be removed once GH#33302 deprecation is enforced
5131-
def _replace_single(
5132-
self, to_replace, method: str, inplace: bool, limit
5133-
) -> Self | None:
5125+
def _replace_single(self, to_replace, inplace: bool) -> Self | None:
51345126
"""
51355127
Replaces values in a Series using the fill method specified when no
51365128
replacement value is given in the replace method
51375129
"""
5130+
limit = None
5131+
method = "pad"
51385132

51395133
result = self if inplace else self.copy()
51405134

pandas/core/shared_docs.py

+1-18
Original file line numberDiff line numberDiff line change
@@ -429,20 +429,11 @@
429429
filled). Regular expressions, strings and lists or dicts of such
430430
objects are also allowed.
431431
{inplace}
432-
limit : int, default None
433-
Maximum size gap to forward or backward fill.
434-
435-
.. deprecated:: 2.1.0
436432
regex : bool or same types as `to_replace`, default False
437433
Whether to interpret `to_replace` and/or `value` as regular
438434
expressions. Alternatively, this could be a regular expression or a
439435
list, dict, or array of regular expressions in which case
440436
`to_replace` must be ``None``.
441-
method : {{'pad', 'ffill', 'bfill'}}
442-
The method to use when for replacement, when `to_replace` is a
443-
scalar, list or tuple and `value` is ``None``.
444-
445-
.. deprecated:: 2.1.0
446437
447438
Returns
448439
-------
@@ -538,14 +529,6 @@
538529
3 1 8 d
539530
4 4 9 e
540531
541-
>>> s.replace([1, 2], method='bfill')
542-
0 3
543-
1 3
544-
2 3
545-
3 4
546-
4 5
547-
dtype: int64
548-
549532
**dict-like `to_replace`**
550533
551534
>>> df.replace({{0: 10, 1: 100}})
@@ -615,7 +598,7 @@
615598
When one uses a dict as the `to_replace` value, it is like the
616599
value(s) in the dict are equal to the `value` parameter.
617600
``s.replace({{'a': None}})`` is equivalent to
618-
``s.replace(to_replace={{'a': None}}, value=None, method=None)``:
601+
``s.replace(to_replace={{'a': None}}, value=None)``:
619602
620603
>>> s.replace({{'a': None}})
621604
0 10

pandas/tests/frame/methods/test_replace.py

-42
Original file line numberDiff line numberDiff line change
@@ -1171,48 +1171,6 @@ def test_replace_with_empty_dictlike(self, mix_abc):
11711171
tm.assert_frame_equal(df, df.replace({"b": {}}))
11721172
tm.assert_frame_equal(df, df.replace(Series({"b": {}})))
11731173

1174-
@pytest.mark.parametrize(
1175-
"to_replace, method, expected",
1176-
[
1177-
(0, "bfill", {"A": [1, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]}),
1178-
(
1179-
np.nan,
1180-
"bfill",
1181-
{"A": [0, 1, 2], "B": [5.0, 7.0, 7.0], "C": ["a", "b", "c"]},
1182-
),
1183-
("d", "ffill", {"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]}),
1184-
(
1185-
[0, 2],
1186-
"bfill",
1187-
{"A": [1, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
1188-
),
1189-
(
1190-
[1, 2],
1191-
"pad",
1192-
{"A": [0, 0, 0], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
1193-
),
1194-
(
1195-
(1, 2),
1196-
"bfill",
1197-
{"A": [0, 2, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]},
1198-
),
1199-
(
1200-
["b", "c"],
1201-
"ffill",
1202-
{"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "a", "a"]},
1203-
),
1204-
],
1205-
)
1206-
def test_replace_method(self, to_replace, method, expected):
1207-
# GH 19632
1208-
df = DataFrame({"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]})
1209-
1210-
msg = "The 'method' keyword in DataFrame.replace is deprecated"
1211-
with tm.assert_produces_warning(FutureWarning, match=msg):
1212-
result = df.replace(to_replace=to_replace, value=None, method=method)
1213-
expected = DataFrame(expected)
1214-
tm.assert_frame_equal(result, expected)
1215-
12161174
@pytest.mark.parametrize(
12171175
"replace_dict, final_data",
12181176
[({"a": 1, "b": 1}, [[3, 3], [2, 2]]), ({"a": 1, "b": 2}, [[3, 1], [2, 3]])],

pandas/tests/frame/test_subclass.py

-12
Original file line numberDiff line numberDiff line change
@@ -742,18 +742,6 @@ def test_equals_subclass(self):
742742
assert df1.equals(df2)
743743
assert df2.equals(df1)
744744

745-
def test_replace_list_method(self):
746-
# https://github.com/pandas-dev/pandas/pull/46018
747-
df = tm.SubclassedDataFrame({"A": [0, 1, 2]})
748-
msg = "The 'method' keyword in SubclassedDataFrame.replace is deprecated"
749-
with tm.assert_produces_warning(
750-
FutureWarning, match=msg, raise_on_extra_warnings=False
751-
):
752-
result = df.replace([1, 2], method="ffill")
753-
expected = tm.SubclassedDataFrame({"A": [0, 0, 0]})
754-
assert isinstance(result, tm.SubclassedDataFrame)
755-
tm.assert_frame_equal(result, expected)
756-
757745

758746
class MySubclassWithMetadata(DataFrame):
759747
_metadata = ["my_metadata"]

pandas/tests/series/methods/test_replace.py

-69
Original file line numberDiff line numberDiff line change
@@ -196,19 +196,6 @@ def test_replace_with_single_list(self):
196196
assert return_value is None
197197
tm.assert_series_equal(s, pd.Series([0, 0, 0, 0, 4]))
198198

199-
# make sure things don't get corrupted when fillna call fails
200-
s = ser.copy()
201-
msg = (
202-
r"Invalid fill method\. Expecting pad \(ffill\) or backfill "
203-
r"\(bfill\)\. Got crash_cymbal"
204-
)
205-
msg3 = "The 'method' keyword in Series.replace is deprecated"
206-
with pytest.raises(ValueError, match=msg):
207-
with tm.assert_produces_warning(FutureWarning, match=msg3):
208-
return_value = s.replace([1, 2, 3], inplace=True, method="crash_cymbal")
209-
assert return_value is None
210-
tm.assert_series_equal(s, ser)
211-
212199
def test_replace_mixed_types(self):
213200
ser = pd.Series(np.arange(5), dtype="int64")
214201

@@ -550,62 +537,6 @@ def test_replace_extension_other(self, frame_or_series):
550537
# should not have changed dtype
551538
tm.assert_equal(obj, result)
552539

553-
def _check_replace_with_method(self, ser: pd.Series):
554-
df = ser.to_frame()
555-
556-
msg1 = "The 'method' keyword in Series.replace is deprecated"
557-
with tm.assert_produces_warning(FutureWarning, match=msg1):
558-
res = ser.replace(ser[1], method="pad")
559-
expected = pd.Series([ser[0], ser[0]] + list(ser[2:]), dtype=ser.dtype)
560-
tm.assert_series_equal(res, expected)
561-
562-
msg2 = "The 'method' keyword in DataFrame.replace is deprecated"
563-
with tm.assert_produces_warning(FutureWarning, match=msg2):
564-
res_df = df.replace(ser[1], method="pad")
565-
tm.assert_frame_equal(res_df, expected.to_frame())
566-
567-
ser2 = ser.copy()
568-
with tm.assert_produces_warning(FutureWarning, match=msg1):
569-
res2 = ser2.replace(ser[1], method="pad", inplace=True)
570-
assert res2 is None
571-
tm.assert_series_equal(ser2, expected)
572-
573-
with tm.assert_produces_warning(FutureWarning, match=msg2):
574-
res_df2 = df.replace(ser[1], method="pad", inplace=True)
575-
assert res_df2 is None
576-
tm.assert_frame_equal(df, expected.to_frame())
577-
578-
def test_replace_ea_dtype_with_method(self, any_numeric_ea_dtype):
579-
arr = pd.array([1, 2, pd.NA, 4], dtype=any_numeric_ea_dtype)
580-
ser = pd.Series(arr)
581-
582-
self._check_replace_with_method(ser)
583-
584-
@pytest.mark.parametrize("as_categorical", [True, False])
585-
def test_replace_interval_with_method(self, as_categorical):
586-
# in particular interval that can't hold NA
587-
588-
idx = pd.IntervalIndex.from_breaks(range(4))
589-
ser = pd.Series(idx)
590-
if as_categorical:
591-
ser = ser.astype("category")
592-
593-
self._check_replace_with_method(ser)
594-
595-
@pytest.mark.parametrize("as_period", [True, False])
596-
@pytest.mark.parametrize("as_categorical", [True, False])
597-
def test_replace_datetimelike_with_method(self, as_period, as_categorical):
598-
idx = pd.date_range("2016-01-01", periods=5, tz="US/Pacific")
599-
if as_period:
600-
idx = idx.tz_localize(None).to_period("D")
601-
602-
ser = pd.Series(idx)
603-
ser.iloc[-2] = pd.NaT
604-
if as_categorical:
605-
ser = ser.astype("category")
606-
607-
self._check_replace_with_method(ser)
608-
609540
def test_replace_with_compiled_regex(self):
610541
# https://github.com/pandas-dev/pandas/issues/35680
611542
s = pd.Series(["a", "b", "c"])

0 commit comments

Comments
 (0)