Skip to content

Commit b84e295

Browse files
jbrockmendelpmhatre1
authored andcommitted
DEPR: Series setitem/getitem treating ints as positional (pandas-dev#58089)
* DEPR: Series setitem/getitem treating ints as positional * 32bit build compat * update exception message for numpy 2
1 parent 593a815 commit b84e295

File tree

11 files changed

+80
-218
lines changed

11 files changed

+80
-218
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ Removal of prior version deprecations/changes
221221
- :meth:`SeriesGroupBy.agg` no longer pins the name of the group to the input passed to the provided ``func`` (:issue:`51703`)
222222
- All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`)
223223
- All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`)
224+
- Changed behavior of :meth:`Series.__getitem__` and :meth:`Series.__setitem__` to always treat integer keys as labels, never as positional, consistent with :class:`DataFrame` behavior (:issue:`50617`)
224225
- Disallow allowing logical operations (``||``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``); wrap the objects in :class:`Series`, :class:`Index`, or ``np.array`` first instead (:issue:`52264`)
225226
- Disallow automatic casting to object in :class:`Series` logical operations (``&``, ``^``, ``||``) between series with mismatched indexes and dtypes other than ``object`` or ``bool`` (:issue:`52538`)
226227
- Disallow calling :meth:`Series.replace` or :meth:`DataFrame.replace` without a ``value`` and with non-dict-like ``to_replace`` (:issue:`33302`)

pandas/core/series.py

+5-84
Original file line numberDiff line numberDiff line change
@@ -901,19 +901,9 @@ def __getitem__(self, key):
901901
if isinstance(key, (list, tuple)):
902902
key = unpack_1tuple(key)
903903

904-
if is_integer(key) and self.index._should_fallback_to_positional:
905-
warnings.warn(
906-
# GH#50617
907-
"Series.__getitem__ treating keys as positions is deprecated. "
908-
"In a future version, integer keys will always be treated "
909-
"as labels (consistent with DataFrame behavior). To access "
910-
"a value by position, use `ser.iloc[pos]`",
911-
FutureWarning,
912-
stacklevel=find_stack_level(),
913-
)
914-
return self._values[key]
915-
916904
elif key_is_scalar:
905+
# Note: GH#50617 in 3.0 we changed int key to always be treated as
906+
# a label, matching DataFrame behavior.
917907
return self._get_value(key)
918908

919909
# Convert generator to list before going through hashable part
@@ -958,35 +948,6 @@ def _get_with(self, key):
958948
elif isinstance(key, tuple):
959949
return self._get_values_tuple(key)
960950

961-
elif not is_list_like(key):
962-
# e.g. scalars that aren't recognized by lib.is_scalar, GH#32684
963-
return self.loc[key]
964-
965-
if not isinstance(key, (list, np.ndarray, ExtensionArray, Series, Index)):
966-
key = list(key)
967-
968-
key_type = lib.infer_dtype(key, skipna=False)
969-
970-
# Note: The key_type == "boolean" case should be caught by the
971-
# com.is_bool_indexer check in __getitem__
972-
if key_type == "integer":
973-
# We need to decide whether to treat this as a positional indexer
974-
# (i.e. self.iloc) or label-based (i.e. self.loc)
975-
if not self.index._should_fallback_to_positional:
976-
return self.loc[key]
977-
else:
978-
warnings.warn(
979-
# GH#50617
980-
"Series.__getitem__ treating keys as positions is deprecated. "
981-
"In a future version, integer keys will always be treated "
982-
"as labels (consistent with DataFrame behavior). To access "
983-
"a value by position, use `ser.iloc[pos]`",
984-
FutureWarning,
985-
stacklevel=find_stack_level(),
986-
)
987-
return self.iloc[key]
988-
989-
# handle the dup indexing case GH#4246
990951
return self.loc[key]
991952

992953
def _get_values_tuple(self, key: tuple):
@@ -1076,27 +1037,8 @@ def __setitem__(self, key, value) -> None:
10761037
except KeyError:
10771038
# We have a scalar (or for MultiIndex or object-dtype, scalar-like)
10781039
# key that is not present in self.index.
1079-
if is_integer(key):
1080-
if not self.index._should_fallback_to_positional:
1081-
# GH#33469
1082-
self.loc[key] = value
1083-
else:
1084-
# positional setter
1085-
# can't use _mgr.setitem_inplace yet bc could have *both*
1086-
# KeyError and then ValueError, xref GH#45070
1087-
warnings.warn(
1088-
# GH#50617
1089-
"Series.__setitem__ treating keys as positions is deprecated. "
1090-
"In a future version, integer keys will always be treated "
1091-
"as labels (consistent with DataFrame behavior). To set "
1092-
"a value by position, use `ser.iloc[pos] = value`",
1093-
FutureWarning,
1094-
stacklevel=find_stack_level(),
1095-
)
1096-
self._set_values(key, value)
1097-
else:
1098-
# GH#12862 adding a new key to the Series
1099-
self.loc[key] = value
1040+
# GH#12862 adding a new key to the Series
1041+
self.loc[key] = value
11001042

