Skip to content

Commit fe66e04

Browse files
author
Roline Stapny Saldanha
committed
BUG: Fix dataframe.reindex to detect matching level when source has single Index
1 parent 693b081 commit fe66e04

File tree

4 files changed

+71
-6
lines changed

4 files changed

+71
-6
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,7 @@ MultiIndex
788788
- Bug in :class:`DataFrame` arithmetic operations in case of unaligned MultiIndex columns (:issue:`60498`)
789789
- Bug in :class:`DataFrame` arithmetic operations with :class:`Series` in case of unaligned MultiIndex (:issue:`61009`)
790790
- Bug in :meth:`MultiIndex.from_tuples` causing wrong output with input of type tuples having NaN values (:issue:`60695`, :issue:`60988`)
791+
- Bug in :meth:`DataFrame.reindex` where reindexing a Dataframe with a named Index to a MultiIndex would incorrectly set all values to ``NaN``. Now correctly preserves values when the source index name matches a target level name (:issue:`60923`)
791792
- Bug in :meth:`Series.reindex` where reindexing a Series with a named Index to a MultiIndex would incorrectly set all values to ``NaN``. Now correctly preserves values when the source index name matches a target level name (:issue:`60923`)
792793

793794
I/O

pandas/core/frame.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5355,6 +5355,18 @@ def reindex(
53555355
limit: int | None = None,
53565356
tolerance=None,
53575357
) -> DataFrame:
5358+
# Automatically detect matching level when reindexing from Index to MultiIndex.
5359+
# This prevents values from being incorrectly set to NaN when the source index
5360+
# name matches a level name in the target MultiIndex. Only applies when source
5361+
# is not already a MultiIndex.
5362+
if (
5363+
level is None
5364+
and index is not None
5365+
and isinstance(index, MultiIndex)
5366+
and not isinstance(self.index, MultiIndex)
5367+
and self.index.name in index.names
5368+
):
5369+
level = self.index.name
53585370
return super().reindex(
53595371
labels=labels,
53605372
index=index,

pandas/tests/frame/methods/test_reindex.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,3 +1258,55 @@ def test_invalid_method(self):
12581258
msg = "Invalid fill method"
12591259
with pytest.raises(ValueError, match=msg):
12601260
df.reindex([1, 0, 2], method="asfreq")
1261+
1262+
def test_reindex_index_name_matches_multiindex_level(self):
1263+
"""
1264+
Test automatic level detection when reindexing from Index to MultiIndex.
1265+
When source index name matches a level name in target MultiIndex and level
1266+
is not specified, should behave same as if level was explicitly set.
1267+
"""
1268+
# Create source DataFrame with named Index
1269+
df = DataFrame(
1270+
{"value": [1, 2], "other": ["A", "B"]},
1271+
index=Index([10, 20], name="a"),
1272+
)
1273+
1274+
# Create target MultiIndex with matching level name
1275+
target = MultiIndex.from_product(
1276+
[[10, 20], ["x", "y"]],
1277+
names=["a", "b"], # 'a' matches source index name
1278+
)
1279+
1280+
result = df.reindex(index=target)
1281+
expected = df.reindex(index=target, level="a")
1282+
1283+
tm.assert_frame_equal(result, expected)
1284+
1285+
# Verify values are propagated correctly
1286+
expected_values = {
1287+
(10, "x"): {"value": 1, "other": "A"},
1288+
(10, "y"): {"value": 1, "other": "A"},
1289+
(20, "x"): {"value": 2, "other": "B"},
1290+
(20, "y"): {"value": 2, "other": "B"},
1291+
}
1292+
for idx, expected_row in expected_values.items():
1293+
for col, val in expected_row.items():
1294+
assert result.loc[idx, col] == val
1295+
1296+
def test_reindex_index_name_no_match_multiindex_level(self):
1297+
"""
1298+
Test reindexing behavior when source index name doesn't match any level
1299+
in target MultiIndex. Should fill with NaN since there's no level match.
1300+
"""
1301+
df = DataFrame({"value": [1, 2]}, index=Index([10, 20], name="different_name"))
1302+
1303+
target = MultiIndex.from_product([[10, 20], ["x", "y"]], names=["a", "b"])
1304+
1305+
result = df.reindex(index=target)
1306+
1307+
# Should fill with NaN since no level match
1308+
assert result.isna().all().all()
1309+
1310+
# Verify shape is correct
1311+
assert result.index.equals(target)
1312+
assert result.columns.equals(df.columns)

pandas/tests/series/methods/test_reindex.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -437,21 +437,21 @@ def test_reindex_expand_nonnano_nat(dtype):
437437

438438

439439
@pytest.mark.parametrize(
440-
"idx,expected_match_level_a",
440+
"name, expected_match_level_a",
441441
[
442442
# Source index has matching name - should match level "a"
443-
(Index([81, 82], name="a"), True),
443+
("a", True),
444444
# Source index has no name - should not match any level
445-
(Index([81, 82]), False),
445+
(None, False),
446446
# Source index name doesn't match any level - should not match
447-
(Index([81, 82], name="x"), False),
447+
("x", False),
448448
],
449449
)
450-
def test_reindex_multiindex_automatic_level(idx, expected_match_level_a):
450+
def test_reindex_multiindex_automatic_level(name, expected_match_level_a):
451451
"""
452452
Test automatic level detection when reindexing from Index to MultiIndex.
453453
"""
454-
series = Series([26.73, 24.255], index=idx)
454+
series = Series([26.73, 24.255], index=Index([81, 82], name=name))
455455
target = MultiIndex.from_product(
456456
[[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]], names=["a", "b", "c"]
457457
)

0 commit comments

Comments
 (0)