Skip to content

Commit 08c1302

Browse files
committed
Merge pull request #6022 from jreback/series_mi
BUG: regression in GH6018, indexing with a Series multi-index
2 parents 2f24ff2 + 71719a9 commit 08c1302

File tree

6 files changed

+171
-191
lines changed

6 files changed

+171
-191
lines changed

doc/source/release.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ Bug Fixes
126126
of pandas in QTConsole, now fixed. If you're using an older version and
127127
need to supress the warnings, see (:issue:`5922`).
128128
- Bug in merging ``timedelta`` dtypes (:issue:`5695`)
129-
- Bug in plotting.scatter_matrix function. Wrong alignment among diagonal
129+
- Bug in plotting.scatter_matrix function. Wrong alignment among diagonal
130130
and off-diagonal plots, see (:issue:`5497`).
131+
- Regression in Series with a multi-index via ix (:issue:`6018`)
132+
- Bug in Series.xs with a multi-index (:issue:`6018`)
131133

132134
pandas 0.13.0
133135
-------------

pandas/core/frame.py

-139
Original file line numberDiff line numberDiff line change
@@ -2037,145 +2037,6 @@ def _sanitize_column(self, key, value):
20372037
def _series(self):
20382038
return self._data.get_series_dict()
20392039

2040-
def xs(self, key, axis=0, level=None, copy=True, drop_level=True):
2041-
"""
2042-
Returns a cross-section (row(s) or column(s)) from the DataFrame.
2043-
Defaults to cross-section on the rows (axis=0).
2044-
2045-
Parameters
2046-
----------
2047-
key : object
2048-
Some label contained in the index, or partially in a MultiIndex
2049-
axis : int, default 0
2050-
Axis to retrieve cross-section on
2051-
level : object, defaults to first n levels (n=1 or len(key))
2052-
In case of a key partially contained in a MultiIndex, indicate
2053-
which levels are used. Levels can be referred by label or position.
2054-
copy : boolean, default True
2055-
Whether to make a copy of the data
2056-
drop_level : boolean, default True
2057-
If False, returns object with same levels as self.
2058-
2059-
Examples
2060-
--------
2061-
>>> df
2062-
A B C
2063-
a 4 5 2
2064-
b 4 0 9
2065-
c 9 7 3
2066-
>>> df.xs('a')
2067-
A 4
2068-
B 5
2069-
C 2
2070-
Name: a
2071-
>>> df.xs('C', axis=1)
2072-
a 2
2073-
b 9
2074-
c 3
2075-
Name: C
2076-
>>> s = df.xs('a', copy=False)
2077-
>>> s['A'] = 100
2078-
>>> df
2079-
A B C
2080-
a 100 5 2
2081-
b 4 0 9
2082-
c 9 7 3
2083-
2084-
2085-
>>> df
2086-
A B C D
2087-
first second third
2088-
bar one 1 4 1 8 9
2089-
two 1 7 5 5 0
2090-
baz one 1 6 6 8 0
2091-
three 2 5 3 5 3
2092-
>>> df.xs(('baz', 'three'))
2093-
A B C D
2094-
third
2095-
2 5 3 5 3
2096-
>>> df.xs('one', level=1)
2097-
A B C D
2098-
first third
2099-
bar 1 4 1 8 9
2100-
baz 1 6 6 8 0
2101-
>>> df.xs(('baz', 2), level=[0, 'third'])
2102-
A B C D
2103-
second
2104-
three 5 3 5 3
2105-
2106-
Returns
2107-
-------
2108-
xs : Series or DataFrame
2109-
2110-
"""
2111-
axis = self._get_axis_number(axis)
2112-
labels = self._get_axis(axis)
2113-
if level is not None:
2114-
loc, new_ax = labels.get_loc_level(key, level=level,
2115-
drop_level=drop_level)
2116-
2117-
if not copy and not isinstance(loc, slice):
2118-
raise ValueError('Cannot retrieve view (copy=False)')
2119-
2120-
# level = 0
2121-
loc_is_slice = isinstance(loc, slice)
2122-
if not loc_is_slice:
2123-
indexer = [slice(None)] * 2
2124-
indexer[axis] = loc
2125-
indexer = tuple(indexer)
2126-
else:
2127-
indexer = loc
2128-
lev_num = labels._get_level_number(level)
2129-
if labels.levels[lev_num].inferred_type == 'integer':
2130-
indexer = self.index[loc]
2131-
2132-
# select on the correct axis
2133-
if axis == 1 and loc_is_slice:
2134-
indexer = slice(None), indexer
2135-
result = self.ix[indexer]
2136-
setattr(result, result._get_axis_name(axis), new_ax)
2137-
return result
2138-
2139-
if axis == 1:
2140-
data = self[key]
2141-
if copy:
2142-
data = data.copy()
2143-
return data
2144-
2145-
self._consolidate_inplace()
2146-
2147-
index = self.index
2148-
if isinstance(index, MultiIndex):
2149-
loc, new_index = self.index.get_loc_level(key,
2150-
drop_level=drop_level)
2151-
else:
2152-
loc = self.index.get_loc(key)
2153-
2154-
if isinstance(loc, np.ndarray):
2155-
if loc.dtype == np.bool_:
2156-
inds, = loc.nonzero()
2157-
return self.take(inds, axis=axis, convert=False)
2158-
else:
2159-
return self.take(loc, axis=axis, convert=True)
2160-
2161-
if not np.isscalar(loc):
2162-
new_index = self.index[loc]
2163-
2164-
if np.isscalar(loc):
2165-
2166-
new_values, copy = self._data.fast_2d_xs(loc, copy=copy)
2167-
result = Series(new_values, index=self.columns,
2168-
name=self.index[loc])
2169-
result.is_copy=True
2170-
2171-
else:
2172-
result = self[loc]
2173-
result.index = new_index
2174-
2175-
return result
2176-
2177-
_xs = xs
2178-
21792040
def lookup(self, row_labels, col_labels):
21802041
"""Label-based "fancy indexing" function for DataFrame.
21812042
Given equal-length arrays of row and column labels, return an

pandas/core/generic.py

+139
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,145 @@ def take(self, indices, axis=0, convert=True, is_copy=True):
11331133

11341134
return result
11351135

1136+
def xs(self, key, axis=0, level=None, copy=True, drop_level=True):
1137+
"""
1138+
Returns a cross-section (row(s) or column(s)) from the Series/DataFrame.
1139+
Defaults to cross-section on the rows (axis=0).
1140+
1141+
Parameters
1142+
----------
1143+
key : object
1144+
Some label contained in the index, or partially in a MultiIndex
1145+
axis : int, default 0
1146+
Axis to retrieve cross-section on
1147+
level : object, defaults to first n levels (n=1 or len(key))
1148+
In case of a key partially contained in a MultiIndex, indicate
1149+
which levels are used. Levels can be referred by label or position.
1150+
copy : boolean, default True
1151+
Whether to make a copy of the data
1152+
drop_level : boolean, default True
1153+
If False, returns object with same levels as self.
1154+
1155+
Examples
1156+
--------
1157+
>>> df
1158+
A B C
1159+
a 4 5 2
1160+
b 4 0 9
1161+
c 9 7 3
1162+
>>> df.xs('a')
1163+
A 4
1164+
B 5
1165+
C 2
1166+
Name: a
1167+
>>> df.xs('C', axis=1)
1168+
a 2
1169+
b 9
1170+
c 3
1171+
Name: C
1172+
>>> s = df.xs('a', copy=False)
1173+
>>> s['A'] = 100
1174+
>>> df
1175+
A B C
1176+
a 100 5 2
1177+
b 4 0 9
1178+
c 9 7 3
1179+
1180+
1181+
>>> df
1182+
A B C D
1183+
first second third
1184+
bar one 1 4 1 8 9
1185+
two 1 7 5 5 0
1186+
baz one 1 6 6 8 0
1187+
three 2 5 3 5 3
1188+
>>> df.xs(('baz', 'three'))
1189+
A B C D
1190+
third
1191+
2 5 3 5 3
1192+
>>> df.xs('one', level=1)
1193+
A B C D
1194+
first third
1195+
bar 1 4 1 8 9
1196+
baz 1 6 6 8 0
1197+
>>> df.xs(('baz', 2), level=[0, 'third'])
1198+
A B C D
1199+
second
1200+
three 5 3 5 3
1201+
1202+
Returns
1203+
-------
1204+
xs : Series or DataFrame
1205+
1206+
"""
1207+
axis = self._get_axis_number(axis)
1208+
labels = self._get_axis(axis)
1209+
if level is not None:
1210+
loc, new_ax = labels.get_loc_level(key, level=level,
1211+
drop_level=drop_level)
1212+
1213+
if not copy and not isinstance(loc, slice):
1214+
raise ValueError('Cannot retrieve view (copy=False)')
1215+
1216+
# level = 0
1217+
loc_is_slice = isinstance(loc, slice)
1218+
if not loc_is_slice:
1219+
indexer = [slice(None)] * self.ndim
1220+
indexer[axis] = loc
1221+
indexer = tuple(indexer)
1222+
else:
1223+
indexer = loc
1224+
lev_num = labels._get_level_number(level)
1225+
if labels.levels[lev_num].inferred_type == 'integer':
1226+
indexer = self.index[loc]
1227+
1228+
# select on the correct axis
1229+
if axis == 1 and loc_is_slice:
1230+
indexer = slice(None), indexer
1231+
result = self.ix[indexer]
1232+
setattr(result, result._get_axis_name(axis), new_ax)
1233+
return result
1234+
1235+
if axis == 1:
1236+
data = self[key]
1237+
if copy:
1238+
data = data.copy()
1239+
return data
1240+
1241+
self._consolidate_inplace()
1242+
1243+
index = self.index
1244+
if isinstance(index, MultiIndex):
1245+
loc, new_index = self.index.get_loc_level(key,
1246+
drop_level=drop_level)
1247+
else:
1248+
loc = self.index.get_loc(key)
1249+
1250+
if isinstance(loc, np.ndarray):
1251+
if loc.dtype == np.bool_:
1252+
inds, = loc.nonzero()
1253+
return self.take(inds, axis=axis, convert=False)
1254+
else:
1255+
return self.take(loc, axis=axis, convert=True)
1256+
1257+
if not np.isscalar(loc):
1258+
new_index = self.index[loc]
1259+
1260+
if np.isscalar(loc):
1261+
from pandas import Series
1262+
new_values, copy = self._data.fast_2d_xs(loc, copy=copy)
1263+
result = Series(new_values, index=self.columns,
1264+
name=self.index[loc])
1265+
result.is_copy=True
1266+
1267+
else:
1268+
result = self[loc]
1269+
result.index = new_index
1270+
1271+
return result
1272+
1273+
_xs = xs
1274+
11361275
# TODO: Check if this was clearer in 0.12
11371276
def select(self, crit, axis=0):
11381277
"""

