Skip to content

Commit 61deba5

Browse files
Joshua Bradtjreback
Joshua Bradt
authored andcommitted
BUG: Fixed handling of non-list value_vars in melt
The value_vars argument of melt is now cast to list like the id_vars argument. closes pandas-dev#15348 Author: Joshua Bradt <[email protected]> Author: Joshua Bradt <[email protected]> Closes pandas-dev#15351 from jbradt/fix-melt and squashes the following commits: a2f2510 [Joshua Bradt] Changed to tm.assertRaisesRegexp for Python 2 compat. 3038f64 [Joshua Bradt] Merge remote-tracking branch 'upstream/master' into fix-melt e907135 [Joshua Bradt] Split test into two parts 20159c1 [Joshua Bradt] Changed exception classes to ValueError. 129d531 [Joshua Bradt] Moved binary operators to satisfy flake8 70d7256 [Joshua Bradt] Merge branch 'master' into fix-melt 455a310 [Joshua Bradt] Tested types when using MultiIndex to ensure they are lists. 7406222 [Joshua Bradt] Fixed formatting. Added comment with issue number to test. d4c5da3 [Joshua Bradt] Improved type checking and tests. Added whatsnew note. 33728de [Joshua Bradt] BUG: Fixed handling of non-list value_vars in melt
1 parent dcb4e47 commit 61deba5

File tree

3 files changed

+52
-4
lines changed

3 files changed

+52
-4
lines changed

doc/source/whatsnew/v0.20.0.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,5 @@ Bug Fixes
578578

579579
- Bug in ``DataFrame.boxplot`` where ``fontsize`` was not applied to the tick labels on both axes (:issue:`15108`)
580580
- Bug in ``Series.replace`` and ``DataFrame.replace`` which failed on empty replacement dicts (:issue:`15289`)
581-
582-
581+
- Bug in ``pd.melt()`` where passing a tuple value for ``value_vars`` caused a ``TypeError`` (:issue:`15348`)
583582
- Bug in ``.eval()`` which caused multiline evals to fail with local variables not on the first line (:issue:`15342`)

pandas/core/reshape.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -761,16 +761,26 @@ def melt(frame, id_vars=None, value_vars=None, var_name=None,
761761
"""
762762
# TODO: what about the existing index?
763763
if id_vars is not None:
764-
if not isinstance(id_vars, (tuple, list, np.ndarray)):
764+
if not is_list_like(id_vars):
765765
id_vars = [id_vars]
766+
elif (isinstance(frame.columns, MultiIndex) and
767+
not isinstance(id_vars, list)):
768+
raise ValueError('id_vars must be a list of tuples when columns'
769+
' are a MultiIndex')
766770
else:
767771
id_vars = list(id_vars)
768772
else:
769773
id_vars = []
770774

771775
if value_vars is not None:
772-
if not isinstance(value_vars, (tuple, list, np.ndarray)):
776+
if not is_list_like(value_vars):
773777
value_vars = [value_vars]
778+
elif (isinstance(frame.columns, MultiIndex) and
779+
not isinstance(value_vars, list)):
780+
raise ValueError('value_vars must be a list of tuples when'
781+
' columns are a MultiIndex')
782+
else:
783+
value_vars = list(value_vars)
774784
frame = frame.loc[:, id_vars + value_vars]
775785
else:
776786
frame = frame.copy()

pandas/tests/test_reshape.py

+39
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,45 @@ def test_value_vars(self):
5656
columns=['id1', 'id2', 'variable', 'value'])
5757
tm.assert_frame_equal(result4, expected4)
5858

59+
def test_value_vars_types(self):
60+
# GH 15348
61+
expected = DataFrame({'id1': self.df['id1'].tolist() * 2,
62+
'id2': self.df['id2'].tolist() * 2,
63+
'variable': ['A'] * 10 + ['B'] * 10,
64+
'value': (self.df['A'].tolist() +
65+
self.df['B'].tolist())},
66+
columns=['id1', 'id2', 'variable', 'value'])
67+
68+
for type_ in (tuple, list, np.array):
69+
result = melt(self.df, id_vars=['id1', 'id2'],
70+
value_vars=type_(('A', 'B')))
71+
tm.assert_frame_equal(result, expected)
72+
73+
def test_vars_work_with_multiindex(self):
74+
expected = DataFrame({
75+
('A', 'a'): self.df1[('A', 'a')],
76+
'CAP': ['B'] * len(self.df1),
77+
'low': ['b'] * len(self.df1),
78+
'value': self.df1[('B', 'b')],
79+
}, columns=[('A', 'a'), 'CAP', 'low', 'value'])
80+
81+
result = melt(self.df1, id_vars=[('A', 'a')], value_vars=[('B', 'b')])
82+
tm.assert_frame_equal(result, expected)
83+
84+
def test_tuple_vars_fail_with_multiindex(self):
85+
# melt should fail with an informative error message if
86+
# the columns have a MultiIndex and a tuple is passed
87+
# for id_vars or value_vars.
88+
tuple_a = ('A', 'a')
89+
list_a = [tuple_a]
90+
tuple_b = ('B', 'b')
91+
list_b = [tuple_b]
92+
93+
for id_vars, value_vars in ((tuple_a, list_b), (list_a, tuple_b),
94+
(tuple_a, tuple_b)):
95+
with tm.assertRaisesRegexp(ValueError, r'MultiIndex'):
96+
melt(self.df1, id_vars=id_vars, value_vars=value_vars)
97+
5998
def test_custom_var_name(self):
6099
result5 = melt(self.df, var_name=self.var_name)
61100
self.assertEqual(result5.columns.tolist(), ['var', 'value'])

0 commit comments

Comments
 (0)