Skip to content

Commit b162331

Browse files
authored
BUG: Let melt name multiple variable columns for labels from a MultiIndex (pandas-dev#58088)
* Let melt name variable columns for a multiindex * Respond to comments * Check is_iterator
1 parent 3b48b17 commit b162331

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ Missing
440440
MultiIndex
441441
^^^^^^^^^^
442442
- :func:`DataFrame.loc` with ``axis=0`` and :class:`MultiIndex` when setting a value adds extra columns (:issue:`58116`)
443+
- :meth:`DataFrame.melt` would not accept multiple names in ``var_name`` when the columns were a :class:`MultiIndex` (:issue:`58033`)
443444
-
444445

445446
I/O

pandas/core/reshape/melt.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
import numpy as np
77

8-
from pandas.core.dtypes.common import is_list_like
8+
from pandas.core.dtypes.common import (
9+
is_iterator,
10+
is_list_like,
11+
)
912
from pandas.core.dtypes.concat import concat_compat
1013
from pandas.core.dtypes.missing import notna
1114

@@ -64,9 +67,10 @@ def melt(
6467
value_vars : scalar, tuple, list, or ndarray, optional
6568
Column(s) to unpivot. If not specified, uses all columns that
6669
are not set as `id_vars`.
67-
var_name : scalar, default None
70+
var_name : scalar, tuple, list, or ndarray, optional
6871
Name to use for the 'variable' column. If None it uses
69-
``frame.columns.name`` or 'variable'.
72+
``frame.columns.name`` or 'variable'. Must be a scalar if columns are a
73+
MultiIndex.
7074
value_name : scalar, default 'value'
7175
Name to use for the 'value' column, can't be an existing column label.
7276
col_level : scalar, optional
@@ -217,7 +221,16 @@ def melt(
217221
frame.columns.name if frame.columns.name is not None else "variable"
218222
]
219223
elif is_list_like(var_name):
220-
raise ValueError(f"{var_name=} must be a scalar.")
224+
if isinstance(frame.columns, MultiIndex):
225+
if is_iterator(var_name):
226+
var_name = list(var_name)
227+
if len(var_name) > len(frame.columns):
228+
raise ValueError(
229+
f"{var_name=} has {len(var_name)} items, "
230+
f"but the dataframe columns only have {len(frame.columns)} levels."
231+
)
232+
else:
233+
raise ValueError(f"{var_name=} must be a scalar.")
221234
else:
222235
var_name = [var_name]
223236

pandas/tests/reshape/test_melt.py

+20
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,26 @@ def test_melt_non_scalar_var_name_raises(self):
533533
with pytest.raises(ValueError, match=r".* must be a scalar."):
534534
df.melt(id_vars=["a"], var_name=[1, 2])
535535

536+
def test_melt_multiindex_columns_var_name(self):
537+
# GH 58033
538+
df = DataFrame({("A", "a"): [1], ("A", "b"): [2]})
539+
540+
expected = DataFrame(
541+
[("A", "a", 1), ("A", "b", 2)], columns=["first", "second", "value"]
542+
)
543+
544+
tm.assert_frame_equal(df.melt(var_name=["first", "second"]), expected)
545+
tm.assert_frame_equal(df.melt(var_name=["first"]), expected[["first", "value"]])
546+
547+
def test_melt_multiindex_columns_var_name_too_many(self):
548+
# GH 58033
549+
df = DataFrame({("A", "a"): [1], ("A", "b"): [2]})
550+
551+
with pytest.raises(
552+
ValueError, match="but the dataframe columns only have 2 levels"
553+
):
554+
df.melt(var_name=["first", "second", "third"])
555+
536556

537557
class TestLreshape:
538558
def test_pairs(self):

0 commit comments

Comments
 (0)