Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEPR: enforce Series.__setitem__ behavior with Float64Index and non-present int key #49530

Merged
merged 2 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ Removal of prior version deprecations/changes
- Changed behavior of comparison of a :class:`Timestamp` with a ``datetime.date`` object; these now compare as un-equal and raise on inequality comparisons, matching the ``datetime.datetime`` behavior (:issue:`36131`)
- 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`)
- Change behavior of :meth:`DataFrame.apply` with list-like so that any partial failure will raise an error (:issue:`43740`)
- 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`)
- Removed ``na_sentinel`` argument from :func:`factorize`, :meth:`.Index.factorize`, and :meth:`.ExtensionArray.factorize` (:issue:`47157`)
-

Expand Down
19 changes: 6 additions & 13 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -1073,21 +1073,14 @@ def __setitem__(self, key, value) -> None:
# We have a scalar (or for MultiIndex or object-dtype, scalar-like)
# key that is not present in self.index.
if is_integer(key) and self.index.inferred_type != "integer":
# positional setter
if not self.index._should_fallback_to_positional:
# GH#33469
warnings.warn(
"Treating integers as positional in Series.__setitem__ "
"with a Float64Index is deprecated. In a future version, "
"`series[an_int] = val` will insert a new key into the "
"Series. Use `series.iloc[an_int] = val` to treat the "
"key as positional.",
FutureWarning,
stacklevel=find_stack_level(),
)
# can't use _mgr.setitem_inplace yet bc could have *both*
# KeyError and then ValueError, xref GH#45070
self._set_values(key, value)
self.loc[key] = value
else:
# positional setter
# can't use _mgr.setitem_inplace yet bc could have *both*
# KeyError and then ValueError, xref GH#45070
self._set_values(key, value)
else:
# GH#12862 adding a new key to the Series
self.loc[key] = value
Expand Down
22 changes: 4 additions & 18 deletions pandas/tests/indexing/test_coercion.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,9 @@ def _assert_setitem_index_conversion(
):
"""test index's coercion triggered by assign key"""
temp = original_series.copy()
warn = None
if isinstance(loc_key, int) and temp.index.dtype == np.float64:
# GH#33469
warn = FutureWarning
with tm.assert_produces_warning(warn):
temp[loc_key] = 5
# GH#33469 pre-2.0 with int loc_key and temp.index.dtype == np.float64
# `temp[loc_key] = 5` treated loc_key as positional
temp[loc_key] = 5
exp = pd.Series([1, 2, 3, 4, 5], index=expected_index)
tm.assert_series_equal(temp, exp)
# check dtype explicitly for sure
Expand Down Expand Up @@ -144,23 +141,12 @@ def test_setitem_index_int64(self, val, exp_dtype):
self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)

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

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

Expand Down
34 changes: 15 additions & 19 deletions pandas/tests/series/indexing/test_setitem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1536,41 +1536,37 @@ def test_setitem_positional_float_into_int_coerces():
tm.assert_series_equal(ser, expected)


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

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

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

ii = IntervalIndex.from_breaks(range(10))[::2]
ser2 = Series(range(len(ii)), index=ii)
expected2 = ser2.copy()
expected2.iloc[-1] = 9
with tm.assert_produces_warning(FutureWarning, match=msg):
ser2[4] = 9
exp_index = ii.astype(object).append(Index([4]))
expected2 = Series([0, 1, 2, 3, 4, 9], index=exp_index)
# pre-enforcement `ser2[4] = 9` interpreted 4 as positional
ser2[4] = 9
tm.assert_series_equal(ser2, expected2)

mi = MultiIndex.from_product([ser.index, ["A", "B"]])
ser3 = Series(range(len(mi)), index=mi)
expected3 = ser3.copy()
expected3.iloc[4] = 99

with tm.assert_produces_warning(FutureWarning, match=msg):
ser3[4] = 99
expected3.loc[4] = 99
# pre-enforcement `ser3[4] = 99` interpreted 4 as positional
ser3[4] = 99
tm.assert_series_equal(ser3, expected3)


Expand Down