11011043
except (TypeError, ValueError, LossySetitemError):
11021044
# The key was OK, but we cannot set the value losslessly
@@ -1155,28 +1097,7 @@ def _set_with(self, key, value) -> None:
11551097
# Without this, the call to infer_dtype will consume the generator
11561098
key = list(key)
11571099

1158-
if not self.index._should_fallback_to_positional:
1159-
# Regardless of the key type, we're treating it as labels
1160-
self._set_labels(key, value)
1161-
1162-
else:
1163-
# Note: key_type == "boolean" should not occur because that
1164-
# should be caught by the is_bool_indexer check in __setitem__
1165-
key_type = lib.infer_dtype(key, skipna=False)
1166-
1167-
if key_type == "integer":
1168-
warnings.warn(
1169-
# GH#50617
1170-
"Series.__setitem__ treating keys as positions is deprecated. "
1171-
"In a future version, integer keys will always be treated "
1172-
"as labels (consistent with DataFrame behavior). To set "
1173-
"a value by position, use `ser.iloc[pos] = value`",
1174-
FutureWarning,
1175-
stacklevel=find_stack_level(),
1176-
)
1177-
self._set_values(key, value)
1178-
else:
1179-
self._set_labels(key, value)
1100+
self._set_labels(key, value)
11801101

11811102
def _set_labels(self, key, value) -> None:
11821103
key = com.asarray_tuplesafe(key)

pandas/tests/copy_view/test_indexing.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -622,16 +622,17 @@ def test_series_subset_set_with_indexer(backend, indexer_si, indexer):
622622
s_orig = s.copy()
623623
subset = s[:]
624624

625-
warn = None
626-
msg = "Series.__setitem__ treating keys as positions is deprecated"
627625
if (
628626
indexer_si is tm.setitem
629627
and isinstance(indexer, np.ndarray)
630628
and indexer.dtype.kind == "i"
631629
):
632-
warn = FutureWarning
633-
with tm.assert_produces_warning(warn, match=msg):
634-
indexer_si(subset)[indexer] = 0
630+
# In 3.0 we treat integers as always-labels
631+
with pytest.raises(KeyError):
632+
indexer_si(subset)[indexer] = 0
633+
return
634+
635+
indexer_si(subset)[indexer] = 0
635636
expected = Series([0, 0, 3], index=["a", "b", "c"])
636637
tm.assert_series_equal(subset, expected)
637638

pandas/tests/extension/base/getitem.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,10 @@ def test_get(self, data):
329329
result = s.get("Z")
330330
assert result is None
331331

332-
msg = "Series.__getitem__ treating keys as positions is deprecated"
333-
with tm.assert_produces_warning(FutureWarning, match=msg):
334-
assert s.get(4) == s.iloc[4]
335-
assert s.get(-1) == s.iloc[-1]
336-
assert s.get(len(s)) is None
332+
# As of 3.0, getitem with int keys treats them as labels
333+
assert s.get(4) is None
334+
assert s.get(-1) is None
335+
assert s.get(len(s)) is None
337336

338337
# GH 21257
339338
s = pd.Series(data)

pandas/tests/indexing/test_coercion.py

+2-10
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,8 @@ def test_setitem_index_object(self, val, exp_dtype):
117117
obj = pd.Series([1, 2, 3, 4], index=pd.Index(list("abcd"), dtype=object))
118118
assert obj.index.dtype == object
119119

120-
if exp_dtype is IndexError:
121-
temp = obj.copy()
122-
warn_msg = "Series.__setitem__ treating keys as positions is deprecated"
123-
msg = "index 5 is out of bounds for axis 0 with size 4"
124-
with pytest.raises(exp_dtype, match=msg):
125-
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
126-
temp[5] = 5
127-
else:
128-
exp_index = pd.Index(list("abcd") + [val], dtype=object)
129-
self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
120+
exp_index = pd.Index(list("abcd") + [val], dtype=object)
121+
self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
130122

131123
@pytest.mark.parametrize(
132124
"val,exp_dtype", [(5, np.int64), (1.1, np.float64), ("x", object)]

pandas/tests/indexing/test_floats.py

+6-9
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ def test_scalar_non_numeric(self, index, frame_or_series, indexer_sl):
8787
],
8888
)
8989
def test_scalar_non_numeric_series_fallback(self, index):
90-
# fallsback to position selection, series only
90+
# starting in 3.0, integer keys are always treated as labels, no longer
91+
# fall back to positional.
9192
s = Series(np.arange(len(index)), index=index)
9293

