diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 67dba3fab23e1..bccfce4f7e077 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -525,6 +525,7 @@ Indexing - Bug in `Series.__getitem__` with an integer key and a :class:`MultiIndex` with leading integer level failing to raise ``KeyError`` if the key is not present in the first level (:issue:`33355`) - Bug in :meth:`DataFrame.iloc` when slicing a single column-:class:`DataFrame`` with ``ExtensionDtype`` (e.g. ``df.iloc[:, :1]``) returning an invalid result (:issue:`32957`) - Bug in :meth:`DatetimeIndex.insert` and :meth:`TimedeltaIndex.insert` causing index ``freq`` to be lost when setting an element into an empty :class:`Series` (:issue:33573`) +- Bug in :meth:`Series.__setitem__` with an :class:`IntervalIndex` and a list-like key of integers (:issue:`33473`) Missing ^^^^^^^ diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 530aaee24c7fb..3c67cfc5dfd12 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4604,9 +4604,9 @@ def _check_indexing_error(self, key): def _should_fallback_to_positional(self) -> bool: """ - If an integer key is not found, should we fall back to positional indexing? + Should an integer key be treated as positional? """ - if len(self) > 0 and (self.holds_integer() or self.is_boolean()): + if self.holds_integer() or self.is_boolean(): return False return True diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 18e995ce4efd7..6ae16db2e82db 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -514,7 +514,7 @@ def is_overlapping(self) -> bool: # GH 23309 return self._engine.is_overlapping - def _should_fallback_to_positional(self): + def _should_fallback_to_positional(self) -> bool: # integer lookups in Series.__getitem__ are unambiguously # positional in this case return self.dtype.subtype.kind in ["m", "M"] diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 42e0d228dab09..d411867af2ef8 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2342,10 +2342,8 @@ def _check_indexing_error(self, key): def _should_fallback_to_positional(self) -> bool: """ - If an integer key is not found, should we fall back to positional indexing? + Should integer key(s) be treated as positional? """ - if not self.nlevels: - return False # GH#33355 return self.levels[0]._should_fallback_to_positional() diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index e2be58a56018d..06040166d0f9e 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -376,7 +376,7 @@ def astype(self, dtype, copy=True): # Indexing Methods @doc(Index._should_fallback_to_positional) - def _should_fallback_to_positional(self): + def _should_fallback_to_positional(self) -> bool: return False @doc(Index._convert_slice_indexer) diff --git a/pandas/core/series.py b/pandas/core/series.py index 3f5927828e541..9182e378fbaeb 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -79,7 +79,6 @@ from pandas.core.indexes.api import ( Float64Index, Index, - IntervalIndex, InvalidIndexError, MultiIndex, ensure_index, @@ -945,9 +944,7 @@ def _get_with(self, key): if key_type == "integer": # We need to decide whether to treat this as a positional indexer # (i.e. self.iloc) or label-based (i.e. self.loc) - if self.index.is_integer() or self.index.is_floating(): - return self.loc[key] - elif isinstance(self.index, IntervalIndex): + if not self.index._should_fallback_to_positional(): return self.loc[key] else: return self.iloc[key] @@ -1070,7 +1067,7 @@ def _set_with(self, key, value): # Note: key_type == "boolean" should not occur because that # should be caught by the is_bool_indexer check in __setitem__ if key_type == "integer": - if self.index.inferred_type == "integer": + if not self.index._should_fallback_to_positional(): self._set_labels(key, value) else: self._set_values(key, value) diff --git a/pandas/tests/series/indexing/test_getitem.py b/pandas/tests/series/indexing/test_getitem.py index a49bd6d59d01b..2922f3c741320 100644 --- a/pandas/tests/series/indexing/test_getitem.py +++ b/pandas/tests/series/indexing/test_getitem.py @@ -90,6 +90,18 @@ def test_getitem_intlist_intindex_periodvalues(self): tm.assert_series_equal(result, exp) assert result.dtype == "Period[D]" + @pytest.mark.parametrize("box", [list, np.array, pd.Index]) + def test_getitem_intlist_intervalindex_non_int(self, box): + # GH#33404 fall back to positional since ints are unambiguous + dti = date_range("2000-01-03", periods=3) + ii = pd.IntervalIndex.from_breaks(dti) + ser = Series(range(len(ii)), index=ii) + + expected = ser.iloc[:1] + key = box([0]) + result = ser[key] + tm.assert_series_equal(result, expected) + def test_getitem_generator(string_series): gen = (x > 0 for x in string_series) diff --git a/pandas/tests/series/indexing/test_indexing.py b/pandas/tests/series/indexing/test_indexing.py index 900374824eb25..c2b5117d395f9 100644 --- a/pandas/tests/series/indexing/test_indexing.py +++ b/pandas/tests/series/indexing/test_indexing.py @@ -159,9 +159,9 @@ def test_getitem_out_of_bounds(datetime_series): datetime_series[len(datetime_series)] # GH #917 - msg = r"index -\d+ is out of bounds for axis 0 with size \d+" + # With a RangeIndex, an int key gives a KeyError s = Series([], dtype=object) - with pytest.raises(IndexError, match=msg): + with pytest.raises(KeyError, match="-1"): s[-1]