@@ -163,6 +163,10 @@ def _setitem_with_indexer(self, indexer, value):
163
163
labels = _safe_append_to_index (index , key )
164
164
self .obj ._data = self .obj .reindex_axis (labels ,i )._data
165
165
166
+ if isinstance (labels ,MultiIndex ):
167
+ self .obj .sortlevel (inplace = True )
168
+ labels = self .obj ._get_axis (i )
169
+
166
170
nindexer .append (labels .get_loc (key ))
167
171
168
172
else :
@@ -198,33 +202,77 @@ def _setitem_with_indexer(self, indexer, value):
198
202
elif self .ndim >= 3 :
199
203
return self .obj .__setitem__ (indexer ,value )
200
204
205
+ # set
206
+ info_axis = self .obj ._info_axis_number
207
+ item_labels = self .obj ._get_axis (info_axis )
208
+
209
+ # if we have a complicated setup, take the split path
210
+ if isinstance (indexer , tuple ) and any ([ isinstance (ax ,MultiIndex ) for ax in self .obj .axes ]):
211
+ take_split_path = True
212
+
201
213
# align and set the values
202
214
if take_split_path :
215
+
203
216
if not isinstance (indexer , tuple ):
204
217
indexer = self ._tuplify (indexer )
205
218
206
219
if isinstance (value , ABCSeries ):
207
220
value = self ._align_series (indexer , value )
208
221
209
- info_axis = self .obj ._info_axis_number
210
222
info_idx = indexer [info_axis ]
211
-
212
223
if com .is_integer (info_idx ):
213
224
info_idx = [info_idx ]
225
+ labels = item_labels [info_idx ]
226
+
227
+ # if we have a partial multiindex, then need to adjust the plane indexer here
228
+ if len (labels ) == 1 and isinstance (self .obj [labels [0 ]].index ,MultiIndex ):
229
+ index = self .obj [labels [0 ]].index
230
+ idx = indexer [:info_axis ][0 ]
231
+ try :
232
+ if idx in index :
233
+ idx = index .get_loc (idx )
234
+ except :
235
+ pass
236
+ plane_indexer = tuple ([idx ]) + indexer [info_axis + 1 :]
237
+ lplane_indexer = _length_of_indexer (plane_indexer [0 ],index )
214
238
215
- plane_indexer = indexer [:info_axis ] + indexer [info_axis + 1 :]
216
- item_labels = self .obj ._get_axis (info_axis )
239
+ if is_list_like (value ) and lplane_indexer != len (value ):
240
+ raise ValueError ("cannot set using a multi-index selection indexer with a different length than the value" )
241
+
242
+ # non-mi
243
+ else :
244
+ plane_indexer = indexer [:info_axis ] + indexer [info_axis + 1 :]
245
+ if info_axis > 0 :
246
+ plane_axis = self .obj .axes [:info_axis ][0 ]
247
+ lplane_indexer = _length_of_indexer (plane_indexer [0 ],plane_axis )
248
+ else :
249
+ lplane_indexer = 0
217
250
218
251
def setter (item , v ):
219
252
s = self .obj [item ]
220
- pi = plane_indexer [0 ] if len ( plane_indexer ) == 1 else plane_indexer
253
+ pi = plane_indexer [0 ] if lplane_indexer == 1 else plane_indexer
221
254
222
255
# set the item, possibly having a dtype change
223
256
s = s .copy ()
224
257
s ._data = s ._data .setitem (pi ,v )
225
258
self .obj [item ] = s
226
259
227
- labels = item_labels [info_idx ]
260
+ def can_do_equal_len ():
261
+ """ return True if we have an equal len settable """
262
+ if not len (labels ) == 1 :
263
+ return False
264
+
265
+ l = len (value )
266
+ item = labels [0 ]
267
+ index = self .obj [item ].index
268
+
269
+ # equal len list/ndarray
270
+ if len (index ) == l :
271
+ return True
272
+ elif lplane_indexer == l :
273
+ return True
274
+
275
+ return False
228
276
229
277
if _is_list_like (value ):
230
278
@@ -251,8 +299,7 @@ def setter(item, v):
251
299
setter (item , value [:,i ])
252
300
253
301
# we have an equal len list/ndarray
254
- elif len (labels ) == 1 and (
255
- len (self .obj [labels [0 ]]) == len (value ) or len (plane_indexer [0 ]) == len (value )):
302
+ elif can_do_equal_len ():
256
303
setter (labels [0 ], value )
257
304
258
305
# per label values
@@ -1104,6 +1151,31 @@ def _convert_key(self, key):
1104
1151
# 32-bit floating point machine epsilon
1105
1152
_eps = np .finfo ('f4' ).eps
1106
1153
1154
+ def _length_of_indexer (indexer ,target = None ):
1155
+ """ return the length of a single non-tuple indexer which could be a slice """
1156
+ if target is not None and isinstance (indexer , slice ):
1157
+ l = len (target )
1158
+ start = indexer .start
1159
+ stop = indexer .stop
1160
+ step = indexer .step
1161
+ if start is None :
1162
+ start = 0
1163
+ elif start < 0 :
1164
+ start += l
1165
+ if stop is None or stop > l :
1166
+ stop = l
1167
+ elif stop < 0 :
1168
+ stop += l
1169
+ if step is None :
1170
+ step = 1
1171
+ elif step < 0 :
1172
+ step = abs (step )
1173
+ return (stop - start ) / step
1174
+ elif isinstance (indexer , (ABCSeries , np .ndarray , list )):
1175
+ return len (indexer )
1176
+ elif not is_list_like (indexer ):
1177
+ return 1
1178
+ raise AssertionError ("cannot find the length of the indexer" )
1107
1179
1108
1180
def _convert_to_index_sliceable (obj , key ):
1109
1181
""" if we are index sliceable, then return my slicer, otherwise return None """
0 commit comments