diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index d721213dc38e7..c8313bcadabb8 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -647,6 +647,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 ``pd.core.internals.api.maybe_infer_ndim`` (:issue:`40226`) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 38be038efcaa5..aaa20c0863a8e 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -749,7 +749,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 180080da4cd00..33de89d1e24be 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -238,7 +238,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: @@ -273,7 +273,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 @@ -382,7 +382,7 @@ def freq(self) -> BaseOffset: # type: ignore[override] """ Return the frequency object for this PeriodArray. """ - return self.dtype.freq + return self.dtype.unit @property def freqstr(self) -> str: @@ -956,7 +956,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. @@ -1244,7 +1244,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 @@ -1315,8 +1315,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 1e6761b2e1db0..912bed745188a 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -1069,11 +1069,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 @@ -1094,6 +1124,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 feca60c6e28a2..c05e11c05e282 100644 --- a/pandas/io/json/_table_schema.py +++ b/pandas/io/json/_table_schema.py @@ -139,7 +139,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 95be5f3768be6..016c6f7486fa8 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -434,11 +434,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]", @@ -449,7 +449,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 @@ -573,10 +573,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 @@ -585,6 +585,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 a3be4e2b4420a..70bdbffb276ea 100644 --- a/pandas/tests/extension/test_period.py +++ b/pandas/tests/extension/test_period.py @@ -93,7 +93,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)