From cd0276934b53f49b97f4842d8c7ef90442d86e38 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 18 Jul 2025 08:39:15 -0700 Subject: [PATCH 1/4] DEPR: PeriodDtype.freq --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/arrays/datetimes.py | 2 +- pandas/core/arrays/period.py | 14 +++++------ pandas/core/dtypes/dtypes.py | 35 +++++++++++++++++++++++++++ pandas/io/json/_table_schema.py | 2 +- pandas/tests/dtypes/test_dtypes.py | 15 ++++++++---- pandas/tests/extension/test_period.py | 2 +- 7 files changed, 56 insertions(+), 15 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 1383202154f04..8ce310114e100 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -469,6 +469,7 @@ Other Deprecations - Deprecated strings ``w``, ``d``, ``MIN``, ``MS``, ``US`` and ``NS`` denoting units in :class:`Timedelta` in favour of ``W``, ``D``, ``min``, ``ms``, ``us`` and ``ns`` (:issue:`59051`) - Deprecated the ``arg`` parameter of ``Series.map``; pass the added ``func`` argument instead. (:issue:`61260`) - Deprecated using ``epoch`` date format in :meth:`DataFrame.to_json` and :meth:`Series.to_json`, use ``iso`` instead. (:issue:`57063`) +- Deprecated :meth:`PeriodDtype.freq`, use ``dtype.unit`` instead (:issue:`??`) .. --------------------------------------------------------------------------- .. _whatsnew_300.prior_deprecations: diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index b31c543188282..57c78864a0cac 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -758,7 +758,7 @@ def astype(self, dtype, copy: bool = True): ) elif isinstance(dtype, PeriodDtype): - return self.to_period(freq=dtype.freq) + return self.to_period(freq=dtype.unit) return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy) # ----------------------------------------------------------------- diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index ae92e17332c76..74c27b9f8d927 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -236,7 +236,7 @@ def __init__(self, values, dtype: Dtype | None = None, copy: bool = False) -> No if isinstance(values, type(self)): if dtype is not None and dtype != values.dtype: - raise raise_on_incompatible(values, dtype.freq) + raise raise_on_incompatible(values, dtype.unit) values, dtype = values._ndarray, values.dtype if not copy: @@ -271,7 +271,7 @@ def _from_sequence( if dtype is not None: dtype = pandas_dtype(dtype) if dtype and isinstance(dtype, PeriodDtype): - freq = dtype.freq + freq = dtype.unit else: freq = None @@ -380,7 +380,7 @@ def freq(self) -> BaseOffset: """ Return the frequency object for this PeriodArray. """ - return self.dtype.freq + return self.dtype.unit @property def freqstr(self) -> str: @@ -945,7 +945,7 @@ def astype(self, dtype, copy: bool = True): else: return self.copy() if isinstance(dtype, PeriodDtype): - return self.asfreq(dtype.freq) + return self.asfreq(dtype.unit) if lib.is_np_dtype(dtype, "M") or isinstance(dtype, DatetimeTZDtype): # GH#45038 match PeriodIndex behavior. @@ -1227,7 +1227,7 @@ def period_array( if isinstance(data_dtype, PeriodDtype): out = PeriodArray(data) if freq is not None: - if freq == data_dtype.freq: + if freq == data_dtype.unit: return out return out.asfreq(freq) return out @@ -1298,8 +1298,8 @@ def validate_dtype_freq( if not isinstance(dtype, PeriodDtype): raise ValueError("dtype must be PeriodDtype") if freq is None: - freq = dtype.freq - elif freq != dtype.freq: + freq = dtype.unit + elif freq != dtype.unit: raise IncompatibleFrequency("specified freq and dtype are different") # error: Incompatible return value type (got "Union[BaseOffset, Any, None]", # expected "BaseOffset") diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 912421dff1026..035fcc73df7ed 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -1071,6 +1071,33 @@ def __new__(cls, freq) -> PeriodDtype: # noqa: PYI034 def __reduce__(self) -> tuple[type_t[Self], tuple[str_type]]: return type(self), (self.name,) + @property + def unit(self): + """ + The unit object of this PeriodDtype. + + The `unit` property returns the `BaseOffset` object that represents the + unit of the PeriodDtype. This unit specifies the interval (e.g., + daily, monthly, yearly) associated with the Period type. It is essential + for operations that depend on time-based calculations within a period index + or series. + + See Also + -------- + Period : Represents a period of time. + PeriodIndex : Immutable ndarray holding ordinal values indicating + regular periods. + PeriodDtype : An ExtensionDtype for Period data. + date_range : Return a fixed frequency range of dates. + + Examples + -------- + >>> dtype = pd.PeriodDtype("D") + >>> dtype.unit + + """ + return self._freq + @property def freq(self) -> BaseOffset: """ @@ -1082,6 +1109,9 @@ def freq(self) -> BaseOffset: for operations that depend on time-based calculations within a period index or series. + .. deprecated: 3.0 + Use dtype.unit instead. + See Also -------- Period : Represents a period of time. @@ -1096,6 +1126,11 @@ def freq(self) -> BaseOffset: >>> dtype.freq """ + warnings.warn( + "PeriodDtype.freq is deprecated, use dtype.unit instead", + FutureWarning, + stacklevel=find_stack_level(), + ) return self._freq @classmethod diff --git a/pandas/io/json/_table_schema.py b/pandas/io/json/_table_schema.py index 7879be18b52c9..62969926cf073 100644 --- a/pandas/io/json/_table_schema.py +++ b/pandas/io/json/_table_schema.py @@ -141,7 +141,7 @@ def convert_pandas_type_to_json_field(arr) -> dict[str, JSONSerializable]: field["constraints"] = {"enum": list(cats)} field["ordered"] = ordered elif isinstance(dtype, PeriodDtype): - field["freq"] = dtype.freq.freqstr + field["freq"] = dtype.unit.freqstr elif isinstance(dtype, DatetimeTZDtype): if timezones.is_utc(dtype.tz): field["tz"] = "UTC" diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index 621217a8c9317..bc3a5fac742f6 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -426,11 +426,11 @@ def test_construction(self): for s in ["period[D]", "Period[D]", "D"]: dt = PeriodDtype(s) - assert dt.freq == pd.tseries.offsets.Day() + assert dt.unit == pd.tseries.offsets.Day() for s in ["period[3D]", "Period[3D]", "3D"]: dt = PeriodDtype(s) - assert dt.freq == pd.tseries.offsets.Day(3) + assert dt.unit == pd.tseries.offsets.Day(3) for s in [ "period[26h]", @@ -441,7 +441,7 @@ def test_construction(self): "1D2h", ]: dt = PeriodDtype(s) - assert dt.freq == pd.tseries.offsets.Hour(26) + assert dt.unit == pd.tseries.offsets.Hour(26) def test_cannot_use_custom_businessday(self): # GH#52534 @@ -565,10 +565,10 @@ def test_not_string(self): def test_perioddtype_caching_dateoffset_normalize(self): # GH 24121 per_d = PeriodDtype(pd.offsets.YearEnd(normalize=True)) - assert per_d.freq.normalize + assert per_d.unit.normalize per_d2 = PeriodDtype(pd.offsets.YearEnd(normalize=False)) - assert not per_d2.freq.normalize + assert not per_d2.unit.normalize def test_dont_keep_ref_after_del(self): # GH 54184 @@ -577,6 +577,11 @@ def test_dont_keep_ref_after_del(self): del dtype assert ref() is None + def test_freq_deprecation(self, dtype): + msg = "PeriodDtype.freq is deprecated, use dtype.unit instead" + with tm.assert_produces_warning(FutureWarning, match=msg): + dtype.freq + class TestIntervalDtype(Base): @pytest.fixture diff --git a/pandas/tests/extension/test_period.py b/pandas/tests/extension/test_period.py index 2e6fe12cbbd13..1941e68820ce8 100644 --- a/pandas/tests/extension/test_period.py +++ b/pandas/tests/extension/test_period.py @@ -94,7 +94,7 @@ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool): expected = exp_op(skipna=skipna) # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no # attribute "freq" - freq = ser.dtype.freq # type: ignore[union-attr] + freq = ser.dtype.unit # type: ignore[union-attr] expected = Period._from_ordinal(int(expected), freq=freq) tm.assert_almost_equal(result, expected) From 859988b1e9f10a3fca5fe4fdaba1f70c980cb757 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 18 Jul 2025 10:16:20 -0700 Subject: [PATCH 2/4] docstring fixup --- pandas/core/dtypes/dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 035fcc73df7ed..25cdd5c978b88 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -1109,7 +1109,7 @@ def freq(self) -> BaseOffset: for operations that depend on time-based calculations within a period index or series. - .. deprecated: 3.0 + .. deprecated:: 3.0 Use dtype.unit instead. See Also From eaa7ee96695ec4b20440a26f6f8d3a8f8fe24f9c Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 18 Jul 2025 10:37:02 -0700 Subject: [PATCH 3/4] GH ref --- doc/source/whatsnew/v3.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 8ce310114e100..89245af015294 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -455,6 +455,7 @@ Other Deprecations - Deprecated :func:`core.internals.api.make_block`, use public APIs instead (:issue:`56815`) - Deprecated :meth:`.DataFrameGroupby.corrwith` (:issue:`57158`) +- Deprecated :meth:`PeriodDtype.freq`, use ``dtype.unit`` instead (:issue:`61897`) - Deprecated :meth:`Timestamp.utcfromtimestamp`, use ``Timestamp.fromtimestamp(ts, "UTC")`` instead (:issue:`56680`) - Deprecated :meth:`Timestamp.utcnow`, use ``Timestamp.now("UTC")`` instead (:issue:`56680`) - Deprecated allowing non-keyword arguments in :meth:`DataFrame.all`, :meth:`DataFrame.min`, :meth:`DataFrame.max`, :meth:`DataFrame.sum`, :meth:`DataFrame.prod`, :meth:`DataFrame.mean`, :meth:`DataFrame.median`, :meth:`DataFrame.sem`, :meth:`DataFrame.var`, :meth:`DataFrame.std`, :meth:`DataFrame.skew`, :meth:`DataFrame.kurt`, :meth:`Series.all`, :meth:`Series.min`, :meth:`Series.max`, :meth:`Series.sum`, :meth:`Series.prod`, :meth:`Series.mean`, :meth:`Series.median`, :meth:`Series.sem`, :meth:`Series.var`, :meth:`Series.std`, :meth:`Series.skew`, and :meth:`Series.kurt`. (:issue:`57087`) @@ -469,7 +470,6 @@ Other Deprecations - Deprecated strings ``w``, ``d``, ``MIN``, ``MS``, ``US`` and ``NS`` denoting units in :class:`Timedelta` in favour of ``W``, ``D``, ``min``, ``ms``, ``us`` and ``ns`` (:issue:`59051`) - Deprecated the ``arg`` parameter of ``Series.map``; pass the added ``func`` argument instead. (:issue:`61260`) - Deprecated using ``epoch`` date format in :meth:`DataFrame.to_json` and :meth:`Series.to_json`, use ``iso`` instead. (:issue:`57063`) -- Deprecated :meth:`PeriodDtype.freq`, use ``dtype.unit`` instead (:issue:`??`) .. --------------------------------------------------------------------------- .. _whatsnew_300.prior_deprecations: From a4652fde3c2fb79e57d15df8226e8cc44f5ae63f Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 18 Jul 2025 13:43:23 -0700 Subject: [PATCH 4/4] docstring fixup --- pandas/core/dtypes/dtypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 25cdd5c978b88..389e32deebbff 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -1103,15 +1103,15 @@ def freq(self) -> BaseOffset: """ The frequency object of this PeriodDtype. + .. deprecated:: 3.0 + Use dtype.unit instead. + The `freq` property returns the `BaseOffset` object that represents the frequency of the PeriodDtype. This frequency specifies the interval (e.g., daily, monthly, yearly) associated with the Period type. It is essential for operations that depend on time-based calculations within a period index or series. - .. deprecated:: 3.0 - Use dtype.unit instead. - See Also -------- Period : Represents a period of time.