Skip to content

Commit 6090042

Browse files
authored
CLEAN: Enforce pdep6 (#59007)
* enforce pdep6 * fixup Block.time_test benchmark * update comment * update warn to raise * add missing assertion * simplify * remove default value for `raise_on_upcast` * add whatsnew
1 parent a93e2e2 commit 6090042

29 files changed

+383
-655
lines changed

asv_bench/benchmarks/indexing.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -546,24 +546,17 @@ def time_chained_indexing(self, mode):
546546

547547

548548
class Block:
549-
params = [
550-
(True, "True"),
551-
(np.array(True), "np.array(True)"),
552-
]
553-
554-
def setup(self, true_value, mode):
549+
def setup(self):
555550
self.df = DataFrame(
556551
False,
557552
columns=np.arange(500).astype(str),
558553
index=date_range("2010-01-01", "2011-01-01"),
559554
)
560555

561-
self.true_value = true_value
562-
563-
def time_test(self, true_value, mode):
556+
def time_test(self):
564557
start = datetime(2010, 5, 1)
565558
end = datetime(2010, 9, 1)
566-
self.df.loc[start:end, :] = true_value
559+
self.df.loc[start:end, :] = True
567560

568561

569562
from .pandas_vb_common import setup # noqa: F401 isort:skip

doc/source/user_guide/categorical.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ Assigning a ``Categorical`` to parts of a column of other types will use the val
793793
:okwarning:
794794
795795
df = pd.DataFrame({"a": [1, 1, 1, 1, 1], "b": ["a", "a", "a", "a", "a"]})
796-
df.loc[1:2, "a"] = pd.Categorical(["b", "b"], categories=["a", "b"])
796+
df.loc[1:2, "a"] = pd.Categorical([2, 2], categories=[2, 3])
797797
df.loc[2:3, "b"] = pd.Categorical(["b", "b"], categories=["a", "b"])
798798
df
799799
df.dtypes

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ Other Removals
360360
- Changed the default value of ``na_action`` in :meth:`Categorical.map` to ``None`` (:issue:`51645`)
361361
- Changed the default value of ``observed`` in :meth:`DataFrame.groupby` and :meth:`Series.groupby` to ``True`` (:issue:`51811`)
362362
- Enforce deprecation in :func:`testing.assert_series_equal` and :func:`testing.assert_frame_equal` with object dtype and mismatched null-like values, which are now considered not-equal (:issue:`18463`)
363+
- Enforce banning of upcasting in in-place setitem-like operations (:issue:`59007`) (see `PDEP6 <https://pandas.pydata.org/pdeps/0006-ban-upcasting.html>`_)
363364
- Enforced deprecation ``all`` and ``any`` reductions with ``datetime64``, :class:`DatetimeTZDtype`, and :class:`PeriodDtype` dtypes (:issue:`58029`)
364365
- Enforced deprecation disallowing ``float`` "periods" in :func:`date_range`, :func:`period_range`, :func:`timedelta_range`, :func:`interval_range`, (:issue:`56036`)
365366
- Enforced deprecation disallowing parsing datetimes with mixed time zones unless user passes ``utc=True`` to :func:`to_datetime` (:issue:`57275`)

pandas/core/indexing.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
)
2626
from pandas.errors.cow import _chained_assignment_msg
2727
from pandas.util._decorators import doc
28-
from pandas.util._exceptions import find_stack_level
2928

