diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index c80838f291358..378742ba6d516 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -449,6 +449,7 @@ Removal of prior version deprecations/changes - Changed behavior of comparison of ``NaT`` with a ``datetime.date`` object; these now raise on inequality comparisons (:issue:`39196`) - 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`) - diff --git a/pandas/core/series.py b/pandas/core/series.py index 839f11c83f028..e069b2766c8fc 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -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 diff --git a/pandas/tests/indexing/test_coercion.py b/pandas/tests/indexing/test_coercion.py index ee2c06150bf53..a76e5fc1c5f57 100644 --- a/pandas/tests/indexing/test_coercion.py +++ b/pandas/tests/indexing/test_coercion.py @@ -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 @@ -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) diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 74d05b7e43b2f..7d77a755e082b 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -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)