Skip to content

Commit 5c9b671

Browse files
authored
BUG: Fix DataFrame binary arithmatic operation handling of unaligned … (#60538)
* BUG: Fix DataFrame binary arithmatic operation handling of unaligned MultiIndex columns * Address review comment
1 parent 817b706 commit 5c9b671

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

Diff for: doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ MultiIndex
689689
- :meth:`DataFrame.melt` would not accept multiple names in ``var_name`` when the columns were a :class:`MultiIndex` (:issue:`58033`)
690690
- :meth:`MultiIndex.insert` would not insert NA value correctly at unified location of index -1 (:issue:`59003`)
691691
- :func:`MultiIndex.get_level_values` accessing a :class:`DatetimeIndex` does not carry the frequency attribute along (:issue:`58327`, :issue:`57949`)
692+
- Bug in :class:`DataFrame` arithmetic operations in case of unaligned MultiIndex columns (:issue:`60498`)
692693
-
693694

694695
I/O

Diff for: pandas/core/frame.py

+17
Original file line numberDiff line numberDiff line change
@@ -7967,6 +7967,16 @@ def _arith_method_with_reindex(self, right: DataFrame, op) -> DataFrame:
79677967

79687968
new_left = left if lcol_indexer is None else left.iloc[:, lcol_indexer]
79697969
new_right = right if rcol_indexer is None else right.iloc[:, rcol_indexer]
7970+
7971+
# GH#60498 For MultiIndex column alignment
7972+
if isinstance(cols, MultiIndex):
7973+
# When overwriting column names, make a shallow copy so as to not modify
7974+
# the input DFs
7975+
new_left = new_left.copy(deep=False)
7976+
new_right = new_right.copy(deep=False)
7977+
new_left.columns = cols
7978+
new_right.columns = cols
7979+
79707980
result = op(new_left, new_right)
79717981

79727982
# Do the join on the columns instead of using left._align_for_op
@@ -7997,6 +8007,13 @@ def _should_reindex_frame_op(self, right, op, axis: int, fill_value, level) -> b
79978007
if not isinstance(right, DataFrame):
79988008
return False
79998009

8010+
if (
8011+
isinstance(self.columns, MultiIndex)
8012+
or isinstance(right.columns, MultiIndex)
8013+
) and not self.columns.equals(right.columns):
8014+
# GH#60498 Reindex if MultiIndexe columns are not matching
8015+
return True
8016+
80008017
if fill_value is None and level is None and axis == 1:
80018018
# TODO: any other cases we should handle here?
80028019

Diff for: pandas/tests/frame/test_arithmetic.py

+25
Original file line numberDiff line numberDiff line change
@@ -2033,6 +2033,31 @@ def test_arithmetic_multiindex_align():
20332033
tm.assert_frame_equal(result, expected)
20342034

20352035

2036+
def test_arithmetic_multiindex_column_align():
2037+
# GH#60498
2038+
df1 = DataFrame(
2039+
data=100,
2040+
columns=MultiIndex.from_product(
2041+
[["1A", "1B"], ["2A", "2B"]], names=["Lev1", "Lev2"]
2042+
),
2043+
index=["C1", "C2"],
2044+
)
2045+
df2 = DataFrame(
2046+
data=np.array([[0.1, 0.25], [0.2, 0.45]]),
2047+
columns=MultiIndex.from_product([["1A", "1B"]], names=["Lev1"]),
2048+
index=["C1", "C2"],
2049+
)
2050+
expected = DataFrame(
2051+
data=np.array([[10.0, 10.0, 25.0, 25.0], [20.0, 20.0, 45.0, 45.0]]),
2052+
columns=MultiIndex.from_product(
2053+
[["1A", "1B"], ["2A", "2B"]], names=["Lev1", "Lev2"]
2054+
),
2055+
index=["C1", "C2"],
2056+
)
2057+
result = df1 * df2
2058+
tm.assert_frame_equal(result, expected)
2059+
2060+
20362061
def test_bool_frame_mult_float():
20372062
# GH 18549
20382063
df = DataFrame(True, list("ab"), list("cd"))

0 commit comments

Comments
 (0)