From 368643f6af89a8aa023b1a457ffa33b7e35e653a Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:22:16 +0100 Subject: [PATCH] DEPR: Deprecate Series.view (#56054) Co-authored-by: Joris Van den Bossche --- doc/source/whatsnew/v2.2.0.rst | 1 + pandas/core/series.py | 42 ++++++----------------- pandas/core/window/ewm.py | 3 ++ pandas/io/stata.py | 4 +-- pandas/tests/copy_view/test_methods.py | 3 +- pandas/tests/frame/indexing/test_where.py | 3 +- pandas/tests/frame/test_constructors.py | 4 +-- pandas/tests/generic/test_finalize.py | 5 --- pandas/tests/groupby/test_cumulative.py | 2 +- pandas/tests/groupby/test_timegrouper.py | 2 +- pandas/tests/io/json/test_pandas.py | 2 +- pandas/tests/io/test_sql.py | 2 +- pandas/tests/series/methods/test_view.py | 4 +++ pandas/tests/series/test_constructors.py | 2 +- pandas/tests/test_algos.py | 2 +- pandas/tests/test_nanops.py | 4 +++ 16 files changed, 37 insertions(+), 48 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 9680ccf409564..b30a11dca7c57 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -362,6 +362,7 @@ Other Deprecations - Deprecated :func:`read_gbq` and :meth:`DataFrame.to_gbq`. Use ``pandas_gbq.read_gbq`` and ``pandas_gbq.to_gbq`` instead https://pandas-gbq.readthedocs.io/en/latest/api.html (:issue:`55525`) - Deprecated :meth:`.DataFrameGroupBy.fillna` and :meth:`.SeriesGroupBy.fillna`; use :meth:`.DataFrameGroupBy.ffill`, :meth:`.DataFrameGroupBy.bfill` for forward and backward filling or :meth:`.DataFrame.fillna` to fill with a single value (or the Series equivalents) (:issue:`55718`) - Deprecated :meth:`Index.format`, use ``index.astype(str)`` or ``index.map(formatter)`` instead (:issue:`55413`) +- Deprecated :meth:`Series.view`, use ``astype`` instead to change the dtype (:issue:`20251`) - Deprecated ``core.internals`` members ``Block``, ``ExtensionBlock``, and ``DatetimeTZBlock``, use public APIs instead (:issue:`55139`) - Deprecated ``year``, ``month``, ``quarter``, ``day``, ``hour``, ``minute``, and ``second`` keywords in the :class:`PeriodIndex` constructor, use :meth:`PeriodIndex.from_fields` instead (:issue:`55960`) - Deprecated allowing non-integer ``periods`` argument in :func:`date_range`, :func:`timedelta_range`, :func:`period_range`, and :func:`interval_range` (:issue:`56036`) diff --git a/pandas/core/series.py b/pandas/core/series.py index a9679f22f9933..6b06786549ade 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -877,6 +877,10 @@ def view(self, dtype: Dtype | None = None) -> Series: """ Create a new view of the Series. + .. deprecated:: 2.2.0 + ``Series.view`` is deprecated and will be removed in a future version. + Use :meth:`Series.astype` as an alternative to change the dtype. + This function will return a new Series with a view of the same underlying values in memory, optionally reinterpreted with a new data type. The new data type must preserve the same size in bytes as to not @@ -907,38 +911,14 @@ def view(self, dtype: Dtype | None = None) -> Series: Examples -------- - >>> s = pd.Series([-2, -1, 0, 1, 2], dtype='int8') - >>> s - 0 -2 - 1 -1 - 2 0 - 3 1 - 4 2 - dtype: int8 - - The 8 bit signed integer representation of `-1` is `0b11111111`, but - the same bytes represent 255 if read as an 8 bit unsigned integer: - - >>> us = s.view('uint8') - >>> us - 0 254 - 1 255 - 2 0 - 3 1 - 4 2 - dtype: uint8 - - The views share the same underlying values: - - >>> us[0] = 128 - >>> s - 0 -128 - 1 -1 - 2 0 - 3 1 - 4 2 - dtype: int8 + Use ``astype`` to change the dtype instead. """ + warnings.warn( + "Series.view is deprecated and will be removed in a future version. " + "Use ``astype`` as an alternative to change the dtype.", + FutureWarning, + stacklevel=2, + ) # self.array instead of self._values so we piggyback on NumpyExtensionArray # implementation res_values = self.array.view(dtype) diff --git a/pandas/core/window/ewm.py b/pandas/core/window/ewm.py index 82b4746aa57a5..db659713c6f16 100644 --- a/pandas/core/window/ewm.py +++ b/pandas/core/window/ewm.py @@ -15,6 +15,7 @@ is_datetime64_ns_dtype, is_numeric_dtype, ) +from pandas.core.dtypes.generic import ABCSeries from pandas.core.dtypes.missing import isna from pandas.core import common @@ -118,6 +119,8 @@ def _calculate_deltas( np.ndarray Diff of the times divided by the half-life """ + if isinstance(times, ABCSeries): + times = times._values _times = np.asarray(times.view(np.int64), dtype=np.float64) # TODO: generalize to non-nano? _halflife = float(Timedelta(halflife).as_unit("ns")._value) diff --git a/pandas/io/stata.py b/pandas/io/stata.py index 70294e8a62cca..1eb8f531dc62a 100644 --- a/pandas/io/stata.py +++ b/pandas/io/stata.py @@ -429,9 +429,9 @@ def parse_dates_safe( d["year"] = date_index._data.year d["month"] = date_index._data.month if days: - days_in_ns = dates.view(np.int64) - to_datetime( + days_in_ns = dates._values.view(np.int64) - to_datetime( d["year"], format="%Y" - ).view(np.int64) + )._values.view(np.int64) d["days"] = days_in_ns // NS_PER_DAY elif infer_dtype(dates, skipna=False) == "datetime": diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index 6416dd50daddc..3fd86f916c771 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -1934,7 +1934,8 @@ def test_series_view(using_copy_on_write, warn_copy_on_write): ser = Series([1, 2, 3]) ser_orig = ser.copy() - ser2 = ser.view() + with tm.assert_produces_warning(FutureWarning, match="is deprecated"): + ser2 = ser.view() assert np.shares_memory(get_array(ser), get_array(ser2)) if using_copy_on_write: assert not ser2._mgr._has_no_reference(0) diff --git a/pandas/tests/frame/indexing/test_where.py b/pandas/tests/frame/indexing/test_where.py index 4576a86ad27cd..6b2bf211ab748 100644 --- a/pandas/tests/frame/indexing/test_where.py +++ b/pandas/tests/frame/indexing/test_where.py @@ -771,7 +771,8 @@ def test_where_datetimelike_noop(self, dtype): # GH#45135, analogue to GH#44181 for Period don't raise on no-op # For td64/dt64/dt64tz we already don't raise, but also are # checking that we don't unnecessarily upcast to object. - ser = Series(np.arange(3) * 10**9, dtype=np.int64).view(dtype) + with tm.assert_produces_warning(FutureWarning, match="is deprecated"): + ser = Series(np.arange(3) * 10**9, dtype=np.int64).view(dtype) df = ser.to_frame() mask = np.array([False, False, False]) diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 3111075c5c1a7..2cca87adc5189 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -1101,8 +1101,8 @@ def test_constructor_maskedarray_nonfloat(self): mat2[0, 0] = 1 mat2[1, 2] = 2 frame = DataFrame(mat2, columns=["A", "B", "C"], index=[1, 2]) - assert 1 == frame["A"].view("i8")[1] - assert 2 == frame["C"].view("i8")[2] + assert 1 == frame["A"].astype("i8")[1] + assert 2 == frame["C"].astype("i8")[2] # masked bool promoted to object mat = ma.masked_all((2, 3), dtype=bool) diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py index 68746b9e9a803..866e9e203ffe3 100644 --- a/pandas/tests/generic/test_finalize.py +++ b/pandas/tests/generic/test_finalize.py @@ -31,11 +31,6 @@ # - Callable: pass the constructed value with attrs set to this. _all_methods = [ - ( - pd.Series, - (np.array([0], dtype="float64")), - operator.methodcaller("view", "int64"), - ), (pd.Series, ([0],), operator.methodcaller("take", [])), (pd.Series, ([0],), operator.methodcaller("__getitem__", [True])), (pd.Series, ([0],), operator.methodcaller("repeat", 2)), diff --git a/pandas/tests/groupby/test_cumulative.py b/pandas/tests/groupby/test_cumulative.py index 25534865b3486..bf572609f3d37 100644 --- a/pandas/tests/groupby/test_cumulative.py +++ b/pandas/tests/groupby/test_cumulative.py @@ -216,7 +216,7 @@ def test_cummax_i8_at_implementation_bound(): # the minimum value used to be treated as NPY_NAT+1 instead of NPY_NAT # for int64 dtype GH#46382 ser = Series([pd.NaT._value + n for n in range(5)]) - df = DataFrame({"A": 1, "B": ser, "C": ser.view("M8[ns]")}) + df = DataFrame({"A": 1, "B": ser, "C": ser._values.view("M8[ns]")}) gb = df.groupby("A") res = gb.cummax() diff --git a/pandas/tests/groupby/test_timegrouper.py b/pandas/tests/groupby/test_timegrouper.py index b79bed32e7fa4..b8b34d365d051 100644 --- a/pandas/tests/groupby/test_timegrouper.py +++ b/pandas/tests/groupby/test_timegrouper.py @@ -726,7 +726,7 @@ def test_groupby_groups_periods(self): def test_groupby_first_datetime64(self): df = DataFrame([(1, 1351036800000000000), (2, 1351036800000000000)]) - df[1] = df[1].view("M8[ns]") + df[1] = df[1].astype("M8[ns]") assert issubclass(df[1].dtype.type, np.datetime64) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 7312facc44c26..411cc90ba41a7 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -168,7 +168,7 @@ def test_frame_non_unique_columns(self, orient, data): # in milliseconds; these are internally stored in nanosecond, # so divide to get where we need # TODO: a to_epoch method would also solve; see GH 14772 - expected.iloc[:, 0] = expected.iloc[:, 0].view(np.int64) // 1000000 + expected.iloc[:, 0] = expected.iloc[:, 0].astype(np.int64) // 1000000 elif orient == "split": expected = df expected.columns = ["x", "x.1"] diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 45d0a839b9e1a..d7c69ff17749c 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -1908,7 +1908,7 @@ def test_api_timedelta(conn, request): name="foo", ) else: - expected = df["foo"].view("int64") + expected = df["foo"].astype("int64") tm.assert_series_equal(result["foo"], expected) diff --git a/pandas/tests/series/methods/test_view.py b/pandas/tests/series/methods/test_view.py index a9b29c9329193..7e0ac372cd443 100644 --- a/pandas/tests/series/methods/test_view.py +++ b/pandas/tests/series/methods/test_view.py @@ -9,6 +9,10 @@ ) import pandas._testing as tm +pytestmark = pytest.mark.filterwarnings( + "ignore:Series.view is deprecated and will be removed in a future version.:FutureWarning" # noqa: E501 +) + class TestView: def test_view_i8_to_datetimelike(self): diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 84c612c43da29..4281610b28951 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -969,7 +969,7 @@ def test_constructor_dtype_datetime64_10(self): # GH3414 related expected = Series(pydates, dtype="datetime64[ms]") - result = Series(Series(dates).view(np.int64) / 1000000, dtype="M8[ms]") + result = Series(Series(dates).astype(np.int64) / 1000000, dtype="M8[ms]") tm.assert_series_equal(result, expected) result = Series(dates, dtype="datetime64[ms]") diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py index 24c4706810154..e5302ec9833f1 100644 --- a/pandas/tests/test_algos.py +++ b/pandas/tests/test_algos.py @@ -976,7 +976,7 @@ def test_isin_datetimelike_values_numeric_comps(self, dtype, dtype1): # Anything but object and we get all-False shortcut dta = date_range("2013-01-01", periods=3)._values - arr = Series(dta.view("i8")).view(dtype1)._values + arr = Series(dta.view("i8")).array.view(dtype1) comps = arr.view("i8").astype(dtype) diff --git a/pandas/tests/test_nanops.py b/pandas/tests/test_nanops.py index a0062d2b6dd44..632d9783c7f81 100644 --- a/pandas/tests/test_nanops.py +++ b/pandas/tests/test_nanops.py @@ -1114,12 +1114,16 @@ def test_nanmean(self, unit): expected = dti[1] for obj in [dti, DatetimeArray(dti), Series(dti)]: + if isinstance(obj, Series): + obj = obj._values result = nanops.nanmean(obj) assert result == expected dti2 = dti.insert(1, pd.NaT) for obj in [dti2, DatetimeArray(dti2), Series(dti2)]: + if isinstance(obj, Series): + obj = obj._values result = nanops.nanmean(obj) assert result == expected