diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index ce9f670df8254..2263c8789f979 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -197,6 +197,9 @@ Copy-on-Write improvements behavior when the NumPy array is modified after creation of the :class:`DataFrame`. +- The :meth:`DataFrame.from_records` will now respect Copy-on-Write when called + with a :class:`DataFrame`. + - Trying to set values using chained assignment (for example, ``df["a"][1:3] = 0``) will now always raise an warning when Copy-on-Write is enabled. In this mode, chained assignment can never work because we are always setting into a temporary diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 98cf2985597d1..767e40e377aed 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2180,6 +2180,17 @@ def from_records( 2 1 c 3 0 d """ + if isinstance(data, DataFrame): + if columns is not None: + if is_scalar(columns): + columns = [columns] + data = data[columns] + if index is not None: + data = data.set_index(index) + if exclude is not None: + data = data.drop(columns=exclude) + return data.copy(deep=False) + result_index = None # Make a copy of the input columns so we can modify it diff --git a/pandas/tests/copy_view/test_constructors.py b/pandas/tests/copy_view/test_constructors.py index 8ceb3cd7d2d2b..93341286b56dd 100644 --- a/pandas/tests/copy_view/test_constructors.py +++ b/pandas/tests/copy_view/test_constructors.py @@ -231,3 +231,17 @@ def test_frame_from_numpy_array(using_copy_on_write, copy, using_array_manager): assert not np.shares_memory(get_array(df, 0), arr) else: assert np.shares_memory(get_array(df, 0), arr) + + +def test_dataframe_from_records_with_dataframe(using_copy_on_write): + df = DataFrame({"a": [1, 2, 3]}) + df_orig = df.copy() + df2 = DataFrame.from_records(df) + if using_copy_on_write: + assert not df._mgr._has_no_reference(0) + assert np.shares_memory(get_array(df, "a"), get_array(df2, "a")) + df2.iloc[0, 0] = 100 + if using_copy_on_write: + tm.assert_frame_equal(df, df_orig) + else: + tm.assert_frame_equal(df, df2) diff --git a/pandas/tests/frame/constructors/test_from_records.py b/pandas/tests/frame/constructors/test_from_records.py index 18bf633d60186..1f192d96dbe37 100644 --- a/pandas/tests/frame/constructors/test_from_records.py +++ b/pandas/tests/frame/constructors/test_from_records.py @@ -193,12 +193,12 @@ def test_from_records_bad_index_column(self): # should fail msg = "|".join( [ - r"Length of values \(10\) does not match length of index \(1\)", + r"'None of \[2\] are in the columns'", ] ) - with pytest.raises(ValueError, match=msg): + with pytest.raises(KeyError, match=msg): DataFrame.from_records(df, index=[2]) - with pytest.raises(KeyError, match=r"^2$"): + with pytest.raises(KeyError, match=msg): DataFrame.from_records(df, index=2) def test_from_records_non_tuple(self):