@@ -1813,21 +1813,10 @@ def _get_setitem_indexer(self, key):
18131813
18141814 # -------------------------------------------------------------------
18151815
1816- def _setitem_with_indexer (self , indexer , value , name : str = "iloc" ) -> None :
1816+ def _decide_split_path (self , indexer , value ) -> bool :
18171817 """
1818- _setitem_with_indexer is for setting values on a Series/DataFrame
1819- using positional indexers.
1820-
1821- If the relevant keys are not present, the Series/DataFrame may be
1822- expanded.
1823-
1824- This method is currently broken when dealing with non-unique Indexes,
1825- since it goes from positional indexers back to labels when calling
1826- BlockManager methods, see GH#12991, GH#22046, GH#15686.
1818+ Decide whether we will take a block-by-block path.
18271819 """
1828- info_axis = self .obj ._info_axis_number
1829-
1830- # maybe partial set
18311820 take_split_path = not self .obj ._mgr .is_single_block
18321821
18331822 if not take_split_path and isinstance (value , ABCDataFrame ):
@@ -1855,77 +1844,88 @@ def _setitem_with_indexer(self, indexer, value, name: str = "iloc") -> None:
18551844 take_split_path = True
18561845 break
18571846
1847+ return take_split_path
1848+
1849+ def _setitem_new_column (self , indexer , key , value , name : str ) -> None :
1850+ """
1851+ _setitem_with_indexer cases that can go through DataFrame.__setitem__.
1852+ """
1853+ # add the new item, and set the value
1854+ # must have all defined axes if we have a scalar
1855+ # or a list-like on the non-info axes if we have a
1856+ # list-like
1857+ if not len (self .obj ):
1858+ if not is_list_like_indexer (value ):
1859+ raise ValueError (
1860+ "cannot set a frame with no defined index and a scalar"
1861+ )
1862+ self .obj [key ] = value
1863+ return
1864+
1865+ # add a new item with the dtype setup
1866+ if com .is_null_slice (indexer [0 ]):
1867+ # We are setting an entire column
1868+ self .obj [key ] = value
1869+ return
1870+ elif is_array_like (value ):
1871+ # GH#42099
1872+ arr = extract_array (value , extract_numpy = True )
1873+ taker = - 1 * np .ones (len (self .obj ), dtype = np .intp )
1874+ empty_value = algos .take_nd (arr , taker )
1875+ if not isinstance (value , ABCSeries ):
1876+ # if not Series (in which case we need to align),
1877+ # we can short-circuit
1878+ if isinstance (arr , np .ndarray ) and arr .ndim == 1 and len (arr ) == 1 :
1879+ # NumPy 1.25 deprecation: https://github.com/numpy/numpy/pull/10615
1880+ arr = arr [0 , ...]
1881+ empty_value [indexer [0 ]] = arr
1882+ self .obj [key ] = empty_value
1883+ return
1884+
1885+ self .obj [key ] = empty_value
1886+ elif not is_list_like (value ):
1887+ self .obj [key ] = construct_1d_array_from_inferred_fill_value (
1888+ value , len (self .obj )
1889+ )
1890+ else :
1891+ # FIXME: GH#42099#issuecomment-864326014
1892+ self .obj [key ] = infer_fill_value (value )
1893+
1894+ new_indexer = convert_from_missing_indexer_tuple (indexer , self .obj .axes )
1895+ self ._setitem_with_indexer (new_indexer , value , name )
1896+
1897+ return
1898+
1899+ def _setitem_with_indexer (self , indexer , value , name : str = "iloc" ) -> None :
1900+ """
1901+ _setitem_with_indexer is for setting values on a Series/DataFrame
1902+ using positional indexers.
1903+
1904+ If the relevant keys are not present, the Series/DataFrame may be
1905+ expanded.
1906+ """
1907+ info_axis = self .obj ._info_axis_number
1908+ take_split_path = self ._decide_split_path (indexer , value )
1909+
18581910 if isinstance (indexer , tuple ):
18591911 nindexer = []
18601912 for i , idx in enumerate (indexer ):
1861- if isinstance (idx , dict ):
1913+ idx , missing = convert_missing_indexer (idx )
1914+ if missing :
18621915 # reindex the axis to the new value
18631916 # and set inplace
1864- key , _ = convert_missing_indexer ( idx )
1917+ key = idx
18651918
18661919 # if this is the items axes, then take the main missing
18671920 # path first
1868- # this correctly sets the dtype and avoids cache issues
1921+ # this correctly sets the dtype
18691922 # essentially this separates out the block that is needed
18701923 # to possibly be modified
18711924 if self .ndim > 1 and i == info_axis :
1872- # add the new item, and set the value
1873- # must have all defined axes if we have a scalar
1874- # or a list-like on the non-info axes if we have a
1875- # list-like
1876- if not len (self .obj ):
1877- if not is_list_like_indexer (value ):
1878- raise ValueError (
1879- "cannot set a frame with no "
1880- "defined index and a scalar"
1881- )
1882- self .obj [key ] = value
1883- return
1884-
1885- # add a new item with the dtype setup
1886- if com .is_null_slice (indexer [0 ]):
1887- # We are setting an entire column
1888- self .obj [key ] = value
1889- return
1890- elif is_array_like (value ):
1891- # GH#42099
1892- arr = extract_array (value , extract_numpy = True )
1893- taker = - 1 * np .ones (len (self .obj ), dtype = np .intp )
1894- empty_value = algos .take_nd (arr , taker )
1895- if not isinstance (value , ABCSeries ):
1896- # if not Series (in which case we need to align),
1897- # we can short-circuit
1898- if (
1899- isinstance (arr , np .ndarray )
1900- and arr .ndim == 1
1901- and len (arr ) == 1
1902- ):
1903- # NumPy 1.25 deprecation: https://github.com/numpy/numpy/pull/10615
1904- arr = arr [0 , ...]
1905- empty_value [indexer [0 ]] = arr
1906- self .obj [key ] = empty_value
1907- return
1908-
1909- self .obj [key ] = empty_value
1910- elif not is_list_like (value ):
1911- self .obj [key ] = construct_1d_array_from_inferred_fill_value (
1912- value , len (self .obj )
1913- )
1914- else :
1915- # FIXME: GH#42099#issuecomment-864326014
1916- self .obj [key ] = infer_fill_value (value )
1917-
1918- new_indexer = convert_from_missing_indexer_tuple (
1919- indexer , self .obj .axes
1920- )
1921- self ._setitem_with_indexer (new_indexer , value , name )
1922-
1925+ self ._setitem_new_column (indexer , key , value , name = name )
19231926 return
19241927
19251928 # reindex the axis
1926- # make sure to clear the cache because we are
1927- # just replacing the block manager here
1928- # so the object is the same
19291929 index = self .obj ._get_axis (i )
19301930 labels = index .insert (len (index ), key )
19311931
@@ -2722,7 +2722,7 @@ def convert_missing_indexer(indexer):
27222722 return indexer , False
27232723
27242724
2725- def convert_from_missing_indexer_tuple (indexer , axes ) :
2725+ def convert_from_missing_indexer_tuple (indexer : tuple , axes : list [ Index ]) -> tuple :
27262726 """
27272727 Create a filtered indexer that doesn't have any missing indexers.
27282728 """
0 commit comments