From f982aa8a92436aee9f5c17ab8746f4cff4c94d71 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 11 Jun 2023 17:44:36 -0700 Subject: [PATCH 1/2] DEPR: NDFrame.interpolate with ffill/bfill methods --- doc/source/whatsnew/v2.1.0.rst | 1 + pandas/core/generic.py | 11 ++++++ pandas/tests/copy_view/test_interp_fillna.py | 23 +++++++++--- .../tests/frame/methods/test_interpolate.py | 4 ++- .../tests/series/methods/test_interpolate.py | 36 +++++++++++++++---- 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index baacc8c421414..0c35b0b49e4d4 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -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`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/generic.py b/pandas/core/generic.py index f73ef36f76086..ff2e306681f49 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -7899,6 +7899,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 diff --git a/pandas/tests/copy_view/test_interp_fillna.py b/pandas/tests/copy_view/test_interp_fillna.py index 576d3a9cdedde..270de481b5441 100644 --- a/pandas/tests/copy_view/test_interp_fillna.py +++ b/pandas/tests/copy_view/test_interp_fillna.py @@ -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")) @@ -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: @@ -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) @@ -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) @@ -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) diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index 9eb1073f4c69c..5a3d53eb96a52 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -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): diff --git a/pandas/tests/series/methods/test_interpolate.py b/pandas/tests/series/methods/test_interpolate.py index 6f4c4ba4dd69d..395206d829682 100644 --- a/pandas/tests/series/methods/test_interpolate.py +++ b/pandas/tests/series/methods/test_interpolate.py @@ -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) @@ -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]) @@ -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", @@ -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( @@ -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): @@ -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), @@ -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] From dda183dd6d2658e3c7d6e874c64e9af01a8c41d1 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 12 Jun 2023 08:57:34 -0700 Subject: [PATCH 2/2] update doctest --- pandas/core/generic.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index ff2e306681f49..2296f769539c3 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -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).