diff --git a/doc/source/release.rst b/doc/source/release.rst index 94652eb489f4a..6b167a8939bb5 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -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 ------------- diff --git a/pandas/core/frame.py b/pandas/core/frame.py index fbd49dbe6eeaf..a400bc1b644ba 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -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 diff --git a/pandas/core/generic.py b/pandas/core/generic.py index f1e890216830a..6393083c182e3 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -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): """ diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 751c020fecc27..474b5ae3ac6d6 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -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') @@ -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 diff --git a/pandas/core/series.py b/pandas/core/series.py index a5459bfb2ae8c..555208a7849dc 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -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 @@ -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 @@ -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 diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index 8f3f122fefb88..7c01f5f700c2b 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -832,6 +832,31 @@ def test_loc_multiindex(self): xp = mi_int.ix[4] assert_frame_equal(rs,xp) + def test_series_getitem_multiindex(self): + + # GH 6018 + # series regression getitem with a multi-index + + s = Series([1,2,3]) + s.index = MultiIndex.from_tuples([(0,0),(1,1), (2,1)]) + + result = s[:,0] + expected = Series([1],index=[0]) + assert_series_equal(result,expected) + + result = s.ix[:,1] + expected = Series([2,3],index=[1,2]) + assert_series_equal(result,expected) + + # xs + result = s.xs(0,level=0) + expected = Series([1],index=[0]) + assert_series_equal(result,expected) + + result = s.xs(1,level=1) + expected = Series([2,3],index=[1,2]) + assert_series_equal(result,expected) + def test_ix_general(self): # ix general issues