From 2c1ab674ec9c423e83918155a625ae8fc29e8d6b Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 17 Dec 2022 15:44:49 +0100 Subject: [PATCH 1/3] REV: revert deprecation of Series.__getitem__ slicing with IntegerIndex (#50283) (cherry picked from commit 1613f26ff0ec75e30828996fd9ec3f9dd5119ca6) --- doc/source/whatsnew/v1.5.3.rst | 1 + pandas/core/frame.py | 13 +++--- pandas/core/indexes/base.py | 47 +------------------- pandas/core/indexes/interval.py | 4 +- pandas/core/indexes/numeric.py | 4 +- pandas/tests/extension/base/getitem.py | 3 +- pandas/tests/indexing/test_floats.py | 3 +- pandas/tests/series/indexing/test_get.py | 3 +- pandas/tests/series/indexing/test_getitem.py | 3 +- pandas/tests/series/indexing/test_setitem.py | 12 ++--- 10 files changed, 19 insertions(+), 74 deletions(-) diff --git a/doc/source/whatsnew/v1.5.3.rst b/doc/source/whatsnew/v1.5.3.rst index a0c38f1f81538..82333a20fd065 100644 --- a/doc/source/whatsnew/v1.5.3.rst +++ b/doc/source/whatsnew/v1.5.3.rst @@ -36,6 +36,7 @@ Bug fixes Other ~~~~~ +- Reverted deprecation (:issue:`45324`) of behavior of :meth:`Series.__getitem__` and :meth:`Series.__setitem__` slicing with an integer :class:`Index`; this will remain positional (:issue:`49612`) - - diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 9e0f1363e073c..59a76492fa495 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -186,7 +186,6 @@ from pandas.core.indexing import ( check_bool_indexer, check_deprecated_indexers, - convert_to_index_sliceable, ) from pandas.core.internals import ( ArrayManager, @@ -3776,8 +3775,8 @@ def __getitem__(self, key): elif is_mi and self.columns.is_unique and key in self.columns: return self._getitem_multilevel(key) # Do we have a slicer (on rows)? - indexer = convert_to_index_sliceable(self, key) - if indexer is not None: + if isinstance(key, slice): + indexer = self.index._convert_slice_indexer(key, kind="getitem") if isinstance(indexer, np.ndarray): indexer = lib.maybe_indices_to_slice( indexer.astype(np.intp, copy=False), len(self) @@ -3956,11 +3955,9 @@ def __setitem__(self, key, value): key = com.apply_if_callable(key, self) # see if we can slice the rows - indexer = convert_to_index_sliceable(self, key) - if indexer is not None: - # either we have a slice or we have a string that can be converted - # to a slice for partial-string date indexing - return self._setitem_slice(indexer, value) + if isinstance(key, slice): + slc = self.index._convert_slice_indexer(key, kind="getitem") + return self._setitem_slice(slc, value) if isinstance(key, DataFrame) or getattr(key, "ndim", None) == 2: self._setitem_frame(key, value) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 677e1dc1a559a..447ba8d70c73e 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -123,7 +123,6 @@ ABCDatetimeIndex, ABCMultiIndex, ABCPeriodIndex, - ABCRangeIndex, ABCSeries, ABCTimedeltaIndex, ) @@ -4213,7 +4212,7 @@ def _validate_positional_slice(self, key: slice) -> None: self._validate_indexer("positional", key.stop, "iloc") self._validate_indexer("positional", key.step, "iloc") - def _convert_slice_indexer(self, key: slice, kind: str_t, is_frame: bool = False): + def _convert_slice_indexer(self, key: slice, kind: str_t): """ Convert a slice indexer. @@ -4224,9 +4223,6 @@ def _convert_slice_indexer(self, key: slice, kind: str_t, is_frame: bool = False ---------- key : label of the slice bound kind : {'loc', 'getitem'} - is_frame : bool, default False - Whether this is a slice called on DataFrame.__getitem__ - as opposed to Series.__getitem__ """ assert kind in ["loc", "getitem"], kind @@ -4248,46 +4244,7 @@ def is_int(v): is_positional = is_index_slice and ints_are_positional if kind == "getitem": - """ - called from the getitem slicers, validate that we are in fact - integers - """ - if self.is_integer(): - if is_frame: - # unambiguously positional, no deprecation - pass - elif start is None and stop is None: - # label-based vs positional is irrelevant - pass - elif isinstance(self, ABCRangeIndex) and self._range == range( - len(self) - ): - # In this case there is no difference between label-based - # and positional, so nothing will change. - pass - elif ( - self.dtype.kind in ["i", "u"] - and self._is_strictly_monotonic_increasing - and len(self) > 0 - and self[0] == 0 - and self[-1] == len(self) - 1 - ): - # We are range-like, e.g. created with Index(np.arange(N)) - pass - elif not is_index_slice: - # we're going to raise, so don't bother warning, e.g. - # test_integer_positional_indexing - pass - else: - warnings.warn( - "The behavior of `series[i:j]` with an integer-dtype index " - "is deprecated. In a future version, this will be treated " - "as *label-based* indexing, consistent with e.g. `series[i]` " - "lookups. To retain the old behavior, use `series.iloc[i:j]`. " - "To get the future behavior, use `series.loc[i:j]`.", - FutureWarning, - stacklevel=find_stack_level(), - ) + # called from the getitem slicers, validate that we are in fact integers if self.is_integer() or is_index_slice: # Note: these checks are redundant if we know is_index_slice self._validate_indexer("slice", key.start, "getitem") diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 92331c9777abb..c68baa4bc2373 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -764,7 +764,7 @@ def _index_as_unique(self) -> bool: "cannot handle overlapping indices; use IntervalIndex.get_indexer_non_unique" ) - def _convert_slice_indexer(self, key: slice, kind: str, is_frame: bool = False): + def _convert_slice_indexer(self, key: slice, kind: str): if not (key.step is None or key.step == 1): # GH#31658 if label-based, we require step == 1, # if positional, we disallow float start/stop @@ -776,7 +776,7 @@ def _convert_slice_indexer(self, key: slice, kind: str, is_frame: bool = False): # i.e. this cannot be interpreted as a positional slice raise ValueError(msg) - return super()._convert_slice_indexer(key, kind, is_frame=is_frame) + return super()._convert_slice_indexer(key, kind) @cache_readonly def _should_fallback_to_positional(self) -> bool: diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index a597bea0eb724..fe11a02eccb3c 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -219,7 +219,7 @@ def _should_fallback_to_positional(self) -> bool: return False @doc(Index._convert_slice_indexer) - def _convert_slice_indexer(self, key: slice, kind: str, is_frame: bool = False): + def _convert_slice_indexer(self, key: slice, kind: str): # TODO(2.0): once #45324 deprecation is enforced we should be able # to simplify this. if is_float_dtype(self.dtype): @@ -231,7 +231,7 @@ def _convert_slice_indexer(self, key: slice, kind: str, is_frame: bool = False): # translate to locations return self.slice_indexer(key.start, key.stop, key.step) - return super()._convert_slice_indexer(key, kind=kind, is_frame=is_frame) + return super()._convert_slice_indexer(key, kind=kind) @doc(Index._maybe_cast_slice_bound) def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): diff --git a/pandas/tests/extension/base/getitem.py b/pandas/tests/extension/base/getitem.py index e966d4602a02c..cf51d9d693155 100644 --- a/pandas/tests/extension/base/getitem.py +++ b/pandas/tests/extension/base/getitem.py @@ -313,8 +313,7 @@ def test_get(self, data): expected = s.iloc[[2, 3]] self.assert_series_equal(result, expected) - with tm.assert_produces_warning(FutureWarning, match="label-based"): - result = s.get(slice(2)) + result = s.get(slice(2)) expected = s.iloc[[0, 1]] self.assert_series_equal(result, expected) diff --git a/pandas/tests/indexing/test_floats.py b/pandas/tests/indexing/test_floats.py index 186cba62c138f..afc2def7ba8a1 100644 --- a/pandas/tests/indexing/test_floats.py +++ b/pandas/tests/indexing/test_floats.py @@ -340,8 +340,7 @@ def test_integer_positional_indexing(self, idx): """ s = Series(range(2, 6), index=range(2, 6)) - with tm.assert_produces_warning(FutureWarning, match="label-based"): - result = s[2:4] + result = s[2:4] expected = s.iloc[2:4] tm.assert_series_equal(result, expected) diff --git a/pandas/tests/series/indexing/test_get.py b/pandas/tests/series/indexing/test_get.py index 1a54796dbeec3..e8034bd4f7160 100644 --- a/pandas/tests/series/indexing/test_get.py +++ b/pandas/tests/series/indexing/test_get.py @@ -167,8 +167,7 @@ def test_get_with_ea(arr): expected = ser.iloc[[2, 3]] tm.assert_series_equal(result, expected) - with tm.assert_produces_warning(FutureWarning, match="label-based"): - result = ser.get(slice(2)) + result = ser.get(slice(2)) expected = ser.iloc[[0, 1]] tm.assert_series_equal(result, expected) diff --git a/pandas/tests/series/indexing/test_getitem.py b/pandas/tests/series/indexing/test_getitem.py index cc67dd9caeea9..acdcc03cee92c 100644 --- a/pandas/tests/series/indexing/test_getitem.py +++ b/pandas/tests/series/indexing/test_getitem.py @@ -338,8 +338,7 @@ def test_getitem_slice_bug(self): def test_getitem_slice_integers(self): ser = Series(np.random.randn(8), index=[2, 4, 6, 8, 10, 12, 14, 16]) - with tm.assert_produces_warning(FutureWarning, match="label-based"): - result = ser[:4] + result = ser[:4] expected = Series(ser.values[:4], index=[2, 4, 6, 8]) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 9ab3b6ead017f..500ff2e90adb1 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -220,15 +220,9 @@ def test_setitem_slice(self): def test_setitem_slice_integers(self): ser = Series(np.random.randn(8), index=[2, 4, 6, 8, 10, 12, 14, 16]) - msg = r"In a future version, this will be treated as \*label-based\* indexing" - with tm.assert_produces_warning(FutureWarning, match=msg): - ser[:4] = 0 - with tm.assert_produces_warning( - FutureWarning, match=msg, check_stacklevel=False - ): - assert (ser[:4] == 0).all() - with tm.assert_produces_warning(FutureWarning, match=msg): - assert not (ser[4:] == 0).any() + ser[:4] = 0 + assert (ser[:4] == 0).all() + assert not (ser[4:] == 0).any() def test_setitem_slicestep(self): # caught this bug when writing tests From 81a532bce0085229f8956a27237657ddae51e9bc Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Thu, 12 Jan 2023 10:24:27 +0100 Subject: [PATCH 2/3] Avoid unrelated changes --- pandas/core/frame.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 59a76492fa495..9e0f1363e073c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -186,6 +186,7 @@ from pandas.core.indexing import ( check_bool_indexer, check_deprecated_indexers, + convert_to_index_sliceable, ) from pandas.core.internals import ( ArrayManager, @@ -3775,8 +3776,8 @@ def __getitem__(self, key): elif is_mi and self.columns.is_unique and key in self.columns: return self._getitem_multilevel(key) # Do we have a slicer (on rows)? - if isinstance(key, slice): - indexer = self.index._convert_slice_indexer(key, kind="getitem") + indexer = convert_to_index_sliceable(self, key) + if indexer is not None: if isinstance(indexer, np.ndarray): indexer = lib.maybe_indices_to_slice( indexer.astype(np.intp, copy=False), len(self) @@ -3955,9 +3956,11 @@ def __setitem__(self, key, value): key = com.apply_if_callable(key, self) # see if we can slice the rows - if isinstance(key, slice): - slc = self.index._convert_slice_indexer(key, kind="getitem") - return self._setitem_slice(slc, value) + indexer = convert_to_index_sliceable(self, key) + if indexer is not None: + # either we have a slice or we have a string that can be converted + # to a slice for partial-string date indexing + return self._setitem_slice(indexer, value) if isinstance(key, DataFrame) or getattr(key, "ndim", None) == 2: self._setitem_frame(key, value) From 4b5f991add4c78e85257ca27fbafdd6a62e70da6 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Thu, 12 Jan 2023 23:17:58 +0100 Subject: [PATCH 3/3] Fix --- pandas/core/indexing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 913aa2e5b0e18..198903f5fceff 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2491,7 +2491,7 @@ def convert_to_index_sliceable(obj: DataFrame, key): """ idx = obj.index if isinstance(key, slice): - return idx._convert_slice_indexer(key, kind="getitem", is_frame=True) + return idx._convert_slice_indexer(key, kind="getitem") elif isinstance(key, str):