Skip to content

BUG: regression in GH6018, indexing with a Series multi-index #6022

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 2 commits into from
Jan 21, 2014
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
4 changes: 3 additions & 1 deletion doc/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ Bug Fixes
of pandas in QTConsole, now fixed. If you're using an older version and
need to supress the warnings, see (:issue:`5922`).
- Bug in merging ``timedelta`` dtypes (:issue:`5695`)
- Bug in plotting.scatter_matrix function. Wrong alignment among diagonal
- Bug in plotting.scatter_matrix function. Wrong alignment among diagonal
and off-diagonal plots, see (:issue:`5497`).
- Regression in Series with a multi-index via ix (:issue:`6018`)
- Bug in Series.xs with a multi-index (:issue:`6018`)

pandas 0.13.0
-------------
Expand Down
139 changes: 0 additions & 139 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -2037,145 +2037,6 @@ 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, drop_level=True):
"""
Returns a cross-section (row(s) or column(s)) from the DataFrame.
Defaults to cross-section on the rows (axis=0).

Parameters
----------
key : object
Some label contained in the index, or partially in a MultiIndex
axis : int, default 0
Axis to retrieve cross-section on
level : object, defaults to first n levels (n=1 or len(key))
In case of a key partially contained in a MultiIndex, indicate
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 : boolean, default True
If False, returns object with same levels as self.

Examples
--------
>>> df
A B C
a 4 5 2
b 4 0 9
c 9 7 3
>>> df.xs('a')
A 4
B 5
C 2
Name: a
>>> df.xs('C', axis=1)
a 2
b 9
c 3
Name: C
>>> s = df.xs('a', copy=False)
>>> s['A'] = 100
>>> df
A B C
a 100 5 2
b 4 0 9
c 9 7 3


>>> df
A B C D
first second third
bar one 1 4 1 8 9
two 1 7 5 5 0
baz one 1 6 6 8 0
three 2 5 3 5 3
>>> df.xs(('baz', 'three'))
A B C D
third
2 5 3 5 3
>>> df.xs('one', level=1)
A B C D
first third
bar 1 4 1 8 9
baz 1 6 6 8 0
>>> df.xs(('baz', 2), level=[0, 'third'])
A B C D
second
three 5 3 5 3

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,
drop_level=drop_level)

if not copy and not isinstance(loc, slice):
raise ValueError('Cannot retrieve view (copy=False)')

# level = 0
loc_is_slice = isinstance(loc, slice)
if not loc_is_slice:
indexer = [slice(None)] * 2
indexer[axis] = loc
indexer = tuple(indexer)
else:
indexer = loc
lev_num = labels._get_level_number(level)
if labels.levels[lev_num].inferred_type == 'integer':
indexer = self.index[loc]

# select on the correct axis
if axis == 1 and loc_is_slice:
indexer = slice(None), indexer
result = self.ix[indexer]
setattr(result, result._get_axis_name(axis), new_ax)
return result

if axis == 1:
data = self[key]
if copy:
data = data.copy()
return data

self._consolidate_inplace()

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

if isinstance(loc, np.ndarray):
if loc.dtype == np.bool_:
inds, = loc.nonzero()
return self.take(inds, axis=axis, convert=False)
else:
return self.take(loc, axis=axis, convert=True)

if not np.isscalar(loc):
new_index = self.index[loc]

if np.isscalar(loc):

new_values, copy = self._data.fast_2d_xs(loc, copy=copy)
result = Series(new_values, index=self.columns,
name=self.index[loc])
result.is_copy=True

else:
result = self[loc]
result.index = new_index

return result

_xs = xs

def lookup(self, row_labels, col_labels):
"""Label-based "fancy indexing" function for DataFrame.
Given equal-length arrays of row and column labels, return an
Expand Down
139 changes: 139 additions & 0 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,145 @@ def take(self, indices, axis=0, convert=True, is_copy=True):

return result

