Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEPR: NDFrame.interpolate with ffill/bfill methods #53607

Merged
merged 3 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ Deprecations
- Deprecated option "mode.use_inf_as_na", convert inf entries to ``NaN`` before instead (:issue:`51684`)
- 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`)
- 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`)
- Deprecated values "pad", "ffill", "bfill", "backfill" for :meth:`Series.interpolate` and :meth:`DataFrame.interpolate`, use ``obj.ffill()`` or ``obj.bfill()`` instead (:issue:`53581`)
-

.. ---------------------------------------------------------------------------
Expand Down
40 changes: 11 additions & 29 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7806,35 +7806,6 @@ def interpolate(
3 3.0
dtype: float64

Filling in ``NaN`` in a Series by padding, but filling at most two
consecutive ``NaN`` at a time.

>>> s = pd.Series([np.nan, "single_one", np.nan,
... "fill_two_more", np.nan, np.nan, np.nan,
... 4.71, np.nan])
>>> s
0 NaN
1 single_one
2 NaN
3 fill_two_more
4 NaN
5 NaN
6 NaN
7 4.71
8 NaN
dtype: object
>>> s.interpolate(method='pad', limit=2)
0 NaN
1 single_one
2 single_one
3 fill_two_more
4 fill_two_more
5 fill_two_more
6 NaN
7 4.71
8 4.71
dtype: object

Filling in ``NaN`` in a Series via polynomial interpolation or splines:
Both 'polynomial' and 'spline' methods require that you also specify
an ``order`` (int).
Expand Down Expand Up @@ -7899,6 +7870,17 @@ def interpolate(
return None
return self.copy()

if not isinstance(method, str):
raise ValueError("'method' should be a string, not None.")
elif method.lower() in fillna_methods:
# GH#53581
warnings.warn(
f"{type(self).__name__}.interpolate with method={method} is "
"deprecated and will raise in a future version. "
"Use obj.ffill() or obj.bfill() instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
if method not in fillna_methods:
axis = self._info_axis_number

Expand Down
23 changes: 18 additions & 5 deletions pandas/tests/copy_view/test_interp_fillna.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ def test_interpolate_no_op(using_copy_on_write, method):
df = DataFrame({"a": [1, 2]})
df_orig = df.copy()

result = df.interpolate(method=method)
warn = None
if method == "pad":
warn = FutureWarning
msg = "DataFrame.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(warn, match=msg):
result = df.interpolate(method=method)

if using_copy_on_write:
assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
Expand Down Expand Up @@ -125,7 +130,9 @@ def test_interpolate_cleaned_fill_method(using_copy_on_write):
def test_interpolate_object_convert_no_op(using_copy_on_write):
df = DataFrame({"a": ["a", "b", "c"], "b": 1})
arr_a = get_array(df, "a")
df.interpolate(method="pad", inplace=True)
msg = "DataFrame.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
df.interpolate(method="pad", inplace=True)

# Now CoW makes a copy, it should not!
if using_copy_on_write:
Expand All @@ -136,7 +143,9 @@ def test_interpolate_object_convert_no_op(using_copy_on_write):
def test_interpolate_object_convert_copies(using_copy_on_write):
df = DataFrame({"a": Series([1, 2], dtype=object), "b": 1})
arr_a = get_array(df, "a")
df.interpolate(method="pad", inplace=True)
msg = "DataFrame.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
df.interpolate(method="pad", inplace=True)

if using_copy_on_write:
assert df._mgr._has_no_reference(0)
Expand All @@ -146,7 +155,9 @@ def test_interpolate_object_convert_copies(using_copy_on_write):
def test_interpolate_downcast(using_copy_on_write):
df = DataFrame({"a": [1, np.nan, 2.5], "b": 1})
arr_a = get_array(df, "a")
df.interpolate(method="pad", inplace=True, downcast="infer")
msg = "DataFrame.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
df.interpolate(method="pad", inplace=True, downcast="infer")

if using_copy_on_write:
assert df._mgr._has_no_reference(0)
Expand All @@ -158,7 +169,9 @@ def test_interpolate_downcast_reference_triggers_copy(using_copy_on_write):
df_orig = df.copy()
arr_a = get_array(df, "a")
view = df[:]
df.interpolate(method="pad", inplace=True, downcast="infer")
msg = "DataFrame.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
df.interpolate(method="pad", inplace=True, downcast="infer")

if using_copy_on_write:
assert df._mgr._has_no_reference(0)
Expand Down
4 changes: 3 additions & 1 deletion pandas/tests/frame/methods/test_interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,9 @@ def test_interp_fillna_methods(self, request, axis, method, using_array_manager)
)
method2 = method if method != "pad" else "ffill"
expected = getattr(df, method2)(axis=axis)
result = df.interpolate(method=method, axis=axis)
msg = f"DataFrame.interpolate with method={method} is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = df.interpolate(method=method, axis=axis)
tm.assert_frame_equal(result, expected)

def test_interpolate_empty_df(self):
Expand Down
36 changes: 30 additions & 6 deletions pandas/tests/series/methods/test_interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ def test_interp_invalid_method(self, invalid_method):
s = Series([1, 3, np.nan, 12, np.nan, 25])

msg = f"method must be one of.* Got '{invalid_method}' instead"
if invalid_method is None:
msg = "'method' should be a string, not None"
with pytest.raises(ValueError, match=msg):
s.interpolate(method=invalid_method)

Expand All @@ -360,8 +362,10 @@ def test_interp_invalid_method_and_value(self):
ser = Series([1, 3, np.nan, 12, np.nan, 25])

msg = "Cannot pass both fill_value and method"
msg2 = "Series.interpolate with method=pad"
with pytest.raises(ValueError, match=msg):
ser.interpolate(fill_value=3, method="pad")
with tm.assert_produces_warning(FutureWarning, match=msg2):
ser.interpolate(fill_value=3, method="pad")

def test_interp_limit_forward(self):
s = Series([1, 3, np.nan, np.nan, np.nan, 11])
Expand Down Expand Up @@ -470,8 +474,10 @@ def test_interp_limit_direction_raises(self, method, limit_direction, expected):
s = Series([1, 2, 3])

msg = f"`limit_direction` must be '{expected}' for method `{method}`"
msg2 = "Series.interpolate with method="
with pytest.raises(ValueError, match=msg):
s.interpolate(method=method, limit_direction=limit_direction)
with tm.assert_produces_warning(FutureWarning, match=msg2):
s.interpolate(method=method, limit_direction=limit_direction)

@pytest.mark.parametrize(
"data, expected_data, kwargs",
Expand Down Expand Up @@ -513,7 +519,9 @@ def test_interp_limit_area_with_pad(self, data, expected_data, kwargs):

s = Series(data)
expected = Series(expected_data)
result = s.interpolate(**kwargs)
msg = "Series.interpolate with method=pad"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = s.interpolate(**kwargs)
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize(
Expand Down Expand Up @@ -546,7 +554,9 @@ def test_interp_limit_area_with_backfill(self, data, expected_data, kwargs):

s = Series(data)
expected = Series(expected_data)
result = s.interpolate(**kwargs)
msg = "Series.interpolate with method=bfill"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = s.interpolate(**kwargs)
tm.assert_series_equal(result, expected)

def test_interp_limit_direction(self):
Expand Down Expand Up @@ -642,7 +652,15 @@ def test_interp_datetime64(self, method, tz_naive_fixture):
df = Series(
[1, np.nan, 3], index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture)
)
result = df.interpolate(method=method)
warn = None if method == "nearest" else FutureWarning
msg = "Series.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(warn, match=msg):
result = df.interpolate(method=method)
if warn is not None:
# check the "use ffill instead" is equivalent
alt = df.ffill()
tm.assert_series_equal(result, alt)

expected = Series(
[1.0, 1.0, 3.0],
index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture),
Expand All @@ -654,7 +672,13 @@ def test_interp_pad_datetime64tz_values(self):
dti = date_range("2015-04-05", periods=3, tz="US/Central")
ser = Series(dti)
ser[1] = pd.NaT
result = ser.interpolate(method="pad")

msg = "Series.interpolate with method=pad is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = ser.interpolate(method="pad")
# check the "use ffill instead" is equivalent
alt = ser.ffill()
tm.assert_series_equal(result, alt)

expected = Series(dti)
expected[1] = expected[0]
Expand Down