Skip to content

Commit 73ab1c1

Browse files
committed
ENH: group by multiple levels, GH #103
1 parent eb3d1b1 commit 73ab1c1

File tree

4 files changed

+41
-18
lines changed

4 files changed

+41
-18
lines changed

RELEASE.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ pandas 0.5.1
5252
- Add `DataFrame.from_dict` with similar `orient` option
5353
- Can now pass list of tuples or list of lists to `DataFrame.from_records`
5454
for fast conversion to DataFrame (GH #357)
55+
- Can pass multiple levels to groupby, e.g. `df.groupby(level=[0, 1])` (GH
56+
#103)
5557
5658
**Improvements to existing features**
5759

pandas/core/generic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def groupby(self, by=None, axis=0, level=None, as_index=True):
8686
If a dict or Series is passed, the Series or dict VALUES will be
8787
used to determine the groups
8888
axis : int, default 0
89-
level : int, default None
89+
level : int, level name, or sequence of such, default None
9090
If the axis is a MultiIndex (hierarchical), group by a particular
91-
level
91+
level or levels
9292
as_index : boolean, default True
9393
For aggregated output, return object with group labels as the
9494
index. Only relevant for DataFrame input. as_index=False is

pandas/core/groupby.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -608,25 +608,29 @@ def _get_groupings(obj, grouper=None, axis=0, level=None):
608608
if level is not None and not isinstance(group_axis, MultiIndex):
609609
raise ValueError('can only specify level with multi-level index')
610610

611+
if not isinstance(grouper, (tuple, list)):
612+
groupers = [grouper]
613+
else:
614+
groupers = grouper
615+
616+
if isinstance(level, (tuple, list)):
617+
if grouper is None:
618+
groupers = [None] * len(level)
619+
levels = level
620+
else:
621+
levels = [level] * len(groupers)
622+
611623
groupings = []
612624
exclusions = []
613-
if isinstance(grouper, (tuple, list)):
614-
for i, arg in enumerate(grouper):
615-
name = 'key_%d' % i
616-
if isinstance(arg, basestring):
617-
exclusions.append(arg)
618-
name = arg
619-
arg = obj[arg]
620-
621-
ping = Grouping(group_axis, arg, name=name, level=level)
622-
groupings.append(ping)
623-
else:
625+
for i, (gpr, level) in enumerate(zip(groupers, levels)):
624626
name = None
625-
if isinstance(grouper, basestring):
626-
exclusions.append(grouper)
627-
name = grouper
628-
grouper = obj[grouper]
629-
ping = Grouping(group_axis, grouper, name=name, level=level)
627+
if isinstance(gpr, basestring):
628+
exclusions.append(gpr)
629+
name = gpr
630+
gpr = obj[gpr]
631+
ping = Grouping(group_axis, gpr, name=name, level=level)
632+
if ping.name is None:
633+
ping.name = 'key_%d' % i
630634
groupings.append(ping)
631635

632636
return groupings, exclusions

pandas/tests/test_multilevel.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,23 @@ def test_frame_group_ops(self):
597597
skipna=skipna)
598598
assert_frame_equal(leftside, rightside)
599599

600+
def test_groupby_multilevel(self):
601+
result = self.ymd.groupby(level=[0, 1]).mean()
602+
603+
k1 = self.ymd.index.get_level_values(0)
604+
k2 = self.ymd.index.get_level_values(1)
605+
606+
expected = self.ymd.groupby([k1, k2]).mean()
607+
608+
assert_frame_equal(result, expected)
609+
self.assertEquals(result.index.names, self.ymd.index.names[:2])
610+
611+
result2 = self.ymd.groupby(level=self.ymd.index.names[:2]).mean()
612+
assert_frame_equal(result, result2)
613+
614+
def test_groupby_multilevel_with_transform(self):
615+
pass
616+
600617
if __name__ == '__main__':
601618

602619
# unittest.main()

0 commit comments

Comments
 (0)