diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 4a6cf117fd196..f7443e614d19a 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -697,6 +697,7 @@ Indexing - Bug in :meth:`DataFrame.from_records` throwing a ``ValueError`` when passed an empty list in ``index`` (:issue:`58594`) - Bug in :meth:`DataFrame.loc` with inconsistent behavior of loc-set with 2 given indexes to Series (:issue:`59933`) - Bug in :meth:`MultiIndex.insert` when a new value inserted to a datetime-like level gets cast to ``NaT`` and fails indexing (:issue:`60388`) +- Bug in :meth:`Series.iloc` with inconsistent behavior between ``__getitem__`` and ``__setitem__`` for Series boolean indexers (:issue:`60994`) - Bug in printing :attr:`Index.names` and :attr:`MultiIndex.levels` would not escape single quotes (:issue:`60190`) Missing diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index bcb27d0320c91..df74899528044 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1637,6 +1637,22 @@ def _has_valid_setitem_indexer(self, indexer) -> bool: "Consider using .loc with a DataFrame indexer for automatic alignment.", ) + # Check for Series boolean indexer + if ( + com.is_bool_indexer(indexer) + and hasattr(indexer, "index") + and isinstance(indexer.index, Index) + ): + if indexer.index.inferred_type == "integer": + raise NotImplementedError( + "iLocation based boolean " + "indexing on an integer type " + "is not available" + ) + raise ValueError( + "iLocation based boolean indexing cannot use an indexable as a mask" + ) + if not isinstance(indexer, tuple): indexer = _tuplify(self.ndim, indexer) diff --git a/pandas/tests/extension/base/reduce.py b/pandas/tests/extension/base/reduce.py index 4b3431d938f96..5194fed431708 100644 --- a/pandas/tests/extension/base/reduce.py +++ b/pandas/tests/extension/base/reduce.py @@ -105,6 +105,11 @@ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna): msg = ( "[Cc]annot perform|Categorical is not ordered for operation|" "does not support operation|" + "unsupported operand type|" + "NotImplemented|" + "not supported between instances|" + "NotImplementedType|" + "iLocation based boolean" ) with pytest.raises(TypeError, match=msg): diff --git a/pandas/tests/frame/indexing/test_getitem.py b/pandas/tests/frame/indexing/test_getitem.py index 25d6e06a4c675..bbd219dff0942 100644 --- a/pandas/tests/frame/indexing/test_getitem.py +++ b/pandas/tests/frame/indexing/test_getitem.py @@ -407,6 +407,21 @@ def test_getitem_frozenset_unique_in_column(self): expected = Series([1], name=frozenset(["KEY"])) tm.assert_series_equal(result, expected) + def test_series_boolean_indexer_iloc_consistency(self): + # GH#60994 - Test consistency between __getitem__ and __setitem__ for Series + # boolean indexers + ser = Series([0, 1, 2]) + mask = Series([True, False, False]) + + # __getitem__ should raise NotImplementedError + msg = "iLocation based boolean indexing on an integer type is not available" + with pytest.raises(NotImplementedError, match=msg): + ser.iloc[mask] + + # __setitem__ should also raise NotImplementedError for consistency + with pytest.raises(NotImplementedError, match=msg): + ser.iloc[mask] = 10 + class TestGetitemSlice: def test_getitem_slice_float64(self, frame_or_series):