diff --git a/doc/source/user_guide/timeseries.rst b/doc/source/user_guide/timeseries.rst index 105d11b67c9bf..0f0e6271d8329 100644 --- a/doc/source/user_guide/timeseries.rst +++ b/doc/source/user_guide/timeseries.rst @@ -1770,7 +1770,8 @@ We can instead only resample those groups where we have points as follows: def round(t, freq): # round a Timestamp to a specified freq freq = to_offset(freq) - return pd.Timestamp((t.value // freq.delta.value) * freq.delta.value) + td = pd.Timedelta(freq) + return pd.Timestamp((t.value // td.value) * td.value) ts.groupby(partial(round, freq="3min")).sum() diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 590cddba60f7a..68c4ef34c8f4f 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -438,6 +438,7 @@ Set the following option to opt into the future behavior: Other Deprecations ^^^^^^^^^^^^^^^^^^ - Changed :meth:`Timedelta.resolution_string` to return ``h``, ``min``, ``s``, ``ms``, ``us``, and ``ns`` instead of ``H``, ``T``, ``S``, ``L``, ``U``, and ``N``, for compatibility with respective deprecations in frequency aliases (:issue:`52536`) +- Deprecated :attr:`offsets.Day.delta`, :attr:`offsets.Hour.delta`, :attr:`offsets.Minute.delta`, :attr:`offsets.Second.delta`, :attr:`offsets.Milli.delta`, :attr:`offsets.Micro.delta`, :attr:`offsets.Nano.delta`, use ``pd.Timedelta(obj)`` instead (:issue:`55498`) - Deprecated :func:`pandas.api.types.is_interval` and :func:`pandas.api.types.is_period`, use ``isinstance(obj, pd.Interval)`` and ``isinstance(obj, pd.Period)`` instead (:issue:`55264`) - Deprecated :func:`pd.core.internals.api.make_block`, use public APIs instead (:issue:`40226`) - Deprecated :func:`read_gbq` and :meth:`DataFrame.to_gbq`. Use ``pandas_gbq.read_gbq`` and ``pandas_gbq.to_gbq`` instead https://pandas-gbq.readthedocs.io/en/latest/api.html (:issue:`55525`) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index b893939969c68..b3788b6003e67 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -913,8 +913,19 @@ cdef class Tick(SingleConstructorOffset): # Since cdef classes have no __dict__, we need to override return "" + @cache_readonly + def _as_pd_timedelta(self): + return Timedelta(self) + @property def delta(self): + warnings.warn( + # GH#55498 + f"{type(self).__name__}.delta is deprecated and will be removed in " + "a future version. Use pd.Timedelta(obj) instead", + FutureWarning, + stacklevel=find_stack_level(), + ) try: return self.n * Timedelta(self._nanos_inc) except OverflowError as err: @@ -962,22 +973,22 @@ cdef class Tick(SingleConstructorOffset): except ValueError: # e.g. "infer" return False - return self.delta == other + return self._as_pd_timedelta == other def __ne__(self, other): return not (self == other) def __le__(self, other): - return self.delta.__le__(other) + return self._as_pd_timedelta.__le__(other) def __lt__(self, other): - return self.delta.__lt__(other) + return self._as_pd_timedelta.__lt__(other) def __ge__(self, other): - return self.delta.__ge__(other) + return self._as_pd_timedelta.__ge__(other) def __gt__(self, other): - return self.delta.__gt__(other) + return self._as_pd_timedelta.__gt__(other) def __mul__(self, other): if is_float_object(other): @@ -997,13 +1008,13 @@ cdef class Tick(SingleConstructorOffset): def __truediv__(self, other): if not isinstance(self, Tick): # cython semantics mean the args are sometimes swapped - result = other.delta.__rtruediv__(self) + result = other._as_pd_timedelta.__rtruediv__(self) else: - result = self.delta.__truediv__(other) + result = self._as_pd_timedelta.__truediv__(other) return _wrap_timedelta_result(result) def __rtruediv__(self, other): - result = self.delta.__rtruediv__(other) + result = self._as_pd_timedelta.__rtruediv__(other) return _wrap_timedelta_result(result) def __add__(self, other): @@ -1011,7 +1022,7 @@ cdef class Tick(SingleConstructorOffset): if type(self) is type(other): return type(self)(self.n + other.n) else: - return delta_to_tick(self.delta + other.delta) + return delta_to_tick(self._as_pd_timedelta + other._as_pd_timedelta) try: return self._apply(other) except ApplyTypeError: @@ -1029,7 +1040,7 @@ cdef class Tick(SingleConstructorOffset): # Timestamp can handle tz and nano sec, thus no need to use apply_wraps if isinstance(other, _Timestamp): # GH#15126 - return other + self.delta + return other + self._as_pd_timedelta elif other is NaT: return NaT elif cnp.is_datetime64_object(other) or PyDate_Check(other): @@ -1037,7 +1048,7 @@ cdef class Tick(SingleConstructorOffset): return Timestamp(other) + self if cnp.is_timedelta64_object(other) or PyDelta_Check(other): - return other + self.delta + return other + self._as_pd_timedelta raise ApplyTypeError(f"Unhandled type: {type(other).__name__}") diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 2b03a64236128..4fc2fdb3202b1 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -533,7 +533,7 @@ def _as_range_index(self) -> RangeIndex: # Convert our i8 representations to RangeIndex # Caller is responsible for checking isinstance(self.freq, Tick) freq = cast(Tick, self.freq) - tick = freq.delta._value + tick = Timedelta(freq).as_unit("ns")._value rng = range(self[0]._value, self[-1]._value + tick, tick) return RangeIndex(rng) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 73143730085d6..c978abd8c2427 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -17,6 +17,8 @@ ) from pandas._libs.tslibs import ( Resolution, + Tick, + Timedelta, periods_per_day, timezones, to_offset, @@ -393,10 +395,11 @@ def _is_dates_only(self) -> bool: ------- bool """ - delta = getattr(self.freq, "delta", None) + if isinstance(self.freq, Tick): + delta = Timedelta(self.freq) - if delta and delta % dt.timedelta(days=1) != dt.timedelta(days=0): - return False + if delta % dt.timedelta(days=1) != dt.timedelta(days=0): + return False return self._values._is_dates_only diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index d3ce0e222e4cd..d8c1786b6b422 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -299,6 +299,12 @@ def test_numeric_arr_rdiv_tdscalar(self, three_days, numeric_idx, box_with_array expected = expected.astype(dtype) elif type(three_days) is timedelta: expected = expected.astype("m8[us]") + elif isinstance( + three_days, + (pd.offsets.Day, pd.offsets.Hour, pd.offsets.Minute, pd.offsets.Second), + ): + # closest reso is Second + expected = expected.astype("m8[s]") index = tm.box_expected(index, box) expected = tm.box_expected(expected, box) diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index 363ae1fa9c644..cc1e91893e308 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -1034,7 +1034,7 @@ def test_compare_tick(self, tick_classes): cls = tick_classes off = cls(4) - td = off.delta + td = off._as_pd_timedelta assert isinstance(td, Timedelta) assert td == off diff --git a/pandas/tests/tseries/offsets/test_ticks.py b/pandas/tests/tseries/offsets/test_ticks.py index abf187ace7cb3..b68b91826bc6f 100644 --- a/pandas/tests/tseries/offsets/test_ticks.py +++ b/pandas/tests/tseries/offsets/test_ticks.py @@ -242,8 +242,10 @@ def test_tick_delta_overflow(): # GH#55503 raise OutOfBoundsTimedelta, not OverflowError tick = offsets.Day(10**9) msg = "Cannot cast 1000000000 days 00:00:00 to unit='ns' without overflow" + depr_msg = "Day.delta is deprecated" with pytest.raises(OutOfBoundsTimedelta, match=msg): - tick.delta + with tm.assert_produces_warning(FutureWarning, match=depr_msg): + tick.delta @pytest.mark.parametrize("cls", tick_classes) @@ -254,24 +256,24 @@ def test_tick_division(cls): assert off / 2 == cls(5) assert off / 2.0 == cls(5) - assert off / off.delta == 1 - assert off / off.delta.to_timedelta64() == 1 + assert off / off._as_pd_timedelta == 1 + assert off / off._as_pd_timedelta.to_timedelta64() == 1 - assert off / Nano(1) == off.delta / Nano(1).delta + assert off / Nano(1) == off._as_pd_timedelta / Nano(1)._as_pd_timedelta if cls is not Nano: # A case where we end up with a smaller class result = off / 1000 assert isinstance(result, offsets.Tick) assert not isinstance(result, cls) - assert result.delta == off.delta / 1000 + assert result._as_pd_timedelta == off._as_pd_timedelta / 1000 if cls._nanos_inc < Timedelta(seconds=1)._value: # Case where we end up with a bigger class result = off / 0.001 assert isinstance(result, offsets.Tick) assert not isinstance(result, cls) - assert result.delta == off.delta / 0.001 + assert result._as_pd_timedelta == off._as_pd_timedelta / 0.001 def test_tick_mul_float(): @@ -293,7 +295,7 @@ def test_tick_mul_float(): @pytest.mark.parametrize("cls", tick_classes) def test_tick_rdiv(cls): off = cls(10) - delta = off.delta + delta = off._as_pd_timedelta td64 = delta.to_timedelta64() instance__type = ".".join([cls.__module__, cls.__name__]) msg = ( @@ -385,7 +387,7 @@ def test_compare_ticks_to_strs(cls): def test_compare_ticks_to_timedeltalike(cls): off = cls(19) - td = off.delta + td = off._as_pd_timedelta others = [td, td.to_timedelta64()] if cls is not Nano: