Skip to content

Commit d345967

Browse files
committed
ENH: unstack/stack multiple levels per #370, use Series index per note in #373
1 parent b1fb3bc commit d345967

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
lines changed

pandas/core/frame.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from itertools import izip
1616
from StringIO import StringIO
1717
import csv
18-
import gc
1918
import operator
2019
import sys
2120

@@ -241,6 +240,10 @@ def _init_ndarray(self, values, index, columns, dtype=None,
241240
if isinstance(values, Series) and values.name is not None:
242241
if columns is None:
243242
columns = [values.name]
243+
if index is None:
244+
index = values.index
245+
else:
246+
values = values.reindex(index)
244247

245248
values = _prep_ndarray(values, copy=copy)
246249

@@ -1958,24 +1961,31 @@ def stack(self, level=-1, dropna=True):
19581961
19591962
Parameters
19601963
----------
1961-
level : int or string, default last level
1962-
Level to stack, can pass level name
1964+
level : int, string, or list of these, default last level
1965+
Level(s) to stack, can pass level name
19631966
19641967
Returns
19651968
-------
19661969
stacked : Series
19671970
"""
19681971
from pandas.core.reshape import stack
1969-
return stack(self, level=level, dropna=dropna)
1972+
1973+
if isinstance(level, (tuple, list)):
1974+
result = self
1975+
for lev in level:
1976+
result = stack(result, lev, dropna=dropna)
1977+
return result
1978+
else:
1979+
return stack(self, level, dropna=dropna)
19701980

19711981
def unstack(self, level=-1):
19721982
"""
19731983
"Unstack" level from MultiLevel index to produce reshaped DataFrame
19741984
19751985
Parameters
19761986
----------
1977-
level : int or string, default last level
1978-
Level to unstack, can pass level name
1987+
level : int, string, or list of these, default last level
1988+
Level(s) to unstack, can pass level name
19791989
19801990
Examples
19811991
--------
@@ -1999,10 +2009,14 @@ def unstack(self, level=-1):
19992009
-------
20002010
unstacked : DataFrame
20012011
"""
2002-
from pandas.core.reshape import _Unstacker
2003-
unstacker = _Unstacker(self.values, self.index, level=level,
2004-
value_columns=self.columns)
2005-
return unstacker.get_result()
2012+
from pandas.core.reshape import unstack
2013+
if isinstance(level, (tuple, list)):
2014+
result = self
2015+
for lev in level:
2016+
result = unstack(result, lev)
2017+
return result
2018+
else:
2019+
return unstack(self, level)
20062020

20072021
def delevel(self):
20082022
"""

pandas/core/reshape.py

+10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pandas.core.common import notnull
1212
from pandas.core.index import MultiIndex
1313

14+
1415
class ReshapeError(Exception):
1516
pass
1617

@@ -280,6 +281,15 @@ def _slow_pivot(index, columns, values):
280281

281282
return DataFrame(tree)
282283

284+
def unstack(obj, level):
285+
if isinstance(obj, DataFrame):
286+
columns = obj.columns
287+
else:
288+
columns = None
289+
unstacker = _Unstacker(obj.values, obj.index, level=level,
290+
value_columns=columns)
291+
return unstacker.get_result()
292+
283293
def stack(frame, level=-1, dropna=True):
284294
"""
285295
Convert DataFrame to Series with multi-level Index. Columns become the

pandas/core/series.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -1331,8 +1331,8 @@ def unstack(self, level=-1):
13311331
13321332
Parameters
13331333
----------
1334-
level : int, default last level
1335-
Level to unstack
1334+
level : int, string, or list of these, default last level
1335+
Level(s) to unstack, can pass level name
13361336
13371337
Examples
13381338
--------
@@ -1356,9 +1356,14 @@ def unstack(self, level=-1):
13561356
-------
13571357
unstacked : DataFrame
13581358
"""
1359-
from pandas.core.reshape import _Unstacker
1360-
unstacker = _Unstacker(self.values, self.index, level=level)
1361-
return unstacker.get_result()
1359+
from pandas.core.reshape import unstack
1360+
if isinstance(level, (tuple, list)):
1361+
result = self
1362+
for lev in level:
1363+
result = unstack(result, lev)
1364+
return result
1365+
else:
1366+
return unstack(self, level)
13621367

13631368
#----------------------------------------------------------------------
13641369
# function application

pandas/tests/test_frame.py

+1
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,7 @@ def test_constructor_Series_named(self):
11471147
a = Series([1,2,3], index=['a','b','c'], name='x')
11481148
df = DataFrame(a)
11491149
self.assert_(df.columns[0] == 'x')
1150+
self.assert_(df.index.equals(a.index))
11501151

11511152
def test_astype(self):
11521153
casted = self.frame.astype(int)

pandas/tests/test_multilevel.py

+21
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def setUp(self):
5555
self.tdf = tm.makeTimeDataFrame()
5656
self.ymd = self.tdf.groupby([lambda x: x.year, lambda x: x.month,
5757
lambda x: x.day]).sum()
58+
self.ymd.index.names = ['year', 'month', 'day']
5859

5960
def test_append(self):
6061
a, b = self.frame[:5], self.frame[5:]
@@ -450,6 +451,26 @@ def test_stack_level_name(self):
450451
expected = self.frame.stack()
451452
assert_series_equal(result, expected)
452453

454+
def test_stack_unstack_multiple(self):
455+
unstacked = self.ymd.unstack(['year', 'month'])
456+
expected = self.ymd.unstack('year').unstack('month')
457+
assert_frame_equal(unstacked, expected)
458+
self.assertEquals(unstacked.columns.names,
459+
expected.columns.names)
460+
461+
# series
462+
s = self.ymd['A']
463+
s_unstacked = s.unstack(['year', 'month'])
464+
assert_frame_equal(s_unstacked, expected['A'])
465+
466+
restacked = unstacked.stack(['year', 'month'])
467+
restacked = restacked.swaplevel(0, 1).swaplevel(1, 2)
468+
restacked = restacked.sortlevel(0)
469+
470+
assert_frame_equal(restacked, self.ymd)
471+
self.assertEquals(restacked.index.names,
472+
self.ymd.index.names)
473+
453474
def test_groupby_transform(self):
454475
s = self.frame['A']
455476
grouper = s.index.get_level_values(0)

0 commit comments

Comments
 (0)