Skip to content

Commit 376bbf7

Browse files
jbrockmendelim-vinicius
authored and
im-vinicius
committed
DEPR: method, limit in NDFrame.replace (pandas-dev#53492)
* DEPR: method, limit in NDFrame.replace * update test, docs * suppress doctest warning * doctests
1 parent 0294273 commit 376bbf7

File tree

8 files changed

+91
-20
lines changed

8 files changed

+91
-20
lines changed

doc/source/user_guide/missing_data.rst

-7
Original file line numberDiff line numberDiff line change
@@ -551,13 +551,6 @@ For a DataFrame, you can specify individual values by column:
551551
552552
df.replace({"a": 0, "b": 5}, 100)
553553
554-
Instead of replacing with specified values, you can treat all given values as
555-
missing and interpolate over them:
556-
557-
.. ipython:: python
558-
559-
ser.replace([1, 2, 3], method="pad")
560-
561554
.. _missing_data.replace_expression:
562555

563556
String/regular expression replacement

doc/source/whatsnew/v2.1.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,11 @@ Deprecations
286286
- Deprecated allowing arbitrary ``fill_value`` in :class:`SparseDtype`, in a future version the ``fill_value`` will need to be compatible with the ``dtype.subtype``, either a scalar that can be held by that subtype or ``NaN`` for integer or bool subtypes (:issue:`23124`)
287287
- Deprecated behavior of :func:`assert_series_equal` and :func:`assert_frame_equal` considering NA-like values (e.g. ``NaN`` vs ``None`` as equivalent) (:issue:`52081`)
288288
- Deprecated constructing :class:`SparseArray` from scalar data, pass a sequence instead (:issue:`53039`)
289+
- Deprecated falling back to filling when ``value`` is not specified in :meth:`DataFrame.replace` and :meth:`Series.replace` with non-dict-like ``to_replace`` (:issue:`33302`)
289290
- Deprecated option "mode.use_inf_as_na", convert inf entries to ``NaN`` before instead (:issue:`51684`)
290291
- Deprecated positional indexing on :class:`Series` with :meth:`Series.__getitem__` and :meth:`Series.__setitem__`, in a future version ``ser[item]`` will *always* interpret ``item`` as a label, not a position (:issue:`50617`)
291292
- Deprecated the "method" and "limit" keywords on :meth:`Series.fillna`, :meth:`DataFrame.fillna`, :meth:`SeriesGroupBy.fillna`, :meth:`DataFrameGroupBy.fillna`, and :meth:`Resampler.fillna`, use ``obj.bfill()`` or ``obj.ffill()`` instead (:issue:`53394`)
293+
- Deprecated the ``method`` and ``limit`` keywords in :meth:`DataFrame.replace` and :meth:`Series.replace` (:issue:`33302`)
292294
- Deprecated values "pad", "ffill", "bfill", "backfill" for :meth:`Series.interpolate` and :meth:`DataFrame.interpolate`, use ``obj.ffill()`` or ``obj.bfill()`` instead (:issue:`53581`)
293295
-
294296

pandas/conftest.py

+2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ def pytest_collection_modifyitems(items, config) -> None:
134134
("is_datetime64tz_dtype", "is_datetime64tz_dtype is deprecated"),
135135
("is_categorical_dtype", "is_categorical_dtype is deprecated"),
136136
("is_sparse", "is_sparse is deprecated"),
137+
("NDFrame.replace", "The 'method' keyword"),
138+
("NDFrame.replace", "Series.replace without 'value'"),
137139
# Docstring divides by zero to show behavior difference
138140
("missing.mask_zero_div_zero", "divide by zero encountered"),
139141
(

pandas/core/generic.py

+33
Original file line numberDiff line numberDiff line change
@@ -7470,6 +7470,39 @@ def replace(
74707470
regex: bool_t = False,
74717471
method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = lib.no_default,
74727472
) -> Self | None:
7473+
if method is not lib.no_default:
7474+
warnings.warn(
7475+
# GH#33302
7476+
f"The 'method' keyword in {type(self).__name__}.replace is "
7477+
"deprecated and will be removed in a future version.",
7478+
FutureWarning,
7479+
stacklevel=find_stack_level(),
7480+
)
7481+
elif limit is not None:
7482+
warnings.warn(
7483+
# GH#33302
7484+
f"The 'limit' keyword in {type(self).__name__}.replace is "
7485+
"deprecated and will be removed in a future version.",
7486+
FutureWarning,
7487+
stacklevel=find_stack_level(),
7488+
)
7489+
if (
7490+
value is lib.no_default
7491+
and method is lib.no_default
7492+
and not is_dict_like(to_replace)
7493+
and regex is False
7494+
):
7495+
# case that goes through _replace_single and defaults to method="pad"
7496+
warnings.warn(
7497+
# GH#33302
7498+
f"{type(self).__name__}.replace without 'value' and with "
7499+
"non-dict-like 'to_replace' is deprecated "
7500+
"and will raise in a future version. "
7501+
"Explicitly specify the new values instead.",
7502+
FutureWarning,
7503+
stacklevel=find_stack_level(),
7504+
)
7505+
74737506
if not (
74747507
is_scalar(to_replace)
74757508
or is_re_compilable(to_replace)

pandas/core/shared_docs.py

+7
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@
562562
{inplace}
563563
limit : int, default None
564564
Maximum size gap to forward or backward fill.
565+
566+
.. deprecated:: 2.1.0
565567
regex : bool or same types as `to_replace`, default False
566568
Whether to interpret `to_replace` and/or `value` as regular
567569
expressions. If this is ``True`` then `to_replace` *must* be a
@@ -572,6 +574,8 @@
572574
The method to use when for replacement, when `to_replace` is a
573575
scalar, list or tuple and `value` is ``None``.
574576
577+
.. deprecated:: 2.1.0
578+
575579
Returns
576580
-------
577581
{klass}
@@ -766,6 +770,9 @@
766770
4 b
767771
dtype: object
768772
773+
.. deprecated:: 2.1.0
774+
The 'method' parameter and padding behavior are deprecated.
775+
769776
On the other hand, if ``None`` is explicitly passed for ``value``, it will
770777
be respected:
771778

pandas/tests/frame/methods/test_replace.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,9 @@ def test_replace_method(self, to_replace, method, expected):
12361236
# GH 19632
12371237
df = DataFrame({"A": [0, 1, 2], "B": [5, np.nan, 7], "C": ["a", "b", "c"]})
12381238

1239-
result = df.replace(to_replace=to_replace, value=None, method=method)
1239+
msg = "The 'method' keyword in DataFrame.replace is deprecated"
1240+
with tm.assert_produces_warning(FutureWarning, match=msg):
1241+
result = df.replace(to_replace=to_replace, value=None, method=method)
12401242
expected = DataFrame(expected)
12411243
tm.assert_frame_equal(result, expected)
12421244

@@ -1327,8 +1329,13 @@ def test_replace_invalid_to_replace(self):
13271329
r"Expecting 'to_replace' to be either a scalar, array-like, "
13281330
r"dict or None, got invalid type.*"
13291331
)
1332+
msg2 = (
1333+
"DataFrame.replace without 'value' and with non-dict-like "
1334+
"'to_replace' is deprecated"
1335+
)
13301336
with pytest.raises(TypeError, match=msg):
1331-
df.replace(lambda x: x.strip())
1337+
with tm.assert_produces_warning(FutureWarning, match=msg2):
1338+
df.replace(lambda x: x.strip())
13321339

13331340
@pytest.mark.parametrize("dtype", ["float", "float64", "int64", "Int64", "boolean"])
13341341
@pytest.mark.parametrize("value", [np.nan, pd.NA])

pandas/tests/frame/test_subclass.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,9 @@ def test_equals_subclass(self):
732732
def test_replace_list_method(self):
733733
# https://github.com/pandas-dev/pandas/pull/46018
734734
df = tm.SubclassedDataFrame({"A": [0, 1, 2]})
735-
result = df.replace([1, 2], method="ffill")
735+
msg = "The 'method' keyword in SubclassedDataFrame.replace is deprecated"
736+
with tm.assert_produces_warning(FutureWarning, match=msg):
737+
result = df.replace([1, 2], method="ffill")
736738
expected = tm.SubclassedDataFrame({"A": [0, 0, 0]})
737739
assert isinstance(result, tm.SubclassedDataFrame)
738740
tm.assert_frame_equal(result, expected)

pandas/tests/series/methods/test_replace.py

+35-10
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,18 @@ def test_replace_gh5319(self):
131131
# GH 5319
132132
ser = pd.Series([0, np.nan, 2, 3, 4])
133133
expected = ser.ffill()
134-
result = ser.replace([np.nan])
134+
msg = (
135+
"Series.replace without 'value' and with non-dict-like "
136+
"'to_replace' is deprecated"
137+
)
138+
with tm.assert_produces_warning(FutureWarning, match=msg):
139+
result = ser.replace([np.nan])
135140
tm.assert_series_equal(result, expected)
136141

137142
ser = pd.Series([0, np.nan, 2, 3, 4])
138143
expected = ser.ffill()
139-
result = ser.replace(np.nan)
144+
with tm.assert_produces_warning(FutureWarning, match=msg):
145+
result = ser.replace(np.nan)
140146
tm.assert_series_equal(result, expected)
141147

142148
def test_replace_datetime64(self):
@@ -169,11 +175,17 @@ def test_replace_timedelta_td64(self):
169175

170176
def test_replace_with_single_list(self):
171177
ser = pd.Series([0, 1, 2, 3, 4])
172-
result = ser.replace([1, 2, 3])
178+
msg2 = (
179+
"Series.replace without 'value' and with non-dict-like "
180+
"'to_replace' is deprecated"
181+
)
182+
with tm.assert_produces_warning(FutureWarning, match=msg2):
183+
result = ser.replace([1, 2, 3])
173184
tm.assert_series_equal(result, pd.Series([0, 0, 0, 0, 4]))
174185

175186
s = ser.copy()
176-
return_value = s.replace([1, 2, 3], inplace=True)
187+
with tm.assert_produces_warning(FutureWarning, match=msg2):
188+
return_value = s.replace([1, 2, 3], inplace=True)
177189
assert return_value is None
178190
tm.assert_series_equal(s, pd.Series([0, 0, 0, 0, 4]))
179191

@@ -183,8 +195,10 @@ def test_replace_with_single_list(self):
183195
r"Invalid fill method\. Expecting pad \(ffill\) or backfill "
184196
r"\(bfill\)\. Got crash_cymbal"
185197
)
198+
msg3 = "The 'method' keyword in Series.replace is deprecated"
186199
with pytest.raises(ValueError, match=msg):
187-
return_value = s.replace([1, 2, 3], inplace=True, method="crash_cymbal")
200+
with tm.assert_produces_warning(FutureWarning, match=msg3):
201+
return_value = s.replace([1, 2, 3], inplace=True, method="crash_cymbal")
188202
assert return_value is None
189203
tm.assert_series_equal(s, ser)
190204

@@ -450,8 +464,13 @@ def test_replace_invalid_to_replace(self):
450464
r"Expecting 'to_replace' to be either a scalar, array-like, "
451465
r"dict or None, got invalid type.*"
452466
)
467+
msg2 = (
468+
"Series.replace without 'value' and with non-dict-like "
469+
"'to_replace' is deprecated"
470+
)
453471
with pytest.raises(TypeError, match=msg):
454-
series.replace(lambda x: x.strip())
472+
with tm.assert_produces_warning(FutureWarning, match=msg2):
473+
series.replace(lambda x: x.strip())
455474

456475
@pytest.mark.parametrize("frame", [False, True])
457476
def test_replace_nonbool_regex(self, frame):
@@ -502,19 +521,25 @@ def test_replace_extension_other(self, frame_or_series):
502521
def _check_replace_with_method(self, ser: pd.Series):
503522
df = ser.to_frame()
504523

505-
res = ser.replace(ser[1], method="pad")
524+
msg1 = "The 'method' keyword in Series.replace is deprecated"
525+
with tm.assert_produces_warning(FutureWarning, match=msg1):
526+
res = ser.replace(ser[1], method="pad")
506527
expected = pd.Series([ser[0], ser[0]] + list(ser[2:]), dtype=ser.dtype)
507528
tm.assert_series_equal(res, expected)
508529

509-
res_df = df.replace(ser[1], method="pad")
530+
msg2 = "The 'method' keyword in DataFrame.replace is deprecated"
531+
with tm.assert_produces_warning(FutureWarning, match=msg2):
532+
res_df = df.replace(ser[1], method="pad")
510533
tm.assert_frame_equal(res_df, expected.to_frame())
511534

512535
ser2 = ser.copy()
513-
res2 = ser2.replace(ser[1], method="pad", inplace=True)
536+
with tm.assert_produces_warning(FutureWarning, match=msg1):
537+
res2 = ser2.replace(ser[1], method="pad", inplace=True)
514538
assert res2 is None
515539
tm.assert_series_equal(ser2, expected)
516540

517-
res_df2 = df.replace(ser[1], method="pad", inplace=True)
541+
with tm.assert_produces_warning(FutureWarning, match=msg2):
542+
res_df2 = df.replace(ser[1], method="pad", inplace=True)
518543
assert res_df2 is None
519544
tm.assert_frame_equal(df, expected.to_frame())
520545

0 commit comments

Comments
 (0)