diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 1383202154f04..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`) 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..389e32deebbff 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -1071,11 +1071,41 @@ 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: """ 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 @@ -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)