def xs(self, key, axis=0, level=None, copy=True, drop_level=True):
"""
Returns a cross-section (row(s) or column(s)) from the Series/DataFrame.
Defaults to cross-section on the rows (axis=0).

Parameters
----------
key : object
Some label contained in the index, or partially in a MultiIndex
axis : int, default 0
Axis to retrieve cross-section on
level : object, defaults to first n levels (n=1 or len(key))
In case of a key partially contained in a MultiIndex, indicate
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 : boolean, default True
If False, returns object with same levels as self.

Examples
--------
>>> df
A B C
a 4 5 2
b 4 0 9
c 9 7 3
>>> df.xs('a')
A 4
B 5
C 2
Name: a
>>> df.xs('C', axis=1)
a 2
b 9
c 3
Name: C
>>> s = df.xs('a', copy=False)
>>> s['A'] = 100
>>> df
A B C
a 100 5 2
b 4 0 9
c 9 7 3


>>> df
A B C D
first second third
bar one 1 4 1 8 9
two 1 7 5 5 0
baz one 1 6 6 8 0
three 2 5 3 5 3
>>> df.xs(('baz', 'three'))
A B C D
third
2 5 3 5 3
>>> df.xs('one', level=1)
A B C D
first third
bar 1 4 1 8 9
baz 1 6 6 8 0
>>> df.xs(('baz', 2), level=[0, 'third'])
A B C D
second
three 5 3 5 3

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,
drop_level=drop_level)

if not copy and not isinstance(loc, slice):
raise ValueError('Cannot retrieve view (copy=False)')

# level = 0
loc_is_slice = isinstance(loc, slice)
if not loc_is_slice:
indexer = [slice(None)] * self.ndim
indexer[axis] = loc
indexer = tuple(indexer)
else:
indexer = loc
lev_num = labels._get_level_number(level)
if labels.levels[lev_num].inferred_type == 'integer':
indexer = self.index[loc]

# select on the correct axis
if axis == 1 and loc_is_slice:
indexer = slice(None), indexer
result = self.ix[indexer]
setattr(result, result._get_axis_name(axis), new_ax)
return result

if axis == 1:
data = self[key]
if copy:
data = data.copy()
return data

self._consolidate_inplace()

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

if isinstance(loc, np.ndarray):
if loc.dtype == np.bool_:
inds, = loc.nonzero()
return self.take(inds, axis=axis, convert=False)
else:
return self.take(loc, axis=axis, convert=True)

if not np.isscalar(loc):
new_index = self.index[loc]

if np.isscalar(loc):
from pandas import Series
new_values, copy = self._data.fast_2d_xs(loc, copy=copy)
result = Series(new_values, index=self.columns,
name=self.index[loc])
result.is_copy=True

else:
result = self[loc]
result.index = new_index

return result

_xs = xs

# TODO: Check if this was clearer in 0.12
def select(self, crit, axis=0):
"""
Expand Down
44 changes: 3 additions & 41 deletions pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ def __getitem__(self, key):

def _get_label(self, label, axis=0):
# ueber-hack
if (isinstance(label, tuple) and
if self.ndim == 1:
return self.obj[label]
elif (isinstance(label, tuple) and
isinstance(label[axis], slice)):

raise IndexingError('no slices here')
Expand Down Expand Up @@ -1364,46 +1366,6 @@ def _crit(v):
return not both_none and (_crit(obj.start) and _crit(obj.stop))


class _SeriesIndexer(_IXIndexer):

"""
Class to support fancy indexing, potentially using labels

Notes
-----
Indexing based on labels is INCLUSIVE
Slicing uses PYTHON SEMANTICS (endpoint is excluded)

If Index contains int labels, these will be used rather than the locations,
so be very careful (ambiguous).

Examples
--------
>>> ts.ix[5:10] # equivalent to ts[5:10]
>>> ts.ix[[date1, date2, date3]]
>>> ts.ix[date1:date2] = 0
"""

def _get_label(self, key, axis=0):
return self.obj[key]

def _get_loc(self, key, axis=0):
return self.obj.values[key]

def _slice(self, indexer, axis=0, typ=None):
return self.obj._get_values(indexer)

def _setitem_with_indexer(self, indexer, value):

# need to delegate to the super setter
if isinstance(indexer, dict):
return super(_SeriesIndexer, self)._setitem_with_indexer(indexer,
value)

# fast access
self.obj._set_values(indexer, value)


def _check_bool_indexer(ax, key):
# boolean indexing, need to check that the data are aligned, otherwise
# disallowed
Expand Down
11 changes: 1 addition & 10 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from pandas.core.index import (Index, MultiIndex, InvalidIndexError,
_ensure_index, _handle_legacy_indexes)
from pandas.core.indexing import (
_SeriesIndexer, _check_bool_indexer, _check_slice_bounds,
_check_bool_indexer, _check_slice_bounds,
_is_index_slice, _maybe_convert_indices)
from pandas.core import generic
from pandas.core.internals import SingleBlockManager
Expand Down Expand Up @@ -445,11 +445,6 @@ def _maybe_box(self, values):

return values

def _xs(self, key, axis=0, level=None, copy=True):
return self.__getitem__(key)

xs = _xs

def _ixs(self, i, axis=0):
"""
Return the i-th value or values in the Series by location
Expand Down Expand Up @@ -2473,10 +2468,6 @@ def to_period(self, freq=None, copy=True):
Series._add_numeric_operations()
_INDEX_TYPES = ndarray, Index, list, tuple

# reinstall the SeriesIndexer
# defined in indexing.py; pylint: disable=E0203
Series._create_indexer('ix', _SeriesIndexer)

#------------------------------------------------------------------------------
# Supplementary functions

Expand Down
Loading