Skip to content

Commit 015f67d

Browse files
phofljbrockmendeljreback
authored
Backport PR #45287 on branch 1.4.x (BUG: frame[x].loc[y] inconsistent with frame.at[x, y]) (#45287) (#47921)
* BUG: frame[x].loc[y] inconsistent with frame.at[x, y] GH#22372 (#45287) Co-authored-by: Jeff Reback <[email protected]> * Add whatsnew * Remove whatsnew file * Move whatsnew Co-authored-by: jbrockmendel <[email protected]> Co-authored-by: Jeff Reback <[email protected]>
1 parent 9fb91db commit 015f67d

File tree

4 files changed

+32
-4
lines changed

4 files changed

+32
-4
lines changed

doc/source/whatsnew/v1.4.4.rst

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Bug fixes
2626
~~~~~~~~~
2727
- The :class:`errors.FutureWarning` raised when passing arguments (other than ``filepath_or_buffer``) as positional in :func:`read_csv` is now raised at the correct stacklevel (:issue:`47385`)
2828
- Bug in :meth:`DataFrame.to_sql` when ``method`` was a ``callable`` that did not return an ``int`` and would raise a ``TypeError`` (:issue:`46891`)
29+
- Bug in :meth:`loc.__getitem__` with a list of keys causing an internal inconsistency that could lead to a disconnect between ``frame.at[x, y]`` vs ``frame[y].loc[x]`` (:issue:`22372`)
2930

3031
.. ---------------------------------------------------------------------------
3132

pandas/core/internals/managers.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,6 @@ def reindex_indexer(
671671
result.axes[axis] = new_axis
672672
return result
673673

674-
if consolidate:
675-
self._consolidate_inplace()
676-
677674
# some axes don't allow reindexing with dups
678675
if not allow_dups:
679676
self.axes[axis]._validate_can_reindex(indexer)
@@ -1681,6 +1678,10 @@ def _consolidate_check(self) -> None:
16811678
self._known_consolidated = True
16821679

16831680
def _consolidate_inplace(self) -> None:
1681+
# In general, _consolidate_inplace should only be called via
1682+
# DataFrame._consolidate_inplace, otherwise we will fail to invalidate
1683+
# the DataFrame's _item_cache. The exception is for newly-created
1684+
# BlockManager objects not yet attached to a DataFrame.
16841685
if not self.is_consolidated():
16851686
self.blocks = tuple(_consolidate(self.blocks))
16861687
self._is_consolidated = True

pandas/tests/indexing/test_at.py

+23
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,29 @@ def test_selection_methods_of_assigned_col():
4848

4949

5050
class TestAtSetItem:
51+
def test_at_setitem_item_cache_cleared(self):
52+
# GH#22372 Note the multi-step construction is necessary to trigger
53+
# the original bug. pandas/issues/22372#issuecomment-413345309
54+
df = DataFrame(index=[0])
55+
df["x"] = 1
56+
df["cost"] = 2
57+
58+
# accessing df["cost"] adds "cost" to the _item_cache
59+
df["cost"]
60+
61+
# This loc[[0]] lookup used to call _consolidate_inplace at the
62+
# BlockManager level, which failed to clear the _item_cache
63+
df.loc[[0]]
64+
65+
df.at[0, "x"] = 4
66+
df.at[0, "cost"] = 789
67+
68+
expected = DataFrame({"x": [4], "cost": 789}, index=[0])
69+
tm.assert_frame_equal(df, expected)
70+
71+
# And in particular, check that the _item_cache has updated correctly.
72+
tm.assert_series_equal(df["cost"], expected["cost"])
73+
5174
def test_at_setitem_mixed_index_assignment(self):
5275
# GH#19860
5376
ser = Series([1, 2, 3, 4, 5], index=["a", "b", "c", 1, 2])

pandas/tests/internals/test_internals.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,10 @@ def test_reindex_items(self):
718718
mgr = create_mgr("a: f8; b: i8; c: f8; d: i8; e: f8; f: bool; g: f8-2")
719719

720720
reindexed = mgr.reindex_axis(["g", "c", "a", "d"], axis=0)
721-
assert reindexed.nblocks == 2
721+
# reindex_axis does not consolidate_inplace, as that risks failing to
722+
# invalidate _item_cache
723+
assert not reindexed.is_consolidated()
724+
722725
tm.assert_index_equal(reindexed.items, Index(["g", "c", "a", "d"]))
723726
tm.assert_almost_equal(
724727
mgr.iget(6).internal_values(), reindexed.iget(0).internal_values()

0 commit comments

Comments
 (0)