diff --git a/doc/source/user_guide/copy_on_write.rst b/doc/source/user_guide/copy_on_write.rst index e2e7dfa42d115..59bdb1926895f 100644 --- a/doc/source/user_guide/copy_on_write.rst +++ b/doc/source/user_guide/copy_on_write.rst @@ -211,6 +211,7 @@ following methods: - :meth:`DataFrame.astype` / :meth:`Series.astype` - :meth:`DataFrame.convert_dtypes` / :meth:`Series.convert_dtypes` - :meth:`DataFrame.join` + - :meth:`DataFrame.eval` - :func:`concat` - :func:`merge` diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 7b9efd7f593dd..06a9f2f41e6d7 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -25,6 +25,7 @@ Copy-on-Write improvements - The :class:`DataFrame` constructor, when constructing a DataFrame from a dictionary of Index objects and specifying ``copy=False``, will now use a lazy copy of those Index objects for the columns of the DataFrame (:issue:`52947`) +- Add lazy copy mechanism to :meth:`DataFrame.eval` (:issue:`53746`) .. _whatsnew_210.enhancements.enhancement2: diff --git a/pandas/core/computation/eval.py b/pandas/core/computation/eval.py index d19730a321b36..ce0c50a810ab1 100644 --- a/pandas/core/computation/eval.py +++ b/pandas/core/computation/eval.py @@ -373,7 +373,11 @@ def eval( # if returning a copy, copy only on the first assignment if not inplace and first_expr: try: - target = env.target.copy() + target = env.target + if isinstance(target, NDFrame): + target = target.copy(deep=None) + else: + target = target.copy() except AttributeError as err: raise ValueError("Cannot return a copy of the target") from err else: @@ -389,7 +393,9 @@ def eval( if inplace and isinstance(target, NDFrame): target.loc[:, assigner] = ret else: - target[assigner] = ret + target[ # pyright: ignore[reportGeneralTypeIssues] + assigner + ] = ret except (TypeError, IndexError) as err: raise ValueError("Cannot assign expression output to target") from err diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index 58881ad6971f4..9e7ae9942ea90 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -1850,3 +1850,30 @@ def test_insert_series(using_copy_on_write): df.iloc[0, 1] = 100 tm.assert_series_equal(ser, ser_orig) + + +def test_eval(using_copy_on_write): + df = DataFrame({"a": [1, 2, 3], "b": 1}) + df_orig = df.copy() + + result = df.eval("c = a+b") + if using_copy_on_write: + assert np.shares_memory(get_array(df, "a"), get_array(result, "a")) + else: + assert not np.shares_memory(get_array(df, "a"), get_array(result, "a")) + + result.iloc[0, 0] = 100 + tm.assert_frame_equal(df, df_orig) + + +def test_eval_inplace(using_copy_on_write): + df = DataFrame({"a": [1, 2, 3], "b": 1}) + df_orig = df.copy() + df_view = df[:] + + df.eval("c = a+b", inplace=True) + assert np.shares_memory(get_array(df, "a"), get_array(df_view, "a")) + + df.iloc[0, 0] = 100 + if using_copy_on_write: + tm.assert_frame_equal(df_view, df_orig)