diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index ecea79be5b4dc..cce6b1b01d802 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -611,6 +611,7 @@ Indexing - Bug in :meth:`DataFrame.reindex` raising ``IndexingError`` wrongly for empty :class:`DataFrame` with ``tolerance`` not None or ``method="nearest"`` (:issue:`27315`) - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using listlike indexer that contains elements that are in the index's ``categories`` but not in the index itself failing to raise ``KeyError`` (:issue:`37901`) - Bug in :meth:`DataFrame.iloc` and :meth:`Series.iloc` aligning objects in ``__setitem__`` (:issue:`22046`) +- Bug in :meth:`DataFrame.loc` did not raise ``KeyError`` when missing combination was given with ``slice(None)`` for remaining levels (:issue:`19556`) Missing ^^^^^^^ diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 9eb34d920a328..76c5ee539e510 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3132,19 +3132,26 @@ def _convert_to_indexer(r) -> Int64Index: r = r.nonzero()[0] return Int64Index(r) - def _update_indexer(idxr: Optional[Index], indexer: Optional[Index]) -> Index: + def _update_indexer( + idxr: Optional[Index], indexer: Optional[Index], key + ) -> Index: if indexer is None: indexer = Index(np.arange(n)) if idxr is None: return indexer - return indexer.intersection(idxr) + indexer_intersection = indexer.intersection(idxr) + if indexer_intersection.empty and not idxr.empty and not indexer.empty: + raise KeyError(key) + return indexer_intersection for i, k in enumerate(seq): if com.is_bool_indexer(k): # a boolean indexer, must be the same length! k = np.asarray(k) - indexer = _update_indexer(_convert_to_indexer(k), indexer=indexer) + indexer = _update_indexer( + _convert_to_indexer(k), indexer=indexer, key=seq + ) elif is_list_like(k): # a collection of labels to include from this level (these @@ -3164,14 +3171,14 @@ def _update_indexer(idxr: Optional[Index], indexer: Optional[Index]) -> Index: continue if indexers is not None: - indexer = _update_indexer(indexers, indexer=indexer) + indexer = _update_indexer(indexers, indexer=indexer, key=seq) else: # no matches we are done return np.array([], dtype=np.int64) elif com.is_null_slice(k): # empty slice - indexer = _update_indexer(None, indexer=indexer) + indexer = _update_indexer(None, indexer=indexer, key=seq) elif isinstance(k, slice): @@ -3181,6 +3188,7 @@ def _update_indexer(idxr: Optional[Index], indexer: Optional[Index]) -> Index: self._get_level_indexer(k, level=i, indexer=indexer) ), indexer=indexer, + key=seq, ) else: # a single label @@ -3189,6 +3197,7 @@ def _update_indexer(idxr: Optional[Index], indexer: Optional[Index]) -> Index: self.get_loc_level(k, level=i, drop_level=False)[0] ), indexer=indexer, + key=seq, ) # empty indexer diff --git a/pandas/tests/indexing/multiindex/test_loc.py b/pandas/tests/indexing/multiindex/test_loc.py index 165d34180dfab..22ffca46a4829 100644 --- a/pandas/tests/indexing/multiindex/test_loc.py +++ b/pandas/tests/indexing/multiindex/test_loc.py @@ -608,6 +608,25 @@ def test_missing_key_raises_keyerror2(self): with pytest.raises(KeyError, match=r"\(0, 3\)"): ser.loc[0, 3] + def test_missing_key_combination(self): + # GH: 19556 + mi = MultiIndex.from_arrays( + [ + np.array(["a", "a", "b", "b"]), + np.array(["1", "2", "2", "3"]), + np.array(["c", "d", "c", "d"]), + ], + names=["one", "two", "three"], + ) + df = DataFrame(np.random.rand(4, 3), index=mi) + msg = r"\('b', '1', slice\(None, None, None\)\)" + with pytest.raises(KeyError, match=msg): + df.loc[("b", "1", slice(None)), :] + with pytest.raises(KeyError, match=msg): + df.index.get_locs(("b", "1", slice(None))) + with pytest.raises(KeyError, match=r"\('b', '1'\)"): + df.loc[("b", "1"), :] + def test_getitem_loc_commutability(multiindex_year_month_day_dataframe_random_data): df = multiindex_year_month_day_dataframe_random_data