From 01943cde338b6d4927cae503a1736f1b6aa5f82e Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Jun 2020 15:30:47 -0700 Subject: [PATCH 1/4] REF: return ndarray from to_perioddelta; use it less --- pandas/_libs/tslibs/offsets.pyx | 33 ++++++++++++------------ pandas/core/arrays/datetimes.py | 5 ++-- pandas/tests/arrays/test_datetimelike.py | 2 +- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 33b478c4d8da4..9f7fa80caca32 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -36,7 +36,7 @@ from pandas._libs.tslibs.base cimport ABCTimestamp from pandas._libs.tslibs.ccalendar import ( MONTH_ALIASES, MONTH_TO_CAL_NUM, weekday_to_int, int_to_weekday, ) -from pandas._libs.tslibs.ccalendar cimport get_days_in_month, dayofweek +from pandas._libs.tslibs.ccalendar cimport get_days_in_month, dayofweek, DAY_NANOS from pandas._libs.tslibs.conversion cimport ( convert_datetime_to_tsobject, localize_pydatetime, @@ -1046,6 +1046,8 @@ cdef class RelativeDeltaOffset(BaseOffset): ------- DatetimeIndex """ + from pandas._libs.tslibs.timedeltas import Timedelta + kwds = self.kwds relativedelta_fast = { "years", @@ -1067,11 +1069,7 @@ cdef class RelativeDeltaOffset(BaseOffset): weeks = kwds.get("weeks", 0) * self.n if weeks: - # integer addition on PeriodIndex is deprecated, - # so we directly use _time_shift instead - asper = index.to_period("W") - shifted = asper._time_shift(weeks) - index = shifted.to_timestamp() + index.to_perioddelta("W") + index = index + Timedelta(days=7*weeks) timedelta_kwds = { k: v @@ -1079,7 +1077,6 @@ cdef class RelativeDeltaOffset(BaseOffset): if k in ["days", "hours", "minutes", "seconds", "microseconds"] } if timedelta_kwds: - from .timedeltas import Timedelta delta = Timedelta(**timedelta_kwds) index = index + (self.n * delta) return index @@ -1383,13 +1380,15 @@ cdef class BusinessDay(BusinessMixin): @apply_index_wraps def apply_index(self, dtindex): - time = dtindex.to_perioddelta("D") + i8other = dtindex.asi8 + time = (i8other % DAY_NANOS).view("timedelta64[ns]") + # to_period rolls forward to next BDay; track and # reduce n where it does when rolling forward asper = dtindex.to_period("B") if self.n > 0: - shifted = (dtindex.to_perioddelta("B") - time).asi8 != 0 + shifted = (dtindex.to_perioddelta("B") - time).view("i8") != 0 # Integer-array addition is deprecated, so we use # _time_shift directly @@ -2275,8 +2274,9 @@ cdef class SemiMonthOffset(SingleConstructorOffset): # determine how many days away from the 1st of the month we are from pandas import Timedelta + i8other = dtindex.asi8 dti = dtindex - days_from_start = dtindex.to_perioddelta("M").asi8 + days_from_start = dtindex.to_perioddelta("M").view("i8") delta = Timedelta(days=self.day_of_month - 1).value # get boolean array for each element before the day_of_month @@ -2289,7 +2289,7 @@ cdef class SemiMonthOffset(SingleConstructorOffset): roll = self._get_roll(dtindex, before_day_of_month, after_day_of_month) # isolate the time since it will be striped away one the next line - time = dtindex.to_perioddelta("D") + time = (i8other % DAY_NANOS).view("timedelta64[ns]") # apply the correct number of months @@ -2504,12 +2504,10 @@ cdef class Week(SingleConstructorOffset): @apply_index_wraps def apply_index(self, dtindex): if self.weekday is None: - # integer addition on PeriodIndex is deprecated, - # so we use _time_shift directly - asper = dtindex.to_period("W") + from pandas._libs.tslibs.timedeltas import Timedelta - shifted = asper._time_shift(self.n) - return shifted.to_timestamp() + dtindex.to_perioddelta("W") + result = np.asarray(dtindex) + Timedelta(days=7 * self.n) + return type(dtindex)._simple_new(result) else: return self._end_apply_index(dtindex) @@ -2529,7 +2527,8 @@ cdef class Week(SingleConstructorOffset): from pandas import Timedelta from .frequencies import get_freq_code # TODO: avoid circular import - off = dtindex.to_perioddelta("D") + i8other = dtindex.asi8 + off = (i8other % DAY_NANOS).view("timedelta64[ns]") base, mult = get_freq_code(self.freqstr) base_period = dtindex.to_period(base) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index f11f3ad974b37..898c19bad15fd 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -1122,14 +1122,13 @@ def to_perioddelta(self, freq): Returns ------- - TimedeltaArray/Index + ndarray[timedelta64[ns]] """ # TODO: consider privatizing (discussion in GH#23113) - from pandas.core.arrays.timedeltas import TimedeltaArray i8delta = self.asi8 - self.to_period(freq).to_timestamp().asi8 m8delta = i8delta.view("m8[ns]") - return TimedeltaArray(m8delta) + return m8delta # ----------------------------------------------------------------- # Properties - Vectorized Timestamp Properties/Methods diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index 1a61b379de943..f9a7de740fe42 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -513,7 +513,7 @@ def test_to_perioddelta(self, datetime_index, freqstr): expected = dti.to_perioddelta(freq=freqstr) result = arr.to_perioddelta(freq=freqstr) - assert isinstance(result, TimedeltaArray) + assert isinstance(result, np.ndarray) # placeholder until these become actual EA subclasses and we can use # an EA-specific tm.assert_ function From a4344297e7cf1d3aa505205a898cc238c46d122a Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Jun 2020 15:32:43 -0700 Subject: [PATCH 2/4] reorder import --- pandas/_libs/tslibs/offsets.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 9f7fa80caca32..85eaf2a6775cb 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -36,7 +36,7 @@ from pandas._libs.tslibs.base cimport ABCTimestamp from pandas._libs.tslibs.ccalendar import ( MONTH_ALIASES, MONTH_TO_CAL_NUM, weekday_to_int, int_to_weekday, ) -from pandas._libs.tslibs.ccalendar cimport get_days_in_month, dayofweek, DAY_NANOS +from pandas._libs.tslibs.ccalendar cimport DAY_NANOS, get_days_in_month, dayofweek from pandas._libs.tslibs.conversion cimport ( convert_datetime_to_tsobject, localize_pydatetime, From 720842623c4d312d91658b76b1f6d8ae78e35b12 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Jun 2020 15:33:08 -0700 Subject: [PATCH 3/4] lint fixup --- pandas/_libs/tslibs/offsets.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 85eaf2a6775cb..6b11bddc1f634 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1069,7 +1069,7 @@ cdef class RelativeDeltaOffset(BaseOffset): weeks = kwds.get("weeks", 0) * self.n if weeks: - index = index + Timedelta(days=7*weeks) + index = index + Timedelta(days=7 * weeks) timedelta_kwds = { k: v From 17edf6e76d7d4c4273f2b8e1c1fc4539ee399149 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Jun 2020 20:38:37 -0700 Subject: [PATCH 4/4] partial revert --- pandas/_libs/tslibs/offsets.pyx | 22 +++++++++++----------- pandas/core/arrays/datetimes.py | 5 +++-- pandas/tests/arrays/test_datetimelike.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 6b11bddc1f634..0deaf082dd1c7 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1046,8 +1046,6 @@ cdef class RelativeDeltaOffset(BaseOffset): ------- DatetimeIndex """ - from pandas._libs.tslibs.timedeltas import Timedelta - kwds = self.kwds relativedelta_fast = { "years", @@ -1069,7 +1067,7 @@ cdef class RelativeDeltaOffset(BaseOffset): weeks = kwds.get("weeks", 0) * self.n if weeks: - index = index + Timedelta(days=7 * weeks) + index = index + timedelta(days=7 * weeks) timedelta_kwds = { k: v @@ -1077,6 +1075,7 @@ cdef class RelativeDeltaOffset(BaseOffset): if k in ["days", "hours", "minutes", "seconds", "microseconds"] } if timedelta_kwds: + from .timedeltas import Timedelta delta = Timedelta(**timedelta_kwds) index = index + (self.n * delta) return index @@ -1388,7 +1387,7 @@ cdef class BusinessDay(BusinessMixin): asper = dtindex.to_period("B") if self.n > 0: - shifted = (dtindex.to_perioddelta("B") - time).view("i8") != 0 + shifted = (dtindex.to_perioddelta("B") - time).asi8 != 0 # Integer-array addition is deprecated, so we use # _time_shift directly @@ -2274,9 +2273,9 @@ cdef class SemiMonthOffset(SingleConstructorOffset): # determine how many days away from the 1st of the month we are from pandas import Timedelta - i8other = dtindex.asi8 dti = dtindex - days_from_start = dtindex.to_perioddelta("M").view("i8") + i8other = dtindex.asi8 + days_from_start = dtindex.to_perioddelta("M").asi8 delta = Timedelta(days=self.day_of_month - 1).value # get boolean array for each element before the day_of_month @@ -2504,10 +2503,11 @@ cdef class Week(SingleConstructorOffset): @apply_index_wraps def apply_index(self, dtindex): if self.weekday is None: - from pandas._libs.tslibs.timedeltas import Timedelta - - result = np.asarray(dtindex) + Timedelta(days=7 * self.n) - return type(dtindex)._simple_new(result) + # integer addition on PeriodIndex is deprecated, + # so we use _time_shift directly + td = timedelta(days=7 * self.n) + td64 = np.timedelta64(td, "ns") + return dtindex + td64 else: return self._end_apply_index(dtindex) @@ -2528,7 +2528,7 @@ cdef class Week(SingleConstructorOffset): from .frequencies import get_freq_code # TODO: avoid circular import i8other = dtindex.asi8 - off = (i8other % DAY_NANOS).view("timedelta64[ns]") + off = (i8other % DAY_NANOS).view("timedelta64") base, mult = get_freq_code(self.freqstr) base_period = dtindex.to_period(base) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 898c19bad15fd..f11f3ad974b37 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -1122,13 +1122,14 @@ def to_perioddelta(self, freq): Returns ------- - ndarray[timedelta64[ns]] + TimedeltaArray/Index """ # TODO: consider privatizing (discussion in GH#23113) + from pandas.core.arrays.timedeltas import TimedeltaArray i8delta = self.asi8 - self.to_period(freq).to_timestamp().asi8 m8delta = i8delta.view("m8[ns]") - return m8delta + return TimedeltaArray(m8delta) # ----------------------------------------------------------------- # Properties - Vectorized Timestamp Properties/Methods diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index f9a7de740fe42..1a61b379de943 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -513,7 +513,7 @@ def test_to_perioddelta(self, datetime_index, freqstr): expected = dti.to_perioddelta(freq=freqstr) result = arr.to_perioddelta(freq=freqstr) - assert isinstance(result, np.ndarray) + assert isinstance(result, TimedeltaArray) # placeholder until these become actual EA subclasses and we can use # an EA-specific tm.assert_ function