Skip to content

Commit 66c4a05

Browse files
committed
Merge pull request #4180 from hayd/xs_drop_selected_level
ENH: drop_level argument for xs
2 parents e3c71f2 + 44c82df commit 66c4a05

File tree

4 files changed

+31
-10
lines changed

4 files changed

+31
-10
lines changed

doc/source/release.rst

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pandas 0.13
5555
overlapping color and style arguments (:issue:`4402`)
5656
- Significant table writing performance improvements in ``HDFStore``
5757
- JSON date serialisation now performed in low-level C code.
58+
- Add ``drop_level`` argument to xs (:issue:`4180`)
5859
- ``Index.copy()`` and ``MultiIndex.copy()`` now accept keyword arguments to
5960
change attributes (i.e., ``names``, ``levels``, ``labels``)
6061
(:issue:`4039`)

pandas/core/frame.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -2063,7 +2063,7 @@ def _sanitize_column(self, key, value):
20632063
def _series(self):
20642064
return self._data.get_series_dict()
20652065

2066-
def xs(self, key, axis=0, level=None, copy=True):
2066+
def xs(self, key, axis=0, level=None, copy=True, drop_level=True):
20672067
"""
20682068
Returns a cross-section (row(s) or column(s)) from the DataFrame.
20692069
Defaults to cross-section on the rows (axis=0).
@@ -2079,6 +2079,8 @@ def xs(self, key, axis=0, level=None, copy=True):
20792079
which levels are used. Levels can be referred by label or position.
20802080
copy : boolean, default True
20812081
Whether to make a copy of the data
2082+
drop_level, default True
2083+
If False, returns object with same levels as self.
20822084
20832085
Examples
20842086
--------
@@ -2130,11 +2132,13 @@ def xs(self, key, axis=0, level=None, copy=True):
21302132
Returns
21312133
-------
21322134
xs : Series or DataFrame
2135+
21332136
"""
21342137
axis = self._get_axis_number(axis)
21352138
labels = self._get_axis(axis)
21362139
if level is not None:
2137-
loc, new_ax = labels.get_loc_level(key, level=level)
2140+
loc, new_ax = labels.get_loc_level(key, level=level,
2141+
drop_level=drop_level)
21382142

21392143
if not copy and not isinstance(loc, slice):
21402144
raise ValueError('Cannot retrieve view (copy=False)')
@@ -2168,7 +2172,8 @@ def xs(self, key, axis=0, level=None, copy=True):
21682172

21692173
index = self.index
21702174
if isinstance(index, MultiIndex):
2171-
loc, new_index = self.index.get_loc_level(key)
2175+
loc, new_index = self.index.get_loc_level(key,
2176+
drop_level=drop_level)
21722177
else:
21732178
loc = self.index.get_loc(key)
21742179

pandas/core/index.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -2560,7 +2560,7 @@ def get_loc(self, key):
25602560
else:
25612561
return self._get_level_indexer(key, level=0)
25622562

2563-
def get_loc_level(self, key, level=0):
2563+
def get_loc_level(self, key, level=0, drop_level=True):
25642564
"""
25652565
Get integer location slice for requested label or tuple
25662566
@@ -2572,7 +2572,9 @@ def get_loc_level(self, key, level=0):
25722572
-------
25732573
loc : int or slice object
25742574
"""
2575-
def _drop_levels(indexer, levels):
2575+
def _maybe_drop_levels(indexer, levels, drop_level):
2576+
if not drop_level:
2577+
return self[indexer]
25762578
# kludgearound
25772579
new_index = self[indexer]
25782580
levels = [self._get_level_number(i) for i in levels]
@@ -2593,7 +2595,8 @@ def _drop_levels(indexer, levels):
25932595
loc = mask
25942596

25952597
result = loc if result is None else result & loc
2596-
return result, _drop_levels(result, level)
2598+
2599+
return result, _maybe_drop_levels(result, level, drop_level)
25972600

25982601
level = self._get_level_number(level)
25992602

@@ -2606,7 +2609,7 @@ def _drop_levels(indexer, levels):
26062609
try:
26072610
if key in self.levels[0]:
26082611
indexer = self._get_level_indexer(key, level=level)
2609-
new_index = _drop_levels(indexer, [0])
2612+
new_index = _maybe_drop_levels(indexer, [0], drop_level)
26102613
return indexer, new_index
26112614
except TypeError:
26122615
pass
@@ -2625,7 +2628,7 @@ def _drop_levels(indexer, levels):
26252628
raise KeyError(key)
26262629
ilevels = [i for i in range(len(key))
26272630
if key[i] != slice(None, None)]
2628-
return indexer, _drop_levels(indexer, ilevels)
2631+
return indexer, _maybe_drop_levels(indexer, ilevels, drop_level)
26292632
else:
26302633
indexer = None
26312634
for i, k in enumerate(key):
@@ -2652,10 +2655,10 @@ def _drop_levels(indexer, levels):
26522655
indexer = slice(None, None)
26532656
ilevels = [i for i in range(len(key))
26542657
if key[i] != slice(None, None)]
2655-
return indexer, _drop_levels(indexer, ilevels)
2658+
return indexer, _maybe_drop_levels(indexer, ilevels, drop_level)
26562659
else:
26572660
indexer = self._get_level_indexer(key, level=level)
2658-
new_index = _drop_levels(indexer, [level])
2661+
new_index = _maybe_drop_levels(indexer, [level], drop_level)
26592662
return indexer, new_index
26602663

26612664
def _get_level_indexer(self, key, level=0):

pandas/tests/test_frame.py

+12
Original file line numberDiff line numberDiff line change
@@ -7330,6 +7330,18 @@ def test_xs_duplicates(self):
73307330
exp = df.irow(2)
73317331
assert_series_equal(cross, exp)
73327332

7333+
def test_xs_keep_level(self):
7334+
df = DataFrame({'day': {0: 'sat', 1: 'sun'},
7335+
'flavour': {0: 'strawberry', 1: 'strawberry'},
7336+
'sales': {0: 10, 1: 12},
7337+
'year': {0: 2008, 1: 2008}}).set_index(['year','flavour','day'])
7338+
result = df.xs('sat', level='day', drop_level=False)
7339+
expected = df[:1]
7340+
assert_frame_equal(result, expected)
7341+
7342+
result = df.xs([2008, 'sat'], level=['year', 'day'], drop_level=False)
7343+
assert_frame_equal(result, expected)
7344+
73337345
def test_pivot(self):
73347346
data = {
73357347
'index': ['A', 'B', 'C', 'C', 'B', 'A'],

0 commit comments

Comments
 (0)