From 04d4e46dc053e6b241da39d9dfa290af65af741b Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 29 May 2023 09:53:21 -0700 Subject: [PATCH] REF: partially decouple Period from Tick --- pandas/_libs/tslibs/dtypes.pxd | 1 + pandas/_libs/tslibs/dtypes.pyi | 5 +++++ pandas/_libs/tslibs/dtypes.pyx | 19 +++++++++++++++++++ pandas/_libs/tslibs/offsets.pyi | 1 - pandas/_libs/tslibs/offsets.pyx | 8 -------- pandas/_libs/tslibs/period.pyx | 5 ++--- pandas/_testing/asserters.py | 2 +- pandas/core/arrays/period.py | 9 ++++----- 8 files changed, 32 insertions(+), 18 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 0e695bf4e57eb..3e86ca034e88c 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -105,3 +105,4 @@ cdef class PeriodDtypeBase: int64_t _n cpdef int _get_to_timestamp_base(self) + cpdef bint _is_tick_like(self) diff --git a/pandas/_libs/tslibs/dtypes.pyi b/pandas/_libs/tslibs/dtypes.pyi index 7142df41a7048..bea3e18273318 100644 --- a/pandas/_libs/tslibs/dtypes.pyi +++ b/pandas/_libs/tslibs/dtypes.pyi @@ -26,6 +26,11 @@ class PeriodDtypeBase: @property def _freqstr(self) -> str: ... def __hash__(self) -> int: ... + def _is_tick_like(self) -> bool: ... + @property + def _creso(self) -> int: ... + @property + def _td64_unit(self) -> str: ... class FreqGroup(Enum): FR_ANN: int diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index c8779d5b244dc..68bd285e6bfb2 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -77,6 +77,25 @@ cdef class PeriodDtypeBase: return FR_SEC return base + cpdef bint _is_tick_like(self): + return self._dtype_code >= PeriodDtypeCode.D + + @property + def _creso(self) -> int: + return { + PeriodDtypeCode.D: NPY_DATETIMEUNIT.NPY_FR_D, + PeriodDtypeCode.H: NPY_DATETIMEUNIT.NPY_FR_h, + PeriodDtypeCode.T: NPY_DATETIMEUNIT.NPY_FR_m, + PeriodDtypeCode.S: NPY_DATETIMEUNIT.NPY_FR_s, + PeriodDtypeCode.L: NPY_DATETIMEUNIT.NPY_FR_ms, + PeriodDtypeCode.U: NPY_DATETIMEUNIT.NPY_FR_us, + PeriodDtypeCode.N: NPY_DATETIMEUNIT.NPY_FR_ns, + }[self._dtype_code] + + @property + def _td64_unit(self) -> str: + return npy_unit_to_abbrev(self._creso) + _period_code_map = { # Annual freqs with various fiscal year ends. diff --git a/pandas/_libs/tslibs/offsets.pyi b/pandas/_libs/tslibs/offsets.pyi index c29a057e99ff5..f65933cf740b2 100644 --- a/pandas/_libs/tslibs/offsets.pyi +++ b/pandas/_libs/tslibs/offsets.pyi @@ -110,7 +110,6 @@ def to_offset(freq: timedelta | str) -> BaseOffset: ... class Tick(SingleConstructorOffset): _creso: int _prefix: str - _td64_unit: str def __init__(self, n: int = ..., normalize: bool = ...) -> None: ... @property def delta(self) -> Timedelta: ... diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index f062db77fb79b..b9522d23afdaa 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -912,7 +912,6 @@ cdef class SingleConstructorOffset(BaseOffset): cdef class Tick(SingleConstructorOffset): _adjust_dst = False _prefix = "undefined" - _td64_unit = "undefined" _attributes = tuple(["n", "normalize"]) def __init__(self, n=1, normalize=False): @@ -1092,7 +1091,6 @@ cdef class Tick(SingleConstructorOffset): cdef class Day(Tick): _nanos_inc = 24 * 3600 * 1_000_000_000 _prefix = "D" - _td64_unit = "D" _period_dtype_code = PeriodDtypeCode.D _creso = NPY_DATETIMEUNIT.NPY_FR_D @@ -1100,7 +1098,6 @@ cdef class Day(Tick): cdef class Hour(Tick): _nanos_inc = 3600 * 1_000_000_000 _prefix = "H" - _td64_unit = "h" _period_dtype_code = PeriodDtypeCode.H _creso = NPY_DATETIMEUNIT.NPY_FR_h @@ -1108,7 +1105,6 @@ cdef class Hour(Tick): cdef class Minute(Tick): _nanos_inc = 60 * 1_000_000_000 _prefix = "T" - _td64_unit = "m" _period_dtype_code = PeriodDtypeCode.T _creso = NPY_DATETIMEUNIT.NPY_FR_m @@ -1116,7 +1112,6 @@ cdef class Minute(Tick): cdef class Second(Tick): _nanos_inc = 1_000_000_000 _prefix = "S" - _td64_unit = "s" _period_dtype_code = PeriodDtypeCode.S _creso = NPY_DATETIMEUNIT.NPY_FR_s @@ -1124,7 +1119,6 @@ cdef class Second(Tick): cdef class Milli(Tick): _nanos_inc = 1_000_000 _prefix = "L" - _td64_unit = "ms" _period_dtype_code = PeriodDtypeCode.L _creso = NPY_DATETIMEUNIT.NPY_FR_ms @@ -1132,7 +1126,6 @@ cdef class Milli(Tick): cdef class Micro(Tick): _nanos_inc = 1000 _prefix = "U" - _td64_unit = "us" _period_dtype_code = PeriodDtypeCode.U _creso = NPY_DATETIMEUNIT.NPY_FR_us @@ -1140,7 +1133,6 @@ cdef class Micro(Tick): cdef class Nano(Tick): _nanos_inc = 1 _prefix = "N" - _td64_unit = "ns" _period_dtype_code = PeriodDtypeCode.N _creso = NPY_DATETIMEUNIT.NPY_FR_ns diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 2625caea2040b..49ff437f305dd 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -104,7 +104,6 @@ from pandas._libs.tslibs.nattype cimport ( from pandas._libs.tslibs.offsets cimport ( BaseOffset, is_offset_object, - is_tick_object, to_offset, ) @@ -1793,7 +1792,7 @@ cdef class _Period(PeriodMixin): cdef: int64_t inc - if not is_tick_object(self.freq): + if not self._dtype._is_tick_like(): raise IncompatibleFrequency("Input cannot be converted to " f"Period(freq={self.freqstr})") @@ -1805,7 +1804,7 @@ cdef class _Period(PeriodMixin): return NaT try: - inc = delta_to_nanoseconds(other, reso=self.freq._creso, round_ok=False) + inc = delta_to_nanoseconds(other, reso=self._dtype._creso, round_ok=False) except ValueError as err: raise IncompatibleFrequency("Input cannot be converted to " f"Period(freq={self.freqstr})") from err diff --git a/pandas/_testing/asserters.py b/pandas/_testing/asserters.py index 02bd91d90362c..d296cc998134b 100644 --- a/pandas/_testing/asserters.py +++ b/pandas/_testing/asserters.py @@ -331,7 +331,7 @@ def _get_ilevel_values(index, level): if check_names: assert_attr_equal("names", left, right, obj=obj) if isinstance(left, PeriodIndex) or isinstance(right, PeriodIndex): - assert_attr_equal("freq", left, right, obj=obj) + assert_attr_equal("dtype", left, right, obj=obj) if isinstance(left, IntervalIndex) or isinstance(right, IntervalIndex): assert_interval_array_equal(left._values, right._values) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 237ac99bcf45f..63a2580c4be31 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -767,14 +767,13 @@ def _add_timedelta_arraylike( ------- PeriodArray """ - freq = self.freq - if not isinstance(freq, Tick): + if not self.dtype._is_tick_like(): # We cannot add timedelta-like to non-tick PeriodArray raise TypeError( f"Cannot add or subtract timedelta64[ns] dtype from {self.dtype}" ) - dtype = np.dtype(f"m8[{freq._td64_unit}]") + dtype = np.dtype(f"m8[{self.dtype._td64_unit}]") # Similar to _check_timedeltalike_freq_compat, but we raise with a # more specific exception message if necessary. @@ -818,9 +817,9 @@ def _check_timedeltalike_freq_compat(self, other): ------ IncompatibleFrequency """ - assert isinstance(self.freq, Tick) # checked by calling function + assert self.dtype._is_tick_like() # checked by calling function - dtype = np.dtype(f"m8[{self.freq._td64_unit}]") + dtype = np.dtype(f"m8[{self.dtype._td64_unit}]") if isinstance(other, (timedelta, np.timedelta64, Tick)): td = np.asarray(Timedelta(other).asm8)