Skip to content

Commit 52649ea

Browse files
CoW: Add warning for fillna (#56288)
Co-authored-by: Joris Van den Bossche <[email protected]>
1 parent 593fa85 commit 52649ea

File tree

4 files changed

+30
-9
lines changed

4 files changed

+30
-9
lines changed

pandas/core/internals/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def fillna(self, value, limit: int | None, inplace: bool, downcast) -> Self:
190190
inplace=inplace,
191191
downcast=downcast,
192192
using_cow=using_copy_on_write(),
193+
already_warned=_AlreadyWarned(),
193194
)
194195

195196
@final

pandas/core/internals/blocks.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,7 @@ def fillna(
15911591
inplace: bool = False,
15921592
downcast=None,
15931593
using_cow: bool = False,
1594+
already_warned=None,
15941595
) -> list[Block]:
15951596
"""
15961597
fillna on the block with the value. If we fail, then convert to
@@ -1626,7 +1627,9 @@ def fillna(
16261627
mask[mask.cumsum(self.ndim - 1) > limit] = False
16271628

16281629
if inplace:
1629-
nbs = self.putmask(mask.T, value, using_cow=using_cow)
1630+
nbs = self.putmask(
1631+
mask.T, value, using_cow=using_cow, already_warned=already_warned
1632+
)
16301633
else:
16311634
# without _downcast, we would break
16321635
# test_fillna_dtype_conversion_equiv_replace
@@ -2208,6 +2211,7 @@ def fillna(
22082211
inplace: bool = False,
22092212
downcast=None,
22102213
using_cow: bool = False,
2214+
already_warned=None,
22112215
) -> list[Block]:
22122216
if isinstance(self.dtype, IntervalDtype):
22132217
# Block.fillna handles coercion (test_fillna_interval)
@@ -2217,6 +2221,7 @@ def fillna(
22172221
inplace=inplace,
22182222
downcast=downcast,
22192223
using_cow=using_cow,
2224+
already_warned=already_warned,
22202225
)
22212226
if using_cow and self._can_hold_na and not self.values._hasna:
22222227
refs = self.refs
@@ -2244,6 +2249,20 @@ def fillna(
22442249
DeprecationWarning,
22452250
stacklevel=find_stack_level(),
22462251
)
2252+
else:
2253+
if (
2254+
not copy
2255+
and warn_copy_on_write()
2256+
and already_warned is not None
2257+
and not already_warned.warned_already
2258+
):
2259+
if self.refs.has_reference():
2260+
warnings.warn(
2261+
COW_WARNING_GENERAL_MSG,
2262+
FutureWarning,
2263+
stacklevel=find_stack_level(),
2264+
)
2265+
already_warned.warned_already = True
22472266

22482267
nb = self.make_block_same_class(new_values, refs=refs)
22492268
return nb._maybe_downcast([nb], downcast, using_cow=using_cow, caller="fillna")

pandas/tests/copy_view/test_interp_fillna.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,15 @@ def test_fillna_inplace(using_copy_on_write, downcast):
229229
assert df._mgr._has_no_reference(1)
230230

231231

232-
def test_fillna_inplace_reference(using_copy_on_write):
232+
def test_fillna_inplace_reference(using_copy_on_write, warn_copy_on_write):
233233
df = DataFrame({"a": [1.5, np.nan], "b": 1})
234234
df_orig = df.copy()
235235
arr_a = get_array(df, "a")
236236
arr_b = get_array(df, "b")
237237
view = df[:]
238238

239-
df.fillna(5.5, inplace=True)
239+
with tm.assert_cow_warning(warn_copy_on_write):
240+
df.fillna(5.5, inplace=True)
240241
if using_copy_on_write:
241242
assert not np.shares_memory(get_array(df, "a"), arr_a)
242243
assert np.shares_memory(get_array(df, "b"), arr_b)
@@ -250,7 +251,7 @@ def test_fillna_inplace_reference(using_copy_on_write):
250251
tm.assert_frame_equal(df, expected)
251252

252253

253-
def test_fillna_interval_inplace_reference(using_copy_on_write):
254+
def test_fillna_interval_inplace_reference(using_copy_on_write, warn_copy_on_write):
254255
# Set dtype explicitly to avoid implicit cast when setting nan
255256
ser = Series(
256257
interval_range(start=0, end=5), name="a", dtype="interval[float64, right]"
@@ -259,7 +260,8 @@ def test_fillna_interval_inplace_reference(using_copy_on_write):
259260

260261
ser_orig = ser.copy()
261262
view = ser[:]
262-
ser.fillna(value=Interval(left=0, right=5), inplace=True)
263+
with tm.assert_cow_warning(warn_copy_on_write):
264+
ser.fillna(value=Interval(left=0, right=5), inplace=True)
263265

264266
if using_copy_on_write:
265267
assert not np.shares_memory(
@@ -330,8 +332,8 @@ def test_fillna_inplace_ea_noop_shares_memory(
330332
df = DataFrame({"a": [1, NA, 3], "b": 1}, dtype=any_numeric_ea_and_arrow_dtype)
331333
df_orig = df.copy()
332334
view = df[:]
333-
# TODO(CoW-warn)
334-
df.fillna(100, inplace=True)
335+
with tm.assert_cow_warning(warn_copy_on_write):
336+
df.fillna(100, inplace=True)
335337

336338
if isinstance(df["a"].dtype, ArrowDtype) or using_copy_on_write:
337339
assert not np.shares_memory(get_array(df, "a"), get_array(view, "a"))

pandas/tests/frame/methods/test_fillna.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -745,8 +745,7 @@ def test_inplace_dict_update_view(
745745
df = DataFrame({"x": [np.nan, 2], "y": [np.nan, 2]})
746746
df_orig = df.copy()
747747
result_view = df[:]
748-
# TODO(CoW-warn) better warning message + should warn in all cases
749-
with tm.assert_cow_warning(warn_copy_on_write and not isinstance(val, int)):
748+
with tm.assert_cow_warning(warn_copy_on_write):
750749
df.fillna(val, inplace=True)
751750
expected = DataFrame({"x": [-1, 2.0], "y": [-1.0, 2]})
752751
tm.assert_frame_equal(df, expected)

0 commit comments

Comments
 (0)