Skip to content

Commit 304433a

Browse files
committed
ENH: xs level can take multiple levels, pass multiple levels to MultiIndex.droplevel, GH #371
1 parent a4b0a2e commit 304433a

File tree

4 files changed

+61
-5
lines changed

4 files changed

+61
-5
lines changed

pandas/core/frame.py

+1
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,7 @@ def xs(self, key, axis=0, level=None, copy=True):
14261426
indexer = loc
14271427

14281428
result = self.ix[indexer]
1429+
14291430
new_ax = result._get_axis(axis).droplevel(level)
14301431
setattr(result, result._get_axis_name(axis), new_ax)
14311432
return result

pandas/core/index.py

+27-4
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,7 @@ def droplevel(self, level=0):
13941394
13951395
Parameters
13961396
----------
1397-
level : int
1397+
level : int/level name or list thereof
13981398
13991399
Notes
14001400
-----
@@ -1404,12 +1404,20 @@ def droplevel(self, level=0):
14041404
-------
14051405
index : Index or MultiIndex
14061406
"""
1407+
levels = level
1408+
if not isinstance(levels, (tuple, list)):
1409+
levels = [level]
1410+
14071411
new_levels = list(self.levels)
1408-
new_levels.pop(level)
14091412
new_labels = list(self.labels)
1410-
new_labels.pop(level)
14111413
new_names = list(self.names)
1412-
new_names.pop(level)
1414+
1415+
levnums = sorted(self._get_level_number(lev) for lev in levels)[::-1]
1416+
1417+
for i in levnums:
1418+
new_levels.pop(i)
1419+
new_labels.pop(i)
1420+
new_names.pop(i)
14131421

14141422
if len(new_levels) == 1:
14151423
result = new_levels[0].take(new_labels[0])
@@ -1692,6 +1700,21 @@ def get_loc_level(self, key, level=0):
16921700
-------
16931701
loc : int or slice object
16941702
"""
1703+
if isinstance(level, (tuple, list)):
1704+
assert(len(key) == len(level))
1705+
result = None
1706+
for lev, k in zip(level, key):
1707+
loc = self.get_loc_level(k, level=lev)
1708+
if isinstance(loc, slice):
1709+
mask = np.zeros(len(self), dtype=bool)
1710+
mask[loc] = True
1711+
loc = mask
1712+
1713+
result = loc if result is None else result & loc
1714+
return result
1715+
1716+
level = self._get_level_number(level)
1717+
16951718
if isinstance(key, tuple) and level == 0:
16961719
if not any(isinstance(k, slice) for k in key):
16971720
if len(key) == self.nlevels:

pandas/tests/test_index.py

+17
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,23 @@ def test_droplevel_with_names(self):
12081208
dropped = index.droplevel(0)
12091209
self.assertEqual(dropped.names, ['two', 'three'])
12101210

1211+
dropped = index.droplevel('two')
1212+
expected = index.droplevel(1)
1213+
self.assert_(dropped.equals(expected))
1214+
1215+
def test_droplevel_multiple(self):
1216+
index = MultiIndex(levels=[Index(range(4)),
1217+
Index(range(4)),
1218+
Index(range(4))],
1219+
labels=[np.array([0, 0, 1, 2, 2, 2, 3, 3]),
1220+
np.array([0, 1, 0, 0, 0, 1, 0, 1]),
1221+
np.array([1, 0, 1, 1, 0, 0, 1, 0])],
1222+
names=['one', 'two', 'three'])
1223+
1224+
dropped = index[:2].droplevel(['three', 'one'])
1225+
expected = index[:2].droplevel(2).droplevel(0)
1226+
self.assert_(dropped.equals(expected))
1227+
12111228
def test_insert(self):
12121229
# key contained in all levels
12131230
new_index = self.index.insert(0, ('bar', 'two'))

pandas/tests/test_multilevel.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ def test_xs_partial(self):
229229
assert_frame_equal(result, result2)
230230

231231
def test_xs_level(self):
232-
result = self.frame.xs('two', level=1)
232+
result = self.frame.xs('two', level='second')
233233
expected = self.frame[self.frame.index.get_level_values(1) == 'two']
234234
expected.index = expected.index.droplevel(1)
235235

@@ -243,6 +243,21 @@ def test_xs_level(self):
243243
expected.index = expected.index.droplevel(2)
244244
assert_frame_equal(result, expected)
245245

246+
def test_xs_level_multiple(self):
247+
from pandas import read_table
248+
from StringIO import StringIO
249+
text = """ A B C D E
250+
one two three four
251+
a b 10.0032 5 -0.5109 -2.3358 -0.4645 0.05076 0.3640
252+
a q 20 4 0.4473 1.4152 0.2834 1.00661 0.1744
253+
x q 30 3 -0.6662 -0.5243 -0.3580 0.89145 2.5838"""
254+
255+
df = read_table(StringIO(text), sep='\s+')
256+
257+
result = df.xs(('a', 4), level=['one', 'four'])
258+
expected = df.xs('a').xs(4, level='four')
259+
assert_frame_equal(result, expected)
260+
246261
def test_xs_level0(self):
247262
from pandas import read_table
248263
from StringIO import StringIO

0 commit comments

Comments
 (0)