diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index f7b64b03a52fd..eda8c8886b451 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -788,6 +788,7 @@ MultiIndex - Bug in :class:`DataFrame` arithmetic operations in case of unaligned MultiIndex columns (:issue:`60498`) - Bug in :class:`DataFrame` arithmetic operations with :class:`Series` in case of unaligned MultiIndex (:issue:`61009`) - Bug in :meth:`MultiIndex.from_tuples` causing wrong output with input of type tuples having NaN values (:issue:`60695`, :issue:`60988`) +- Bug in :meth:`DataFrame.reindex` and :meth:`Series.reindex` where reindexing :class:`Index` to a :class:`MultiIndex` would incorrectly set all values to ``NaN``.(:issue:`60923`) I/O ^^^ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index cbd853886a0f4..2e921a2a3aa96 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -5373,6 +5373,18 @@ def reindex( """ # TODO: Decide if we care about having different examples for different # kinds + + # Automatically detect matching level when reindexing from Index to MultiIndex. + # This prevents values from being incorrectly set to NaN when the source index + # name matches a index name in the target MultiIndex + if ( + level is None + and index is not None + and isinstance(index, MultiIndex) + and not isinstance(self.index, MultiIndex) + and self.index.name in index.names + ): + level = self.index.name self._check_copy_deprecation(copy) if index is not None and columns is not None and labels is not None: diff --git a/pandas/tests/frame/methods/test_reindex.py b/pandas/tests/frame/methods/test_reindex.py index 37adc31fb0f4d..533902af9aa06 100644 --- a/pandas/tests/frame/methods/test_reindex.py +++ b/pandas/tests/frame/methods/test_reindex.py @@ -1258,3 +1258,28 @@ def test_invalid_method(self): msg = "Invalid fill method" with pytest.raises(ValueError, match=msg): df.reindex([1, 0, 2], method="asfreq") + + def test_reindex_index_name_matches_multiindex_level(self): + df = DataFrame( + {"value": [1, 2], "other": ["A", "B"]}, + index=Index([10, 20], name="a"), + ) + target = MultiIndex.from_product( + [[10, 20], ["x", "y"]], + names=["a", "b"], + ) + + result = df.reindex(index=target) + expected = df.reindex(index=target, level="a") + tm.assert_frame_equal(result, expected) + + def test_reindex_index_name_no_match_multiindex_level(self): + df = DataFrame({"value": [1, 2]}, index=Index([10, 20], name="different_name")) + target = MultiIndex.from_product([[10, 20], ["x", "y"]], names=["a", "b"]) + + result = df.reindex(index=target) + expected = DataFrame( + {"value": [np.nan] * 4}, + index=MultiIndex.from_product([[10, 20], ["x", "y"]], names=["a", "b"]), + ) + tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/series/methods/test_reindex.py b/pandas/tests/series/methods/test_reindex.py index 442d73cadfe47..979acec6d5182 100644 --- a/pandas/tests/series/methods/test_reindex.py +++ b/pandas/tests/series/methods/test_reindex.py @@ -434,3 +434,27 @@ def test_reindex_expand_nonnano_nat(dtype): np.array([1, getattr(np, dtype)("nat", "s")], dtype=f"{dtype}[s]") ) tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "name, expected_match_level_a", + [ + ("a", True), + (None, False), + ("x", False), + ], +) +def test_reindex_multiindex_automatic_level(name, expected_match_level_a): + series = Series([26.73, 24.255], index=Index([81, 82], name=name)) + target = MultiIndex.from_product( + [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]], names=["a", "b", "c"] + ) + + result = series.reindex(target) + + if expected_match_level_a: + expected = series.reindex(target, level="a") + else: + expected = Series(np.nan, index=target, dtype=series.dtype) + + tm.assert_series_equal(result, expected)