Skip to content

Commit d3f53c0

Browse files
warn in case of chained assignment
1 parent 55b483b commit d3f53c0

File tree

7 files changed

+134
-47
lines changed

7 files changed

+134
-47
lines changed

pandas/core/frame.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from pandas._config import (
4343
get_option,
4444
using_copy_on_write,
45+
warn_copy_on_write,
4546
)
4647
from pandas._config.config import _get_option
4748

@@ -4166,11 +4167,13 @@ def isetitem(self, loc, value) -> None:
41664167
self._iset_item_mgr(loc, arraylike, inplace=False, refs=refs)
41674168

41684169
def __setitem__(self, key, value) -> None:
4169-
if not PYPY and using_copy_on_write():
4170-
if sys.getrefcount(self) <= 3:
4170+
if not PYPY:
4171+
if using_copy_on_write() and sys.getrefcount(self) <= 3:
41714172
warnings.warn(
41724173
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
41734174
)
4175+
elif warn_copy_on_write() and sys.getrefcount(self) <= 3:
4176+
warnings.warn("ChainedAssignmentError", FutureWarning, stacklevel=2)
41744177

41754178
key = com.apply_if_callable(key, self)
41764179

pandas/core/internals/managers.py

+26-7
Original file line numberDiff line numberDiff line change
@@ -1981,7 +1981,9 @@ def get_numeric_data(self, copy: bool = False) -> Self:
19811981
def _can_hold_na(self) -> bool:
19821982
return self._block._can_hold_na
19831983

