diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index df898ccf47bfa..3a0d7603ef1a5 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -39,6 +39,7 @@ is_datetime_array, no_default, ) +from pandas._libs.missing import is_matching_na from pandas._libs.tslibs import ( OutOfBoundsDatetime, Timestamp, @@ -2118,6 +2119,30 @@ def _sort_levels_monotonic(self) -> Self: """ return self + @final + def _validate_positional_level(self, level: int) -> None: + """ + Validate that level is 0 or -1 for single-level Index. + + Parameters + ---------- + level : int + The positional level to validate. + + Raises + ------ + IndexError + If level is not 0 or -1. + """ + if level < 0 and level != -1: + raise IndexError( + f"Too many levels: Index has only 1 level, not {level + 1}" + ) + elif level > 0: + raise IndexError( + f"Too many levels: Index has only 1 level, not {level + 1}" + ) + @final def _validate_index_level(self, level) -> None: """ @@ -2127,21 +2152,50 @@ def _validate_index_level(self, level) -> None: verification must be done like in MultiIndex. """ - if isinstance(level, int): - if level < 0 and level != -1: - raise IndexError( - "Too many levels: Index has only 1 level, " - f"{level} is not a valid level number" - ) - if level > 0: - raise IndexError( - f"Too many levels: Index has only 1 level, not {level + 1}" - ) - elif level != self.name: + level_is_na = isna(level) + name_is_na = isna(self.name) + + if level_is_na and name_is_na: + if is_matching_na(level, self.name): + return + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + + if level_is_na: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) + if name_is_na: + if lib.is_integer(level): + self._validate_positional_level(level) + return + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + + if isinstance(level, bool) or isinstance(self.name, bool): + if level == self.name: + return + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + + if lib.is_integer(level): + if isinstance(self.name, int) and level == self.name: + return + + self._validate_positional_level(level) + return + + if level == self.name: + return + + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + def _get_level_number(self, level) -> int: self._validate_index_level(level) return 0 diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 68c97b140fda0..78c3d2311b015 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -962,6 +962,118 @@ def test_get_level_values(self, index, name, level): result = expected.get_level_values(level) tm.assert_index_equal(result, expected) + def test_get_level_values_boolean_name(self): + # GH#62175 + idx = Index([1, 2, 3], name=True) + result = idx.get_level_values(True) + tm.assert_index_equal(result, idx) + + idx = Index([1, 2, 3], name=False) + result = idx.get_level_values(False) + tm.assert_index_equal(result, idx) + + idx = Index([1, 2, 3], name=True) + msg = r"Requested level \(False\) does not match index name \(True\)" + with pytest.raises(KeyError, match=msg): + idx.get_level_values(False) + + msg = r"Too many levels: Index has only 1 level" + with pytest.raises(IndexError, match=msg): + idx.get_level_values(1) + + def test_get_level_values_na_types(self): + # GH#62175 + idx = Index([1, 2, 3], name=pd.NA) + result = idx.get_level_values(pd.NA) + tm.assert_index_equal(result, idx) + + msg = r"Requested level \(.+\) does not match index name \(.+\)" + with pytest.raises(KeyError, match=msg): + idx.get_level_values(np.nan) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(None) + + idx = Index([1, 2, 3], name=np.nan) + result = idx.get_level_values(np.nan) + tm.assert_index_equal(result, idx) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(pd.NA) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(None) + + idx = Index([1, 2, 3], name=None) + result = idx.get_level_values(0) + tm.assert_index_equal(result, idx) + + result = idx.get_level_values(-1) + tm.assert_index_equal(result, idx) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(pd.NA) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(np.nan) + + idx = Index([1, 2, 3], name=pd.NaT) + result = idx.get_level_values(pd.NaT) + tm.assert_index_equal(result, idx) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(np.nan) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(pd.NA) + + def test_get_level_values_integer_name_vs_position(self): + # GH#62175 + idx = Index([1, 2, 3], name=0) + result = idx.get_level_values(0) + tm.assert_index_equal(result, idx) + + result = idx.get_level_values(-1) + tm.assert_index_equal(result, idx) + + idx = Index([1, 2, 3], name=5) + result = idx.get_level_values(5) + tm.assert_index_equal(result, idx) + + result = idx.get_level_values(0) + tm.assert_index_equal(result, idx) + + msg = r"Too many levels: Index has only 1 level, not 2" + with pytest.raises(IndexError, match=msg): + idx.get_level_values(1) + + msg = r"Too many levels: Index has only 1 level, not 1" + with pytest.raises(IndexError, match=msg): + idx.get_level_values(-2) + + def test_get_level_values_error_messages(self): + # GH#62175 + idx = Index([1, 2, 3], name="foo") + + msg = r"Requested level \(bar\) does not match index name \(foo\)" + with pytest.raises(KeyError, match=msg): + idx.get_level_values("bar") + + msg = r"Too many levels: Index has only 1 level, not 2" + with pytest.raises(IndexError, match=msg): + idx.get_level_values(1) + + msg = r"Too many levels: Index has only 1 level, not 1" + with pytest.raises(IndexError, match=msg): + idx.get_level_values(-2) + + msg = r"Requested level \(.+\) does not match index name \(foo\)" + with pytest.raises(KeyError, match=msg): + idx.get_level_values(pd.NA) + + with pytest.raises(KeyError, match=msg): + idx.get_level_values(np.nan) + def test_slice_keep_name(self): index = Index(["a", "b"], name="asdf") assert index.name == index[1:].name