Skip to content

Commit e6d83dc

Browse files
committed
Fixed apply_index
Closes pandas-dev#34580
1 parent 031fb16 commit e6d83dc

File tree

4 files changed

+101
-6
lines changed

4 files changed

+101
-6
lines changed

doc/source/whatsnew/v1.1.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ Deprecations
811811
- :meth:`DatetimeIndex.week` and `DatetimeIndex.weekofyear` are deprecated and will be removed in a future version, use :meth:`DatetimeIndex.isocalendar().week` instead (:issue:`33595`)
812812
- :meth:`DatetimeArray.week` and `DatetimeArray.weekofyear` are deprecated and will be removed in a future version, use :meth:`DatetimeArray.isocalendar().week` instead (:issue:`33595`)
813813
- :meth:`DateOffset.__call__` is deprecated and will be removed in a future version, use ``offset + other`` instead (:issue:`34171`)
814+
- :meth:`~BusinessDay.apply_index` is deprecated and will be removed in a future version. Use ``offset + other`` instead (:issue:`34580`)
814815
- :meth:`DataFrame.tshift` and :meth:`Series.tshift` are deprecated and will be removed in a future version, use :meth:`DataFrame.shift` and :meth:`Series.shift` instead (:issue:`11631`)
815816
- Indexing an :class:`Index` object with a float key is deprecated, and will
816817
raise an ``IndexError`` in the future. You can manually convert to an integer key

pandas/_libs/tslibs/offsets.pyx

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,45 @@ cdef bint _is_normalized(datetime dt):
8282
return True
8383

8484

85+
def apply_wrapper_core(func, self, other):
86+
result = func(self, other)
87+
result = np.asarray(result)
88+
89+
if self.normalize:
90+
result = normalize_i8_timestamps(result.view("i8"), None)
91+
92+
return result
93+
94+
8595
def apply_index_wraps(func):
8696
# Note: normally we would use `@functools.wraps(func)`, but this does
8797
# not play nicely with cython class methods
88-
def wrapper(self, other) -> np.ndarray:
98+
def wrapper(self, other):
8999
# other is a DatetimeArray
100+
result = apply_wrapper_core(func, self, other)
101+
result = type(other)(result)
102+
warnings.warn("'Offset.apply_index(other)' is deprecated. "
103+
"Use 'offset + other' instead.", FutureWarning)
104+
return result
90105

91-
result = func(self, other)
92-
result = np.asarray(result)
106+
# do @functools.wraps(func) manually since it doesn't work on cdef funcs
107+
wrapper.__name__ = func.__name__
108+
wrapper.__doc__ = func.__doc__
109+
try:
110+
wrapper.__module__ = func.__module__
111+
except AttributeError:
112+
# AttributeError: 'method_descriptor' object has no
113+
# attribute '__module__'
114+
pass
115+
return wrapper
93116

94-
if self.normalize:
95-
result = normalize_i8_timestamps(result.view("i8"), None)
117+
118+
def apply_array_wraps(func):
119+
# Note: normally we would use `@functools.wraps(func)`, but this does
120+
# not play nicely with cython class methods
121+
def wrapper(self, other) -> np.ndarray:
122+
# other is a DatetimeArray
123+
result = apply_wrapper_core(func, self, other)
96124
return result
97125

98126
# do @functools.wraps(func) manually since it doesn't work on cdef funcs
@@ -554,6 +582,10 @@ cdef class BaseOffset:
554582
raises NotImplementedError for offsets without a
555583
vectorized implementation.
556584
585+
.. deprecated:: 1.1.0
586+
587+
Use ``offset + dtindex`` instead.
588+
557589
Parameters
558590
----------
559591
index : DatetimeIndex
@@ -567,6 +599,13 @@ cdef class BaseOffset:
567599
"does not have a vectorized implementation"
568600
)
569601

