Skip to content

ENH: drop_level argument for xs #4180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 27, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pandas 0.13
overlapping color and style arguments (:issue:`4402`)
- Significant table writing performance improvements in ``HDFStore``
- JSON date serialisation now performed in low-level C code.
- Add ``drop_level`` argument to xs (:issue:`4180`)
- ``Index.copy()`` and ``MultiIndex.copy()`` now accept keyword arguments to
change attributes (i.e., ``names``, ``levels``, ``labels``)
(:issue:`4039`)
Expand Down
11 changes: 8 additions & 3 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -2063,7 +2063,7 @@ def _sanitize_column(self, key, value):
def _series(self):
return self._data.get_series_dict()

def xs(self, key, axis=0, level=None, copy=True):
def xs(self, key, axis=0, level=None, copy=True, drop_level=True):
"""
Returns a cross-section (row(s) or column(s)) from the DataFrame.
Defaults to cross-section on the rows (axis=0).
Expand All @@ -2079,6 +2079,8 @@ def xs(self, key, axis=0, level=None, copy=True):
which levels are used. Levels can be referred by label or position.
copy : boolean, default True
Whether to make a copy of the data
drop_level, default True
If False, returns object with same levels as self.

Examples
--------
Expand Down Expand Up @@ -2130,11 +2132,13 @@ def xs(self, key, axis=0, level=None, copy=True):
Returns
-------
xs : Series or DataFrame

"""
axis = self._get_axis_number(axis)
labels = self._get_axis(axis)
if level is not None:
loc, new_ax = labels.get_loc_level(key, level=level)
loc, new_ax = labels.get_loc_level(key, level=level,
drop_level=drop_level)

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

index = self.index
if isinstance(index, MultiIndex):
loc, new_index = self.index.get_loc_level(key)
loc, new_index = self.index.get_loc_level(key,
drop_level=drop_level)
else:
loc = self.index.get_loc(key)

Expand Down
17 changes: 10 additions & 7 deletions pandas/core/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -2560,7 +2560,7 @@ def get_loc(self, key):
else:
return self._get_level_indexer(key, level=0)

def get_loc_level(self, key, level=0):
def get_loc_level(self, key, level=0, drop_level=True):
"""
Get integer location slice for requested label or tuple

Expand All @@ -2572,7 +2572,9 @@ def get_loc_level(self, key, level=0):
-------
loc : int or slice object
"""
def _drop_levels(indexer, levels):
def _maybe_drop_levels(indexer, levels, drop_level):
if not drop_level:
return self[indexer]
# kludgearound
new_index = self[indexer]
levels = [self._get_level_number(i) for i in levels]
Expand All @@ -2593,7 +2595,8 @@ def _drop_levels(indexer, levels):
loc = mask

result = loc if result is None else result & loc
return result, _drop_levels(result, level)

return result, _maybe_drop_levels(result, level, drop_level)

level = self._get_level_number(level)

Expand All @@ -2606,7 +2609,7 @@ def _drop_levels(indexer, levels):
try:
if key in self.levels[0]:
indexer = self._get_level_indexer(key, level=level)
new_index = _drop_levels(indexer, [0])
new_index = _maybe_drop_levels(indexer, [0], drop_level)
return indexer, new_index
except TypeError:
pass
Expand All @@ -2625,7 +2628,7 @@ def _drop_levels(indexer, levels):
raise KeyError(key)
ilevels = [i for i in range(len(key))
if key[i] != slice(None, None)]
return indexer, _drop_levels(indexer, ilevels)
return indexer, _maybe_drop_levels(indexer, ilevels, drop_level)
else:
indexer = None
for i, k in enumerate(key):
Expand All @@ -2652,10 +2655,10 @@ def _drop_levels(indexer, levels):
indexer = slice(None, None)
ilevels = [i for i in range(len(key))
if key[i] != slice(None, None)]
return indexer, _drop_levels(indexer, ilevels)
return indexer, _maybe_drop_levels(indexer, ilevels, drop_level)
else:
indexer = self._get_level_indexer(key, level=level)
new_index = _drop_levels(indexer, [level])
new_index = _maybe_drop_levels(indexer, [level], drop_level)
return indexer, new_index

def _get_level_indexer(self, key, level=0):
Expand Down
12 changes: 12 additions & 0 deletions pandas/tests/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -7261,6 +7261,18 @@ def test_xs_duplicates(self):
exp = df.irow(2)
assert_series_equal(cross, exp)

def test_xs_keep_level(self):
df = DataFrame({'day': {0: 'sat', 1: 'sun'},
'flavour': {0: 'strawberry', 1: 'strawberry'},
'sales': {0: 10, 1: 12},
'year': {0: 2008, 1: 2008}}).set_index(['year','flavour','day'])
result = df.xs('sat', level='day', drop_level=False)
expected = df[:1]
assert_frame_equal(result, expected)

result = df.xs([2008, 'sat'], level=['year', 'day'], drop_level=False)
assert_frame_equal(result, expected)

def test_pivot(self):
data = {
'index': ['A', 'B', 'C', 'C', 'B', 'A'],
Expand Down