93-
msg = "Series.__getitem__ treating keys as positions is deprecated"
94-
with tm.assert_produces_warning(FutureWarning, match=msg):
94+
with pytest.raises(KeyError, match="3"):
9595
s[3]
9696
with pytest.raises(KeyError, match="^3.0$"):
9797
s[3.0]
@@ -118,12 +118,9 @@ def test_scalar_with_mixed(self, indexer_sl):
118118
indexer_sl(s3)[1.0]
119119

120120
if indexer_sl is not tm.loc:
121-
# __getitem__ falls back to positional
122-
msg = "Series.__getitem__ treating keys as positions is deprecated"
123-
with tm.assert_produces_warning(FutureWarning, match=msg):
124-
result = s3[1]
125-
expected = 2
126-
assert result == expected
121+
# as of 3.0, __getitem__ no longer falls back to positional
122+
with pytest.raises(KeyError, match="^1$"):
123+
s3[1]
127124

128125
with pytest.raises(KeyError, match=r"^1\.0$"):
129126
indexer_sl(s3)[1.0]

pandas/tests/series/indexing/test_datetime.py

-7
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ def test_fancy_getitem():
3636

3737
s = Series(np.arange(len(dti)), index=dti)
3838

39-
msg = "Series.__getitem__ treating keys as positions is deprecated"
40-
with tm.assert_produces_warning(FutureWarning, match=msg):
41-
assert s[48] == 48
4239
assert s["1/2/2009"] == 48
4340
assert s["2009-1-2"] == 48
4441
assert s[datetime(2009, 1, 2)] == 48
@@ -57,10 +54,6 @@ def test_fancy_setitem():
5754

5855
s = Series(np.arange(len(dti)), index=dti)
5956

60-
msg = "Series.__setitem__ treating keys as positions is deprecated"
61-
with tm.assert_produces_warning(FutureWarning, match=msg):
62-
s[48] = -1
63-
assert s.iloc[48] == -1
6457
s["1/2/2009"] = -2
6558
assert s.iloc[48] == -2
6659
s["1/2/2009":"2009-06-05"] = -3

pandas/tests/series/indexing/test_get.py

+8-18
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,8 @@ def test_get_with_default():
157157
assert s.get("e", "z") == "z"
158158
assert s.get("e", "e") == "e"
159159

160-
msg = "Series.__getitem__ treating keys as positions is deprecated"
161-
warn = None
162-
if index is d0:
163-
warn = FutureWarning
164-
with tm.assert_produces_warning(warn, match=msg):
165-
assert s.get(10, "z") == "z"
166-
assert s.get(10, 10) == 10
160+
assert s.get(10, "z") == "z"
161+
assert s.get(10, 10) == 10
167162

168163

169164
@pytest.mark.parametrize(
@@ -201,13 +196,10 @@ def test_get_with_ea(arr):
201196
result = ser.get("Z")
202197
assert result is None
203198

204-
msg = "Series.__getitem__ treating keys as positions is deprecated"
205-
with tm.assert_produces_warning(FutureWarning, match=msg):
206-
assert ser.get(4) == ser.iloc[4]
207-
with tm.assert_produces_warning(FutureWarning, match=msg):
208-
assert ser.get(-1) == ser.iloc[-1]
209-
with tm.assert_produces_warning(FutureWarning, match=msg):
210-
assert ser.get(len(ser)) is None
199+
# As of 3.0, ints are treated as labels
200+
assert ser.get(4) is None
201+
assert ser.get(-1) is None
202+
assert ser.get(len(ser)) is None
211203

212204
# GH#21257
213205
ser = Series(arr)
@@ -216,16 +208,14 @@ def test_get_with_ea(arr):
216208

217209

218210
def test_getitem_get(string_series, object_series):
219-
msg = "Series.__getitem__ treating keys as positions is deprecated"
220-
221211
for obj in [string_series, object_series]:
222212
idx = obj.index[5]
223213

224214
assert obj[idx] == obj.get(idx)
225215
assert obj[idx] == obj.iloc[5]
226216

227-
with tm.assert_produces_warning(FutureWarning, match=msg):
228-
assert string_series.get(-1) == string_series.get(string_series.index[-1])
217+
# As of 3.0, ints are treated as labels
218+
assert string_series.get(-1) is None
229219
assert string_series.iloc[5] == string_series.get(string_series.index[5])
230220

231221

0 commit comments

Comments
 (0)