From 0e16bf3f02e8c50249dcffed17e49721d1583eaa Mon Sep 17 00:00:00 2001 From: Matias Lindgren Date: Sun, 21 Sep 2025 11:34:44 -0400 Subject: [PATCH 1/4] avoid reindex with empty multi-index --- pandas/core/frame.py | 5 +++++ .../indexing/multiindex/test_multiindex.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 473f69591aa70..a74c947504833 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4450,6 +4450,11 @@ def _set_item_frame_value(self, key, value: DataFrame) -> None: loc, (slice, Series, np.ndarray, Index) ): cols_droplevel = maybe_droplevels(cols, key) + if ( + not isinstance(cols_droplevel, MultiIndex) + and not cols_droplevel.any() + ): + return if len(cols_droplevel) and not cols_droplevel.equals(value.columns): value = value.reindex(cols_droplevel, axis=1) diff --git a/pandas/tests/indexing/multiindex/test_multiindex.py b/pandas/tests/indexing/multiindex/test_multiindex.py index 7140ad7d1e9f5..4b2c9002a3e4d 100644 --- a/pandas/tests/indexing/multiindex/test_multiindex.py +++ b/pandas/tests/indexing/multiindex/test_multiindex.py @@ -249,3 +249,20 @@ def test_groupyby_rename_categories_operation_with_multiindex(self, operation): expected = getattr(a, operation)(b.sort_index(ascending=False)) tm.assert_series_equal(result, expected) + + def test_multiindex_assign_aligns_as_implicit_tuple(self): + # GH 61841 + cols = MultiIndex.from_tuples([("A", "B")]) + df1 = DataFrame([[i] for i in range(3)], columns=cols) + df2 = DataFrame([[i] for i in range(3)], columns=cols) + df3 = DataFrame([[i] for i in range(3)], columns=cols) + s1 = df1["A"].rolling(2).mean() + s2 = df2["A"].rolling(2).mean() + s3 = df3["A"].rolling(2).mean() + df1["C"] = s1 + df1["C"] = s1 + df1["C"] = s1 + df2["C"] = s2 + df3[("C", "")] = s3 + tm.assert_frame_equal(df1, df2) + tm.assert_frame_equal(df1, df3) From 169bf6301f283bc2d48c0e72f7bb00ae8533df8c Mon Sep 17 00:00:00 2001 From: Matias Lindgren Date: Mon, 22 Sep 2025 21:38:13 -0400 Subject: [PATCH 2/4] use copy in tests --- .../indexing/multiindex/test_multiindex.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pandas/tests/indexing/multiindex/test_multiindex.py b/pandas/tests/indexing/multiindex/test_multiindex.py index 4b2c9002a3e4d..2fe8f9affba2d 100644 --- a/pandas/tests/indexing/multiindex/test_multiindex.py +++ b/pandas/tests/indexing/multiindex/test_multiindex.py @@ -254,15 +254,20 @@ def test_multiindex_assign_aligns_as_implicit_tuple(self): # GH 61841 cols = MultiIndex.from_tuples([("A", "B")]) df1 = DataFrame([[i] for i in range(3)], columns=cols) - df2 = DataFrame([[i] for i in range(3)], columns=cols) - df3 = DataFrame([[i] for i in range(3)], columns=cols) + df2 = df1.copy() + df3 = df1.copy() s1 = df1["A"].rolling(2).mean() - s2 = df2["A"].rolling(2).mean() - s3 = df3["A"].rolling(2).mean() - df1["C"] = s1 - df1["C"] = s1 - df1["C"] = s1 + s2 = s1.copy() + s3 = s1.copy() + df2["C"] = s2 df3[("C", "")] = s3 + tm.assert_frame_equal(df2, df3) + + df1["C"] = s1 + tm.assert_frame_equal(df1, df2) + tm.assert_frame_equal(df1, df3) + + df1["C"] = s1 tm.assert_frame_equal(df1, df2) tm.assert_frame_equal(df1, df3) From 53c80b95768459aca1da03a7b9931e8b0ec6a421 Mon Sep 17 00:00:00 2001 From: Matias Lindgren Date: Mon, 22 Sep 2025 21:38:20 -0400 Subject: [PATCH 3/4] add whatsnew entry --- doc/source/whatsnew/v3.0.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index f91d40c4d9ea9..b511074cc3040 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -995,6 +995,7 @@ MultiIndex - 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`) +- Bug in :meth:`DataFrame.__setitem__` where column alignment logic would reindex the assigned value with an empty index, incorrectly setting all values to ``NaN``.(:issue:`61841`) I/O ^^^ From b7a989dcb4390879f4699fafb48049241033e391 Mon Sep 17 00:00:00 2001 From: Matias Lindgren Date: Tue, 23 Sep 2025 22:17:07 -0400 Subject: [PATCH 4/4] fix precommit --- doc/source/whatsnew/v3.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index b511074cc3040..16563ace81b47 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -994,8 +994,8 @@ 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`) - Bug in :meth:`DataFrame.__setitem__` where column alignment logic would reindex the assigned value with an empty index, incorrectly setting all values to ``NaN``.(:issue:`61841`) +- 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 ^^^