Skip to content

Commit 2c6b145

Browse files
committed
Merge pull request #9256 from behzadnouri/mi-insert
BUG: bug in multi-index where insert fails
2 parents dcbd007 + f2f9f23 commit 2c6b145

File tree

4 files changed

+75
-31
lines changed

4 files changed

+75
-31
lines changed

doc/source/whatsnew/v0.16.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Bug Fixes
104104
- Bug in ``MultiIndex.has_duplicates`` when having many levels causes an indexer overflow (:issue:`9075`, :issue:`5873`)
105105
- Bug in ``pivot`` and `unstack`` where ``nan`` values would break index alignment (:issue:`7466`)
106106
- Bug in left ``join`` on multi-index with ``sort=True`` or null values (:issue:`9210`).
107+
- Bug in ``MultiIndex`` where inserting new keys would fail (:issue:`9250`).
107108

108109

109110
- Fixed character encoding bug in ``read_stata`` and ``StataReader`` when loading data from a URL (:issue:`9231`).

pandas/core/indexing.py

+22-29
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,27 @@ def _get_loc(self, key, axis=0):
9393
def _slice(self, obj, axis=0, typ=None):
9494
return self.obj._slice(obj, axis=axis, typ=typ)
9595

96-
def __setitem__(self, key, value):
97-
96+
def _get_setitem_indexer(self, key):
9897
if self.axis is not None:
99-
indexer = self._convert_tuple(key, is_setter=True)
98+
return self._convert_tuple(key, is_setter=True)
10099

101-
else:
100+
axis = self.obj._get_axis(0)
101+
if isinstance(axis, MultiIndex):
102+
try:
103+
return axis.get_loc(key)
104+
except Exception:
105+
pass
102106

103-
# kludgetastic
104-
ax = self.obj._get_axis(0)
105-
if isinstance(ax, MultiIndex):
106-
try:
107-
indexer = ax.get_loc(key)
108-
self._setitem_with_indexer(indexer, value)
109-
return
110-
except Exception:
111-
pass
107+
if isinstance(key, tuple) and not self.ndim < len(key):
108+
return self._convert_tuple(key, is_setter=True)
112109

113-
if isinstance(key, tuple):
114-
if len(key) > self.ndim:
115-
raise IndexingError('only tuples of length <= %d supported' %
116-
self.ndim)
117-
indexer = self._convert_tuple(key, is_setter=True)
118-
else:
119-
indexer = self._convert_to_indexer(key, is_setter=True)
110+
try:
111+
return self._convert_to_indexer(key, is_setter=True)
112+
except TypeError:
113+
raise IndexingError(key)
120114

115+
def __setitem__(self, key, value):
116+
indexer = self._get_setitem_indexer(key)
121117
self._setitem_with_indexer(indexer, value)
122118

123119
def _has_valid_type(self, k, axis):
@@ -259,10 +255,6 @@ def _setitem_with_indexer(self, indexer, value):
259255
self.obj._maybe_update_cacher(clear=True)
260256
self.obj.is_copy=None
261257

262-
if isinstance(labels, MultiIndex):
263-
self.obj.sortlevel(inplace=True)
264-
labels = self.obj._get_axis(i)
265-
266258
nindexer.append(labels.get_loc(key))
267259

268260
else:
@@ -1064,7 +1056,12 @@ def _convert_to_indexer(self, obj, axis=0, is_setter=False):
10641056
# if we are a label return me
10651057
try:
10661058
return labels.get_loc(obj)
1067-
except (KeyError, TypeError):
1059+
except KeyError:
1060+
if isinstance(obj, tuple) and isinstance(labels, MultiIndex):
1061+
if is_setter and len(obj) == labels.nlevels:
1062+
return {'key': obj}
1063+
raise
1064+
except TypeError:
10681065
pass
10691066
except (ValueError):
10701067
if not is_int_positional:
@@ -1136,10 +1133,6 @@ def _convert_to_indexer(self, obj, axis=0, is_setter=False):
11361133

11371134
mask = check == -1
11381135
if mask.any():
1139-
1140-
# mi here
1141-
if isinstance(obj, tuple) and is_setter:
1142-
return {'key': obj}
11431136
raise KeyError('%s not in index' % objarr[mask])
11441137

11451138
return _values_from_object(indexer)

pandas/tests/test_frame.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1351,8 +1351,8 @@ def test_getitem_setitem_fancy_exceptions(self):
13511351
ix = self.frame.ix
13521352
with assertRaisesRegexp(IndexingError, 'Too many indexers'):
13531353
ix[:, :, :]
1354-
with assertRaisesRegexp(IndexingError, 'only tuples of length <= 2 '
1355-
'supported'):
1354+
1355+
with assertRaises(IndexingError):
13561356
ix[:, :, :] = 1
13571357

13581358
def test_getitem_setitem_boolean_misaligned(self):

pandas/tests/test_index.py

+50
Original file line numberDiff line numberDiff line change
@@ -3354,6 +3354,56 @@ def test_insert(self):
33543354
assertRaisesRegexp(ValueError, "Item must have length equal to number"
33553355
" of levels", self.index.insert, 0, ('foo2',))
33563356

3357+
left = pd.DataFrame([['a', 'b', 0], ['b', 'd', 1]],
3358+
columns=['1st', '2nd', '3rd'])
3359+
left.set_index(['1st', '2nd'], inplace=True)
3360+
ts = left['3rd'].copy(deep=True)
3361+
3362+
left.loc[('b', 'x'), '3rd'] = 2
3363+
left.loc[('b', 'a'), '3rd'] = -1
3364+
left.loc[('b', 'b'), '3rd'] = 3
3365+
left.loc[('a', 'x'), '3rd'] = 4
3366+
left.loc[('a', 'w'), '3rd'] = 5
3367+
left.loc[('a', 'a'), '3rd'] = 6
3368+
3369+
ts.loc[('b', 'x')] = 2
3370+
ts.loc['b', 'a'] = -1
3371+
ts.loc[('b', 'b')] = 3
3372+
ts.loc['a', 'x'] = 4
3373+
ts.loc[('a', 'w')] = 5
3374+
ts.loc['a', 'a'] = 6
3375+
3376+
right = pd.DataFrame([['a', 'b', 0],
3377+
['b', 'd', 1],
3378+
['b', 'x', 2],
3379+
['b', 'a', -1],
3380+
['b', 'b', 3],
3381+
['a', 'x', 4],
3382+
['a', 'w', 5],
3383+
['a', 'a', 6]],
3384+
columns=['1st', '2nd', '3rd'])
3385+
right.set_index(['1st', '2nd'], inplace=True)
3386+
# FIXME data types changes to float because
3387+
# of intermediate nan insertion;
3388+
tm.assert_frame_equal(left, right, check_dtype=False)
3389+
tm.assert_series_equal(ts, right['3rd'])
3390+
3391+
# GH9250
3392+
idx = [('test1', i) for i in range(5)] + \
3393+
[('test2', i) for i in range(6)] + \
3394+
[('test', 17), ('test', 18)]
3395+
3396+
left = pd.Series(np.linspace(0, 10, 11),
3397+
pd.MultiIndex.from_tuples(idx[:-2]))
3398+
3399+
left.loc[('test', 17)] = 11
3400+
left.ix[('test', 18)] = 12
3401+
3402+
right = pd.Series(np.linspace(0, 12, 13),
3403+
pd.MultiIndex.from_tuples(idx))
3404+
3405+
tm.assert_series_equal(left, right)
3406+
33573407
def test_take_preserve_name(self):
33583408
taken = self.index.take([3, 0, 1])
33593409
self.assertEqual(taken.names, self.index.names)

0 commit comments

Comments
 (0)