From 093aff45fd2e749b145b4fbc1a04320c176a2cf4 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 5 Nov 2025 14:50:14 -0800 Subject: [PATCH] BUG: empty_frame[tuple] = values corrupting frame --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/indexes/base.py | 8 +++++++- pandas/tests/frame/indexing/test_setitem.py | 9 +++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 0396d1704b579..edaac092b9240 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -1132,6 +1132,7 @@ Indexing ^^^^^^^^ - Bug in :meth:`DataFrame.__getitem__` returning modified columns when called with ``slice`` in Python 3.12 (:issue:`57500`) - Bug in :meth:`DataFrame.__getitem__` when slicing a :class:`DataFrame` with many rows raised an ``OverflowError`` (:issue:`59531`) +- Bug in :meth:`DataFrame.__setitem__` on an empty :class:`DataFrame` with a tuple corrupting the frame (:issue:`54385`) - Bug in :meth:`DataFrame.from_records` throwing a ``ValueError`` when passed an empty list in ``index`` (:issue:`58594`) - Bug in :meth:`DataFrame.loc` and :meth:`DataFrame.iloc` returning incorrect dtype when selecting from a :class:`DataFrame` with mixed data types. (:issue:`60600`) - Bug in :meth:`DataFrame.loc` with inconsistent behavior of loc-set with 2 given indexes to Series (:issue:`59933`) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 4aecddee4c9fc..4186a13926c6f 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -5193,7 +5193,9 @@ def _validate_fill_value(self, value): """ dtype = self.dtype if isinstance(dtype, np.dtype) and dtype.kind not in "mM": - # return np_can_hold_element(dtype, value) + if isinstance(value, tuple) and dtype != object: + # GH#54385 + raise TypeError try: return np_can_hold_element(dtype, value) except LossySetitemError as err: @@ -6357,6 +6359,10 @@ def _find_common_type_compat(self, target) -> DtypeObj: """ target_dtype, _ = infer_dtype_from(target) + if isinstance(target, tuple): + # GH#54385 + return np.dtype(object) + if using_string_dtype(): # special case: if left or right is a zero-length RangeIndex or # Index[object], those can be created by the default empty constructors diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index c0fead4889932..906c7654ef11f 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -1002,6 +1002,15 @@ def test_loc_expansion_with_timedelta_type(self): ) tm.assert_frame_equal(result, expected) + def test_setitem_tuple_key_in_empty_frame(self): + # GH#54385 + df = DataFrame() + df[(0, 0)] = [1, 2, 3] + + cols = Index([(0, 0)], tupleize_cols=False) + expected = DataFrame({(0, 0): [1, 2, 3]}, columns=cols) + tm.assert_frame_equal(df, expected) + class TestDataFrameSetItemSlicing: def test_setitem_slice_position(self):