diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 0f07a9cf3c0e0..ac90483513af5 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -820,6 +820,25 @@ def to_period(self, freq=None): return PeriodArrayMixin(self.values, freq=freq) + def to_perioddelta(self, freq): + """ + Calculate TimedeltaArray of difference between index + values and index converted to PeriodArray at specified + freq. Used for vectorized offsets + + Parameters + ---------- + freq: Period frequency + + Returns + ------- + TimedeltaArray/Index + """ + # TODO: consider privatizing (discussion in GH#23113) + from pandas.core.arrays.timedeltas import TimedeltaArrayMixin + i8delta = self.asi8 - self.to_period(freq).to_timestamp().asi8 + return TimedeltaArrayMixin(i8delta) + # ----------------------------------------------------------------- # Properties - Vectorized Timestamp Properties/Methods diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index e0219acc115b5..7fb7e24cce94e 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -42,7 +42,6 @@ from pandas.tseries.offsets import ( CDay, prefix_mapping) -from pandas.core.tools.timedeltas import to_timedelta from pandas.util._decorators import Appender, cache_readonly, Substitution import pandas.core.common as com import pandas.tseries.offsets as offsets @@ -545,13 +544,6 @@ def to_series(self, keep_tz=False, index=None, name=None): return Series(values, index=index, name=name) - @Appender(DatetimeArrayMixin.to_period.__doc__) - def to_period(self, freq=None): - from pandas.core.indexes.period import PeriodIndex - - result = DatetimeArrayMixin.to_period(self, freq=freq) - return PeriodIndex(result, name=self.name) - def snap(self, freq='S'): """ Snap time stamps to nearest occurring frequency @@ -623,23 +615,6 @@ def union(self, other): result.freq = to_offset(result.inferred_freq) return result - def to_perioddelta(self, freq): - """ - Calculate TimedeltaIndex of difference between index - values and index converted to periodIndex at specified - freq. Used for vectorized offsets - - Parameters - ---------- - freq: Period frequency - - Returns - ------- - y: TimedeltaIndex - """ - return to_timedelta(self.asi8 - self.to_period(freq) - .to_timestamp().asi8) - def union_many(self, others): """ A bit of a hack to accelerate unioning a collection of indexes @@ -1168,6 +1143,9 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): is_year_end = wrap_field_accessor(DatetimeArrayMixin.is_year_end) is_leap_year = wrap_field_accessor(DatetimeArrayMixin.is_leap_year) + to_perioddelta = wrap_array_method(DatetimeArrayMixin.to_perioddelta, + False) + to_period = wrap_array_method(DatetimeArrayMixin.to_period, True) normalize = wrap_array_method(DatetimeArrayMixin.normalize, True) to_julian_date = wrap_array_method(DatetimeArrayMixin.to_julian_date, False) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index cbcd39317e17e..6c68ba2c35919 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -289,9 +289,9 @@ def __array_wrap__(self, result, context=None): """ if isinstance(context, tuple) and len(context) > 0: func = context[0] - if (func is np.add): + if func is np.add: pass - elif (func is np.subtract): + elif func is np.subtract: name = self.name left = context[1][0] right = context[1][1] @@ -312,7 +312,7 @@ def __array_wrap__(self, result, context=None): return result # the result is object dtype array of Period # cannot pass _simple_new as it is - return self._shallow_copy(result, freq=self.freq, name=self.name) + return type(self)(result, freq=self.freq, name=self.name) @property def size(self): diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index bfce5fb1462d9..ac7692c4afa74 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -67,6 +67,20 @@ def test_astype_object(self, tz_naive_fixture): assert asobj.dtype == 'O' assert list(asobj) == list(dti) + @pytest.mark.parametrize('freqstr', ['D', 'B', 'W', 'M', 'Q', 'Y']) + def test_to_perioddelta(self, datetime_index, freqstr): + # GH#23113 + dti = datetime_index + arr = DatetimeArrayMixin(dti) + + expected = dti.to_perioddelta(freq=freqstr) + result = arr.to_perioddelta(freq=freqstr) + assert isinstance(result, TimedeltaArrayMixin) + + # placeholder until these become actual EA subclasses and we can use + # an EA-specific tm.assert_ function + tm.assert_index_equal(pd.Index(result), pd.Index(expected)) + @pytest.mark.parametrize('freqstr', ['D', 'B', 'W', 'M', 'Q', 'Y']) def test_to_period(self, datetime_index, freqstr): dti = datetime_index