Skip to content

DEPR: Tick.delta #56558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/source/user_guide/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
33 changes: 22 additions & 11 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand All @@ -997,21 +1008,21 @@ 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):
if isinstance(other, Tick):
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:
Expand All @@ -1029,15 +1040,15 @@ 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):
# PyDate_Check includes date, datetime
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__}")

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
9 changes: 6 additions & 3 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
)
from pandas._libs.tslibs import (
Resolution,
Tick,
Timedelta,
periods_per_day,
timezones,
to_offset,
Expand Down Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions pandas/tests/arithmetic/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/scalar/timedelta/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 10 additions & 8 deletions pandas/tests/tseries/offsets/test_ticks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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():
Expand All @@ -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 = (
Expand Down Expand Up @@ -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:
Expand Down