Skip to content

Commit 7d70710

Browse files
committed
ENH: make it possible to pass keyword argument to .loc
ENH: allow the axis keyword to short-circuit indexing
1 parent 03284f3 commit 7d70710

File tree

4 files changed

+165
-48
lines changed

4 files changed

+165
-48
lines changed

doc/source/indexing.rst

+23-9
Original file line numberDiff line numberDiff line change
@@ -1771,41 +1771,55 @@ As usual, **both sides** of the slicers are included as this is label indexing.
17711771
columns=micolumns).sortlevel().sortlevel(axis=1)
17721772
dfmi
17731773
1774+
Basic multi-index slicing using slices, lists, and labels.
1775+
17741776
.. ipython:: python
17751777
17761778
dfmi.loc[(slice('A1','A3'),slice(None), ['C1','C3']),:]
1777-
dfmi.loc[(slice(None),slice(None), ['C1','C3']),:]
1779+
1780+
You can use a ``pd.IndexSlice`` to shortcut the creation of these slices
1781+
1782+
.. ipython:: python
1783+
1784+
idx = pd.IndexSlice
1785+
dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
17781786
17791787
It is possible to perform quite complicated selections using this method on multiple
17801788
axes at the same time.
17811789

17821790
.. ipython:: python
17831791
17841792
dfmi.loc['A1',(slice(None),'foo')]
1785-
dfmi.loc[(slice(None),slice(None), ['C1','C3']),(slice(None),'foo')]
1786-
dfmi.loc[df[('a','foo')]>200,slice(None), ['C1','C3']),(slice(None),'foo')]
1793+
dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
17871794
1788-
You can use a ``pd.IndexSlice`` to shortcut the creation of these slices
1795+
Using a boolean indexer you can provide selection related to the *values*.
17891796

17901797
.. ipython:: python
17911798
1792-
idx = pd.IndexSlice
1793-
dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
1799+
mask = dfmi[('a','foo')]>200
1800+
dfmi.loc[idx[mask,:,['C1','C3']],idx[:,'foo']]
1801+
1802+
You can also specify the ``axis`` argument to ``.loc`` to interpret the passed
1803+
slicers on a single axis.
1804+
1805+
.. ipython:: python
1806+
1807+
dfmi.loc(axis=0)[:,:,['C1','C3']]
17941808
17951809
Furthermore you can *set* the values using these methods
17961810

17971811
.. ipython:: python
17981812
17991813
df2 = dfmi.copy()
1800-
df2.loc[(slice(None),slice(None), ['C1','C3']),:] = -10
1814+
df2.loc(axis=0)[:,:,['C1','C3']] = -10
18011815
df2
18021816
1803-
You use a right-hand-side of an alignable object as well.
1817+
You can use a right-hand-side of an alignable object as well.
18041818

18051819
.. ipython:: python
18061820
18071821
df2 = dfmi.copy()
1808-
df2.loc[(slice(None),slice(None), ['C1','C3']),:] = df2*1000
1822+
df2.loc[idx[:,:,['C1','C3']],:] = df2*1000
18091823
df2
18101824
18111825
.. _indexing.xs:

doc/source/v0.14.0.txt

+23-9
Original file line numberDiff line numberDiff line change
@@ -85,41 +85,55 @@ See also issues (:issue:`6134`, :issue:`4036`, :issue:`3057`, :issue:`2598`, :is
8585
columns=columns).sortlevel().sortlevel(axis=1)
8686
df
8787

88+
Basic multi-index slicing using slices, lists, and labels.
89+
8890
.. ipython:: python
8991

9092
df.loc[(slice('A1','A3'),slice(None), ['C1','C3']),:]
91-
df.loc[(slice(None),slice(None), ['C1','C3']),:]
93+
94+
You can use a ``pd.IndexSlice`` to shortcut the creation of these slices
95+
96+
.. ipython:: python
97+
98+
idx = pd.IndexSlice
99+
df.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
92100

