1
1
# pylint: disable=W0223
2
2
3
3
from datetime import datetime
4
- from pandas .core .common import _asarray_tuplesafe
4
+ from pandas .core .common import _asarray_tuplesafe , is_list_like
5
5
from pandas .core .index import Index , MultiIndex , _ensure_index
6
6
from pandas .compat import range , zip
7
7
import pandas .compat as compat
@@ -86,27 +86,66 @@ def __setitem__(self, key, value):
86
86
if len (key ) > self .ndim :
87
87
raise IndexingError ('only tuples of length <= %d supported' ,
88
88
self .ndim )
89
- indexer = self ._convert_tuple (key )
89
+ indexer = self ._convert_tuple (key , is_setter = True )
90
90
else :
91
- indexer = self ._convert_to_indexer (key )
91
+ indexer = self ._convert_to_indexer (key , is_setter = True )
92
92
93
93
self ._setitem_with_indexer (indexer , value )
94
94
95
95
def _has_valid_tuple (self , key ):
96
96
pass
97
97
98
- def _convert_tuple (self , key ):
98
+ def _convert_tuple (self , key , is_setter = False ):
99
99
keyidx = []
100
100
for i , k in enumerate (key ):
101
- idx = self ._convert_to_indexer (k , axis = i )
101
+ idx = self ._convert_to_indexer (k , axis = i , is_setter = is_setter )
102
102
keyidx .append (idx )
103
103
return tuple (keyidx )
104
104
105
105
def _setitem_with_indexer (self , indexer , value ):
106
106
107
107
# also has the side effect of consolidating in-place
108
- # mmm, spaghetti
108
+ from pandas import Panel , DataFrame , Series
109
109
110
+ # maybe partial set
111
+ if isinstance (indexer ,tuple ):
112
+ nindexer = []
113
+ for i , idx in enumerate (indexer ):
114
+ if isinstance (idx , dict ):
115
+
116
+ # reindex the axis to the new value
117
+ # and set inplace
118
+ key ,_ = _convert_missing_indexer (idx )
119
+ labels = self .obj ._get_axis (i ) + Index ([key ])
120
+ self .obj ._data = self .obj .reindex_axis (labels ,i )._data
121
+
122
+ nindexer .append (labels .get_loc (key ))
123
+ else :
124
+ nindexer .append (idx )
125
+
126
+ indexer = tuple (nindexer )
127
+ else :
128
+
129
+ indexer , missing = _convert_missing_indexer (indexer )
130
+
131
+ if missing :
132
+
133
+ # reindex the axis to the new value
134
+ # and set inplace
135
+ if self .ndim == 1 :
136
+ self .obj ._data = self .obj .append (Series (value ,index = [indexer ]))._data
137
+ return
138
+
139
+ elif self .ndim == 2 :
140
+ labels = self .obj ._get_axis (0 ) + Index ([indexer ])
141
+ self .obj ._data = self .obj .reindex_axis (labels ,0 )._data
142
+ return getattr (self .obj ,self .name ).__setitem__ (indexer ,value )
143
+
144
+ # set using setitem (Panel and > dims)
145
+ elif self .ndim >= 3 :
146
+ return self .obj .__setitem__ (indexer ,value )
147
+
148
+ # align and set the values
110
149
if self .obj ._is_mixed_type :
111
150
if not isinstance (indexer , tuple ):
112
151
indexer = self ._tuplify (indexer )
@@ -192,14 +231,73 @@ def setter(item, v):
192
231
def _align_series (self , indexer , ser ):
193
232
# indexer to assign Series can be tuple or scalar
194
233
if isinstance (indexer , tuple ):
234
+
235
+ aligners = [ not _is_null_slice (idx ) for idx in indexer ]
236
+ single_aligner = sum (aligners ) == 1
237
+ is_frame = self .obj .ndim == 2
238
+ is_panel = self .obj .ndim >= 3
239
+
240
+ # are we a single alignable value on a non-primary
241
+ # dim (e.g. panel: 1,2, or frame: 0) ?
242
+ # hence need to align to a single axis dimension
243
+ # rather that find all valid dims
244
+
245
+ # frame
246
+ if is_frame :
247
+ single_aligner = single_aligner and aligners [0 ]
248
+
249
+ # panel
250
+ elif is_panel :
251
+ single_aligner = single_aligner and (aligners [1 ] or aligners [2 ])
252
+
253
+ obj = self .obj
195
254
for i , idx in enumerate (indexer ):
196
- ax = self .obj .axes [i ]
255
+ ax = obj .axes [i ]
256
+
257
+ # multiple aligners (or null slices)
197
258
if com ._is_sequence (idx ) or isinstance (idx , slice ):
259
+ if single_aligner and _is_null_slice (idx ):
260
+ continue
198
261
new_ix = ax [idx ]
262
+ if not is_list_like (new_ix ):
263
+ new_ix = Index ([new_ix ])
199
264
if ser .index .equals (new_ix ):
200
265
return ser .values .copy ()
201
266
return ser .reindex (new_ix ).values
202
267
268
+ # 2 dims
269
+ elif single_aligner and is_frame :
270
+
271
+ # reindex along index
272
+ ax = self .obj .axes [1 ]
273
+ if ser .index .equals (ax ):
274
+ return ser .values .copy ()
275
+ return ser .reindex (ax ).values
276
+
277
+ # >2 dims
278
+ elif single_aligner :
279
+
280
+ broadcast = []
281
+ for n , labels in enumerate (self .obj ._get_plane_axes (i )):
282
+
283
+ # reindex along the matching dimensions
284
+ if len (labels & ser .index ):
285
+ ser = ser .reindex (labels )
286
+ else :
287
+ broadcast .append ((n ,len (labels )))
288
+
289
+ # broadcast along other dims
290
+ ser = ser .values .copy ()
291
+ for (axis ,l ) in broadcast :
292
+ shape = [ - 1 ] * (len (broadcast )+ 1 )
293
+ shape [axis ] = l
294
+ ser = np .tile (ser ,l ).reshape (shape )
295
+
296
+ if self .obj .ndim == 3 :
297
+ ser = ser .T
298
+
299
+ return ser
300
+
203
301
elif np .isscalar (indexer ):
204
302
ax = self .obj ._get_axis (1 )
205
303
@@ -521,7 +619,7 @@ def _reindex(keys, level=None):
521
619
522
620
return result
523
621
524
- def _convert_to_indexer (self , obj , axis = 0 ):
622
+ def _convert_to_indexer (self , obj , axis = 0 , is_setter = False ):
525
623
"""
526
624
Convert indexing key into something we can use to do actual fancy
527
625
indexing on an ndarray
@@ -639,7 +737,14 @@ def _convert_to_indexer(self, obj, axis=0):
639
737
return indexer
640
738
641
739
else :
642
- return labels .get_loc (obj )
740
+ try :
741
+ return labels .get_loc (obj )
742
+ except (KeyError ):
743
+
744
+ # allow a not found key only if we are a setter
745
+ if np .isscalar (obj ) and is_setter :
746
+ return { 'key' : obj }
747
+ raise
643
748
644
749
def _tuplify (self , loc ):
645
750
tup = [slice (None , None ) for _ in range (self .ndim )]
@@ -877,7 +982,7 @@ def _getitem_axis(self, key, axis=0):
877
982
878
983
return self ._get_loc (key ,axis = axis )
879
984
880
- def _convert_to_indexer (self , obj , axis = 0 ):
985
+ def _convert_to_indexer (self , obj , axis = 0 , is_setter = False ):
881
986
""" much simpler as we only have to deal with our valid types """
882
987
if self ._has_valid_type (obj ,axis ):
883
988
return obj
@@ -1028,6 +1133,12 @@ def _slice(self, indexer, axis=0):
1028
1133
return self .obj ._get_values (indexer )
1029
1134
1030
1135
def _setitem_with_indexer (self , indexer , value ):
1136
+
1137
+ # need to delegate to the super setter
1138
+ if isinstance (indexer , dict ):
1139
+ return super (_SeriesIndexer , self )._setitem_with_indexer (indexer , value )
1140
+
1141
+ # fast access
1031
1142
self .obj ._set_values (indexer , value )
1032
1143
1033
1144
def _check_bool_indexer (ax , key ):
@@ -1053,6 +1164,21 @@ def _check_bool_indexer(ax, key):
1053
1164
return result
1054
1165
1055
1166
1167
+ def _convert_missing_indexer (indexer ):
1168
+ """ reverse convert a missing indexer, which is a dict
1169
+ return the scalar indexer and a boolean indicating if we converted """
1170
+
1171
+ if isinstance (indexer , dict ):
1172
+
1173
+ # a missing key (but not a tuple indexer)
1174
+ indexer = indexer ['key' ]
1175
+
1176
+ if isinstance (indexer , bool ):
1177
+ raise KeyError ("cannot use a single bool to index into setitem" )
1178
+ return indexer , True
1179
+
1180
+ return indexer , False
1181
+
1056
1182
def _maybe_convert_indices (indices , n ):
1057
1183
""" if we have negative indicies, translate to postive here
1058
1184
if have indicies that are out-of-bounds, raise an IndexError """
0 commit comments