Skip to content

Commit 194db26

Browse files
committed
BUG: support partial setting with .ix in Series and DataFrame, refactoring, GH #397
1 parent d15088c commit 194db26

File tree

3 files changed

+96
-45
lines changed

3 files changed

+96
-45
lines changed

TODO.rst

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ TODO
77
----
88
- _consolidate, does it always copy?
99
- Series.align with fill method. Will have to generate more Cython code
10+
- TYPE inference in Index-- more than just datetime!
1011

1112
TODO docs
1213
---------

pandas/core/indexing.py

+79-45
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,33 @@ def _getitem_xs(self, idx, axis=0):
3636
return self.obj.xs(idx, axis=axis, copy=True)
3737

3838
def __setitem__(self, key, value):
39+
# kludgetastic
40+
ax = self.obj._get_axis(0)
41+
if isinstance(ax, MultiIndex):
42+
try:
43+
indexer = ax.get_loc(key)
44+
self._setitem_with_indexer(indexer, value)
45+
return
46+
except Exception:
47+
pass
48+
3949
if isinstance(key, tuple):
4050
if len(key) > self.ndim:
4151
raise IndexingError('only tuples of length <= %d supported',
4252
self.ndim)
43-
44-
keyidx = []
45-
for i, k in enumerate(key):
46-
idx = self._convert_to_indexer(k, axis=i)
47-
keyidx.append(idx)
48-
indexer = _maybe_convert_ix(*keyidx)
53+
indexer = self._convert_tuple(key)
4954
else:
5055
indexer = self._convert_to_indexer(key)
5156

5257
self._setitem_with_indexer(indexer, value)
5358

59+
def _convert_tuple(self, key):
60+
keyidx = []
61+
for i, k in enumerate(key):
62+
idx = self._convert_to_indexer(k, axis=i)
63+
keyidx.append(idx)
64+
return _maybe_convert_ix(*keyidx)
65+
5466
def _setitem_with_indexer(self, indexer, value):
5567
# also has the side effect of consolidating in-place
5668
if self.obj._is_mixed_type:
@@ -274,51 +286,73 @@ class _SeriesIndexer(_NDFrameIndexer):
274286
"""
275287

276288
def __getitem__(self, key):
277-
op = self._fancy_index(key, operation='get')
278-
return op()
279-
280-
def __setitem__(self, key, value):
281-
op = self._fancy_index(key, value, operation='set')
282-
op()
283-
284-
def _fancy_index(self, key, value=None, operation='get'):
285-
# going to great lengths to avoid code dup
286-
obj = self.obj
289+
ax = self.obj.index
290+
if isinstance(ax, MultiIndex):
291+
try:
292+
# key = ax.get_loc(key)
293+
return self._get_default(key)
294+
except Exception:
295+
pass
287296

288-
if operation == 'get':
289-
def do_default():
290-
return obj[key]
297+
if _isboolarr(key):
298+
self._check_boolean_key(key)
299+
elif isinstance(key, slice):
300+
key = self._convert_slice(key)
301+
elif _is_list_like(key):
302+
return self._get_list_like(key)
303+
return self._get_default(key)
291304

292-
def do_list_like():
293-
if isinstance(obj.index, MultiIndex):
294-
try:
295-
return obj[key]
296-
except (KeyError, TypeError, IndexError):
297-
pass
298-
return obj.reindex(key)
299-
else:
300-
def do_default():
301-
obj[key] = value
305+
def __setitem__(self, key, value):
306+
ax = self.obj.index
307+
if isinstance(ax, MultiIndex):
308+
try:
309+
key = ax.get_loc(key)
310+
self._set_default(key, value)
311+
return
312+
except Exception:
313+
pass
302314

303-
def do_list_like():
304-
inds = obj.index.get_indexer(key)
305-
mask = inds == -1
306-
if mask.any():
307-
raise IndexingError('Indices %s not found' % key[mask])
308-
obj.put(inds, value)
309-
op = do_default
310315
if _isboolarr(key):
311-
if _is_series(key):
312-
if not key.index.equals(obj.index):
313-
raise IndexingError('Cannot use boolean index with '
314-
'misaligned or unequal labels')
316+
self._check_boolean_key(key)
315317
elif isinstance(key, slice):
316-
if _is_label_slice(obj.index, key):
317-
i, j = obj.index.slice_locs(key.start, key.stop)
318-
key = slice(i, j)
318+
key = self._convert_slice(key)
319319
elif _is_list_like(key):
320-
op = do_list_like
321-
return op
320+
self._set_list_like(key, value)
321+
return
322+
return self._set_default(key, value)
323+
324+
def _check_boolean_key(self, key):
325+
if _is_series(key):
326+
if not key.index.equals(self.obj.index):
327+
raise IndexingError('Cannot use boolean index with '
328+
'misaligned or unequal labels')
329+
330+
def _convert_slice(self, key):
331+
if _is_label_slice(self.obj.index, key):
332+
i, j = self.obj.index.slice_locs(key.start, key.stop)
333+
key = slice(i, j)
334+
return key
335+
336+
def _get_default(self, key):
337+
return self.obj[key]
338+
339+
def _get_list_like(self, key):
340+
if isinstance(self.obj.index, MultiIndex):
341+
try:
342+
return self.obj[key]
343+
except (KeyError, TypeError, IndexError):
344+
pass
345+
return self.obj.reindex(key)
346+
347+
def _set_default(self, key, value):
348+
self.obj[key] = value
349+
350+
def _set_list_like(self, key, value):
351+
inds = self.obj.index.get_indexer(key)
352+
mask = inds == -1
353+
if mask.any():
354+
raise IndexingError('Indices %s not found' % key[mask])
355+
self.obj.put(inds, value)
322356

323357
def _is_series(obj):
324358
from pandas.core.series import Series

pandas/tests/test_multilevel.py

+16
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,22 @@ def test_ix_preserve_names(self):
663663
self.assertEquals(result.index.name, self.ymd.index.names[2])
664664
self.assertEquals(result2.index.name, self.ymd.index.names[2])
665665

666+
def test_partial_set(self):
667+
# GH #397
668+
df = self.ymd.copy()
669+
exp = self.ymd.copy()
670+
df.ix[2000, 4] = 0
671+
exp.ix[2000, 4].values[:] = 0
672+
assert_frame_equal(df, exp)
673+
674+
df['A'].ix[2000, 4] = 1
675+
exp['A'].ix[2000, 4].values[:] = 1
676+
assert_frame_equal(df, exp)
677+
678+
df.ix[2000] = 5
679+
exp.ix[2000].values[:] = 5
680+
assert_frame_equal(df, exp)
681+
666682
if __name__ == '__main__':
667683

668684
# unittest.main()

0 commit comments

Comments
 (0)