diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 09134763977c3..6d813f367374d 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -37,6 +37,7 @@ Other enhancements updated to work correctly with NumPy >= 2 (:issue:`57739`) - :meth:`Series.str.decode` result now has ``StringDtype`` when ``future.infer_string`` is True (:issue:`60709`) - :meth:`~Series.to_hdf` and :meth:`~DataFrame.to_hdf` now round-trip with ``StringDtype`` (:issue:`60663`) +- The :meth:`DataFrame.iloc` now works correctly with ``copy_on_write`` option when assigning values after subsetting the columns of a DataFrame and using a slice (:issue:`60309`) - The :meth:`Series.str.decode` has gained the argument ``dtype`` to control the dtype of the result (:issue:`60940`) - The :meth:`~Series.cumsum`, :meth:`~Series.cummin`, and :meth:`~Series.cummax` reductions are now implemented for ``StringDtype`` columns (:issue:`60633`) - The :meth:`~Series.sum` reduction is now implemented for ``StringDtype`` columns (:issue:`59853`) diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index a3738bb25f56c..2e6701916a8d4 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -572,7 +572,12 @@ def setitem(self, indexer, value) -> Self: 0, blk_loc, values ) # first block equals values - self.blocks[0].setitem((indexer[0], np.arange(len(blk_loc))), value) + col_indexer: slice | np.ndarray + if isinstance(indexer[1], slice) and indexer[1] == slice(None): + col_indexer = slice(None) + else: + col_indexer = np.arange(len(blk_loc)) + self.blocks[0].setitem((indexer[0], col_indexer), value) return self # No need to split if we either set all columns or on a single block # manager diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index 2f6998a85c80b..d38169c76d584 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -22,6 +22,7 @@ date_range, interval_range, isna, + option_context, to_datetime, ) import pandas._testing as tm @@ -1448,3 +1449,26 @@ def test_iloc_nullable_int64_size_1_nan(self): result = DataFrame({"a": ["test"], "b": [np.nan]}) with pytest.raises(TypeError, match="Invalid value"): result.loc[:, "b"] = result.loc[:, "b"].astype("Int64") + + def test_iloc_setitem_list_with_cow(self): + # GH#60309 + with option_context("mode.copy_on_write", True): + dftest = DataFrame( + {"A": [1, 4, 1, 5], "B": [2, 5, 2, 6], "C": [3, 6, 1, 7]} + ) + df = dftest[["B", "C"]] + + # Perform the iloc operation + df.iloc[[1, 3], :] = [[2, 2], [2, 2]] + + # Check that original DataFrame is unchanged + expected_orig = DataFrame( + {"A": [1, 4, 1, 5], "B": [2, 5, 2, 6], "C": [3, 6, 1, 7]} + ) + tm.assert_frame_equal(dftest, expected_orig) + + # Check that df is modified correctly + expected_df = DataFrame( + {"B": [2, 2, 2, 2], "C": [3, 2, 1, 2]}, index=df.index + ) + tm.assert_frame_equal(df, expected_df)