3029
from pandas.core.dtypes.cast import (
3130
can_hold_element,
@@ -2124,14 +2123,14 @@ def _setitem_single_column(self, loc: int, value, plane_indexer) -> None:
21242123
self.obj._mgr.column_setitem(
21252124
loc, plane_indexer, value, inplace_only=True
21262125
)
2127-
except (ValueError, TypeError, LossySetitemError):
2126+
except (ValueError, TypeError, LossySetitemError) as exc:
21282127
# If we're setting an entire column and we can't do it inplace,
21292128
# then we can use value's dtype (or inferred dtype)
21302129
# instead of object
21312130
dtype = self.obj.dtypes.iloc[loc]
21322131
if dtype not in (np.void, object) and not self.obj.empty:
21332132
# - Exclude np.void, as that is a special case for expansion.
2134-
# We want to warn for
2133+
# We want to raise for
21352134
# df = pd.DataFrame({'a': [1, 2]})
21362135
# df.loc[:, 'a'] = .3
21372136
# but not for
@@ -2140,14 +2139,9 @@ def _setitem_single_column(self, loc: int, value, plane_indexer) -> None:
21402139
# - Exclude `object`, as then no upcasting happens.
21412140
# - Exclude empty initial object with enlargement,
21422141
# as then there's nothing to be inconsistent with.
2143-
warnings.warn(
2144-
f"Setting an item of incompatible dtype is deprecated "
2145-
"and will raise in a future error of pandas. "
2146-
f"Value '{value}' has dtype incompatible with {dtype}, "
2147-
"please explicitly cast to a compatible dtype first.",
2148-
FutureWarning,
2149-
stacklevel=find_stack_level(),
2150-
)
2142+
raise TypeError(
2143+
f"Invalid value '{value}' for dtype '{dtype}'"
2144+
) from exc
21512145
self.obj.isetitem(loc, value)
21522146
else:
21532147
# set value into the column (first attempting to operate inplace, then

pandas/core/internals/blocks.py

+17-24
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ def split_and_operate(self, func, *args, **kwargs) -> list[Block]:
428428
# Up/Down-casting
429429

430430
@final
431-
def coerce_to_target_dtype(self, other, warn_on_upcast: bool = False) -> Block:
431+
def coerce_to_target_dtype(self, other, raise_on_upcast: bool) -> Block:
432432
"""
433433
coerce the current block to a dtype compat for other
434434
we will return a block, possibly object, and not raise
@@ -455,25 +455,18 @@ def coerce_to_target_dtype(self, other, warn_on_upcast: bool = False) -> Block:
455455
isinstance(other, (np.datetime64, np.timedelta64)) and np.isnat(other)
456456
)
457457
):
458-
warn_on_upcast = False
458+
raise_on_upcast = False
459459
elif (
460460
isinstance(other, np.ndarray)
461461
and other.ndim == 1
462462
and is_integer_dtype(self.values.dtype)
463463
and is_float_dtype(other.dtype)
464464
and lib.has_only_ints_or_nan(other)
465465
):
466-
warn_on_upcast = False
467-
468-
if warn_on_upcast:
469-
warnings.warn(
470-
f"Setting an item of incompatible dtype is deprecated "
471-
"and will raise an error in a future version of pandas. "
472-
f"Value '{other}' has dtype incompatible with {self.values.dtype}, "
473-
"please explicitly cast to a compatible dtype first.",
474-
FutureWarning,
475-
stacklevel=find_stack_level(),
476-
)
466+
raise_on_upcast = False
467+
468+
if raise_on_upcast:
469+
raise TypeError(f"Invalid value '{other}' for dtype '{self.values.dtype}'")
477470
if self.values.dtype == new_dtype:
478471
raise AssertionError(
479472
f"Did not expect new dtype {new_dtype} to equal self.dtype "
@@ -720,7 +713,7 @@ def replace(
720713
if value is None or value is NA:
721714
blk = self.astype(np.dtype(object))
722715
else:
723-
blk = self.coerce_to_target_dtype(value)
716+
blk = self.coerce_to_target_dtype(value, raise_on_upcast=False)
724717
return blk.replace(
725718
to_replace=to_replace,
726719
value=value,
@@ -1105,7 +1098,7 @@ def setitem(self, indexer, value) -> Block:
11051098
casted = np_can_hold_element(values.dtype, value)
11061099
except LossySetitemError:
11071100
# current dtype cannot store value, coerce to common dtype
1108-
nb = self.coerce_to_target_dtype(value, warn_on_upcast=True)
1101+
nb = self.coerce_to_target_dtype(value, raise_on_upcast=True)
11091102
return nb.setitem(indexer, value)
11101103
else:
11111104
if self.dtype == _dtype_obj:
@@ -1176,7 +1169,7 @@ def putmask(self, mask, new) -> list[Block]:
11761169
if not is_list_like(new):
11771170
# using just new[indexer] can't save us the need to cast
11781171
return self.coerce_to_target_dtype(
1179-
new, warn_on_upcast=True
1172+
new, raise_on_upcast=True
11801173
).putmask(mask, new)
11811174
else:
11821175
indexer = mask.nonzero()[0]
@@ -1244,7 +1237,7 @@ def where(self, other, cond) -> list[Block]:
12441237
if self.ndim == 1 or self.shape[0] == 1:
12451238
# no need to split columns
12461239

1247-
block = self.coerce_to_target_dtype(other)
1240+
block = self.coerce_to_target_dtype(other, raise_on_upcast=False)
12481241
return block.where(orig_other, cond)
12491242

12501243
else:
@@ -1438,7 +1431,7 @@ def shift(self, periods: int, fill_value: Any = None) -> list[Block]:
14381431
fill_value,
14391432
)
14401433
except LossySetitemError:
1441-
nb = self.coerce_to_target_dtype(fill_value)
1434+
nb = self.coerce_to_target_dtype(fill_value, raise_on_upcast=False)
14421435
return nb.shift(periods, fill_value=fill_value)
14431436

14441437
else:
@@ -1637,11 +1630,11 @@ def setitem(self, indexer, value):
16371630
except (ValueError, TypeError):
16381631
if isinstance(self.dtype, IntervalDtype):
16391632
# see TestSetitemFloatIntervalWithIntIntervalValues
1640-
nb = self.coerce_to_target_dtype(orig_value, warn_on_upcast=True)
1633+
nb = self.coerce_to_target_dtype(orig_value, raise_on_upcast=True)
16411634
return nb.setitem(orig_indexer, orig_value)
16421635

16431636
elif isinstance(self, NDArrayBackedExtensionBlock):
1644-
nb = self.coerce_to_target_dtype(orig_value, warn_on_upcast=True)
1637+
nb = self.coerce_to_target_dtype(orig_value, raise_on_upcast=True)
16451638
return nb.setitem(orig_indexer, orig_value)
16461639

16471640
else:
@@ -1676,13 +1669,13 @@ def where(self, other, cond) -> list[Block]:
16761669
if self.ndim == 1 or self.shape[0] == 1:
16771670
if isinstance(self.dtype, IntervalDtype):
16781671
# TestSetitemFloatIntervalWithIntIntervalValues
1679-
blk = self.coerce_to_target_dtype(orig_other)
1672+
blk = self.coerce_to_target_dtype(orig_other, raise_on_upcast=False)
16801673
return blk.where(orig_other, orig_cond)
16811674

16821675
elif isinstance(self, NDArrayBackedExtensionBlock):
16831676
# NB: not (yet) the same as
16841677
# isinstance(values, NDArrayBackedExtensionArray)
1685-
blk = self.coerce_to_target_dtype(orig_other)
1678+
blk = self.coerce_to_target_dtype(orig_other, raise_on_upcast=False)
16861679
return blk.where(orig_other, orig_cond)
16871680

16881681
else:
@@ -1737,13 +1730,13 @@ def putmask(self, mask, new) -> list[Block]:
17371730
if isinstance(self.dtype, IntervalDtype):
17381731
# Discussion about what we want to support in the general
17391732
# case GH#39584
1740-
blk = self.coerce_to_target_dtype(orig_new, warn_on_upcast=True)
1733+
blk = self.coerce_to_target_dtype(orig_new, raise_on_upcast=True)
17411734
return blk.putmask(orig_mask, orig_new)
17421735

17431736
elif isinstance(self, NDArrayBackedExtensionBlock):
17441737
# NB: not (yet) the same as
17451738
# isinstance(values, NDArrayBackedExtensionArray)
1746-
blk = self.coerce_to_target_dtype(orig_new, warn_on_upcast=True)
1739+
blk = self.coerce_to_target_dtype(orig_new, raise_on_upcast=True)
17471740
return blk.putmask(orig_mask, orig_new)
17481741

17491742
else:

pandas/tests/copy_view/test_indexing.py

+11-15
Original file line numberDiff line numberDiff line change
@@ -725,15 +725,13 @@ def test_column_as_series_set_with_upcast(backend):
725725
with pytest.raises(TypeError, match="Invalid value"):
726726
s[0] = "foo"
727727
expected = Series([1, 2, 3], name="a")
728+
tm.assert_series_equal(s, expected)
729+
tm.assert_frame_equal(df, df_orig)
730+
# ensure cached series on getitem is not the changed series
731+
tm.assert_series_equal(df["a"], df_orig["a"])
728732
else:
729-
with tm.assert_produces_warning(FutureWarning, match="incompatible dtype"):
733+
with pytest.raises(TypeError, match="Invalid value"):
730734
s[0] = "foo"
731-
expected = Series(["foo", 2, 3], dtype=object, name="a")
732-
733-
tm.assert_series_equal(s, expected)
734-
tm.assert_frame_equal(df, df_orig)
735-
# ensure cached series on getitem is not the changed series
736-
tm.assert_series_equal(df["a"], df_orig["a"])
737735

738736

739737
@pytest.mark.parametrize(
@@ -805,16 +803,14 @@ def test_set_value_copy_only_necessary_column(indexer_func, indexer, val, col):
805803
view = df[:]
806804

807805
if val == "a":
808-
with tm.assert_produces_warning(
809-
FutureWarning, match="Setting an item of incompatible dtype is deprecated"
810-
):
806+
with pytest.raises(TypeError, match="Invalid value"):
811807
indexer_func(df)[indexer] = val
808+
else:
809+
indexer_func(df)[indexer] = val
812810

813-
indexer_func(df)[indexer] = val
814-
815-
assert np.shares_memory(get_array(df, "b"), get_array(view, "b"))
816-
assert not np.shares_memory(get_array(df, "a"), get_array(view, "a"))
817-
tm.assert_frame_equal(view, df_orig)
811+
assert np.shares_memory(get_array(df, "b"), get_array(view, "b"))
812+
assert not np.shares_memory(get_array(df, "a"), get_array(view, "a"))
813+
tm.assert_frame_equal(view, df_orig)
818814

819815

820816
def test_series_midx_slice():

pandas/tests/copy_view/test_methods.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -1105,26 +1105,26 @@ def test_putmask_aligns_rhs_no_reference(dtype):
11051105
assert np.shares_memory(arr_a, get_array(df, "a"))
11061106

11071107

1108-
@pytest.mark.parametrize(
1109-
"val, exp, warn", [(5.5, True, FutureWarning), (5, False, None)]
1110-
)
1111-
def test_putmask_dont_copy_some_blocks(val, exp, warn):
1108+
@pytest.mark.parametrize("val, exp, raises", [(5.5, True, True), (5, False, False)])
1109+
def test_putmask_dont_copy_some_blocks(val, exp, raises: bool):
11121110
df = DataFrame({"a": [1, 2], "b": 1, "c": 1.5})
11131111
view = df[:]
11141112
df_orig = df.copy()
11151113
indexer = DataFrame(
11161114
[[True, False, False], [True, False, False]], columns=list("abc")
11171115
)
1118-
with tm.assert_produces_warning(warn, match="incompatible dtype"):
1116+
if raises:
1117+
with pytest.raises(TypeError, match="Invalid value"):
1118+
df[indexer] = val
1119+
else:
11191120
df[indexer] = val
1120-
1121-
assert not np.shares_memory(get_array(view, "a"), get_array(df, "a"))
1122-
# TODO(CoW): Could split blocks to avoid copying the whole block
1123-
assert np.shares_memory(get_array(view, "b"), get_array(df, "b")) is exp
1124-
assert np.shares_memory(get_array(view, "c"), get_array(df, "c"))
1125-
assert df._mgr._has_no_reference(1) is not exp
1126-
assert not df._mgr._has_no_reference(2)
1127-
tm.assert_frame_equal(view, df_orig)
1121+
assert not np.shares_memory(get_array(view, "a"), get_array(df, "a"))
1122+
# TODO(CoW): Could split blocks to avoid copying the whole block
1123+
assert np.shares_memory(get_array(view, "b"), get_array(df, "b")) is exp
1124+
assert np.shares_memory(get_array(view, "c"), get_array(df, "c"))
1125+
assert df._mgr._has_no_reference(1) is not exp
1126+
assert not df._mgr._has_no_reference(2)
1127+
tm.assert_frame_equal(view, df_orig)
11281128

11291129

11301130
@pytest.mark.parametrize("dtype", ["int64", "Int64"])

pandas/tests/frame/indexing/test_coercion.py

+6-30
Original file line numberDiff line numberDiff line change
@@ -49,35 +49,19 @@ def test_loc_setitem_multiindex_columns(self, consolidate):
4949
def test_37477():
5050
# fixed by GH#45121
5151
orig = DataFrame({"A": [1, 2, 3], "B": [3, 4, 5]})
52-
expected = DataFrame({"A": [1, 2, 3], "B": [3, 1.2, 5]})
5352

5453
df = orig.copy()
55-
with tm.assert_produces_warning(
56-
FutureWarning, match="Setting an item of incompatible dtype"
57-
):
54+
with pytest.raises(TypeError, match="Invalid value"):
5855
df.at[1, "B"] = 1.2
59-
tm.assert_frame_equal(df, expected)
6056

61-
df = orig.copy()
62-
with tm.assert_produces_warning(
63-
FutureWarning, match="Setting an item of incompatible dtype"
64-
):
57+
with pytest.raises(TypeError, match="Invalid value"):
6558
df.loc[1, "B"] = 1.2
66-
tm.assert_frame_equal(df, expected)
6759

68-
df = orig.copy()
69-
with tm.assert_produces_warning(
70-
FutureWarning, match="Setting an item of incompatible dtype"
71-
):
60+
with pytest.raises(TypeError, match="Invalid value"):
7261
df.iat[1, 1] = 1.2
73-
tm.assert_frame_equal(df, expected)
7462

75-
df = orig.copy()
76-
with tm.assert_produces_warning(
77-
FutureWarning, match="Setting an item of incompatible dtype"
78-
):
63+
with pytest.raises(TypeError, match="Invalid value"):
7964
df.iloc[1, 1] = 1.2
80-
tm.assert_frame_equal(df, expected)
8165

8266

8367
def test_6942(indexer_al):
@@ -107,19 +91,11 @@ def test_26395(indexer_al):
10791
expected = DataFrame({"D": [0, 0, 2]}, index=["A", "B", "C"], dtype=np.int64)
10892
tm.assert_frame_equal(df, expected)
10993

110-
with tm.assert_produces_warning(
111-
FutureWarning, match="Setting an item of incompatible dtype"
112-
):
94+
with pytest.raises(TypeError, match="Invalid value"):
11395
indexer_al(df)["C", "D"] = 44.5
114-
expected = DataFrame({"D": [0, 0, 44.5]}, index=["A", "B", "C"], dtype=np.float64)
115-
tm.assert_frame_equal(df, expected)
11696

117-
with tm.assert_produces_warning(
118-
FutureWarning, match="Setting an item of incompatible dtype"
119-
):
97+
with pytest.raises(TypeError, match="Invalid value"):
12098
indexer_al(df)["C", "D"] = "hello"
121-
expected = DataFrame({"D": [0, 0, "hello"]}, index=["A", "B", "C"], dtype=object)
122-
tm.assert_frame_equal(df, expected)
12399

124100

125101
@pytest.mark.xfail(reason="unwanted upcast")

0 commit comments

Comments
 (0)