From 530ceaf946c2b28fd2bc4e76ff68949db65339dd Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 11 Feb 2020 11:01:55 -0800 Subject: [PATCH 1/3] CLN: simplify _setitem_with_indexer --- pandas/core/indexing.py | 57 ++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 44b3c318366d2..22a79b20f5c90 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1690,31 +1690,25 @@ def _setitem_with_indexer(self, indexer, value): info_idx = [info_idx] labels = item_labels[info_idx] - # if we have a partial multiindex, then need to adjust the plane - # indexer here - if len(labels) == 1 and isinstance( - self.obj[labels[0]].axes[0], ABCMultiIndex - ): + if len(labels) == 1: + # We can operate on a single column item = labels[0] - obj = self.obj[item] - index = obj.index - idx = indexer[:info_axis][0] + ser = self.obj[item] + idx = indexer[0] - plane_indexer = tuple([idx]) + indexer[info_axis + 1 :] - lplane_indexer = length_of_indexer(plane_indexer[0], index) + plane_indexer = tuple([idx]) + lplane_indexer = length_of_indexer(plane_indexer[0], self.obj.index) # require that we are setting the right number of values that # we are indexing - if ( - is_list_like_indexer(value) - and np.iterable(value) - and lplane_indexer != len(value) - ): + if is_list_like_indexer(value) and lplane_indexer != len(value): - if len(obj[idx]) != len(value): + if len(ser[idx]) != len(value) and len(ser[idx]) != 0: + # empty ser[idx] can happen with e.g. all-False + # boolean indexing raise ValueError( - "cannot set using a multi-index " - "selection indexer with a different " + "cannot set using an " + "indexer with a different " "length than the value" ) @@ -1722,20 +1716,19 @@ def _setitem_with_indexer(self, indexer, value): value = getattr(value, "values", value).ravel() # we can directly set the series here - obj._consolidate_inplace() - obj = obj.copy() - obj._data = obj._data.setitem(indexer=tuple([idx]), value=value) - self.obj[item] = obj + ser._consolidate_inplace() + ser = ser.copy() + ser._data = ser._data.setitem(indexer=tuple([idx]), value=value) + self.obj[item] = ser return # non-mi else: - plane_indexer = indexer[:info_axis] + indexer[info_axis + 1 :] - plane_axis = self.obj.axes[:info_axis][0] - lplane_indexer = length_of_indexer(plane_indexer[0], plane_axis) + plane_indexer = indexer[:1] + lplane_indexer = length_of_indexer(plane_indexer[0], self.obj.index) def setter(item, v): - s = self.obj[item] + ser = self.obj[item] pi = plane_indexer[0] if lplane_indexer == 1 else plane_indexer # perform the equivalent of a setitem on the info axis @@ -1747,16 +1740,16 @@ def setter(item, v): com.is_null_slice(idx) or com.is_full_slice(idx, len(self.obj)) for idx in pi ): - s = v + ser = v else: # set the item, possibly having a dtype change - s._consolidate_inplace() - s = s.copy() - s._data = s._data.setitem(indexer=pi, value=v) - s._maybe_update_cacher(clear=True) + ser._consolidate_inplace() + ser = ser.copy() + ser._data = ser._data.setitem(indexer=pi, value=v) + ser._maybe_update_cacher(clear=True) # reset the sliced object if unique - self.obj[item] = s + self.obj[item] = ser # we need an iterable, with a ndim of at least 1 # eg. don't pass through np.array(0) From 17a90f8347ca3017dba2c12af8731334dd8c467c Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Fri, 14 Feb 2020 11:21:26 -0800 Subject: [PATCH 2/3] docstring --- pandas/core/indexing.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 1af3da20f14d3..75643d56550a7 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1560,6 +1560,17 @@ def _convert_to_indexer(self, key, axis: int, is_setter: bool = False): # ------------------------------------------------------------------- def _setitem_with_indexer(self, indexer, value): + """ + _setitem_with_indexer is for setting values on a Series/DataFrame + using positional indexers. + + If the relevant keys are not present, the Series/DataFrame may be + expanded. + + This method is currently broken when dealing with non-unique Indexes, + since it goes from positional indexers back to labels when calling + BlockManager methods, see GH#12991, GH#22046, GH#15686. + """ # also has the side effect of consolidating in-place from pandas import Series From f0dd71d7fa3f7a4a4d2f9dae71fe0724a61deeb7 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 17 Feb 2020 18:33:41 -0800 Subject: [PATCH 3/3] post-merge fixup --- pandas/core/indexing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index b8298bc718e9e..081f87078d9c9 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1700,7 +1700,8 @@ def _setitem_with_indexer(self, indexer, value): # require that we are setting the right number of values that # we are indexing - if is_list_like_indexer(value) and lplane_indexer != len(value): + if is_list_like_indexer(value) and 0 != lplane_indexer != len(value): + # Exclude zero-len for e.g. boolean masking that is all-false raise ValueError( "cannot set using a multi-index " "selection indexer with a different "