93101
It is possible to perform quite complicated selections using this method on multiple
94102
axes at the same time.
95103

96104
.. ipython:: python
97105

98106
df.loc['A1',(slice(None),'foo')]
99-
df.loc[(slice(None),slice(None), ['C1','C3']),(slice(None),'foo')]
100-
df.loc[df[('a','foo')]>200,slice(None), ['C1','C3']),(slice(None),'foo')]
107+
df.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
101108

102-
You can use a ``pd.IndexSlice`` to shortcut the creation of these slices
109+
Using a boolean indexer you can provide selection related to the *values*.
103110

104111
.. ipython:: python
105112

106-
idx = pd.IndexSlice
107-
df.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
113+
mask = df[('a','foo')]>200
114+
df.loc[idx[mask,:,['C1','C3']],idx[:,'foo']]
115+
116+
You can also specify the ``axis`` argument to ``.loc`` to interpret the passed
117+
slicers on a single axis.
118+
119+
.. ipython:: python
120+
121+
df.loc(axis=0)[:,:,['C1','C3']]
108122

109123
Furthermore you can *set* the values using these methods
110124

111125
.. ipython:: python
112126

113127
df2 = df.copy()
114-
df2.loc[(slice(None),slice(None), ['C1','C3']),:] = -10
128+
df2.loc(axis=0)[:,:,['C1','C3']] = -10
115129
df2
116130

117-
You use a right-hand-side of an alignable object as well.
131+
You can use a right-hand-side of an alignable object as well.
118132

119133
.. ipython:: python
120134

121135
df2 = df.copy()
122-
df2.loc[(slice(None),slice(None), ['C1','C3']),:] = df2*1000
136+
df2.loc[idx[:,:,['C1','C3']],:] = df2*1000
123137
df2
124138

125139
Prior Version Deprecations/Changes

pandas/core/indexing.py

+46-18
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ def __init__(self, obj, name):
4444
self.obj = obj
4545
self.ndim = obj.ndim
4646
self.name = name
47+
self.axis = None
48+
49+
def __call__(self, *args, **kwargs):
50+
# we need to return a copy of ourselves
51+
self = self.__class__(self.obj, self.name)
52+
53+
# set the passed in values
54+
for k, v in compat.iteritems(kwargs):
55+
setattr(self,k,v)
56+
return self
4757

4858
def __iter__(self):
4959
raise NotImplementedError('ix is not iterable')
@@ -104,23 +114,28 @@ def _slice(self, obj, axis=0, raise_on_error=False, typ=None):
104114

105115
def __setitem__(self, key, value):
106116

107-
# kludgetastic
108-
ax = self.obj._get_axis(0)
109-
if isinstance(ax, MultiIndex):
110-
try:
111-
indexer = ax.get_loc(key)
112-
self._setitem_with_indexer(indexer, value)
113-
return
114-
except Exception:
115-
pass
116-
117-
if isinstance(key, tuple):
118-
if len(key) > self.ndim:
119-
raise IndexingError('only tuples of length <= %d supported' %
120-
self.ndim)
117+
if self.axis is not None:
121118
indexer = self._convert_tuple(key, is_setter=True)
119+
122120
else:
123-
indexer = self._convert_to_indexer(key, is_setter=True)
121+
122+
# kludgetastic
123+
ax = self.obj._get_axis(0)
124+
if isinstance(ax, MultiIndex):
125+
try:
126+
indexer = ax.get_loc(key)
127+
self._setitem_with_indexer(indexer, value)
128+
return
129+
except Exception:
130+
pass
131+
132+
if isinstance(key, tuple):
133+
if len(key) > self.ndim:
134+
raise IndexingError('only tuples of length <= %d supported' %
135+
self.ndim)
136+
indexer = self._convert_tuple(key, is_setter=True)
137+
else:
138+
indexer = self._convert_to_indexer(key, is_setter=True)
124139

125140
self._setitem_with_indexer(indexer, value)
126141

