Skip to content

Commit b9980b6

Browse files
PERF: first try inplace setitem for .at indexer (#49772)
1 parent 4901410 commit b9980b6

File tree

5 files changed

+29
-10
lines changed

5 files changed

+29
-10
lines changed

asv_bench/benchmarks/indexing.py

+6
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ def setup(self):
139139
def time_loc(self):
140140
self.df.loc[self.idx_scalar, self.col_scalar]
141141

142+
def time_at(self):
143+
self.df.at[self.idx_scalar, self.col_scalar]
144+
145+
def time_at_setitem(self):
146+
self.df.at[self.idx_scalar, self.col_scalar] = 0.0
147+
142148
def time_getitem_scalar(self):
143149
self.df[self.col_scalar][self.idx_scalar]
144150

doc/source/whatsnew/v1.5.3.rst

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Fixed regressions
1818
- Fixed regression in :meth:`DataFrameGroupBy.transform` when used with ``as_index=False`` (:issue:`49834`)
1919
- Enforced reversion of ``color`` as an alias for ``c`` and ``size`` as an alias for ``s`` in function :meth:`DataFrame.plot.scatter` (:issue:`49732`)
2020
- Fixed regression in :meth:`SeriesGroupBy.apply` setting a ``name`` attribute on the result if the result was a :class:`DataFrame` (:issue:`49907`)
21+
- Fixed performance regression in setting with the :meth:`~DataFrame.at` indexer (:issue:`49771`)
2122
-
2223

2324
.. ---------------------------------------------------------------------------

pandas/core/frame.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
)
110110

111111
from pandas.core.dtypes.cast import (
112+
LossySetitemError,
112113
can_hold_element,
113114
construct_1d_arraylike_from_scalar,
114115
construct_2d_arraylike_from_scalar,
@@ -4216,13 +4217,14 @@ def _set_value(
42164217
else:
42174218
icol = self.columns.get_loc(col)
42184219
iindex = self.index.get_loc(index)
4219-
self._mgr.column_setitem(icol, iindex, value)
4220+
self._mgr.column_setitem(icol, iindex, value, inplace=True)
42204221
self._clear_item_cache()
42214222

4222-
except (KeyError, TypeError, ValueError):
4223+
except (KeyError, TypeError, ValueError, LossySetitemError):
42234224
# get_loc might raise a KeyError for missing labels (falling back
42244225
# to (i)loc will do expansion of the index)
4225-
# column_setitem will do validation that may raise TypeError or ValueError
4226+
# column_setitem will do validation that may raise TypeError,
4227+
# ValueError, or LossySetitemError
42264228
# set using a non-recursive method & reset the cache
42274229
if takeable:
42284230
self.iloc[index, col] = value

pandas/core/internals/array_manager.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,9 @@ def iset(
855855
self.arrays[mgr_idx] = value_arr
856856
return
857857

858-
def column_setitem(self, loc: int, idx: int | slice | np.ndarray, value) -> None:
858+
def column_setitem(
859+
self, loc: int, idx: int | slice | np.ndarray, value, inplace: bool = False
860+
) -> None:
859861
"""
860862
Set values ("setitem") into a single column (not setting the full column).
861863
@@ -866,9 +868,12 @@ def column_setitem(self, loc: int, idx: int | slice | np.ndarray, value) -> None
866868
raise TypeError("The column index should be an integer")
867869
arr = self.arrays[loc]
868870
mgr = SingleArrayManager([arr], [self._axes[0]])
869-
new_mgr = mgr.setitem((idx,), value)
870-
# update existing ArrayManager in-place
871-
self.arrays[loc] = new_mgr.arrays[0]
871+
if inplace:
872+
mgr.setitem_inplace(idx, value)
873+
else:
874+
new_mgr = mgr.setitem((idx,), value)
875+
# update existing ArrayManager in-place
876+
self.arrays[loc] = new_mgr.arrays[0]
872877

873878
def insert(self, loc: int, item: Hashable, value: ArrayLike) -> None:
874879
"""

pandas/core/internals/managers.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,9 @@ def _iset_single(
13351335
self._clear_reference_block(blkno)
13361336
return
13371337

1338-
def column_setitem(self, loc: int, idx: int | slice | np.ndarray, value) -> None:
1338+
def column_setitem(
1339+
self, loc: int, idx: int | slice | np.ndarray, value, inplace: bool = False
1340+
) -> None:
13391341
"""
13401342
Set values ("setitem") into a single column (not setting the full column).
13411343
@@ -1353,8 +1355,11 @@ def column_setitem(self, loc: int, idx: int | slice | np.ndarray, value) -> None
13531355
# this manager is only created temporarily to mutate the values in place
13541356
# so don't track references, otherwise the `setitem` would perform CoW again
13551357
col_mgr = self.iget(loc, track_ref=False)
1356-
new_mgr = col_mgr.setitem((idx,), value)
1357-
self.iset(loc, new_mgr._block.values, inplace=True)
1358+
if inplace:
1359+
col_mgr.setitem_inplace(idx, value)
1360+
else:
1361+
new_mgr = col_mgr.setitem((idx,), value)
1362+
self.iset(loc, new_mgr._block.values, inplace=True)
13581363

13591364
def insert(self, loc: int, item: Hashable, value: ArrayLike) -> None:
13601365
"""

0 commit comments

Comments
 (0)