diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 0b450fab53137..62a99ce3cae44 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -885,6 +885,7 @@ Indexing - Bug when setting a value too large for a :class:`Series` dtype failing to coerce to a common type (:issue:`26049`, :issue:`32878`) - Bug in :meth:`loc.__setitem__` treating ``range`` keys as positional instead of label-based (:issue:`45479`) - Bug in :meth:`Series.__setitem__` when setting ``boolean`` dtype values containing ``NA`` incorrectly raising instead of casting to ``boolean`` dtype (:issue:`45462`) +- Bug in :meth:`Series.loc` raising with boolean indexer containing ``NA`` when :class:`Index` did not match (:issue:`46551`) - Bug in :meth:`Series.__setitem__` where setting :attr:`NA` into a numeric-dtype :class:`Series` would incorrectly upcast to object-dtype rather than treating the value as ``np.nan`` (:issue:`44199`) - Bug in :meth:`DataFrame.loc` when setting values to a column and right hand side is a dictionary (:issue:`47216`) - Bug in :meth:`DataFrame.loc` when setting a :class:`DataFrame` not aligning index in some cases (:issue:`47578`) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 665333d0d7b4f..30d6a8a9f019b 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -30,6 +30,7 @@ from pandas.core.dtypes.common import ( is_array_like, is_bool_dtype, + is_extension_array_dtype, is_hashable, is_integer, is_iterator, @@ -2531,15 +2532,20 @@ def check_bool_indexer(index: Index, key) -> np.ndarray: """ result = key if isinstance(key, ABCSeries) and not key.index.equals(index): - result = result.reindex(index) - mask = isna(result._values) - if mask.any(): + indexer = result.index.get_indexer_for(index) + if -1 in indexer: raise IndexingError( "Unalignable boolean Series provided as " "indexer (index of the boolean Series and of " "the indexed object do not match)." ) - return result.astype(bool)._values + + result = result.take(indexer) + + # fall through for boolean + if not is_extension_array_dtype(result.dtype): + return result.astype(bool)._values + if is_object_dtype(key): # key might be object-dtype bool, check_array_indexer needs bool array result = np.asarray(result, dtype=bool) diff --git a/pandas/tests/series/indexing/test_indexing.py b/pandas/tests/series/indexing/test_indexing.py index 3a8e14576a55d..2f4fffe57593f 100644 --- a/pandas/tests/series/indexing/test_indexing.py +++ b/pandas/tests/series/indexing/test_indexing.py @@ -5,7 +5,10 @@ import numpy as np import pytest +from pandas.errors import IndexingError + from pandas import ( + NA, DataFrame, IndexSlice, MultiIndex, @@ -330,6 +333,22 @@ def test_loc_setitem_all_false_indexer(): tm.assert_series_equal(ser, expected) +def test_loc_boolean_indexer_non_matching_index(): + # GH#46551 + ser = Series([1]) + result = ser.loc[Series([NA, False], dtype="boolean")] + expected = Series([], dtype="int64") + tm.assert_series_equal(result, expected) + + +def test_loc_boolean_indexer_miss_matching_index(): + # GH#46551 + ser = Series([1]) + indexer = Series([NA, False], dtype="boolean", index=[1, 2]) + with pytest.raises(IndexingError, match="Unalignable"): + ser.loc[indexer] + + class TestDeprecatedIndexers: @pytest.mark.parametrize("key", [{1}, {1: 1}]) def test_getitem_dict_and_set_deprecated(self, key):