From ca9eb22763ac246227a815afc9401588138968b3 Mon Sep 17 00:00:00 2001 From: Tobias Pitters <31857876+closechoice@users.noreply.github.com> Date: Fri, 19 Aug 2022 12:21:24 +0200 Subject: [PATCH 1/2] REGR: fix eval with inplace=True to correctly update column values inplace (#47550) * fix pre-commit issues * fix linting errors * add inplace argument to isetitem and use in eval * changes due to PR discussions * fix issues * update eval * update whatsnew * add PR suggestions * update imports in eval.py * check inplace and use NDFrame + small update to test * update test to use using_copy_on_write * skip test for array manager Co-authored-by: Joris Van den Bossche --- doc/source/whatsnew/v1.4.4.rst | 1 + pandas/core/computation/eval.py | 6 +++++- pandas/tests/computation/test_eval.py | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.4.4.rst b/doc/source/whatsnew/v1.4.4.rst index 57b8fdee5888a..1e7ed256c05ef 100644 --- a/doc/source/whatsnew/v1.4.4.rst +++ b/doc/source/whatsnew/v1.4.4.rst @@ -19,6 +19,7 @@ Fixed regressions - Fixed regression in :meth:`DataFrame.loc` not updating the cache correctly after values were set (:issue:`47867`) - Fixed regression in :meth:`DataFrame.loc` not aligning index in some cases when setting a :class:`DataFrame` (:issue:`47578`) - Fixed regression in setting ``None`` or non-string value into a ``string``-dtype Series using a mask (:issue:`47628`) +- Fixed regression in :meth:`DataFrame.eval` creating a copy when updating inplace (:issue:`47449`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/computation/eval.py b/pandas/core/computation/eval.py index d82cc37b90ad4..70a8bff5d509c 100644 --- a/pandas/core/computation/eval.py +++ b/pandas/core/computation/eval.py @@ -18,6 +18,7 @@ from pandas.core.computation.ops import BinOp from pandas.core.computation.parsing import tokenize_string from pandas.core.computation.scope import ensure_scope +from pandas.core.generic import NDFrame from pandas.io.formats.printing import pprint_thing @@ -384,7 +385,10 @@ def eval( try: with warnings.catch_warnings(record=True): # TODO: Filter the warnings we actually care about here. - target[assigner] = ret + if inplace and isinstance(target, NDFrame): + target.loc[:, assigner] = ret + else: + target[assigner] = ret except (TypeError, IndexError) as err: raise ValueError("Cannot assign expression output to target") from err diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index 3517068b3d0cc..2dad8132dced7 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -1966,6 +1966,26 @@ def test_negate_lt_eq_le(engine, parser): tm.assert_frame_equal(result, expected) +@td.skip_array_manager_not_yet_implemented +def test_set_inplace(using_copy_on_write): + # https://github.com/pandas-dev/pandas/issues/47449 + # Ensure we don't only update the DataFrame inplace, but also the actual + # column values, such that references to this column also get updated + df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]}) + result_view = df[:] + ser = df["A"] + df.eval("A = B + C", inplace=True) + expected = DataFrame({"A": [11, 13, 15], "B": [4, 5, 6], "C": [7, 8, 9]}) + tm.assert_frame_equal(df, expected) + if not using_copy_on_write: + tm.assert_series_equal(ser, expected["A"]) + tm.assert_series_equal(result_view["A"], expected["A"]) + else: + expected = Series([1, 2, 3], name="A") + tm.assert_series_equal(ser, expected) + tm.assert_series_equal(result_view["A"], expected) + + class TestValidate: @pytest.mark.parametrize("value", [1, "True", [1, 2, 3], 5.0]) def test_validate_bool_args(self, value): From 9aed9c7fc569fbd6db29fb560fbbebaa19f2c485 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Fri, 19 Aug 2022 14:07:31 +0200 Subject: [PATCH 2/2] Remove copy on write fixture --- pandas/tests/computation/test_eval.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index 2dad8132dced7..3051683853a46 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -1967,7 +1967,7 @@ def test_negate_lt_eq_le(engine, parser): @td.skip_array_manager_not_yet_implemented -def test_set_inplace(using_copy_on_write): +def test_set_inplace(): # https://github.com/pandas-dev/pandas/issues/47449 # Ensure we don't only update the DataFrame inplace, but also the actual # column values, such that references to this column also get updated @@ -1977,13 +1977,8 @@ def test_set_inplace(using_copy_on_write): df.eval("A = B + C", inplace=True) expected = DataFrame({"A": [11, 13, 15], "B": [4, 5, 6], "C": [7, 8, 9]}) tm.assert_frame_equal(df, expected) - if not using_copy_on_write: - tm.assert_series_equal(ser, expected["A"]) - tm.assert_series_equal(result_view["A"], expected["A"]) - else: - expected = Series([1, 2, 3], name="A") - tm.assert_series_equal(ser, expected) - tm.assert_series_equal(result_view["A"], expected) + tm.assert_series_equal(ser, expected["A"]) + tm.assert_series_equal(result_view["A"], expected["A"]) class TestValidate: