Skip to content

Commit 677a4ea

Browse files
authored
CLN: enforce deprecation of NDFrame.interpolate with ffill/bfill/pad/backfill methods (#57869)
* enforce deprecation of interpolate with ffill, bfill-pad, backfill methods * remove redundant if branch * remove unuseful cheek from interpolate * move checking for a fillna_method from NDFrame.interpolate to Block.interpolate, correct tests * remove the check from Block.interpolate * add a note to v3.0.0 * correct def _interpolate_scipy_wrapper: use alt_methods instead of valid
1 parent eddd8e3 commit 677a4ea

File tree

6 files changed

+76
-189
lines changed

6 files changed

+76
-189
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ Removal of prior version deprecations/changes
211211
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting frequencies in :class:`Minute`, :class:`Second`, :class:`Milli`, :class:`Micro`, :class:`Nano` (:issue:`57627`)
212212
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting units in :class:`Timedelta` (:issue:`57627`)
213213
- Enforced deprecation of the behavior of :func:`concat` when ``len(keys) != len(objs)`` would truncate to the shorter of the two. Now this raises a ``ValueError`` (:issue:`43485`)
214+
- Enforced deprecation of values "pad", "ffill", "bfill", and "backfill" for :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` (:issue:`57869`)
214215
- Enforced silent-downcasting deprecation for :ref:`all relevant methods <whatsnew_220.silent_downcasting>` (:issue:`54710`)
215216
- In :meth:`DataFrame.stack`, the default value of ``future_stack`` is now ``True``; specifying ``False`` will raise a ``FutureWarning`` (:issue:`55448`)
216217
- Iterating over a :class:`.DataFrameGroupBy` or :class:`.SeriesGroupBy` will return tuples of length 1 for the groups when grouping by ``level`` a list of length 1 (:issue:`50064`)

pandas/core/generic.py

+16-68
Original file line numberDiff line numberDiff line change
@@ -7624,7 +7624,6 @@ def interpolate(
76247624
* 'time': Works on daily and higher resolution data to interpolate
76257625
given length of interval.
76267626
* 'index', 'values': use the actual numerical values of the index.
7627-
* 'pad': Fill in NaNs using existing values.
76287627
* 'nearest', 'zero', 'slinear', 'quadratic', 'cubic',
76297628
'barycentric', 'polynomial': Passed to
76307629
`scipy.interpolate.interp1d`, whereas 'spline' is passed to
@@ -7648,23 +7647,9 @@ def interpolate(
76487647
0.
76497648
inplace : bool, default False
76507649
Update the data in place if possible.
7651-
limit_direction : {{'forward', 'backward', 'both'}}, Optional
7650+
limit_direction : {{'forward', 'backward', 'both'}}, optional, default 'forward'
76527651
Consecutive NaNs will be filled in this direction.
76537652
7654-
If limit is specified:
7655-
* If 'method' is 'pad' or 'ffill', 'limit_direction' must be 'forward'.
7656-
* If 'method' is 'backfill' or 'bfill', 'limit_direction' must be
7657-
'backwards'.
7658-
7659-
If 'limit' is not specified:
7660-
* If 'method' is 'backfill' or 'bfill', the default is 'backward'
7661-
* else the default is 'forward'
7662-
7663-
raises ValueError if `limit_direction` is 'forward' or 'both' and
7664-
method is 'backfill' or 'bfill'.
7665-
raises ValueError if `limit_direction` is 'backward' or 'both' and
7666-
method is 'pad' or 'ffill'.
7667-
76687653
limit_area : {{`None`, 'inside', 'outside'}}, default None
76697654
If limit is specified, consecutive NaNs will be filled with this
76707655
restriction.
@@ -7797,30 +7782,11 @@ def interpolate(
77977782
if not isinstance(method, str):
77987783
raise ValueError("'method' should be a string, not None.")
77997784

7800-
fillna_methods = ["ffill", "bfill", "pad", "backfill"]
7801-
if method.lower() in fillna_methods:
7802-
# GH#53581
7803-
warnings.warn(
7804-
f"{type(self).__name__}.interpolate with method={method} is "
7805-
"deprecated and will raise in a future version. "
7806-
"Use obj.ffill() or obj.bfill() instead.",
7807-
FutureWarning,
7808-
stacklevel=find_stack_level(),
7809-
)
7810-
obj, should_transpose = self, False
7811-
else:
7812-
obj, should_transpose = (self.T, True) if axis == 1 else (self, False)
7813-
# GH#53631
7814-
if np.any(obj.dtypes == object):
7815-
raise TypeError(
7816-
f"{type(self).__name__} cannot interpolate with object dtype."
7817-
)
7818-
7819-
if method in fillna_methods and "fill_value" in kwargs:
7820-
raise ValueError(
7821-
"'fill_value' is not a valid keyword for "
7822-
f"{type(self).__name__}.interpolate with method from "
7823-
f"{fillna_methods}"
7785+
obj, should_transpose = (self.T, True) if axis == 1 else (self, False)
7786+
# GH#53631
7787+
if np.any(obj.dtypes == object):
7788+
raise TypeError(
7789+
f"{type(self).__name__} cannot interpolate with object dtype."
78247790
)
78257791

78267792
if isinstance(obj.index, MultiIndex) and method != "linear":
@@ -7830,34 +7796,16 @@ def interpolate(
78307796

78317797
limit_direction = missing.infer_limit_direction(limit_direction, method)
78327798

7833-
if method.lower() in fillna_methods:
7834-
# TODO(3.0): remove this case
7835-
# TODO: warn/raise on limit_direction or kwargs which are ignored?
7836-
# as of 2023-06-26 no tests get here with either
7837-
if not self._mgr.is_single_block and axis == 1:
7838-
# GH#53898
7839-
if inplace:
7840-
raise NotImplementedError()
7841-
obj, axis, should_transpose = self.T, 1 - axis, True
7842-
7843-
new_data = obj._mgr.pad_or_backfill(
7844-
method=method,
7845-
axis=self._get_block_manager_axis(axis),
7846-
limit=limit,
7847-
limit_area=limit_area,
7848-
inplace=inplace,
7849-
)
7850-
else:
7851-
index = missing.get_interp_index(method, obj.index)
7852-
new_data = obj._mgr.interpolate(
7853-
method=method,
7854-
index=index,
7855-
limit=limit,
7856-
limit_direction=limit_direction,
7857-
limit_area=limit_area,
7858-
inplace=inplace,
7859-
**kwargs,
7860-
)
7799+
index = missing.get_interp_index(method, obj.index)
7800+
new_data = obj._mgr.interpolate(
7801+
method=method,
7802+
index=index,
7803+
limit=limit,
7804+
limit_direction=limit_direction,
7805+
limit_area=limit_area,
7806+
inplace=inplace,
7807+
**kwargs,
7808+
)
78617809

78627810
result = self._constructor_from_mgr(new_data, axes=new_data.axes)
78637811
if should_transpose:

pandas/core/missing.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,17 @@ def get_interp_index(method, index: Index) -> Index:
322322
or isinstance(index.dtype, DatetimeTZDtype)
323323
or lib.is_np_dtype(index.dtype, "mM")
324324
)
325-
if method not in methods and not is_numeric_or_datetime:
326-
raise ValueError(
327-
"Index column must be numeric or datetime type when "
328-
f"using {method} method other than linear. "
329-
"Try setting a numeric or datetime index column before "
330-
"interpolating."
331-
)
325+
valid = NP_METHODS + SP_METHODS
326+
if method in valid:
327+
if method not in methods and not is_numeric_or_datetime:
328+
raise ValueError(
329+
"Index column must be numeric or datetime type when "
330+
f"using {method} method other than linear. "
331+
"Try setting a numeric or datetime index column before "
332+
"interpolating."
333+
)
334+
else:
335+
raise ValueError(f"Can not interpolate with method={method}.")
332336

333337
if isna(index).any():
334338
raise NotImplementedError(
@@ -611,7 +615,9 @@ def _interpolate_scipy_wrapper(
611615
y = y.copy()
612616
if not new_x.flags.writeable:
613617
new_x = new_x.copy()
614-
terp = alt_methods[method]
618+
terp = alt_methods.get(method, None)
619+
if terp is None:
620+
raise ValueError(f"Can not interpolate with method={method}.")
615621
new_y = terp(x, y, new_x, **kwargs)
616622
return new_y
617623

pandas/tests/copy_view/test_interp_fillna.py

+15-18
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,18 @@ def test_interpolate_no_op(method):
1919
df = DataFrame({"a": [1, 2]})
2020
df_orig = df.copy()
2121

22-
warn = None
2322
if method == "pad":
24-
warn = FutureWarning
25-
msg = "DataFrame.interpolate with method=pad is deprecated"
26-
with tm.assert_produces_warning(warn, match=msg):
23+
msg = f"Can not interpolate with method={method}"
24+
with pytest.raises(ValueError, match=msg):
25+
df.interpolate(method=method)
26+
else:
2727
result = df.interpolate(method=method)
28+
assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
2829

29-
assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
30+
result.iloc[0, 0] = 100
3031

31-
result.iloc[0, 0] = 100
32-
33-
assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
34-
tm.assert_frame_equal(df, df_orig)
32+
assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
33+
tm.assert_frame_equal(df, df_orig)
3534

3635

3736
@pytest.mark.parametrize("func", ["ffill", "bfill"])
@@ -122,9 +121,6 @@ def test_interpolate_cannot_with_object_dtype():
122121
def test_interpolate_object_convert_no_op():
123122
df = DataFrame({"a": ["a", "b", "c"], "b": 1})
124123
arr_a = get_array(df, "a")
125-
msg = "DataFrame.interpolate with method=pad is deprecated"
126-
with tm.assert_produces_warning(FutureWarning, match=msg):
127-
df.interpolate(method="pad", inplace=True)
128124

129125
# Now CoW makes a copy, it should not!
130126
assert df._mgr._has_no_reference(0)
@@ -134,8 +130,8 @@ def test_interpolate_object_convert_no_op():
134130
def test_interpolate_object_convert_copies():
135131
df = DataFrame({"a": [1, np.nan, 2.5], "b": 1})
136132
arr_a = get_array(df, "a")
137-
msg = "DataFrame.interpolate with method=pad is deprecated"
138-
with tm.assert_produces_warning(FutureWarning, match=msg):
133+
msg = "Can not interpolate with method=pad"
134+
with pytest.raises(ValueError, match=msg):
139135
df.interpolate(method="pad", inplace=True, downcast="infer")
140136

141137
assert df._mgr._has_no_reference(0)
@@ -147,12 +143,13 @@ def test_interpolate_downcast_reference_triggers_copy():
147143
df_orig = df.copy()
148144
arr_a = get_array(df, "a")
149145
view = df[:]
150-
msg = "DataFrame.interpolate with method=pad is deprecated"
151-
with tm.assert_produces_warning(FutureWarning, match=msg):
146+
147+
msg = "Can not interpolate with method=pad"
148+
with pytest.raises(ValueError, match=msg):
152149
df.interpolate(method="pad", inplace=True, downcast="infer")
150+
assert df._mgr._has_no_reference(0)
151+
assert not np.shares_memory(arr_a, get_array(df, "a"))
153152

154-
assert df._mgr._has_no_reference(0)
155-
assert not np.shares_memory(arr_a, get_array(df, "a"))
156153
tm.assert_frame_equal(df_orig, view)
157154

158155

pandas/tests/frame/methods/test_interpolate.py

+4-13
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,7 @@ def test_interp_bad_method(self):
129129
"C": [1, 2, 3, 5],
130130
}
131131
)
132-
msg = (
133-
r"method must be one of \['linear', 'time', 'index', 'values', "
134-
r"'nearest', 'zero', 'slinear', 'quadratic', 'cubic', "
135-
r"'barycentric', 'krogh', 'spline', 'polynomial', "
136-
r"'from_derivatives', 'piecewise_polynomial', 'pchip', 'akima', "
137-
r"'cubicspline'\]. Got 'not_a_method' instead."
138-
)
132+
msg = "Can not interpolate with method=not_a_method"
139133
with pytest.raises(ValueError, match=msg):
140134
df.interpolate(method="not_a_method")
141135

@@ -398,12 +392,9 @@ def test_interp_fillna_methods(self, axis, multiblock, method):
398392
df["D"] = np.nan
399393
df["E"] = 1.0
400394

401-
method2 = method if method != "pad" else "ffill"
402-
expected = getattr(df, method2)(axis=axis)
403-
msg = f"DataFrame.interpolate with method={method} is deprecated"
404-
with tm.assert_produces_warning(FutureWarning, match=msg):
405-
result = df.interpolate(method=method, axis=axis)
406-
tm.assert_frame_equal(result, expected)
395+
msg = f"Can not interpolate with method={method}"
396+
with pytest.raises(ValueError, match=msg):
397+
df.interpolate(method=method, axis=axis)
407398

408399
def test_interpolate_empty_df(self):
409400
# GH#53199

0 commit comments

Comments
 (0)