Skip to content

Commit e34a111

Browse files
authored
ENH / CoW: Add lazy copy to eval (#53746)
1 parent 81115d8 commit e34a111

File tree

4 files changed

+37
-2
lines changed

4 files changed

+37
-2
lines changed

doc/source/user_guide/copy_on_write.rst

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ following methods:
211211
- :meth:`DataFrame.astype` / :meth:`Series.astype`
212212
- :meth:`DataFrame.convert_dtypes` / :meth:`Series.convert_dtypes`
213213
- :meth:`DataFrame.join`
214+
- :meth:`DataFrame.eval`
214215
- :func:`concat`
215216
- :func:`merge`
216217

doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Copy-on-Write improvements
2525
- The :class:`DataFrame` constructor, when constructing a DataFrame from a dictionary
2626
of Index objects and specifying ``copy=False``, will now use a lazy copy
2727
of those Index objects for the columns of the DataFrame (:issue:`52947`)
28+
- Add lazy copy mechanism to :meth:`DataFrame.eval` (:issue:`53746`)
2829

2930
.. _whatsnew_210.enhancements.enhancement2:
3031

pandas/core/computation/eval.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,11 @@ def eval(
373373
# if returning a copy, copy only on the first assignment
374374
if not inplace and first_expr:
375375
try:
376-
target = env.target.copy()
376+
target = env.target
377+
if isinstance(target, NDFrame):
378+
target = target.copy(deep=None)
379+
else:
380+
target = target.copy()
377381
except AttributeError as err:
378382
raise ValueError("Cannot return a copy of the target") from err
379383
else:
@@ -389,7 +393,9 @@ def eval(
389393
if inplace and isinstance(target, NDFrame):
390394
target.loc[:, assigner] = ret
391395
else:
392-
target[assigner] = ret
396+
target[ # pyright: ignore[reportGeneralTypeIssues]
397+
assigner
398+
] = ret
393399
except (TypeError, IndexError) as err:
394400
raise ValueError("Cannot assign expression output to target") from err
395401

pandas/tests/copy_view/test_methods.py

+27
Original file line numberDiff line numberDiff line change
@@ -1850,3 +1850,30 @@ def test_insert_series(using_copy_on_write):
18501850

18511851
df.iloc[0, 1] = 100
18521852
tm.assert_series_equal(ser, ser_orig)
1853+
1854+
1855+
def test_eval(using_copy_on_write):
1856+
df = DataFrame({"a": [1, 2, 3], "b": 1})
1857+
df_orig = df.copy()
1858+
1859+
result = df.eval("c = a+b")
1860+
if using_copy_on_write:
1861+
assert np.shares_memory(get_array(df, "a"), get_array(result, "a"))
1862+
else:
1863+
assert not np.shares_memory(get_array(df, "a"), get_array(result, "a"))
1864+
1865+
result.iloc[0, 0] = 100
1866+
tm.assert_frame_equal(df, df_orig)
1867+
1868+
1869+
def test_eval_inplace(using_copy_on_write):
1870+
df = DataFrame({"a": [1, 2, 3], "b": 1})
1871+
df_orig = df.copy()
1872+
df_view = df[:]
1873+
1874+
df.eval("c = a+b", inplace=True)
1875+
assert np.shares_memory(get_array(df, "a"), get_array(df_view, "a"))
1876+
1877+
df.iloc[0, 0] = 100
1878+
if using_copy_on_write:
1879+
tm.assert_frame_equal(df_view, df_orig)

0 commit comments

Comments
 (0)