From 9066359b290c58898d281c89082c2818ca78ffd7 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 24 Jun 2021 19:49:33 -0700 Subject: [PATCH 1/3] DEPR: Series.__setitem__ with Float64Index falling back to positional --- doc/source/whatsnew/v1.4.0.rst | 2 +- pandas/core/series.py | 11 +++++++++++ pandas/tests/indexing/test_coercion.py | 12 ++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index f992d6aa09ead..6722542866ff2 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -97,7 +97,7 @@ Other API changes Deprecations ~~~~~~~~~~~~ - Deprecated :meth:`Index.is_type_compatible` (:issue:`42113`) -- +- Deprecated treating integer keys in :meth:`Series.__setitem__` as positional when the index is a :class:`Float64Index` not containing the key (:issue:`33469`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/series.py b/pandas/core/series.py index c20e09e9eac5d..82a5a9f1f89ba 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1069,6 +1069,17 @@ def __setitem__(self, key, value) -> None: values = self._values 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=2, + ) values[key] = value else: # GH#12862 adding a new key to the Series diff --git a/pandas/tests/indexing/test_coercion.py b/pandas/tests/indexing/test_coercion.py index 7911cd7f12e0c..761e67bedbf8c 100644 --- a/pandas/tests/indexing/test_coercion.py +++ b/pandas/tests/indexing/test_coercion.py @@ -273,7 +273,12 @@ def _assert_setitem_index_conversion( ): """test index's coercion triggered by assign key""" temp = original_series.copy() - temp[loc_key] = 5 + 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 exp = pd.Series([1, 2, 3, 4, 5], index=expected_index) tm.assert_series_equal(temp, exp) # check dtype explicitly for sure @@ -324,7 +329,10 @@ def test_setitem_index_float64(self, val, exp_dtype, request): temp = obj.copy() msg = "index 5 is out of bounds for axis 0 with size 4" with pytest.raises(exp_dtype, match=msg): - temp[5] = 5 + # 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]) From 1a6f3f9b235bba549b3d06d13862a1637dd4ead4 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 25 Jun 2021 09:20:12 -0700 Subject: [PATCH 2/3] catch warning in test_arithmetic --- pandas/tests/series/test_arithmetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index aac26c13c2a7c..6b7c2c698c211 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -874,7 +874,7 @@ def test_none_comparison(series_with_simple_index): # bug brought up by #1079 # changed from TypeError in 0.17.0 - series[0] = np.nan + series.iloc[0] = np.nan # noinspection PyComparisonWithNone result = series == None # noqa From 4e0c1557e67a85e69d50c04d95a001212f1828e5 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 26 Jun 2021 11:06:40 -0700 Subject: [PATCH 3/3] dedicated test, update whatsnew --- doc/source/whatsnew/v1.4.0.rst | 2 +- pandas/tests/series/indexing/test_setitem.py | 39 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 299c977aad041..980283f5ef6fb 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -97,7 +97,7 @@ Other API changes Deprecations ~~~~~~~~~~~~ - Deprecated :meth:`Index.is_type_compatible` (:issue:`42113`) -- Deprecated treating integer keys in :meth:`Series.__setitem__` as positional when the index is a :class:`Float64Index` not containing the key (:issue:`33469`) +- Deprecated treating integer keys in :meth:`Series.__setitem__` as positional when the index is a :class:`Float64Index` not containing the key, a :class:`IntervalIndex` with no entries containing the key, or a :class:`MultiIndex` with leading :class:`Float64Index` level not containing the key (:issue:`33469`) .. --------------------------------------------------------------------------- diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 13054062defb4..cfb617cd7098b 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -10,6 +10,7 @@ Categorical, DatetimeIndex, Index, + IntervalIndex, MultiIndex, NaT, Series, @@ -906,3 +907,41 @@ def val(self): def is_inplace(self, obj): # This is specific to the 4 cases currently implemented for this class. return obj.dtype.kind != "i" + + +def test_setitem_int_as_positional_fallback_deprecation(): + # GH#42215 deprecated falling back to positional on __setitem__ with an + # int not contained in the index + 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 + 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]) + + 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 + 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 + tm.assert_series_equal(ser3, expected3)