diff --git a/pandas/core/base.py b/pandas/core/base.py index d9b2647d19f93..b32419b307b6e 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -20,6 +20,8 @@ import numpy as np +from pandas._config import using_copy_on_write + from pandas._libs import lib from pandas._typing import ( Axis, @@ -592,10 +594,16 @@ def to_numpy( result = np.asarray(values, dtype=dtype) - if copy and na_value is lib.no_default: + if (copy and na_value is lib.no_default) or ( + not copy and using_copy_on_write() + ): if np.shares_memory(self._values[:2], result[:2]): # Take slices to improve performance of check - result = result.copy() + if using_copy_on_write() and not copy: + result = result.view() + result.flags.writeable = False + else: + result = result.copy() return result diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 95ac522833b35..7eac3be5bcaba 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1991,7 +1991,13 @@ def empty(self) -> bool_t: __array_priority__: int = 1000 def __array__(self, dtype: npt.DTypeLike | None = None) -> np.ndarray: - return np.asarray(self._values, dtype=dtype) + values = self._values + arr = np.asarray(values, dtype=dtype) + if arr is values and using_copy_on_write(): + # TODO(CoW) also properly handle extension dtypes + arr = arr.view() + arr.flags.writeable = False + return arr @final def __array_ufunc__( diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index e5b30b20a79cd..cb336d2f718a6 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -14,6 +14,8 @@ import numpy as np +from pandas._config import using_copy_on_write + from pandas._libs import ( internals as libinternals, lib, @@ -2592,6 +2594,12 @@ def external_values(values: ArrayLike) -> ArrayLike: # NB: for datetime64tz this is different from np.asarray(values), since # that returns an object-dtype ndarray of Timestamps. # Avoid raising in .astype in casting from dt64tz to dt64 - return values._ndarray - else: - return values + values = values._ndarray + + if isinstance(values, np.ndarray) and using_copy_on_write(): + values = values.view() + values.flags.writeable = False + + # TODO(CoW) we should also mark our ExtensionArrays as read-only + + return values diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index 1324f5aeccc0d..e5f50bb35d6bd 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -1713,13 +1713,16 @@ def as_array( arr = np.asarray(blk.get_values()) if dtype: arr = arr.astype(dtype, copy=False) + + if copy: + arr = arr.copy() + elif using_copy_on_write(): + arr = arr.view() + arr.flags.writeable = False else: arr = self._interleave(dtype=dtype, na_value=na_value) - # The underlying data was copied within _interleave - copy = False - - if copy: - arr = arr.copy() + # The underlying data was copied within _interleave, so no need + # to further copy if copy=True or setting na_value if na_value is not lib.no_default: arr[isna(arr)] = na_value diff --git a/pandas/core/series.py b/pandas/core/series.py index 1dac028d7b54a..7fa645cbcc0d4 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -889,7 +889,13 @@ def __array__(self, dtype: npt.DTypeLike | None = None) -> np.ndarray: array(['1999-12-31T23:00:00.000000000', ...], dtype='datetime64[ns]') """ - return np.asarray(self._values, dtype) + values = self._values + arr = np.asarray(values, dtype=dtype) + if arr is values and using_copy_on_write(): + # TODO(CoW) also properly handle extension dtypes + arr = arr.view() + arr.flags.writeable = False + return arr # ---------------------------------------------------------------------- # Unary Methods diff --git a/pandas/io/parsers/base_parser.py b/pandas/io/parsers/base_parser.py index 90ad0d04e0ea7..6ee00d285555d 100644 --- a/pandas/io/parsers/base_parser.py +++ b/pandas/io/parsers/base_parser.py @@ -1126,7 +1126,7 @@ def converter(*date_cols, col: Hashable): dayfirst=dayfirst, errors="ignore", cache=cache_dates, - ).to_numpy() + )._values else: try: result = tools.to_datetime( diff --git a/pandas/tests/copy_view/test_array.py b/pandas/tests/copy_view/test_array.py new file mode 100644 index 0000000000000..501ef27bc291e --- /dev/null +++ b/pandas/tests/copy_view/test_array.py @@ -0,0 +1,112 @@ +import numpy as np +import pytest + +from pandas import ( + DataFrame, + Series, +) +import pandas._testing as tm +from pandas.tests.copy_view.util import get_array + +# ----------------------------------------------------------------------------- +# Copy/view behaviour for accessing underlying array of Series/DataFrame + + +@pytest.mark.parametrize( + "method", + [lambda ser: ser.values, lambda ser: np.asarray(ser)], + ids=["values", "asarray"], +) +def test_series_values(using_copy_on_write, method): + ser = Series([1, 2, 3], name="name") + ser_orig = ser.copy() + + arr = method(ser) + + if using_copy_on_write: + # .values still gives a view but is read-only + assert np.shares_memory(arr, get_array(ser, "name")) + assert arr.flags.writeable is False + + # mutating series through arr therefore doesn't work + with pytest.raises(ValueError, match="read-only"): + arr[0] = 0 + tm.assert_series_equal(ser, ser_orig) + + # mutating the series itself still works + ser.iloc[0] = 0 + assert ser.values[0] == 0 + else: + assert arr.flags.writeable is True + arr[0] = 0 + assert ser.iloc[0] == 0 + + +@pytest.mark.parametrize( + "method", + [lambda df: df.values, lambda df: np.asarray(df)], + ids=["values", "asarray"], +) +def test_dataframe_values(using_copy_on_write, using_array_manager, method): + df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + df_orig = df.copy() + + arr = method(df) + + if using_copy_on_write: + # .values still gives a view but is read-only + assert np.shares_memory(arr, get_array(df, "a")) + assert arr.flags.writeable is False + + # mutating series through arr therefore doesn't work + with pytest.raises(ValueError, match="read-only"): + arr[0, 0] = 0 + tm.assert_frame_equal(df, df_orig) + + # mutating the series itself still works + df.iloc[0, 0] = 0 + assert df.values[0, 0] == 0 + else: + assert arr.flags.writeable is True + arr[0, 0] = 0 + if not using_array_manager: + assert df.iloc[0, 0] == 0 + else: + tm.assert_frame_equal(df, df_orig) + + +def test_series_to_numpy(using_copy_on_write): + ser = Series([1, 2, 3], name="name") + ser_orig = ser.copy() + + # default: copy=False, no dtype or NAs + arr = ser.to_numpy() + if using_copy_on_write: + # to_numpy still gives a view but is read-only + assert np.shares_memory(arr, get_array(ser, "name")) + assert arr.flags.writeable is False + + # mutating series through arr therefore doesn't work + with pytest.raises(ValueError, match="read-only"): + arr[0] = 0 + tm.assert_series_equal(ser, ser_orig) + + # mutating the series itself still works + ser.iloc[0] = 0 + assert ser.values[0] == 0 + else: + assert arr.flags.writeable is True + arr[0] = 0 + assert ser.iloc[0] == 0 + + # specify copy=False gives a writeable array + ser = Series([1, 2, 3], name="name") + arr = ser.to_numpy(copy=True) + assert not np.shares_memory(arr, get_array(ser, "name")) + assert arr.flags.writeable is True + + # specifying a dtype that already causes a copy also gives a writeable array + ser = Series([1, 2, 3], name="name") + arr = ser.to_numpy(dtype="float64") + assert not np.shares_memory(arr, get_array(ser, "name")) + assert arr.flags.writeable is True diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py index efbd7058dc3b0..05b50c180ced0 100644 --- a/pandas/tests/frame/indexing/test_indexing.py +++ b/pandas/tests/frame/indexing/test_indexing.py @@ -345,7 +345,7 @@ def test_setitem2(self): def test_setitem_boolean(self, float_frame): df = float_frame.copy() - values = float_frame.values + values = float_frame.values.copy() df[df["A"] > 0] = 4 values[values[:, 0] > 0] = 4 @@ -381,16 +381,18 @@ def test_setitem_boolean(self, float_frame): df[df * 0] = 2 # index with DataFrame + df_orig = df.copy() mask = df > np.abs(df) - expected = df.copy() df[df > np.abs(df)] = np.nan - expected.values[mask.values] = np.nan + values = df_orig.values.copy() + values[mask.values] = np.nan + expected = DataFrame(values, index=df_orig.index, columns=df_orig.columns) tm.assert_frame_equal(df, expected) # set from DataFrame - expected = df.copy() df[df > np.abs(df)] = df * 2 - np.putmask(expected.values, mask.values, df.values * 2) + np.putmask(values, mask.values, df.values * 2) + expected = DataFrame(values, index=df_orig.index, columns=df_orig.columns) tm.assert_frame_equal(df, expected) def test_setitem_cast(self, float_frame): @@ -664,16 +666,20 @@ def test_setitem_fancy_boolean(self, float_frame): # from 2d, set with booleans frame = float_frame.copy() expected = float_frame.copy() + values = expected.values.copy() mask = frame["A"] > 0 frame.loc[mask] = 0.0 - expected.values[mask.values] = 0.0 + values[mask.values] = 0.0 + expected = DataFrame(values, index=expected.index, columns=expected.columns) tm.assert_frame_equal(frame, expected) frame = float_frame.copy() expected = float_frame.copy() + values = expected.values.copy() frame.loc[mask, ["A", "B"]] = 0.0 - expected.values[mask.values, :2] = 0.0 + values[mask.values, :2] = 0.0 + expected = DataFrame(values, index=expected.index, columns=expected.columns) tm.assert_frame_equal(frame, expected) def test_getitem_fancy_ints(self, float_frame): diff --git a/pandas/tests/frame/indexing/test_insert.py b/pandas/tests/frame/indexing/test_insert.py index 5d0a51ea0d462..666a6ec3710a6 100644 --- a/pandas/tests/frame/indexing/test_insert.py +++ b/pandas/tests/frame/indexing/test_insert.py @@ -71,7 +71,7 @@ def test_insert_with_columns_dups(self): ) tm.assert_frame_equal(df, exp) - def test_insert_item_cache(self, using_array_manager): + def test_insert_item_cache(self, using_array_manager, using_copy_on_write): df = DataFrame(np.random.randn(4, 3)) ser = df[0] @@ -85,9 +85,14 @@ def test_insert_item_cache(self, using_array_manager): for n in range(100): df[n + 3] = df[1] * n - ser.values[0] = 99 - - assert df.iloc[0, 0] == df[0][0] + if using_copy_on_write: + ser.iloc[0] = 99 + assert df.iloc[0, 0] == df[0][0] + assert df.iloc[0, 0] != 99 + else: + ser.values[0] = 99 + assert df.iloc[0, 0] == df[0][0] + assert df.iloc[0, 0] == 99 def test_insert_EA_no_warning(self): # PerformanceWarning about fragmented frame should not be raised when diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index c20db86904d06..998ac9c8395ce 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -1002,8 +1002,9 @@ def test_setitem_boolean_mask(self, mask_type, float_frame): result = df.copy() result[mask] = np.nan - expected = df.copy() - expected.values[np.array(mask)] = np.nan + expected = df.values.copy() + expected[np.array(mask)] = np.nan + expected = DataFrame(expected, index=df.index, columns=df.columns) tm.assert_frame_equal(result, expected) @pytest.mark.xfail(reason="Currently empty indexers are treated as all False") diff --git a/pandas/tests/frame/indexing/test_where.py b/pandas/tests/frame/indexing/test_where.py index 6e3798d62e39b..a1939de0d2a8d 100644 --- a/pandas/tests/frame/indexing/test_where.py +++ b/pandas/tests/frame/indexing/test_where.py @@ -981,7 +981,7 @@ def test_where_dt64_2d(): df = DataFrame(dta, columns=["A", "B"]) - mask = np.asarray(df.isna()) + mask = np.asarray(df.isna()).copy() mask[:, 1] = True # setting all of one column, none of the other diff --git a/pandas/tests/frame/methods/test_copy.py b/pandas/tests/frame/methods/test_copy.py index 1c0b0755e7d94..1e685fcce9f05 100644 --- a/pandas/tests/frame/methods/test_copy.py +++ b/pandas/tests/frame/methods/test_copy.py @@ -18,6 +18,7 @@ def test_copy_index_name_checking(self, float_frame, attr): getattr(cp, attr).name = "foo" assert getattr(float_frame, attr).name is None + @td.skip_copy_on_write_invalid_test def test_copy_cache(self): # GH#31784 _item_cache not cleared on copy causes incorrect reads after updates df = DataFrame({"a": [1]}) diff --git a/pandas/tests/frame/methods/test_join.py b/pandas/tests/frame/methods/test_join.py index e158a99eedc1e..98f3926968ad0 100644 --- a/pandas/tests/frame/methods/test_join.py +++ b/pandas/tests/frame/methods/test_join.py @@ -417,7 +417,7 @@ def test_join(self, multiindex_dataframe_random_data): b = frame.loc[frame.index[2:], ["B", "C"]] joined = a.join(b, how="outer").reindex(frame.index) - expected = frame.copy().values + expected = frame.copy().values.copy() expected[np.isnan(joined.values)] = np.nan expected = DataFrame(expected, index=frame.index, columns=frame.columns) diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index a2d958c31c8bb..5d2833f6fbfdf 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -766,7 +766,9 @@ def test_quantile_empty_no_columns(self, interp_method): expected.columns.name = "captain tightpants" tm.assert_frame_equal(result, expected) - def test_quantile_item_cache(self, using_array_manager, interp_method): + def test_quantile_item_cache( + self, using_array_manager, interp_method, using_copy_on_write + ): # previous behavior incorrect retained an invalid _item_cache entry interpolation, method = interp_method df = DataFrame(np.random.randn(4, 3), columns=["A", "B", "C"]) @@ -776,9 +778,15 @@ def test_quantile_item_cache(self, using_array_manager, interp_method): assert len(df._mgr.blocks) == 2 df.quantile(numeric_only=False, interpolation=interpolation, method=method) - ser.values[0] = 99 - assert df.iloc[0, 0] == df["A"][0] + if using_copy_on_write: + ser.iloc[0] = 99 + assert df.iloc[0, 0] == df["A"][0] + assert df.iloc[0, 0] != 99 + else: + ser.values[0] = 99 + assert df.iloc[0, 0] == df["A"][0] + assert df.iloc[0, 0] == 99 def test_invalid_method(self): with pytest.raises(ValueError, match="Invalid method: foo"): diff --git a/pandas/tests/frame/methods/test_sort_values.py b/pandas/tests/frame/methods/test_sort_values.py index 5ea78bb2131e5..9092f30e0e4d0 100644 --- a/pandas/tests/frame/methods/test_sort_values.py +++ b/pandas/tests/frame/methods/test_sort_values.py @@ -595,7 +595,7 @@ def test_sort_values_nat_na_position_default(self): result = expected.sort_values(["A", "date"]) tm.assert_frame_equal(result, expected) - def test_sort_values_item_cache(self, using_array_manager): + def test_sort_values_item_cache(self, using_array_manager, using_copy_on_write): # previous behavior incorrect retained an invalid _item_cache entry df = DataFrame(np.random.randn(4, 3), columns=["A", "B", "C"]) df["D"] = df["A"] * 2 @@ -604,9 +604,15 @@ def test_sort_values_item_cache(self, using_array_manager): assert len(df._mgr.blocks) == 2 df.sort_values(by="A") - ser.values[0] = 99 - assert df.iloc[0, 0] == df["A"][0] + if using_copy_on_write: + ser.iloc[0] = 99 + assert df.iloc[0, 0] == df["A"][0] + assert df.iloc[0, 0] != 99 + else: + ser.values[0] = 99 + assert df.iloc[0, 0] == df["A"][0] + assert df.iloc[0, 0] == 99 def test_sort_values_reshaping(self): # GH 39426 diff --git a/pandas/tests/frame/methods/test_transpose.py b/pandas/tests/frame/methods/test_transpose.py index e8710cea95219..881af8a41f82c 100644 --- a/pandas/tests/frame/methods/test_transpose.py +++ b/pandas/tests/frame/methods/test_transpose.py @@ -112,11 +112,14 @@ def test_transpose_float(self, float_frame): assert s.dtype == np.object_ @td.skip_array_manager_invalid_test - def test_transpose_get_view(self, float_frame): + def test_transpose_get_view(self, float_frame, using_copy_on_write): dft = float_frame.T - dft.values[:, 5:10] = 5 + dft.iloc[:, 5:10] = 5 - assert (float_frame.values[5:10] == 5).all() + if using_copy_on_write: + assert (float_frame.values[5:10] != 5).all() + else: + assert (float_frame.values[5:10] == 5).all() @td.skip_array_manager_invalid_test def test_transpose_get_view_dt64tzget_view(self): diff --git a/pandas/tests/frame/methods/test_values.py b/pandas/tests/frame/methods/test_values.py index 134534e3c8f8c..71f0bf6e24832 100644 --- a/pandas/tests/frame/methods/test_values.py +++ b/pandas/tests/frame/methods/test_values.py @@ -16,9 +16,14 @@ class TestDataFrameValues: @td.skip_array_manager_invalid_test - def test_values(self, float_frame): - float_frame.values[:, 0] = 5.0 - assert (float_frame.values[:, 0] == 5).all() + def test_values(self, float_frame, using_copy_on_write): + if using_copy_on_write: + with pytest.raises(ValueError, match="read-only"): + float_frame.values[:, 0] = 5.0 + assert (float_frame.values[:, 0] != 5).all() + else: + float_frame.values[:, 0] = 5.0 + assert (float_frame.values[:, 0] == 5).all() def test_more_values(self, float_string_frame): values = float_string_frame.values diff --git a/pandas/tests/frame/test_block_internals.py b/pandas/tests/frame/test_block_internals.py index 8b0ac237f1480..88b681d18fa3b 100644 --- a/pandas/tests/frame/test_block_internals.py +++ b/pandas/tests/frame/test_block_internals.py @@ -85,7 +85,13 @@ def test_consolidate_inplace(self, float_frame): for letter in range(ord("A"), ord("Z")): float_frame[chr(letter)] = chr(letter) - def test_modify_values(self, float_frame): + def test_modify_values(self, float_frame, using_copy_on_write): + if using_copy_on_write: + with pytest.raises(ValueError, match="read-only"): + float_frame.values[5] = 5 + assert (float_frame.values[5] != 5).all() + return + float_frame.values[5] = 5 assert (float_frame.values[5] == 5).all() diff --git a/pandas/tests/indexing/multiindex/test_setitem.py b/pandas/tests/indexing/multiindex/test_setitem.py index c890754d2d433..27b4a4be73032 100644 --- a/pandas/tests/indexing/multiindex/test_setitem.py +++ b/pandas/tests/indexing/multiindex/test_setitem.py @@ -281,7 +281,7 @@ def test_series_setitem(self, multiindex_year_month_day_dataframe_random_data): def test_frame_getitem_setitem_boolean(self, multiindex_dataframe_random_data): frame = multiindex_dataframe_random_data df = frame.T.copy() - values = df.values + values = df.values.copy() result = df[df > 0] expected = df.where(df > 0) @@ -482,12 +482,19 @@ def test_setitem_new_column_all_na(self): @td.skip_array_manager_invalid_test # df["foo"] select multiple columns -> .values # is not a view -def test_frame_setitem_view_direct(multiindex_dataframe_random_data): +def test_frame_setitem_view_direct( + multiindex_dataframe_random_data, using_copy_on_write +): # this works because we are modifying the underlying array # really a no-no df = multiindex_dataframe_random_data.T - df["foo"].values[:] = 0 - assert (df["foo"].values == 0).all() + if using_copy_on_write: + with pytest.raises(ValueError, match="read-only"): + df["foo"].values[:] = 0 + assert (df["foo"].values != 0).all() + else: + df["foo"].values[:] = 0 + assert (df["foo"].values == 0).all() def test_frame_setitem_copy_raises( diff --git a/pandas/tests/indexing/test_chaining_and_caching.py b/pandas/tests/indexing/test_chaining_and_caching.py index 12d7d04353d6f..d2224988b70fc 100644 --- a/pandas/tests/indexing/test_chaining_and_caching.py +++ b/pandas/tests/indexing/test_chaining_and_caching.py @@ -563,7 +563,7 @@ def test_cache_updating(self): assert "Hello Friend" in df["A"].index assert "Hello Friend" in df["B"].index - def test_cache_updating2(self): + def test_cache_updating2(self, using_copy_on_write): # 10264 df = DataFrame( np.zeros((5, 5), dtype="int64"), @@ -571,7 +571,13 @@ def test_cache_updating2(self): index=range(5), ) df["f"] = 0 - # TODO(CoW) protect underlying values of being written to? + df_orig = df.copy() + if using_copy_on_write: + with pytest.raises(ValueError, match="read-only"): + df.f.values[3] = 1 + tm.assert_frame_equal(df, df_orig) + return + df.f.values[3] = 1 df.f.values[3] = 2 diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index 86510c7be257b..bd9abbdb32441 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -110,7 +110,7 @@ def test_iloc_setitem_fullcol_categorical(self, indexer, key, using_array_manage tm.assert_frame_equal(df, expected) @pytest.mark.parametrize("box", [array, Series]) - def test_iloc_setitem_ea_inplace(self, frame_or_series, box): + def test_iloc_setitem_ea_inplace(self, frame_or_series, box, using_copy_on_write): # GH#38952 Case with not setting a full column # IntegerArray without NAs arr = array([1, 2, 3, 4]) @@ -131,7 +131,11 @@ def test_iloc_setitem_ea_inplace(self, frame_or_series, box): # Check that we are actually in-place if frame_or_series is Series: - assert obj.values is values + if using_copy_on_write: + assert obj.values is not values + assert np.shares_memory(obj.values, values) + else: + assert obj.values is values else: assert np.shares_memory(obj[0].values, values) diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index bbea179843f3a..9b01c6b45918c 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -2566,8 +2566,10 @@ def test_loc_setitem_boolean_and_column(self, float_frame): mask = float_frame["A"] > 0 float_frame.loc[mask, "B"] = 0 - expected.values[mask.values, 1] = 0 + values = expected.values.copy() + values[mask.values, 1] = 0 + expected = DataFrame(values, index=expected.index, columns=expected.columns) tm.assert_frame_equal(float_frame, expected) def test_loc_setitem_ndframe_values_alignment(self, using_copy_on_write): diff --git a/pandas/tests/series/indexing/test_indexing.py b/pandas/tests/series/indexing/test_indexing.py index f12d752a1a764..b347691c3c101 100644 --- a/pandas/tests/series/indexing/test_indexing.py +++ b/pandas/tests/series/indexing/test_indexing.py @@ -95,8 +95,6 @@ def test_basic_getitem_dt64tz_values(): def test_getitem_setitem_ellipsis(): s = Series(np.random.randn(10)) - np.fix(s) - result = s[...] tm.assert_series_equal(result, s) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index f143155bc07da..ac36103edcdcc 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -5,6 +5,8 @@ import numpy as np import pytest +import pandas.util._test_decorators as td + import pandas as pd import pandas._testing as tm from pandas.arrays import SparseArray @@ -451,3 +453,14 @@ def add3(x, y, z): ) with pytest.raises(NotImplementedError, match=re.escape(msg)): ufunc(ser, ser, df) + + +# TODO(CoW) see https://github.com/pandas-dev/pandas/pull/51082 +@td.skip_copy_on_write_not_yet_implemented +def test_np_fix(): + # np.fix is not a ufunc but is composed of several ufunc calls under the hood + # with `out` and `where` keywords + ser = pd.Series([-1.5, -0.5, 0.5, 1.5]) + result = np.fix(ser) + expected = pd.Series([-1.0, -0.0, 0.0, 1.0]) + tm.assert_series_equal(result, expected) diff --git a/pandas/tests/window/test_pairwise.py b/pandas/tests/window/test_pairwise.py index 695627668b80b..0f691f452c99a 100644 --- a/pandas/tests/window/test_pairwise.py +++ b/pandas/tests/window/test_pairwise.py @@ -93,7 +93,9 @@ def test_flex_binary_frame(method, frame): tm.assert_frame_equal(res2, exp) frame2 = frame.copy() - frame2.values[:] = np.random.randn(*frame2.shape) + frame2 = DataFrame( + np.random.randn(*frame2.shape), index=frame2.index, columns=frame2.columns + ) res3 = getattr(frame.rolling(window=10), method)(frame2) exp = DataFrame( diff --git a/pandas/util/_test_decorators.py b/pandas/util/_test_decorators.py index c0d1e5147bb8c..55cb97093adbf 100644 --- a/pandas/util/_test_decorators.py +++ b/pandas/util/_test_decorators.py @@ -261,3 +261,8 @@ def mark_array_manager_not_yet_implemented(request) -> None: get_option("mode.copy_on_write"), reason="Not yet implemented/adapted for Copy-on-Write mode", ) + +skip_copy_on_write_invalid_test = pytest.mark.skipif( + get_option("mode.copy_on_write"), + reason="Test not valid for Copy-on-Write mode", +)