@@ -143,9 +158,17 @@ def _is_nested_tuple_indexer(self, tup):
143158

144159
def _convert_tuple(self, key, is_setter=False):
145160
keyidx = []
146-
for i, k in enumerate(key):
147-
idx = self._convert_to_indexer(k, axis=i, is_setter=is_setter)
148-
keyidx.append(idx)
161+
if self.axis is not None:
162+
axis = self.obj._get_axis_number(self.axis)
163+
for i in range(self.ndim):
164+
if i == axis:
165+
keyidx.append(self._convert_to_indexer(key, axis=axis, is_setter=is_setter))
166+
else:
167+
keyidx.append(slice(None))
168+
else:
169+
for i, k in enumerate(key):
170+
idx = self._convert_to_indexer(k, axis=i, is_setter=is_setter)
171+
keyidx.append(idx)
149172
return tuple(keyidx)
150173

151174
def _convert_scalar_indexer(self, key, axis):
@@ -732,6 +755,11 @@ def _handle_lowerdim_multi_index_axis0(self, tup):
732755

733756
def _getitem_lowerdim(self, tup):
734757

758+
# we can directly get the axis result since the axis is specified
759+
if self.axis is not None:
760+
axis = self.obj._get_axis_number(self.axis)
761+
return self._getitem_axis(tup, axis=axis, validate_iterable=True)
762+
735763
# we may have a nested tuples indexer here
736764
if self._is_nested_tuple_indexer(tup):
737765
return self._getitem_nested_tuple(tup)

pandas/tests/test_indexing.py

+73-12
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def _axify(obj, key, axis):
8383
return k
8484

8585

86+
def _mklbl(prefix,n):
87+
return ["%s%s" % (prefix,i) for i in range(n)]
88+
8689
class TestIndexing(tm.TestCase):
8790

8891
_multiprocess_can_split_ = True
@@ -1066,11 +1069,9 @@ def test_per_axis_per_level_getitem(self):
10661069

10671070
# GH6134
10681071
# example test case
1069-
def mklbl(prefix,n):
1070-
return ["%s%s" % (prefix,i) for i in range(n)]
1071-
1072-
ix = MultiIndex.from_product([mklbl('A',5),mklbl('B',7),mklbl('C',4),mklbl('D',2)])
1072+
ix = MultiIndex.from_product([_mklbl('A',5),_mklbl('B',7),_mklbl('C',4),_mklbl('D',2)])
10731073
df = DataFrame(np.arange(len(ix.get_values())),index=ix)
1074+
10741075
result = df.loc[(slice('A1','A3'),slice(None), ['C1','C3']),:]
10751076
expected = df.loc[[ tuple([a,b,c,d]) for a,b,c,d in df.index.values if (
10761077
a == 'A1' or a == 'A2' or a == 'A3') and (c == 'C1' or c == 'C3')]]
@@ -1150,19 +1151,16 @@ def f():
11501151
df.loc[(slice(None),[1])]
11511152
self.assertRaises(KeyError, f)
11521153

1153-
def test_per_axis_per_level_getitem_doc_examples(self):
1154+
def test_per_axis_per_level_doc_examples(self):
11541155

11551156
# test index maker
11561157
idx = pd.IndexSlice
11571158

11581159
# from indexing.rst / advanced
1159-
def mklbl(prefix,n):
1160-
return ["%s%s" % (prefix,i) for i in range(n)]
1161-
1162-
index = MultiIndex.from_product([mklbl('A',4),
1163-
mklbl('B',2),
1164-
mklbl('C',4),
1165-
mklbl('D',2)])
1160+
index = MultiIndex.from_product([_mklbl('A',4),
1161+
_mklbl('B',2),
1162+
_mklbl('C',4),
1163+
_mklbl('D',2)])
11661164
columns = MultiIndex.from_tuples([('a','foo'),('a','bar'),
11671165
('b','foo'),('b','bah')],
11681166
names=['lvl0', 'lvl1'])
@@ -1189,9 +1187,60 @@ def f():
11891187
self.assertRaises(KeyError, f)
11901188
df = df.sortlevel(axis=1)
11911189

