diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 65982ecdb810c..af4e2d0ec3f94 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -1132,6 +1132,7 @@ Interval Indexing ^^^^^^^^ +- Bug in :func:`pandas.concat` incorrectly constructing the :class:`MultiIndex` when an inner level contained :obj:`pandas.NA` with :class:`pandas.Int64Dtype`, causing a :exc:`KeyError` on lookup (:issue:`62903`) - Bug in :meth:`DataFrame.__getitem__` returning modified columns when called with ``slice`` in Python 3.12 (:issue:`57500`) - Bug in :meth:`DataFrame.__getitem__` when slicing a :class:`DataFrame` with many rows raised an ``OverflowError`` (:issue:`59531`) - Bug in :meth:`DataFrame.__setitem__` on an empty :class:`DataFrame` with a tuple corrupting the frame (:issue:`54385`) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 43e6469e078f0..a818fb8bce65c 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3325,6 +3325,12 @@ def _maybe_to_slice(loc): try: return self._engine.get_loc(key) except KeyError as err: + if any(isna(k) for k in key): + loc, _ = self.get_loc_level( + key, range(self.nlevels), drop_level=False + ) + if lib.is_integer(loc): + return loc raise KeyError(key) from err except TypeError: # e.g. test_partial_slicing_with_multiindex partial string slicing diff --git a/pandas/tests/indexes/multi/test_indexing.py b/pandas/tests/indexes/multi/test_indexing.py index f098690be2afa..45ab01165350a 100644 --- a/pandas/tests/indexes/multi/test_indexing.py +++ b/pandas/tests/indexes/multi/test_indexing.py @@ -710,6 +710,14 @@ def test_get_loc_nan(self, level, nulls_fixture): idx = MultiIndex.from_product(levels) assert idx.get_loc(tuple(key)) == 3 + def test_multiindex_at_lookup_with_na_key(self): + index = MultiIndex(levels=[[1, 2], [2, pd.NA]], codes=[[0, 1], [0, 1]]) + df = DataFrame({"a": [1, 2]}, index=index) + result = df.at[(2, pd.NA), "a"] + assert result == 2 + loc_result = df.loc[(2, pd.NA), "a"] + assert loc_result == 2 + def test_get_loc_missing_nan(self): # GH 8569 idx = MultiIndex.from_arrays([[1.0, 2.0], [3.0, 4.0]])