602+
@apply_array_wraps
603+
def _apply_array(self, dtindex):
604+
raise NotImplementedError(
605+
f"DateOffset subclass {type(self).__name__} "
606+
"does not have a vectorized implementation"
607+
)
608+
570609
def rollback(self, dt) -> datetime:
571610
"""
572611
Roll provided date backward to next offset only if not on offset.
@@ -1031,6 +1070,10 @@ cdef class RelativeDeltaOffset(BaseOffset):
10311070
-------
10321071
ndarray[datetime64[ns]]
10331072
"""
1073+
return self._apply_array(dtindex)
1074+
1075+
@apply_array_wraps
1076+
def _apply_array(self, dtindex):
10341077
dt64other = np.asarray(dtindex)
10351078
kwds = self.kwds
10361079
relativedelta_fast = {
@@ -1360,6 +1403,10 @@ cdef class BusinessDay(BusinessMixin):
13601403

13611404
@apply_index_wraps
13621405
def apply_index(self, dtindex):
1406+
return self._apply_array(dtindex)
1407+
1408+
@apply_array_wraps
1409+
def _apply_array(self, dtindex):
13631410
i8other = dtindex.view("i8")
13641411
return shift_bdays(i8other, self.n)
13651412

@@ -1843,6 +1890,10 @@ cdef class YearOffset(SingleConstructorOffset):
18431890

18441891
@apply_index_wraps
18451892
def apply_index(self, dtindex):
1893+
return self._apply_array(dtindex)
1894+
1895+
@apply_array_wraps
1896+
def _apply_array(self, dtindex):
18461897
shifted = shift_quarters(
18471898
dtindex.view("i8"), self.n, self.month, self._day_opt, modby=12
18481899
)
@@ -1996,6 +2047,10 @@ cdef class QuarterOffset(SingleConstructorOffset):
19962047

19972048
@apply_index_wraps
19982049
def apply_index(self, dtindex):
2050+
return self._apply_array(dtindex)
2051+
2052+
@apply_array_wraps
2053+
def _apply_array(self, dtindex):
19992054
shifted = shift_quarters(
20002055
dtindex.view("i8"), self.n, self.startingMonth, self._day_opt
20012056
)
@@ -2111,6 +2166,10 @@ cdef class MonthOffset(SingleConstructorOffset):
21112166

21122167
@apply_index_wraps
21132168
def apply_index(self, dtindex):
2169+
return self._apply_array(dtindex)
2170+
2171+
@apply_array_wraps
2172+
def _apply_array(self, dtindex):
21142173
shifted = shift_months(dtindex.view("i8"), self.n, self._day_opt)
21152174
return shifted
21162175

@@ -2248,6 +2307,12 @@ cdef class SemiMonthOffset(SingleConstructorOffset):
22482307
@cython.wraparound(False)
22492308
@cython.boundscheck(False)
22502309
def apply_index(self, dtindex):
2310+
return self._apply_array(dtindex)
2311+
2312+
@apply_array_wraps
2313+
@cython.wraparound(False)
2314+
@cython.boundscheck(False)
2315+
def _apply_array(self, dtindex):
22512316
cdef:
22522317
int64_t[:] i8other = dtindex.view("i8")
22532318
Py_ssize_t i, count = len(i8other)
@@ -2407,6 +2472,10 @@ cdef class Week(SingleConstructorOffset):
24072472

24082473
@apply_index_wraps
24092474
def apply_index(self, dtindex):
2475+
return self._apply_array(dtindex)
2476+
2477+
@apply_array_wraps
2478+
def _apply_array(self, dtindex):
24102479
if self.weekday is None:
24112480
td = timedelta(days=7 * self.n)
24122481
td64 = np.timedelta64(td, "ns")
@@ -3185,6 +3254,9 @@ cdef class CustomBusinessDay(BusinessDay):
31853254
def apply_index(self, dtindex):
31863255
raise NotImplementedError
31873256

3257+
def _apply_array(self, dtindex):
3258+
raise NotImplementedError
3259+
31883260
def is_on_offset(self, dt: datetime) -> bool:
31893261
if self.normalize and not _is_normalized(dt):
31903262
return False

pandas/core/arrays/datetimes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ def _add_offset(self, offset):
679679
values = self.tz_localize(None)
680680
else:
681681
values = self
682-
result = offset.apply_index(values)
682+
result = offset._apply_array(values)
683683
result = DatetimeArray._simple_new(result)
684684
result = result.tz_localize(self.tz)
685685

pandas/tests/tseries/offsets/test_offsets.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3673,6 +3673,28 @@ def test_apply_index(self, case):
36733673
exp = DatetimeIndex(cases.values())
36743674
tm.assert_index_equal(result, exp)
36753675

3676+
@pytest.mark.parametrize(
3677+
"offset",
3678+
[
3679+
offsets.YearEnd(),
3680+
offsets.YearBegin(),
3681+
offsets.QuarterEnd(),
3682+
offsets.QuarterBegin(),
3683+
offsets.MonthEnd(),
3684+
offsets.MonthBegin(),
3685+
offsets.DateOffset(months=2, days=2),
3686+
offsets.BusinessDay(),
3687+
offsets.SemiMonthEnd(),
3688+
offsets.SemiMonthBegin(),
3689+
],
3690+
)
3691+
def test_apply_index_2(self, offset):
3692+
# https://github.com/pandas-dev/pandas/issues/34580
3693+
rng = date_range(start="1/1/2000", periods=10, freq="T")
3694+
with tm.assert_produces_warning(FutureWarning):
3695+
result = offset.apply_index(rng)
3696+
assert isinstance(result, DatetimeIndex)
3697+
36763698
on_offset_cases = [
36773699
(datetime(2007, 12, 31), True),
36783700
(datetime(2007, 12, 15), True),

0 commit comments

Comments
 (0)