Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 71 additions & 71 deletions pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1813,21 +1813,10 @@ def _get_setitem_indexer(self, key):

# -------------------------------------------------------------------

def _setitem_with_indexer(self, indexer, value, name: str = "iloc") -> None:
def _decide_split_path(self, indexer, value) -> bool:
"""
_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.
Decide whether we will take a block-by-block path.
"""
info_axis = self.obj._info_axis_number

# maybe partial set
take_split_path = not self.obj._mgr.is_single_block

if not take_split_path and isinstance(value, ABCDataFrame):
Expand Down Expand Up @@ -1855,77 +1844,88 @@ def _setitem_with_indexer(self, indexer, value, name: str = "iloc") -> None:
take_split_path = True
break

return take_split_path

def _setitem_new_column(self, indexer, key, value, name: str) -> None:
"""
_setitem_with_indexer cases that can go through DataFrame.__setitem__.
"""
# add the new item, and set the value
# must have all defined axes if we have a scalar
# or a list-like on the non-info axes if we have a
# list-like
if not len(self.obj):
if not is_list_like_indexer(value):
raise ValueError(
"cannot set a frame with no defined index and a scalar"
)
self.obj[key] = value
return

# add a new item with the dtype setup
if com.is_null_slice(indexer[0]):
# We are setting an entire column
self.obj[key] = value
return
elif is_array_like(value):
# GH#42099
arr = extract_array(value, extract_numpy=True)
taker = -1 * np.ones(len(self.obj), dtype=np.intp)
empty_value = algos.take_nd(arr, taker)
if not isinstance(value, ABCSeries):
# if not Series (in which case we need to align),
# we can short-circuit
if isinstance(arr, np.ndarray) and arr.ndim == 1 and len(arr) == 1:
# NumPy 1.25 deprecation: https://github.com/numpy/numpy/pull/10615
arr = arr[0, ...]
empty_value[indexer[0]] = arr
self.obj[key] = empty_value
return

self.obj[key] = empty_value
elif not is_list_like(value):
self.obj[key] = construct_1d_array_from_inferred_fill_value(
value, len(self.obj)
)
else:
# FIXME: GH#42099#issuecomment-864326014
self.obj[key] = infer_fill_value(value)

new_indexer = convert_from_missing_indexer_tuple(indexer, self.obj.axes)
self._setitem_with_indexer(new_indexer, value, name)

return

def _setitem_with_indexer(self, indexer, value, name: str = "iloc") -> None:
"""
_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.
"""
info_axis = self.obj._info_axis_number
take_split_path = self._decide_split_path(indexer, value)

if isinstance(indexer, tuple):
nindexer = []
for i, idx in enumerate(indexer):
if isinstance(idx, dict):
idx, missing = convert_missing_indexer(idx)
if missing:
# reindex the axis to the new value
# and set inplace
key, _ = convert_missing_indexer(idx)
key = idx

# if this is the items axes, then take the main missing
# path first
# this correctly sets the dtype and avoids cache issues
# this correctly sets the dtype
# essentially this separates out the block that is needed
# to possibly be modified
if self.ndim > 1 and i == info_axis:
# add the new item, and set the value
# must have all defined axes if we have a scalar
# or a list-like on the non-info axes if we have a
# list-like
if not len(self.obj):
if not is_list_like_indexer(value):
raise ValueError(
"cannot set a frame with no "
"defined index and a scalar"
)
self.obj[key] = value
return

# add a new item with the dtype setup
if com.is_null_slice(indexer[0]):
# We are setting an entire column
self.obj[key] = value
return
elif is_array_like(value):
# GH#42099
arr = extract_array(value, extract_numpy=True)
taker = -1 * np.ones(len(self.obj), dtype=np.intp)
empty_value = algos.take_nd(arr, taker)
if not isinstance(value, ABCSeries):
# if not Series (in which case we need to align),
# we can short-circuit
if (
isinstance(arr, np.ndarray)
and arr.ndim == 1
and len(arr) == 1
):
# NumPy 1.25 deprecation: https://github.com/numpy/numpy/pull/10615
arr = arr[0, ...]
empty_value[indexer[0]] = arr
self.obj[key] = empty_value
return

self.obj[key] = empty_value
elif not is_list_like(value):
self.obj[key] = construct_1d_array_from_inferred_fill_value(
value, len(self.obj)
)
else:
# FIXME: GH#42099#issuecomment-864326014
self.obj[key] = infer_fill_value(value)

new_indexer = convert_from_missing_indexer_tuple(
indexer, self.obj.axes
)
self._setitem_with_indexer(new_indexer, value, name)

self._setitem_new_column(indexer, key, value, name=name)
return

# reindex the axis
# make sure to clear the cache because we are
# just replacing the block manager here
# so the object is the same
index = self.obj._get_axis(i)
labels = index.insert(len(index), key)

Expand Down Expand Up @@ -2722,7 +2722,7 @@ def convert_missing_indexer(indexer):
return indexer, False


def convert_from_missing_indexer_tuple(indexer, axes):
def convert_from_missing_indexer_tuple(indexer: tuple, axes: list[Index]) -> tuple:
"""
Create a filtered indexer that doesn't have any missing indexers.
"""
Expand Down
2 changes: 2 additions & 0 deletions pandas/util/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def deprecate_kwarg(
def _deprecate_kwarg(func: F) -> F:
@wraps(func)
def wrapper(*args, **kwargs) -> Callable[..., Any]:
__tracebackhide__ = True
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is unrelated to the rest of the PR, just to de-noise tracebacks im seeing in other branches


old_arg_value = kwargs.pop(old_arg_name, None)

if old_arg_value is not None:
Expand Down
Loading