Skip to content

Commit 657da07

Browse files
CoW: Add warnings for interpolate (#56289)
Co-authored-by: Joris Van den Bossche <[email protected]>
1 parent 91e251c commit 657da07

File tree

5 files changed

+75
-9
lines changed

5 files changed

+75
-9
lines changed

pandas/core/internals/base.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,11 @@ def replace_list(
284284

285285
def interpolate(self, inplace: bool, **kwargs) -> Self:
286286
return self.apply_with_block(
287-
"interpolate", inplace=inplace, **kwargs, using_cow=using_copy_on_write()
287+
"interpolate",
288+
inplace=inplace,
289+
**kwargs,
290+
using_cow=using_copy_on_write(),
291+
already_warned=_AlreadyWarned(),
288292
)
289293

290294
def pad_or_backfill(self, inplace: bool, **kwargs) -> Self:
@@ -293,6 +297,7 @@ def pad_or_backfill(self, inplace: bool, **kwargs) -> Self:
293297
inplace=inplace,
294298
**kwargs,
295299
using_cow=using_copy_on_write(),
300+
already_warned=_AlreadyWarned(),
296301
)
297302

298303
def shift(self, periods: int, fill_value) -> Self:

pandas/core/internals/blocks.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,7 @@ def pad_or_backfill(
16571657
limit_area: Literal["inside", "outside"] | None = None,
16581658
downcast: Literal["infer"] | None = None,
16591659
using_cow: bool = False,
1660+
already_warned=None,
16601661
) -> list[Block]:
16611662
if not self._can_hold_na:
16621663
# If there are no NAs, then interpolate is a no-op
@@ -1677,6 +1678,19 @@ def pad_or_backfill(
16771678
limit_area=limit_area,
16781679
copy=copy,
16791680
)
1681+
if (
1682+
not copy
1683+
and warn_copy_on_write()
1684+
and already_warned is not None
1685+
and not already_warned.warned_already
1686+
):
1687+
if self.refs.has_reference():
1688+
warnings.warn(
1689+
COW_WARNING_GENERAL_MSG,
1690+
FutureWarning,
1691+
stacklevel=find_stack_level(),
1692+
)
1693+
already_warned.warned_already = True
16801694
if axis == 1:
16811695
new_values = new_values.T
16821696

@@ -1697,6 +1711,7 @@ def interpolate(
16971711
limit_area: Literal["inside", "outside"] | None = None,
16981712
downcast: Literal["infer"] | None = None,
16991713
using_cow: bool = False,
1714+
already_warned=None,
17001715
**kwargs,
17011716
) -> list[Block]:
17021717
inplace = validate_bool_kwarg(inplace, "inplace")
@@ -1735,6 +1750,20 @@ def interpolate(
17351750
)
17361751
data = extract_array(new_values, extract_numpy=True)
17371752

1753+
if (
1754+
not copy
1755+
and warn_copy_on_write()
1756+
and already_warned is not None
1757+
and not already_warned.warned_already
1758+
):
1759+
if self.refs.has_reference():
1760+
warnings.warn(
1761+
COW_WARNING_GENERAL_MSG,
1762+
FutureWarning,
1763+
stacklevel=find_stack_level(),
1764+
)
1765+
already_warned.warned_already = True
1766+
17381767
nb = self.make_block_same_class(data, refs=refs)
17391768
return nb._maybe_downcast([nb], downcast, using_cow, caller="interpolate")
17401769

@@ -2178,9 +2207,9 @@ def pad_or_backfill(
21782207
limit_area: Literal["inside", "outside"] | None = None,
21792208
downcast: Literal["infer"] | None = None,
21802209
using_cow: bool = False,
2210+
already_warned=None,
21812211
) -> list[Block]:
21822212
values = self.values
2183-
copy, refs = self._get_refs_and_copy(using_cow, inplace)
21842213

21852214
if values.ndim == 2 and axis == 1:
21862215
# NDArrayBackedExtensionArray.fillna assumes axis=0

pandas/tests/copy_view/test_chained_assignment_deprecation.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,16 @@ def test_methods_iloc_warn(using_copy_on_write):
4242
("ffill", ()),
4343
],
4444
)
45-
def test_methods_iloc_getitem_item_cache(func, args, using_copy_on_write):
46-
df = DataFrame({"a": [1, 2, 3], "b": 1})
45+
def test_methods_iloc_getitem_item_cache(
46+
func, args, using_copy_on_write, warn_copy_on_write
47+
):
48+
df = DataFrame({"a": [1.5, 2, 3], "b": 1.5})
4749
ser = df.iloc[:, 0]
48-
# TODO(CoW-warn) should warn about updating a view
49-
getattr(ser, func)(*args, inplace=True)
50+
# TODO(CoW-warn) should warn about updating a view for all methods
51+
with tm.assert_cow_warning(
52+
warn_copy_on_write and func not in ("replace", "fillna")
53+
):
54+
getattr(ser, func)(*args, inplace=True)
5055

5156
# parent that holds item_cache is dead, so don't increase ref count
5257
ser = df.copy()["a"]

pandas/tests/copy_view/test_interp_fillna.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,13 @@ def test_interpolate_inplace_no_reference_no_copy(using_copy_on_write, vals):
9191
@pytest.mark.parametrize(
9292
"vals", [[1, np.nan, 2], [Timestamp("2019-12-31"), NaT, Timestamp("2020-12-31")]]
9393
)
94-
def test_interpolate_inplace_with_refs(using_copy_on_write, vals):
94+
def test_interpolate_inplace_with_refs(using_copy_on_write, vals, warn_copy_on_write):
9595
df = DataFrame({"a": [1, np.nan, 2]})
9696
df_orig = df.copy()
9797
arr = get_array(df, "a")
9898
view = df[:]
99-
df.interpolate(method="linear", inplace=True)
99+
with tm.assert_cow_warning(warn_copy_on_write):
100+
df.interpolate(method="linear", inplace=True)
100101

101102
if using_copy_on_write:
102103
# Check that copy was triggered in interpolate and that we don't
@@ -109,6 +110,31 @@ def test_interpolate_inplace_with_refs(using_copy_on_write, vals):
109110
assert np.shares_memory(arr, get_array(df, "a"))
110111

111112

113+
@pytest.mark.parametrize("func", ["ffill", "bfill"])
114+
@pytest.mark.parametrize("dtype", ["float64", "Float64"])
115+
def test_interp_fill_functions_inplace(
116+
using_copy_on_write, func, warn_copy_on_write, dtype
117+
):
118+
# Check that these takes the same code paths as interpolate
119+
df = DataFrame({"a": [1, np.nan, 2]}, dtype=dtype)
120+
df_orig = df.copy()
121+
arr = get_array(df, "a")
122+
view = df[:]
123+
124+
with tm.assert_cow_warning(warn_copy_on_write and dtype == "float64"):
125+
getattr(df, func)(inplace=True)
126+
127+
if using_copy_on_write:
128+
# Check that copy was triggered in interpolate and that we don't
129+
# have any references left
130+
assert not np.shares_memory(arr, get_array(df, "a"))
131+
tm.assert_frame_equal(df_orig, view)
132+
assert df._mgr._has_no_reference(0)
133+
assert view._mgr._has_no_reference(0)
134+
else:
135+
assert np.shares_memory(arr, get_array(df, "a")) is (dtype == "float64")
136+
137+
112138
def test_interpolate_cleaned_fill_method(using_copy_on_write):
113139
# Check that "method is set to None" case works correctly
114140
df = DataFrame({"a": ["a", np.nan, "c"], "b": 1})

pandas/tests/copy_view/test_methods.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1607,7 +1607,8 @@ def test_interpolate_creates_copy(using_copy_on_write, warn_copy_on_write):
16071607
view = df[:]
16081608
expected = df.copy()
16091609

1610-
df.ffill(inplace=True)
1610+
with tm.assert_cow_warning(warn_copy_on_write):
1611+
df.ffill(inplace=True)
16111612
with tm.assert_cow_warning(warn_copy_on_write):
16121613
df.iloc[0, 0] = 100.5
16131614

0 commit comments

Comments
 (0)