pandas/core/indexing.py

+3-41
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ def __getitem__(self, key):
5757

5858
def _get_label(self, label, axis=0):
5959
# ueber-hack
60-
if (isinstance(label, tuple) and
60+
if self.ndim == 1:
61+
return self.obj[label]
62+
elif (isinstance(label, tuple) and
6163
isinstance(label[axis], slice)):
6264

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

13661368

1367-
class _SeriesIndexer(_IXIndexer):
1368-
1369-
"""
1370-
Class to support fancy indexing, potentially using labels
1371-
1372-
Notes
1373-
-----
1374-
Indexing based on labels is INCLUSIVE
1375-
Slicing uses PYTHON SEMANTICS (endpoint is excluded)
1376-
1377-
If Index contains int labels, these will be used rather than the locations,
1378-
so be very careful (ambiguous).
1379-
1380-
Examples
1381-
--------
1382-
>>> ts.ix[5:10] # equivalent to ts[5:10]
1383-
>>> ts.ix[[date1, date2, date3]]
1384-
>>> ts.ix[date1:date2] = 0
1385-
"""
1386-
1387-
def _get_label(self, key, axis=0):
1388-
return self.obj[key]
1389-
1390-
def _get_loc(self, key, axis=0):
1391-
return self.obj.values[key]
1392-
1393-
def _slice(self, indexer, axis=0, typ=None):
1394-
return self.obj._get_values(indexer)
1395-
1396-
def _setitem_with_indexer(self, indexer, value):
1397-
1398-
# need to delegate to the super setter
1399-
if isinstance(indexer, dict):
1400-
return super(_SeriesIndexer, self)._setitem_with_indexer(indexer,
1401-
value)
1402-
1403-
# fast access
1404-
self.obj._set_values(indexer, value)
1405-
1406-
14071369
def _check_bool_indexer(ax, key):
14081370
# boolean indexing, need to check that the data are aligned, otherwise
14091371
# disallowed

pandas/core/series.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from pandas.core.index import (Index, MultiIndex, InvalidIndexError,
2929
_ensure_index, _handle_legacy_indexes)
3030
from pandas.core.indexing import (
31-
_SeriesIndexer, _check_bool_indexer, _check_slice_bounds,
31+
_check_bool_indexer, _check_slice_bounds,
3232
_is_index_slice, _maybe_convert_indices)
3333
from pandas.core import generic
3434
from pandas.core.internals import SingleBlockManager
@@ -445,11 +445,6 @@ def _maybe_box(self, values):
445445

446446
return values
447447

448-
def _xs(self, key, axis=0, level=None, copy=True):
449-
return self.__getitem__(key)
450-
451-
xs = _xs
452-
453448
def _ixs(self, i, axis=0):
454449
"""
455450
Return the i-th value or values in the Series by location
@@ -2473,10 +2468,6 @@ def to_period(self, freq=None, copy=True):
24732468
Series._add_numeric_operations()
24742469
_INDEX_TYPES = ndarray, Index, list, tuple
24752470

2476-
# reinstall the SeriesIndexer
2477-
# defined in indexing.py; pylint: disable=E0203
2478-
Series._create_indexer('ix', _SeriesIndexer)
2479-
24802471
#------------------------------------------------------------------------------
24812472
# Supplementary functions
24822473

0 commit comments

Comments
 (0)