@@ -1564,6 +1564,17 @@ def _convert_to_indexer(self, key, axis: int, is_setter: bool = False):
15641564 # -------------------------------------------------------------------
15651565
15661566 def _setitem_with_indexer (self , indexer , value ):
1567+ """
1568+ _setitem_with_indexer is for setting values on a Series/DataFrame
1569+ using positional indexers.
1570+
1571+ If the relevant keys are not present, the Series/DataFrame may be
1572+ expanded.
1573+
1574+ This method is currently broken when dealing with non-unique Indexes,
1575+ since it goes from positional indexers back to labels when calling
1576+ BlockManager methods, see GH#12991, GH#22046, GH#15686.
1577+ """
15671578
15681579 # also has the side effect of consolidating in-place
15691580 from pandas import Series
@@ -1678,24 +1689,19 @@ def _setitem_with_indexer(self, indexer, value):
16781689 info_idx = [info_idx ]
16791690 labels = item_labels [info_idx ]
16801691
1681- # if we have a partial multiindex, then need to adjust the plane
1682- # indexer here
1683- if len (labels ) == 1 and isinstance (
1684- self .obj [labels [0 ]].axes [0 ], ABCMultiIndex
1685- ):
1692+ if len (labels ) == 1 :
1693+ # We can operate on a single column
16861694 item = labels [0 ]
1687- obj = self .obj [item ]
1688- index = obj .index
1689- idx = indexer [:info_axis ][0 ]
1695+ idx = indexer [0 ]
16901696
1691- plane_indexer = tuple ([idx ]) + indexer [ info_axis + 1 :]
1692- lplane_indexer = length_of_indexer (plane_indexer [0 ], index )
1697+ plane_indexer = tuple ([idx ])
1698+ lplane_indexer = length_of_indexer (plane_indexer [0 ], self . obj . index )
16931699 # lplane_indexer gives the expected length of obj[idx]
16941700
16951701 # require that we are setting the right number of values that
16961702 # we are indexing
1697- if is_list_like_indexer (value ) and lplane_indexer != len (value ):
1698-
1703+ if is_list_like_indexer (value ) and 0 != lplane_indexer != len (value ):
1704+ # Exclude zero-len for e.g. boolean masking that is all-false
16991705 raise ValueError (
17001706 "cannot set using a multi-index "
17011707 "selection indexer with a different "
@@ -1704,12 +1710,11 @@ def _setitem_with_indexer(self, indexer, value):
17041710
17051711 # non-mi
17061712 else :
1707- plane_indexer = indexer [:info_axis ] + indexer [info_axis + 1 :]
1708- plane_axis = self .obj .axes [:info_axis ][0 ]
1709- lplane_indexer = length_of_indexer (plane_indexer [0 ], plane_axis )
1713+ plane_indexer = indexer [:1 ]
1714+ lplane_indexer = length_of_indexer (plane_indexer [0 ], self .obj .index )
17101715
17111716 def setter (item , v ):
1712- s = self .obj [item ]
1717+ ser = self .obj [item ]
17131718 pi = plane_indexer [0 ] if lplane_indexer == 1 else plane_indexer
17141719
17151720 # perform the equivalent of a setitem on the info axis
@@ -1721,16 +1726,16 @@ def setter(item, v):
17211726 com .is_null_slice (idx ) or com .is_full_slice (idx , len (self .obj ))
17221727 for idx in pi
17231728 ):
1724- s = v
1729+ ser = v
17251730 else :
17261731 # set the item, possibly having a dtype change
1727- s ._consolidate_inplace ()
1728- s = s .copy ()
1729- s ._data = s ._data .setitem (indexer = pi , value = v )
1730- s ._maybe_update_cacher (clear = True )
1732+ ser ._consolidate_inplace ()
1733+ ser = ser .copy ()
1734+ ser ._data = ser ._data .setitem (indexer = pi , value = v )
1735+ ser ._maybe_update_cacher (clear = True )
17311736
17321737 # reset the sliced object if unique
1733- self .obj [item ] = s
1738+ self .obj [item ] = ser
17341739
17351740 # we need an iterable, with a ndim of at least 1
17361741 # eg. don't pass through np.array(0)
0 commit comments