1984-
def setitem_inplace(self, indexer, value, warn: bool = True) -> None:
1984+
def setitem_inplace(
1985+
self, indexer, value, warn: bool = True, cow_context=None
1986+
) -> None:
19851987
"""
19861988
Set values with indexer.
19871989
@@ -1996,12 +1998,29 @@ def setitem_inplace(self, indexer, value, warn: bool = True) -> None:
19961998
self.blocks = (self._block.copy(),)
19971999
self._cache.clear()
19982000
elif warn and warn_copy_on_write():
1999-
warnings.warn(
2000-
"Setting value on view: behaviour will change in pandas 3.0 "
2001-
"with Copy-on-Write ...",
2002-
FutureWarning,
2003-
stacklevel=find_stack_level(),
2004-
)
2001+
if cow_context == "chained-assignment":
2002+
warnings.warn(
2003+
"ChainedAssignmentError: behaviour will change in pandas 3.0 "
2004+
"with Copy-on-Write ...",
2005+
FutureWarning,
2006+
stacklevel=find_stack_level(),
2007+
)
2008+
else:
2009+
warnings.warn(
2010+
"Setting value on view: behaviour will change in pandas 3.0 "
2011+
"with Copy-on-Write ...",
2012+
FutureWarning,
2013+
stacklevel=find_stack_level(),
2014+
)
2015+
else:
2016+
if warn and warn_copy_on_write():
2017+
if cow_context == "chained-assignment":
2018+
warnings.warn(
2019+
"ChainedAssignmentError: behaviour will change in pandas 3.0 "
2020+
"with Copy-on-Write ...",
2021+
FutureWarning,
2022+
stacklevel=find_stack_level(),
2023+
)
20052024

20062025
super().setitem_inplace(indexer, value)
20072026

pandas/core/series.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
import numpy as np
2828

29-
from pandas._config import using_copy_on_write
29+
from pandas._config import (
30+
using_copy_on_write,
31+
warn_copy_on_write,
32+
)
3033
from pandas._config.config import _get_option
3134

3235
from pandas._libs import (
@@ -1177,11 +1180,14 @@ def _get_value(self, label, takeable: bool = False):
11771180
return self.iloc[loc]
11781181

11791182
def __setitem__(self, key, value) -> None:
1180-
if not PYPY and using_copy_on_write():
1181-
if sys.getrefcount(self) <= 3:
1183+
cow_context = None
1184+
if not PYPY:
1185+
if using_copy_on_write() and sys.getrefcount(self) <= 3:
11821186
warnings.warn(
11831187
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
11841188
)
1189+
elif warn_copy_on_write() and sys.getrefcount(self) <= 3:
1190+
cow_context = "chained-assignment"
11851191

11861192
check_dict_or_set_indexers(key)
11871193
key = com.apply_if_callable(key, self)
@@ -1195,7 +1201,7 @@ def __setitem__(self, key, value) -> None:
11951201
return self._set_values(indexer, value)
11961202

11971203
try:
1198-
self._set_with_engine(key, value)
1204+
self._set_with_engine(key, value, cow_context)
11991205
except KeyError:
12001206
# We have a scalar (or for MultiIndex or object-dtype, scalar-like)
12011207
# key that is not present in self.index.
@@ -1266,11 +1272,11 @@ def __setitem__(self, key, value) -> None:
12661272
if cacher_needs_updating:
12671273
self._maybe_update_cacher(inplace=True)
12681274

1269-
def _set_with_engine(self, key, value) -> None:
1275+
def _set_with_engine(self, key, value, cow_context=None) -> None:
12701276
loc = self.index.get_loc(key)
12711277

12721278
# this is equivalent to self._values[key] = value
1273-
self._mgr.setitem_inplace(loc, value)
1279+
self._mgr.setitem_inplace(loc, value, cow_context=cow_context)
12741280

12751281
def _set_with(self, key, value) -> None:
12761282
# We got here via exception-handling off of InvalidIndexError, so

pandas/tests/indexing/multiindex/test_chaining_and_caching.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import pandas._testing as tm
1313

1414

15-
def test_detect_chained_assignment(using_copy_on_write):
15+
def test_detect_chained_assignment(using_copy_on_write, warn_copy_on_write):
1616
# Inplace ops, originally from:
1717
# https://stackoverflow.com/questions/20508968/series-fillna-in-a-multiindex-dataframe-does-not-fill-is-this-a-bug
1818
a = [12, 23]
@@ -32,6 +32,9 @@ def test_detect_chained_assignment(using_copy_on_write):
3232
if using_copy_on_write:
3333
with tm.raises_chained_assignment_error():
3434
zed["eyes"]["right"].fillna(value=555, inplace=True)
35+
elif warn_copy_on_write:
36+
# TODO(CoW) should warn
37+
zed["eyes"]["right"].fillna(value=555, inplace=True)
3538
else:
3639
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
3740
with pytest.raises(SettingWithCopyError, match=msg):

pandas/tests/indexing/multiindex/test_setitem.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,21 @@ def test_groupby_example(self):
273273
new_vals = np.arange(df2.shape[0])
274274
df.loc[name, "new_col"] = new_vals
275275

276-
def test_series_setitem(self, multiindex_year_month_day_dataframe_random_data):
276+
def test_series_setitem(
277+
self, multiindex_year_month_day_dataframe_random_data, warn_copy_on_write
278+
):
277279
ymd = multiindex_year_month_day_dataframe_random_data
278280
s = ymd["A"]
279281

280-
s[2000, 3] = np.nan
282+
warn = FutureWarning if warn_copy_on_write else None
283+
with tm.assert_produces_warning(warn, match="Setting value on view"):
284+
s[2000, 3] = np.nan
281285
assert isna(s.values[42:65]).all()
282286
assert notna(s.values[:42]).all()
283287
assert notna(s.values[65:]).all()
284288

285-
s[2000, 3, 10] = np.nan
289+
with tm.assert_produces_warning(warn, match="Setting value on view"):
290+
s[2000, 3, 10] = np.nan
286291
assert isna(s.iloc[49])
287292

288293
with pytest.raises(KeyError, match="49"):
@@ -527,28 +532,34 @@ def test_frame_setitem_view_direct(
527532

528533

529534
def test_frame_setitem_copy_raises(
530-
multiindex_dataframe_random_data, using_copy_on_write
535+
multiindex_dataframe_random_data, using_copy_on_write, warn_copy_on_write
531536
):
532537
# will raise/warn as its chained assignment
533538
df = multiindex_dataframe_random_data.T
534539
if using_copy_on_write:
535540
with tm.raises_chained_assignment_error():
536541
df["foo"]["one"] = 2
542+
elif warn_copy_on_write:
543+
with tm.assert_produces_warning(FutureWarning, match="ChainedAssignmentError"):
544+
df["foo"]["one"] = 2
537545
else:
538546
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
539547
with pytest.raises(SettingWithCopyError, match=msg):
540548
df["foo"]["one"] = 2
541549

542550

543551
def test_frame_setitem_copy_no_write(
544-
multiindex_dataframe_random_data, using_copy_on_write
552+
multiindex_dataframe_random_data, using_copy_on_write, warn_copy_on_write
545553
):
546554
frame = multiindex_dataframe_random_data.T
547555
expected = frame
548556
df = frame.copy()
549557
if using_copy_on_write:
550558
with tm.raises_chained_assignment_error():
551559
df["foo"]["one"] = 2
560+
elif warn_copy_on_write:
561+
with tm.assert_produces_warning(FutureWarning, match="ChainedAssignmentError"):
562+
df["foo"]["one"] = 2
552563
else:
553564
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
554565
with pytest.raises(SettingWithCopyError, match=msg):

0 commit comments

Comments
 (0)