Skip to content

Commit 0f21256

Browse files
authored
Backport PR #45706: BUG: Frame.iat item_cache invalidation bug (#45744)
1 parent bcc2a5d commit 0f21256

File tree

5 files changed

+48
-17
lines changed

5 files changed

+48
-17
lines changed

doc/source/whatsnew/v1.4.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Fixed regressions
1717
- Regression in :meth:`Series.mask` with ``inplace=True`` and ``PeriodDtype`` and an incompatible ``other`` coercing to a common dtype instead of raising (:issue:`45546`)
1818
- Regression in :func:`.assert_frame_equal` not respecting ``check_flags=False`` (:issue:`45554`)
1919
- Regression in :meth:`Series.fillna` with ``downcast=False`` incorrectly downcasting ``object`` dtype (:issue:`45603`)
20+
- Regression in :meth:`DataFrame.iat` setting values leading to not propagating correctly in subsequent lookups (:issue:`45684`)
2021
- Regression when setting values with :meth:`DataFrame.loc` losing :class:`Index` name if :class:`DataFrame` was empty before (:issue:`45621`)
2122
- Regression in :meth:`~Index.join` with overlapping :class:`IntervalIndex` raising an ``InvalidIndexError`` (:issue:`45661`)
2223
- Regression in :func:`read_sql` with a DBAPI2 connection that is not an instance of ``sqlite3.Connection`` incorrectly requiring SQLAlchemy be installed (:issue:`45660`)

pandas/conftest.py

+8
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,14 @@ def indexer_al(request):
17571757
return request.param
17581758

17591759

1760+
@pytest.fixture(params=[tm.iat, tm.iloc])
1761+
def indexer_ial(request):
1762+
"""
1763+
Parametrize over iat.__setitem__, iloc.__setitem__
1764+
"""
1765+
return request.param
1766+
1767+
17601768
@pytest.fixture
17611769
def using_array_manager(request):
17621770
"""

pandas/core/frame.py

+8-16
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@
9393
)
9494

9595
from pandas.core.dtypes.cast import (
96-
can_hold_element,
9796
construct_1d_arraylike_from_scalar,
9897
construct_2d_arraylike_from_scalar,
9998
find_common_type,
@@ -3864,23 +3863,16 @@ def _set_value(
38643863
try:
38653864
if takeable:
38663865
series = self._ixs(col, axis=1)
3867-
series._set_value(index, value, takeable=True)
3868-
return
3869-
3870-
series = self._get_item_cache(col)
3871-
loc = self.index.get_loc(index)
3872-
dtype = series.dtype
3873-
if isinstance(dtype, np.dtype) and dtype.kind not in ["m", "M"]:
3874-
# otherwise we have EA values, and this check will be done
3875-
# via setitem_inplace
3876-
if not can_hold_element(series._values, value):
3877-
# We'll go through loc and end up casting.
3878-
raise TypeError
3866+
loc = index
3867+
else:
3868+
series = self._get_item_cache(col)
3869+
loc = self.index.get_loc(index)
38793870

3871+
# setitem_inplace will do validation that may raise TypeError
3872+
# or ValueError
38803873
series._mgr.setitem_inplace(loc, value)
3881-
# Note: trying to use series._set_value breaks tests in
3882-
# tests.frame.indexing.test_indexing and tests.indexing.test_partial
3883-
except (KeyError, TypeError):
3874+
3875+
except (KeyError, TypeError, ValueError):
38843876
# set using a non-recursive method & reset the cache
38853877
if takeable:
38863878
self.iloc[index, col] = value

pandas/core/internals/base.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
final,
1010
)
1111

12+
import numpy as np
13+
1214
from pandas._typing import (
1315
ArrayLike,
1416
DtypeObj,
1517
Shape,
1618
)
1719
from pandas.errors import AbstractMethodError
1820

19-
from pandas.core.dtypes.cast import find_common_type
21+
from pandas.core.dtypes.cast import (
22+
find_common_type,
23+
np_can_hold_element,
24+
)
2025

2126
from pandas.core.base import PandasObject
2227
from pandas.core.indexes.api import (
@@ -174,6 +179,14 @@ def setitem_inplace(self, indexer, value) -> None:
174179
in place, not returning a new Manager (and Block), and thus never changing
175180
the dtype.
176181
"""
182+
arr = self.array
183+
184+
# EAs will do this validation in their own __setitem__ methods.
185+
if isinstance(arr, np.ndarray):
186+
# Note: checking for ndarray instead of np.dtype means we exclude
187+
# dt64/td64, which do their own validation.
188+
value = np_can_hold_element(arr.dtype, value)
189+
177190
self.array[indexer] = value
178191

179192
def grouped_reduce(self, func, ignore_failures: bool = False):

pandas/tests/indexing/test_iat.py

+17
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,20 @@ def test_iat_getitem_series_with_period_index():
2929
expected = ser[index[0]]
3030
result = ser.iat[0]
3131
assert expected == result
32+
33+
34+
def test_iat_setitem_item_cache_cleared(indexer_ial):
35+
# GH#45684
36+
data = {"x": np.arange(8, dtype=np.int64), "y": np.int64(0)}
37+
df = DataFrame(data).copy()
38+
ser = df["y"]
39+
40+
# previously this iat setting would split the block and fail to clear
41+
# the item_cache.
42+
indexer_ial(df)[7, 0] = 9999
43+
44+
indexer_ial(df)[7, 1] = 1234
45+
46+
assert df.iat[7, 1] == 1234
47+
assert ser.iloc[-1] == 1234
48+
assert df.iloc[-1, -1] == 1234

0 commit comments

Comments
 (0)