Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3df788a
BUG: Fix Index.get_level_values() mishandling of boolean, pd.NA, np.n…
whyvineet Aug 23, 2025
fe07d18
CLN: Remove redundant checks for NA, NaT, and NaN in Index class
whyvineet Aug 23, 2025
f57ddc5
CLN: Move import of NA to maintain consistent import order
whyvineet Aug 23, 2025
eca19a2
BUG: Fix Index level validation to handle integer index names correctly
whyvineet Aug 23, 2025
390f3ef
CLN: Remove unnecessary blank line and simplify condition in Index cl…
whyvineet Aug 23, 2025
629bdf9
BUG: Update Index class level validation to use lib.is_integer for ty…
whyvineet Aug 23, 2025
041994c
BUG: Enhance Index level validation to explicitly handle NA values an…
whyvineet Aug 24, 2025
d881247
BUG: Improve Index level validation to reject all NA-like values and …
whyvineet Aug 25, 2025
3e63865
BUG: Update error message for NA-like level validation to include lev…
whyvineet Aug 25, 2025
7f541de
BUG: Enhance index level validation to handle NA-like index names and…
whyvineet Aug 25, 2025
703085e
BUG: Refactor error message formatting in Index class for clarity
whyvineet Aug 25, 2025
3793317
BUG: Refactor index level validation to improve handling of NA-like v…
whyvineet Aug 25, 2025
d32d836
Merge branch 'pandas-dev:main' into main
whyvineet Oct 4, 2025
c03d480
Fix _validate_index_level to handle None values correctly and fix CRL…
whyvineet Oct 4, 2025
19832f3
Merge branch 'pandas-dev:main' into main
whyvineet Nov 9, 2025
198a309
ENH: Add validation for positional levels and improve error messages …
whyvineet Dec 7, 2025
dd0960a
Merge branch 'pandas-dev:main' into main
whyvineet Dec 7, 2025
06e35cd
ENH: Refine boolean checks in Index class for level validation
whyvineet Dec 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 74 additions & 11 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
is_datetime_array,
no_default,
)
from pandas._libs.missing import is_matching_na
from pandas._libs.tslibs import (
OutOfBoundsDatetime,
Timestamp,
Expand Down Expand Up @@ -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:
"""
Expand All @@ -2127,21 +2152,59 @@ 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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is a better verification that validates what is expected from the documentation.

Suggested change
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})"
)

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) and 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 not isinstance(self.name, bool)
and level == self.name
):
return

self._validate_positional_level(level)
return

if isinstance(self.name, bool):
raise KeyError(
f"Requested level ({level}) does not match index name ({self.name})"
)

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
Expand Down
112 changes: 112 additions & 0 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading