From 3df788a0f52eb01736df4cfe52272d57d27fc1aa Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 23 Aug 2025 16:45:30 +0530 Subject: [PATCH 01/14] BUG: Fix Index.get_level_values() mishandling of boolean, pd.NA, np.nan, and pd.NaT levels --- pandas/_libs/__init__.py | 2 ++ pandas/core/indexes/base.py | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pandas/_libs/__init__.py b/pandas/_libs/__init__.py index d499f9a6cd75e..592e849917dc4 100644 --- a/pandas/_libs/__init__.py +++ b/pandas/_libs/__init__.py @@ -1,4 +1,5 @@ __all__ = [ + "NA", "Interval", "NaT", "NaTType", @@ -15,6 +16,7 @@ # see pandas_datetime_exec in pd_datetime.c import pandas._libs.pandas_parser # isort: skip # type: ignore[reportUnusedImport] import pandas._libs.pandas_datetime # noqa: F401 # isort: skip # type: ignore[reportUnusedImport] +from pandas._libs.missing import NA from pandas._libs.interval import Interval from pandas._libs.tslibs import ( NaT, diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 59ac122e4f9ea..3c609cc803a99 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -26,6 +26,7 @@ ) from pandas._libs import ( + NA, NaT, algos as libalgos, index as libindex, @@ -2084,7 +2085,7 @@ def _validate_index_level(self, level) -> None: verification must be done like in MultiIndex. """ - if isinstance(level, int): + if type(level) is int: if level < 0 and level != -1: raise IndexError( "Too many levels: Index has only 1 level, " @@ -2094,10 +2095,25 @@ def _validate_index_level(self, level) -> None: raise IndexError( f"Too many levels: Index has only 1 level, not {level + 1}" ) - elif level != self.name: - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) + + else: + if level is NA: + raise KeyError( + "Requested level is pandas.NA, which is not a valid index name" + ) + if level is NaT: + raise KeyError( + "Requested level is pandas.NaT, which is not a valid index name" + ) + if isinstance(level, float) and np.isnan(level): + raise KeyError( + "Requested level is NaN, which is not a valid index name" + ) + + if level != self.name: + 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) From fe07d18702db208979fb6964bdcfdd18e5690e6f Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 23 Aug 2025 19:48:32 +0530 Subject: [PATCH 02/14] CLN: Remove redundant checks for NA, NaT, and NaN in Index class --- pandas/core/indexes/base.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 3c609cc803a99..5bd001a11cb00 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -26,7 +26,6 @@ ) from pandas._libs import ( - NA, NaT, algos as libalgos, index as libindex, @@ -2097,19 +2096,6 @@ def _validate_index_level(self, level) -> None: ) else: - if level is NA: - raise KeyError( - "Requested level is pandas.NA, which is not a valid index name" - ) - if level is NaT: - raise KeyError( - "Requested level is pandas.NaT, which is not a valid index name" - ) - if isinstance(level, float) and np.isnan(level): - raise KeyError( - "Requested level is NaN, which is not a valid index name" - ) - if level != self.name: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" From f57ddc5e1ae4d14a719e097f8bd6e0c11c8ca7a1 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 23 Aug 2025 20:51:40 +0530 Subject: [PATCH 03/14] CLN: Move import of NA to maintain consistent import order --- pandas/_libs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/__init__.py b/pandas/_libs/__init__.py index 592e849917dc4..7a988add09817 100644 --- a/pandas/_libs/__init__.py +++ b/pandas/_libs/__init__.py @@ -16,8 +16,8 @@ # see pandas_datetime_exec in pd_datetime.c import pandas._libs.pandas_parser # isort: skip # type: ignore[reportUnusedImport] import pandas._libs.pandas_datetime # noqa: F401 # isort: skip # type: ignore[reportUnusedImport] -from pandas._libs.missing import NA from pandas._libs.interval import Interval +from pandas._libs.missing import NA from pandas._libs.tslibs import ( NaT, NaTType, From eca19a2ce4f4a31cc3f14fbf8b28ba2ba4c0aa95 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 23 Aug 2025 23:27:23 +0530 Subject: [PATCH 04/14] BUG: Fix Index level validation to handle integer index names correctly --- pandas/core/indexes/base.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 5bd001a11cb00..39a48717fcaa9 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2085,6 +2085,10 @@ def _validate_index_level(self, level) -> None: """ if type(level) is int: + + if isinstance(self.name, int) and level == self.name: + return + if level < 0 and level != -1: raise IndexError( "Too many levels: Index has only 1 level, " @@ -2095,11 +2099,14 @@ def _validate_index_level(self, level) -> None: f"Too many levels: Index has only 1 level, not {level + 1}" ) - else: - if level != self.name: - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) + elif ( + isinstance(level, str) + and isinstance(self.name, str) + and level != self.name + ): + 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) From 390f3ef893358251ed58df49c540fdf98eb38282 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 23 Aug 2025 23:40:46 +0530 Subject: [PATCH 05/14] CLN: Remove unnecessary blank line and simplify condition in Index class level validation --- pandas/core/indexes/base.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 39a48717fcaa9..d9e586968625e 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2085,7 +2085,6 @@ def _validate_index_level(self, level) -> None: """ if type(level) is int: - if isinstance(self.name, int) and level == self.name: return @@ -2100,9 +2099,7 @@ def _validate_index_level(self, level) -> None: ) elif ( - isinstance(level, str) - and isinstance(self.name, str) - and level != self.name + isinstance(level, str) and isinstance(self.name, str) and level != self.name ): raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" From 629bdf92db70ddea9f3857062cf6d7aa3461cb23 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sun, 24 Aug 2025 00:53:51 +0530 Subject: [PATCH 06/14] BUG: Update Index class level validation to use lib.is_integer for type checking --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index d9e586968625e..0fecc88e3f386 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2084,7 +2084,7 @@ def _validate_index_level(self, level) -> None: verification must be done like in MultiIndex. """ - if type(level) is int: + if lib.is_integer(level): if isinstance(self.name, int) and level == self.name: return From 041994ca7acf5c7a987bf28d452beed3d41f1014 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sun, 24 Aug 2025 09:55:31 +0530 Subject: [PATCH 07/14] BUG: Enhance Index level validation to explicitly handle NA values and improve error messaging --- pandas/core/indexes/base.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 0fecc88e3f386..b5c716ee86fb4 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2084,11 +2084,21 @@ def _validate_index_level(self, level) -> None: verification must be done like in MultiIndex. """ - if lib.is_integer(level): - if isinstance(self.name, int) and level == self.name: - return + # Explicitly raise for missing/null values to match pandas convention + if isna(level): + raise KeyError( + "Requested level is NA/NaN/NaT, which is not a valid level name" + ) - if level < 0 and level != -1: + # Standard integer check, but reject bool + if lib.is_integer(level) and not isinstance(level, bool): + # If the index itself is named as integer, accept + if lib.is_integer(self.name) and level == self.name: + return + # Only allow 0 and -1 for a single-level Index + if level in (0, -1): + return + if level < 0: raise IndexError( "Too many levels: Index has only 1 level, " f"{level} is not a valid level number" @@ -2098,13 +2108,20 @@ def _validate_index_level(self, level) -> None: f"Too many levels: Index has only 1 level, not {level + 1}" ) - elif ( - isinstance(level, str) and isinstance(self.name, str) and level != self.name + # String level: only match if name is exactly the same string + elif isinstance(level, str) and not ( + isinstance(self.name, str) and level == self.name ): raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) + # If level type is not int, str, or is NA, always raise KeyError + else: + raise KeyError( + f"Requested level ({level}) is not a valid level name or number" + ) + def _get_level_number(self, level) -> int: self._validate_index_level(level) return 0 From d88124760a5b96441b0a7f73d73cb225aca1143f Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Mon, 25 Aug 2025 10:15:50 +0530 Subject: [PATCH 08/14] BUG: Improve Index level validation to reject all NA-like values and enhance error handling for boolean levels --- pandas/core/indexes/base.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index b5c716ee86fb4..7694f989e7d2a 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2085,20 +2085,24 @@ def _validate_index_level(self, level) -> None: """ # Explicitly raise for missing/null values to match pandas convention + # Also reject all NA-like values (np.nan, pd.NA, pd.NaT, etc.) if isna(level): raise KeyError( "Requested level is NA/NaN/NaT, which is not a valid level name" ) - # Standard integer check, but reject bool - if lib.is_integer(level) and not isinstance(level, bool): - # If the index itself is named as integer, accept - if lib.is_integer(self.name) and level == self.name: - return - # Only allow 0 and -1 for a single-level Index - if level in (0, -1): - return - if level < 0: + # Reject booleans unless the index name is actually a boolean and matches + if isinstance(level, bool): + if level != self.name: + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + return + + # Integer-like levels + if lib.is_integer(level): + # Exclude bools (already handled above) + if level < 0 and level != -1: raise IndexError( "Too many levels: Index has only 1 level, " f"{level} is not a valid level number" @@ -2107,21 +2111,15 @@ def _validate_index_level(self, level) -> None: raise IndexError( f"Too many levels: Index has only 1 level, not {level + 1}" ) + return - # String level: only match if name is exactly the same string - elif isinstance(level, str) and not ( - isinstance(self.name, str) and level == self.name - ): + # For all other types, require exact match to index name + # Use pandas' isna for both level and self.name to catch NA-like names + if isna(self.name) or level != self.name: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) - # If level type is not int, str, or is NA, always raise KeyError - else: - raise KeyError( - f"Requested level ({level}) is not a valid level name or number" - ) - def _get_level_number(self, level) -> int: self._validate_index_level(level) return 0 From 3e6386542621b5fc99e32391019c0b474d90dd69 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Mon, 25 Aug 2025 11:42:59 +0530 Subject: [PATCH 09/14] BUG: Update error message for NA-like level validation to include level and index name --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 7694f989e7d2a..277725dba22ee 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2088,7 +2088,7 @@ def _validate_index_level(self, level) -> None: # Also reject all NA-like values (np.nan, pd.NA, pd.NaT, etc.) if isna(level): raise KeyError( - "Requested level is NA/NaN/NaT, which is not a valid level name" + f"Requested level ({level}) does not match index name ({self.name})" ) # Reject booleans unless the index name is actually a boolean and matches From 7f541de64da0bff8d0cc21a99e688eb01b93d4b9 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Tue, 26 Aug 2025 00:26:25 +0530 Subject: [PATCH 10/14] BUG: Enhance index level validation to handle NA-like index names and improve error messaging --- pandas/_libs/__init__.py | 2 -- pandas/core/indexes/base.py | 24 +++++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pandas/_libs/__init__.py b/pandas/_libs/__init__.py index 7a988add09817..d499f9a6cd75e 100644 --- a/pandas/_libs/__init__.py +++ b/pandas/_libs/__init__.py @@ -1,5 +1,4 @@ __all__ = [ - "NA", "Interval", "NaT", "NaTType", @@ -17,7 +16,6 @@ import pandas._libs.pandas_parser # isort: skip # type: ignore[reportUnusedImport] import pandas._libs.pandas_datetime # noqa: F401 # isort: skip # type: ignore[reportUnusedImport] from pandas._libs.interval import Interval -from pandas._libs.missing import NA from pandas._libs.tslibs import ( NaT, NaTType, diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 277725dba22ee..0b907c7980314 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2091,6 +2091,12 @@ def _validate_index_level(self, level) -> None: f"Requested level ({level}) does not match index name ({self.name})" ) + # Handle NA-like index.name as well + if isna(self.name): + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + # Reject booleans unless the index name is actually a boolean and matches if isinstance(level, bool): if level != self.name: @@ -2102,20 +2108,24 @@ def _validate_index_level(self, level) -> None: # Integer-like levels if lib.is_integer(level): # Exclude bools (already handled above) + if isinstance(self.name, int) and level == self.name: + return if level < 0 and level != -1: raise IndexError( - "Too many levels: Index has only 1 level, " - f"{level} is not a valid level number" + "Too many levels: Index has only 1 level, not {}".format(level + 1) ) - if level > 0: - raise IndexError( - f"Too many levels: Index has only 1 level, not {level + 1}" + return + + # For string-level, require both to be strings and equal + if isinstance(level, str) and isinstance(self.name, str): + if level != self.name: + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" ) return # For all other types, require exact match to index name - # Use pandas' isna for both level and self.name to catch NA-like names - if isna(self.name) or level != self.name: + if level != self.name: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) From 703085e3f1b351e56443c8fa38353b9a99e715da Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Tue, 26 Aug 2025 00:32:07 +0530 Subject: [PATCH 11/14] BUG: Refactor error message formatting in Index class for clarity --- pandas/core/indexes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 0b907c7980314..e151c2081adca 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2112,7 +2112,7 @@ def _validate_index_level(self, level) -> None: return if level < 0 and level != -1: raise IndexError( - "Too many levels: Index has only 1 level, not {}".format(level + 1) + f"Too many levels: Index has only 1 level, not {level + 1}" ) return From 37933173f2d0cd6a23820dccb52ac8d483166a0f Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Tue, 26 Aug 2025 01:11:39 +0530 Subject: [PATCH 12/14] BUG: Refactor index level validation to improve handling of NA-like values and streamline error messaging --- pandas/core/indexes/base.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index e151c2081adca..83d55b9deae8a 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2084,48 +2084,35 @@ def _validate_index_level(self, level) -> None: verification must be done like in MultiIndex. """ - # Explicitly raise for missing/null values to match pandas convention - # Also reject all NA-like values (np.nan, pd.NA, pd.NaT, etc.) - if isna(level): - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) + if isna(level) and isna(self.name): + return - # Handle NA-like index.name as well - if isna(self.name): + elif isna(level) or isna(self.name): raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) - # Reject booleans unless the index name is actually a boolean and matches - if isinstance(level, bool): - if level != self.name: - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) - return - - # Integer-like levels - if lib.is_integer(level): - # Exclude bools (already handled above) + elif lib.is_integer(level): if isinstance(self.name, int) and level == self.name: return 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}" + ) return - # For string-level, require both to be strings and equal - if isinstance(level, str) and isinstance(self.name, str): + elif isinstance(level, str) and isinstance(self.name, str): if level != self.name: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) return - # For all other types, require exact match to index name - if level != self.name: + elif level != self.name: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) From c03d480a3208b4bc5e3169a6f96d4a6d6f6ab2b8 Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 4 Oct 2025 20:59:46 +0530 Subject: [PATCH 13/14] Fix _validate_index_level to handle None values correctly and fix CRLF line endings --- pandas/core/indexes/base.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 1b1d9d075129a..172ddf29273d1 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2092,11 +2092,29 @@ def _validate_index_level(self, level) -> None: if isna(level) and isna(self.name): return - elif isna(level) or isna(self.name): + elif isna(level) and not isna(self.name): raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) + elif not isna(level) and isna(self.name): + # level is not NA, but self.name is NA + # This is valid for integer levels (0, -1) accessing unnamed index + if lib.is_integer(level): + 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}" + ) + return + else: + raise KeyError( + f"Requested level ({level}) does not match index name ({self.name})" + ) + elif lib.is_integer(level): if isinstance(self.name, int) and level == self.name: return From 198a309fc44ff042bce840931262c3fd5631f5cb Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sun, 7 Dec 2025 21:53:48 +0530 Subject: [PATCH 14/14] ENH: Add validation for positional levels and improve error messages in Index class --- pandas/core/indexes/base.py | 90 ++++++++++++++---------- pandas/tests/indexes/test_base.py | 112 ++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 36 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 932e8110aa44a..cadd32f2f25b8 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,56 +2152,49 @@ def _validate_index_level(self, level) -> None: verification must be done like in MultiIndex. """ - if isna(level) and isna(self.name): - return + 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})" + ) - elif isna(level) and not isna(self.name): + if level_is_na: raise KeyError( f"Requested level ({level}) does not match index name ({self.name})" ) - elif not isna(level) and isna(self.name): - # level is not NA, but self.name is NA - # This is valid for integer levels (0, -1) accessing unnamed index + if name_is_na: if lib.is_integer(level): - 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}" - ) + self._validate_positional_level(level) return - else: - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) + 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})" + ) - elif lib.is_integer(level): + if lib.is_integer(level): if isinstance(self.name, int) and level == self.name: return - 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}" - ) + + self._validate_positional_level(level) return - elif isinstance(level, str) and isinstance(self.name, str): - if level != self.name: - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) + if level == self.name: return - elif level != self.name: - raise KeyError( - f"Requested level ({level}) does not match index name ({self.name})" - ) + 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) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 26eb33195ccbc..a4fdc79180087 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