1190+
# slicing
11921191
df.loc['A1',(slice(None),'foo')]
11931192
df.loc[(slice(None),slice(None), ['C1','C3']),(slice(None),'foo')]
11941193

1194+
# setitem
1195+
df.loc(axis=0)[:,:,['C1','C3']] = -10
1196+
1197+
def test_loc_arguments(self):
1198+
1199+
index = MultiIndex.from_product([_mklbl('A',4),
1200+
_mklbl('B',2),
1201+
_mklbl('C',4),
1202+
_mklbl('D',2)])
1203+
columns = MultiIndex.from_tuples([('a','foo'),('a','bar'),
1204+
('b','foo'),('b','bah')],
1205+
names=['lvl0', 'lvl1'])
1206+
df = DataFrame(np.arange(len(index)*len(columns)).reshape((len(index),len(columns))),
1207+
index=index,
1208+
columns=columns).sortlevel().sortlevel(axis=1)
1209+
1210+
1211+
# axis 0
1212+
result = df.loc(axis=0)['A1':'A3',:,['C1','C3']]
1213+
expected = df.loc[[ tuple([a,b,c,d]) for a,b,c,d in df.index.values if (
1214+
a == 'A1' or a == 'A2' or a == 'A3') and (c == 'C1' or c == 'C3')]]
1215+
assert_frame_equal(result, expected)
1216+
1217+
result = df.loc(axis='index')[:,:,['C1','C3']]
1218+
expected = df.loc[[ tuple([a,b,c,d]) for a,b,c,d in df.index.values if (
1219+
c == 'C1' or c == 'C3')]]
1220+
assert_frame_equal(result, expected)
1221+
1222+
# axis 1
1223+
result = df.loc(axis=1)[:,'foo']
1224+
expected = df.loc[:,(slice(None),'foo')]
1225+
assert_frame_equal(result, expected)
1226+
1227+
result = df.loc(axis='columns')[:,'foo']
1228+
expected = df.loc[:,(slice(None),'foo')]
1229+
assert_frame_equal(result, expected)
1230+
1231+
# invalid axis
1232+
def f():
1233+
df.loc(axis=-1)[:,:,['C1','C3']]
1234+
self.assertRaises(ValueError, f)
1235+
1236+
def f():
1237+
df.loc(axis=2)[:,:,['C1','C3']]
1238+
self.assertRaises(ValueError, f)
1239+
1240+
def f():
1241+
df.loc(axis='foo')[:,:,['C1','C3']]
1242+
self.assertRaises(ValueError, f)
1243+
11951244
def test_per_axis_per_level_setitem(self):
11961245

11971246
# test index maker
@@ -1213,6 +1262,12 @@ def test_per_axis_per_level_setitem(self):
12131262
expected.iloc[:,:] = 100
12141263
assert_frame_equal(df, expected)
12151264

1265+
df = df_orig.copy()
1266+
df.loc(axis=0)[:,:] = 100
1267+
expected = df_orig.copy()
1268+
expected.iloc[:,:] = 100
1269+
assert_frame_equal(df, expected)
1270+
12161271
df = df_orig.copy()
12171272
df.loc[(slice(None),slice(None)),(slice(None),slice(None))] = 100
12181273
expected = df_orig.copy()
@@ -1238,6 +1293,12 @@ def test_per_axis_per_level_setitem(self):
12381293
expected.iloc[[0,3]] = 100
12391294
assert_frame_equal(df, expected)
12401295

1296+
df = df_orig.copy()
1297+
df.loc(axis=0)[:,1] = 100
1298+
expected = df_orig.copy()
1299+
expected.iloc[[0,3]] = 100
1300+
assert_frame_equal(df, expected)
1301+
12411302
# columns
12421303
df = df_orig.copy()
12431304
df.loc[:,(slice(None),['foo'])] = 100

0 commit comments

Comments
 (0)