diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 898d18af56f0e..c33cd505d0948 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -590,6 +590,9 @@ Deprecations - :func:`pandas.api.types.is_categorical` is deprecated and will be removed in a future version; use `:func:pandas.api.types.is_categorical_dtype` instead (:issue:`33385`) - :meth:`Index.get_value` is deprecated and will be removed in a future version (:issue:`19728`) +- :meth:`Series.dt.week` and `Series.dt.weekofyear` are deprecated and will be removed in a future version, use :meth:`Series.dt.isocalendar().week` instead (:issue:`33595`) +- :meth:`DatetimeIndex.week` and `DatetimeIndex.weekofyear` are deprecated and will be removed in a future version, use :meth:`DatetimeIndex.isocalendar().week` instead (:issue:`33595`) +- :meth:`DatetimeArray.week` and `DatetimeArray.weekofyear` are deprecated and will be removed in a future version, use :meth:`DatetimeArray.isocalendar().week` instead (:issue:`33595`) - :meth:`DateOffset.__call__` is deprecated and will be removed in a future version, use ``offset + other`` instead (:issue:`34171`) - Indexing an :class:`Index` object with a float key is deprecated, and will raise an ``IndexError`` in the future. You can manually convert to an integer key diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 90088c370697e..50d792aeb12f4 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -1298,6 +1298,32 @@ def isocalendar(self): iso_calendar_df.iloc[self._isnan] = None return iso_calendar_df + @property + def weekofyear(self): + """ + The week ordinal of the year. + + .. deprecated:: 1.1.0 + + weekofyear and week have been deprecated. + Please use DatetimeIndex.isocalendar().week instead. + """ + warnings.warn( + "weekofyear and week have been deprecated, please use " + "DatetimeIndex.isocalendar().week instead, which returns " + "a Series. To exactly reproduce the behavior of week and " + "weekofyear and return an Index, you may call " + "pd.Int64Index(idx.isocalendar().week)", + FutureWarning, + stacklevel=3, + ) + week_series = self.isocalendar().week + if week_series.hasnans: + return week_series.to_numpy(dtype="float64", na_value=np.nan) + return week_series.to_numpy(dtype="int64") + + week = weekofyear + year = _field_accessor( "year", "Y", @@ -1482,14 +1508,6 @@ def isocalendar(self): dtype: int64 """, ) - weekofyear = _field_accessor( - "weekofyear", - "woy", - """ - The week ordinal of the year. - """, - ) - week = weekofyear _dayofweek_doc = """ The day of the week with Monday=0, Sunday=6. diff --git a/pandas/core/indexes/accessors.py b/pandas/core/indexes/accessors.py index 598d228723ac8..881d5ce1fbaab 100644 --- a/pandas/core/indexes/accessors.py +++ b/pandas/core/indexes/accessors.py @@ -2,6 +2,7 @@ datetimelike delegation """ from typing import TYPE_CHECKING +import warnings import numpy as np @@ -250,6 +251,30 @@ def isocalendar(self): """ return self._get_values().isocalendar().set_index(self._parent.index) + @property + def weekofyear(self): + """ + The week ordinal of the year. + + .. deprecated:: 1.1.0 + + Series.dt.weekofyear and Series.dt.week have been deprecated. + Please use Series.dt.isocalendar().week instead. + """ + warnings.warn( + "Series.dt.weekofyear and Series.dt.week have been deprecated. " + "Please use Series.dt.isocalendar().week instead.", + FutureWarning, + stacklevel=2, + ) + week_series = self.isocalendar().week + week_series.name = self.name + if week_series.hasnans: + return week_series.astype("float64") + return week_series.astype("int64") + + week = weekofyear + @delegate_names( delegate=TimedeltaArray, accessors=TimedeltaArray._datetimelike_ops, typ="property" diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index d0bf5bb41bb2c..1a61b379de943 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -546,6 +546,9 @@ def test_bool_properties(self, datetime_index, propname): @pytest.mark.parametrize("propname", pd.DatetimeIndex._field_ops) def test_int_properties(self, datetime_index, propname): + if propname in ["week", "weekofyear"]: + # GH#33595 Deprecate week and weekofyear + return dti = datetime_index arr = DatetimeArray(dti) diff --git a/pandas/tests/groupby/transform/test_transform.py b/pandas/tests/groupby/transform/test_transform.py index e1042bf35acc4..7997247ca0307 100644 --- a/pandas/tests/groupby/transform/test_transform.py +++ b/pandas/tests/groupby/transform/test_transform.py @@ -1022,7 +1022,7 @@ def test_groupby_transform_with_datetimes(func, values): dates = pd.date_range("1/1/2011", periods=10, freq="D") stocks = pd.DataFrame({"price": np.arange(10.0)}, index=dates) - stocks["week_id"] = pd.to_datetime(stocks.index).week + stocks["week_id"] = dates.isocalendar().set_index(dates).week result = stocks.groupby(stocks["week_id"])["price"].transform(func) diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index 42a72125ba411..b9373328eb87f 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -156,8 +156,8 @@ def test_datetimeindex_accessors(self): assert dti.dayofyear[0] == 1 assert dti.dayofyear[120] == 121 - assert dti.weekofyear[0] == 1 - assert dti.weekofyear[120] == 18 + assert dti.isocalendar().week[0] == 1 + assert dti.isocalendar().week[120] == 18 assert dti.quarter[0] == 1 assert dti.quarter[120] == 2 @@ -192,7 +192,7 @@ def test_datetimeindex_accessors(self): assert len(dti.microsecond) == 365 assert len(dti.dayofweek) == 365 assert len(dti.dayofyear) == 365 - assert len(dti.weekofyear) == 365 + assert len(dti.isocalendar()) == 365 assert len(dti.quarter) == 365 assert len(dti.is_month_start) == 365 assert len(dti.is_month_end) == 365 @@ -205,6 +205,9 @@ def test_datetimeindex_accessors(self): # non boolean accessors -> return Index for accessor in DatetimeIndex._field_ops: + if accessor in ["week", "weekofyear"]: + # GH#33595 Deprecate week and weekofyear + continue res = getattr(dti, accessor) assert len(res) == 365 assert isinstance(res, Index) @@ -285,7 +288,7 @@ def test_datetimeindex_accessors(self): dates = ["2013/12/29", "2013/12/30", "2013/12/31"] dates = DatetimeIndex(dates, tz="Europe/Brussels") expected = [52, 1, 1] - assert dates.weekofyear.tolist() == expected + assert dates.isocalendar().week.tolist() == expected assert [d.weekofyear for d in dates] == expected # GH 12806 @@ -383,6 +386,15 @@ def test_iter_readonly(): list(dti) +def test_week_and_weekofyear_are_deprecated(): + # GH#33595 Deprecate week and weekofyear + idx = pd.date_range(start="2019-12-29", freq="D", periods=4) + with tm.assert_produces_warning(FutureWarning): + idx.week + with tm.assert_produces_warning(FutureWarning): + idx.weekofyear + + def test_isocalendar_returns_correct_values_close_to_new_year_with_tz(): # GH 6538: Check that DatetimeIndex and its TimeStamp elements # return the same weekofyear accessor close to new year w/ tz diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 21ee8649172da..e5d1277aed9cd 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -40,8 +40,6 @@ def test_dti_date_out_of_range(self, data): [ "dayofweek", "dayofyear", - "week", - "weekofyear", "quarter", "days_in_month", "is_month_start", @@ -59,6 +57,12 @@ def test_dti_timestamp_fields(self, field): result = getattr(Timestamp(idx[-1]), field) assert result == expected + def test_dti_timestamp_isocalendar_fields(self): + idx = tm.makeDateIndex(100) + expected = tuple(idx.isocalendar().iloc[-1].to_list()) + result = idx[-1].isocalendar() + assert result == expected + def test_dti_timestamp_freq_fields(self): # extra fields from DatetimeIndex like quarter and week idx = tm.makeDateIndex(100) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index 5a92ee37342d5..e1e2ea1a5cec8 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -66,6 +66,9 @@ def test_nat_vector_field_access(): # on NaT/Timestamp for compat with datetime if field == "weekday": continue + if field in ["week", "weekofyear"]: + # GH#33595 Deprecate week and weekofyear + continue result = getattr(idx, field) expected = Index([getattr(x, field) for x in idx]) @@ -78,6 +81,9 @@ def test_nat_vector_field_access(): # on NaT/Timestamp for compat with datetime if field == "weekday": continue + if field in ["week", "weekofyear"]: + # GH#33595 Deprecate week and weekofyear + continue result = getattr(ser.dt, field) expected = [getattr(x, field) for x in idx] diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index a6430b4525d4a..042841bb4e019 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -719,6 +719,9 @@ def test_dt_accessor_api_for_categorical(self): tm.assert_equal(res, exp) for attr in attr_names: + if attr in ["week", "weekofyear"]: + # GH#33595 Deprecate week and weekofyear + continue res = getattr(c.dt, attr) exp = getattr(s.dt, attr) diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index 8b1f58414e175..0fd51b8828bc5 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -89,7 +89,8 @@ def compare(s, name): for s in cases: for prop in ok_for_dt: # we test freq below - if prop != "freq": + # we ignore week and weekofyear because they are deprecated + if prop not in ["freq", "week", "weekofyear"]: compare(s, prop) for prop in ok_for_dt_methods: @@ -122,7 +123,8 @@ def compare(s, name): for prop in ok_for_dt: # we test freq below - if prop != "freq": + # we ignore week and weekofyear because they are deprecated + if prop not in ["freq", "week", "weekofyear"]: compare(s, prop) for prop in ok_for_dt_methods: @@ -687,3 +689,12 @@ def test_isocalendar(self, input_series, expected_output): expected_output, columns=["year", "week", "day"], dtype="UInt32" ) tm.assert_frame_equal(result, expected_frame) + + +def test_week_and_weekofyear_are_deprecated(): + # GH#33595 Deprecate week and weekofyear + series = pd.to_datetime(pd.Series(["2020-01-01"])) + with tm.assert_produces_warning(FutureWarning): + series.dt.week + with tm.assert_produces_warning(FutureWarning): + series.dt.weekofyear