Skip to content

Commit ca99c94

Browse files
authored
DEPR: enforce Series.__setitem__ behavior with Float64Index and non-present int key (#49530)
1 parent 7bffe51 commit ca99c94

File tree

4 files changed

+26
-50
lines changed

4 files changed

+26
-50
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ Removal of prior version deprecations/changes
449449
- Changed behavior of comparison of ``NaT`` with a ``datetime.date`` object; these now raise on inequality comparisons (:issue:`39196`)
450450
- Enforced deprecation of silently dropping columns that raised a ``TypeError`` in :class:`Series.transform` and :class:`DataFrame.transform` when used with a list or dictionary (:issue:`43740`)
451451
- Change behavior of :meth:`DataFrame.apply` with list-like so that any partial failure will raise an error (:issue:`43740`)
452+
- Changed behavior of :meth:`Series.__setitem__` with an integer key and a :class:`Float64Index` when the key is not present in the index; previously we treated the key as positional (behaving like ``series.iloc[key] = val``), now we treat it is a label (behaving like ``series.loc[key] = val``), consistent with :meth:`Series.__getitem__`` behavior (:issue:`33469`)
452453
- Removed ``na_sentinel`` argument from :func:`factorize`, :meth:`.Index.factorize`, and :meth:`.ExtensionArray.factorize` (:issue:`47157`)
453454
-
454455

pandas/core/series.py

+6-13
Original file line numberDiff line numberDiff line change
@@ -1073,21 +1073,14 @@ def __setitem__(self, key, value) -> None:
10731073
# We have a scalar (or for MultiIndex or object-dtype, scalar-like)
10741074
# key that is not present in self.index.
10751075
if is_integer(key) and self.index.inferred_type != "integer":
1076-
# positional setter
10771076
if not self.index._should_fallback_to_positional:
10781077
# GH#33469
1079-
warnings.warn(
1080-
"Treating integers as positional in Series.__setitem__ "
1081-
"with a Float64Index is deprecated. In a future version, "
1082-
"`series[an_int] = val` will insert a new key into the "
1083-
"Series. Use `series.iloc[an_int] = val` to treat the "
1084-
"key as positional.",
1085-
FutureWarning,
1086-
stacklevel=find_stack_level(),
1087-
)
1088-
# can't use _mgr.setitem_inplace yet bc could have *both*
1089-
# KeyError and then ValueError, xref GH#45070
1090-
self._set_values(key, value)
1078+
self.loc[key] = value
1079+
else:
1080+
# positional setter
1081+
# can't use _mgr.setitem_inplace yet bc could have *both*
1082+
# KeyError and then ValueError, xref GH#45070
1083+
self._set_values(key, value)
10911084
else:
10921085
# GH#12862 adding a new key to the Series
10931086
self.loc[key] = value

pandas/tests/indexing/test_coercion.py

+4-18
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,9 @@ def _assert_setitem_index_conversion(
9999
):
100100
"""test index's coercion triggered by assign key"""
101101
temp = original_series.copy()
102-
warn = None
103-
if isinstance(loc_key, int) and temp.index.dtype == np.float64:
104-
# GH#33469
105-
warn = FutureWarning
106-
with tm.assert_produces_warning(warn):
107-
temp[loc_key] = 5
102+
# GH#33469 pre-2.0 with int loc_key and temp.index.dtype == np.float64
103+
# `temp[loc_key] = 5` treated loc_key as positional
104+
temp[loc_key] = 5
108105
exp = pd.Series([1, 2, 3, 4, 5], index=expected_index)
109106
tm.assert_series_equal(temp, exp)
110107
# check dtype explicitly for sure
@@ -144,23 +141,12 @@ def test_setitem_index_int64(self, val, exp_dtype):
144141
self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
145142

146143
@pytest.mark.parametrize(
147-
"val,exp_dtype", [(5, IndexError), (5.1, np.float64), ("x", object)]
144+
"val,exp_dtype", [(5, np.float64), (5.1, np.float64), ("x", object)]
148145
)
149146
def test_setitem_index_float64(self, val, exp_dtype, request):
150147
obj = pd.Series([1, 2, 3, 4], index=[1.1, 2.1, 3.1, 4.1])
151148
assert obj.index.dtype == np.float64
152149

153-
if exp_dtype is IndexError:
154-
# float + int -> int
155-
temp = obj.copy()
156-
msg = "index 5 is out of bounds for axis 0 with size 4"
157-
with pytest.raises(exp_dtype, match=msg):
158-
# GH#33469
159-
depr_msg = "Treating integers as positional"
160-
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
161-
temp[5] = 5
162-
mark = pytest.mark.xfail(reason="TODO_GH12747 The result must be float")
163-
request.node.add_marker(mark)
164150
exp_index = pd.Index([1.1, 2.1, 3.1, 4.1, val])
165151
self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
166152

pandas/tests/series/indexing/test_setitem.py

+15-19
Original file line numberDiff line numberDiff line change
@@ -1536,41 +1536,37 @@ def test_setitem_positional_float_into_int_coerces():
15361536
tm.assert_series_equal(ser, expected)
15371537

15381538

1539-
def test_setitem_int_as_positional_fallback_deprecation():
1539+
def test_setitem_int_not_positional():
15401540
# GH#42215 deprecated falling back to positional on __setitem__ with an
1541-
# int not contained in the index
1541+
# int not contained in the index; enforced in 2.0
15421542
ser = Series([1, 2, 3, 4], index=[1.1, 2.1, 3.0, 4.1])
15431543
assert not ser.index._should_fallback_to_positional
15441544
# assert not ser.index.astype(object)._should_fallback_to_positional
15451545

1546-
with tm.assert_produces_warning(None):
1547-
# 3.0 is in our index, so future behavior is unchanged
1548-
ser[3] = 10
1546+
# 3.0 is in our index, so post-enforcement behavior is unchanged
1547+
ser[3] = 10
15491548
expected = Series([1, 2, 10, 4], index=ser.index)
15501549
tm.assert_series_equal(ser, expected)
15511550

1552-
msg = "Treating integers as positional in Series.__setitem__"
1553-
with tm.assert_produces_warning(FutureWarning, match=msg):
1554-
with pytest.raises(IndexError, match="index 5 is out of bounds"):
1555-
ser[5] = 5
1556-
# Once the deprecation is enforced, we will have
1557-
# expected = Series([1, 2, 3, 4, 5], index=[1.1, 2.1, 3.0, 4.1, 5.0])
1551+
# pre-enforcement `ser[5] = 5` raised IndexError
1552+
ser[5] = 5
1553+
expected = Series([1, 2, 10, 4, 5], index=[1.1, 2.1, 3.0, 4.1, 5.0])
1554+
tm.assert_series_equal(ser, expected)
15581555

15591556
ii = IntervalIndex.from_breaks(range(10))[::2]
15601557
ser2 = Series(range(len(ii)), index=ii)
1561-
expected2 = ser2.copy()
1562-
expected2.iloc[-1] = 9
1563-
with tm.assert_produces_warning(FutureWarning, match=msg):
1564-
ser2[4] = 9
1558+
exp_index = ii.astype(object).append(Index([4]))
1559+
expected2 = Series([0, 1, 2, 3, 4, 9], index=exp_index)
1560+
# pre-enforcement `ser2[4] = 9` interpreted 4 as positional
1561+
ser2[4] = 9
15651562
tm.assert_series_equal(ser2, expected2)
15661563

15671564
mi = MultiIndex.from_product([ser.index, ["A", "B"]])
15681565
ser3 = Series(range(len(mi)), index=mi)
15691566
expected3 = ser3.copy()
1570-
expected3.iloc[4] = 99
1571-
1572-
with tm.assert_produces_warning(FutureWarning, match=msg):
1573-
ser3[4] = 99
1567+
expected3.loc[4] = 99
1568+
# pre-enforcement `ser3[4] = 99` interpreted 4 as positional
1569+
ser3[4] = 99
15741570
tm.assert_series_equal(ser3, expected3)
15751571

15761572

0 commit comments

Comments
 (0)