From 3df788a0f52eb01736df4cfe52272d57d27fc1aa Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sat, 23 Aug 2025 16:45:30 +0530 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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