Skip to content

Commit 66003e4

Browse files
Fixed apply_index (#35165)
1 parent d99d44a commit 66003e4

File tree

5 files changed

+110
-17
lines changed

5 files changed

+110
-17
lines changed

doc/source/reference/offset_frequency.rst

+18
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Methods
3333
:toctree: api/
3434

3535
DateOffset.apply
36+
DateOffset.apply_index
3637
DateOffset.copy
3738
DateOffset.isAnchored
3839
DateOffset.onOffset
@@ -117,6 +118,7 @@ Methods
117118
:toctree: api/
118119

119120
BusinessHour.apply
121+
BusinessHour.apply_index
120122
BusinessHour.copy
121123
BusinessHour.isAnchored
122124
BusinessHour.onOffset
@@ -201,6 +203,7 @@ Methods
201203
:toctree: api/
202204

203205
CustomBusinessHour.apply
206+
CustomBusinessHour.apply_index
204207
CustomBusinessHour.copy
205208
CustomBusinessHour.isAnchored
206209
CustomBusinessHour.onOffset
@@ -401,6 +404,7 @@ Methods
401404
:toctree: api/
402405

403406
CustomBusinessMonthEnd.apply
407+
CustomBusinessMonthEnd.apply_index
404408
CustomBusinessMonthEnd.copy
405409
CustomBusinessMonthEnd.isAnchored
406410
CustomBusinessMonthEnd.onOffset
@@ -447,6 +451,7 @@ Methods
447451
:toctree: api/
448452

449453
CustomBusinessMonthBegin.apply
454+
CustomBusinessMonthBegin.apply_index
450455
CustomBusinessMonthBegin.copy
451456
CustomBusinessMonthBegin.isAnchored
452457
CustomBusinessMonthBegin.onOffset
@@ -586,6 +591,7 @@ Methods
586591
:toctree: api/
587592

588593
WeekOfMonth.apply
594+
WeekOfMonth.apply_index
589595
WeekOfMonth.copy
590596
WeekOfMonth.isAnchored
591597
WeekOfMonth.onOffset
@@ -622,6 +628,7 @@ Methods
622628
:toctree: api/
623629

624630
LastWeekOfMonth.apply
631+
LastWeekOfMonth.apply_index
625632
LastWeekOfMonth.copy
626633
LastWeekOfMonth.isAnchored
627634
LastWeekOfMonth.onOffset
@@ -938,6 +945,7 @@ Methods
938945
:toctree: api/
939946

940947
FY5253.apply
948+
FY5253.apply_index
941949
FY5253.copy
942950
FY5253.get_rule_code_suffix
943951
FY5253.get_year_end
@@ -977,6 +985,7 @@ Methods
977985
:toctree: api/
978986

979987
FY5253Quarter.apply
988+
FY5253Quarter.apply_index
980989
FY5253Quarter.copy
981990
FY5253Quarter.get_rule_code_suffix
982991
FY5253Quarter.get_weeks
@@ -1013,6 +1022,7 @@ Methods
10131022
:toctree: api/
10141023

10151024
Easter.apply
1025+
Easter.apply_index
10161026
Easter.copy
10171027
Easter.isAnchored
10181028
Easter.onOffset
@@ -1053,6 +1063,7 @@ Methods
10531063
Tick.is_on_offset
10541064
Tick.__call__
10551065
Tick.apply
1066+
Tick.apply_index
10561067

10571068
Day
10581069
---
@@ -1087,6 +1098,7 @@ Methods
10871098
Day.is_on_offset
10881099
Day.__call__
10891100
Day.apply
1101+
Day.apply_index
10901102

10911103
Hour
10921104
----
@@ -1121,6 +1133,7 @@ Methods
11211133
Hour.is_on_offset
11221134
Hour.__call__
11231135
Hour.apply
1136+
Hour.apply_index
11241137

11251138
Minute
11261139
------
@@ -1155,6 +1168,7 @@ Methods
11551168
Minute.is_on_offset
11561169
Minute.__call__
11571170
Minute.apply
1171+
Minute.apply_index
11581172

11591173
Second
11601174
------
@@ -1189,6 +1203,7 @@ Methods
11891203
Second.is_on_offset
11901204
Second.__call__
11911205
Second.apply
1206+
Second.apply_index
11921207

11931208
Milli
11941209
-----
@@ -1223,6 +1238,7 @@ Methods
12231238
Milli.is_on_offset
12241239
Milli.__call__
12251240
Milli.apply
1241+
Milli.apply_index
12261242

12271243
Micro
12281244
-----
@@ -1257,6 +1273,7 @@ Methods
12571273
Micro.is_on_offset
12581274
Micro.__call__
12591275
Micro.apply
1276+
Micro.apply_index
12601277

12611278
Nano
12621279
----
@@ -1291,6 +1308,7 @@ Methods
12911308
Nano.is_on_offset
12921309
Nano.__call__
12931310
Nano.apply
1311+
Nano.apply_index
12941312

12951313
.. _api.frequencies:
12961314

doc/source/whatsnew/v1.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ Deprecations
787787
- :meth:`DatetimeIndex.week` and `DatetimeIndex.weekofyear` are deprecated and will be removed in a future version, use :meth:`DatetimeIndex.isocalendar().week` instead (:issue:`33595`)
788788
- :meth:`DatetimeArray.week` and `DatetimeArray.weekofyear` are deprecated and will be removed in a future version, use :meth:`DatetimeArray.isocalendar().week` instead (:issue:`33595`)
789789
- :meth:`DateOffset.__call__` is deprecated and will be removed in a future version, use ``offset + other`` instead (:issue:`34171`)
790+
- :meth:`~BusinessDay.apply_index` is deprecated and will be removed in a future version. Use ``offset + other`` instead (:issue:`34580`)
790791
- :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`)
791792
- Indexing an :class:`Index` object with a float key is deprecated, and will
792793
raise an ``IndexError`` in the future. You can manually convert to an integer key

pandas/_libs/tslibs/offsets.pyx

+84-15
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,38 @@ cdef bint _is_normalized(datetime dt):
8686
return True
8787

8888

89+
def apply_wrapper_core(func, self, other) -> ndarray:
90+
result = func(self, other)
91+
result = np.asarray(result)
92+
93+
if self.normalize:
94+
# TODO: Avoid circular/runtime import
95+
from .vectorized import normalize_i8_timestamps
96+
result = normalize_i8_timestamps(result.view("i8"), None)
97+
98+
return result
99+
100+
89101
def apply_index_wraps(func):
90102
# Note: normally we would use `@functools.wraps(func)`, but this does
91103
# not play nicely with cython class methods
92-
def wrapper(self, other) -> np.ndarray:
104+
def wrapper(self, other):
93105
# other is a DatetimeArray
106+
result = apply_wrapper_core(func, self, other)
107+
result = type(other)(result)
108+
warnings.warn("'Offset.apply_index(other)' is deprecated. "
109+
"Use 'offset + other' instead.", FutureWarning)
110+
return result
94111

95-
result = func(self, other)
96-
result = np.asarray(result)
112+
return wrapper
97113

98-
if self.normalize:
99-
# TODO: Avoid circular/runtime import
100-
from .vectorized import normalize_i8_timestamps
101-
result = normalize_i8_timestamps(result.view("i8"), None)
114+
115+
def apply_array_wraps(func):
116+
# Note: normally we would use `@functools.wraps(func)`, but this does
117+
# not play nicely with cython class methods
118+
def wrapper(self, other) -> np.ndarray:
119+
# other is a DatetimeArray
120+
result = apply_wrapper_core(func, self, other)
102121
return result
103122

104123
# do @functools.wraps(func) manually since it doesn't work on cdef funcs
@@ -515,19 +534,36 @@ cdef class BaseOffset:
515534
raises NotImplementedError for offsets without a
516535
vectorized implementation.
517536
537+
.. deprecated:: 1.1.0
538+
539+
Use ``offset + dtindex`` instead.
540+
518541
Parameters
519542
----------
520543
index : DatetimeIndex
521544
522545
Returns
523546
-------
524547
DatetimeIndex
548+
549+
Raises
550+
------
551+
NotImplementedError
552+
When the specific offset subclass does not have a vectorized
553+
implementation.
525554
"""
526555
raise NotImplementedError(
527556
f"DateOffset subclass {type(self).__name__} "
528557
"does not have a vectorized implementation"
529558
)
530559

560+
@apply_array_wraps
561+
def _apply_array(self, dtarr):
562+
raise NotImplementedError(
563+
f"DateOffset subclass {type(self).__name__} "
564+
"does not have a vectorized implementation"
565+
)
566+
531567
def rollback(self, dt) -> datetime:
532568
"""
533569
Roll provided date backward to next offset only if not on offset.
@@ -992,7 +1028,11 @@ cdef class RelativeDeltaOffset(BaseOffset):
9921028
-------
9931029
ndarray[datetime64[ns]]
9941030
"""
995-
dt64other = np.asarray(dtindex)
1031+
return self._apply_array(dtindex)
1032+
1033+
@apply_array_wraps
1034+
def _apply_array(self, dtarr):
1035+
dt64other = np.asarray(dtarr)
9961036
kwds = self.kwds
9971037
relativedelta_fast = {
9981038
"years",
@@ -1321,7 +1361,11 @@ cdef class BusinessDay(BusinessMixin):
13211361

13221362
@apply_index_wraps
13231363
def apply_index(self, dtindex):
1324-
i8other = dtindex.view("i8")
1364+
return self._apply_array(dtindex)
1365+
1366+
@apply_array_wraps
1367+
def _apply_array(self, dtarr):
1368+
i8other = dtarr.view("i8")
13251369
return shift_bdays(i8other, self.n)
13261370

13271371
def is_on_offset(self, dt: datetime) -> bool:
@@ -1804,8 +1848,12 @@ cdef class YearOffset(SingleConstructorOffset):
18041848

18051849
@apply_index_wraps
18061850
def apply_index(self, dtindex):
1851+
return self._apply_array(dtindex)
1852+
1853+
@apply_array_wraps
1854+
def _apply_array(self, dtarr):
18071855
shifted = shift_quarters(
1808-
dtindex.view("i8"), self.n, self.month, self._day_opt, modby=12
1856+
dtarr.view("i8"), self.n, self.month, self._day_opt, modby=12
18091857
)
18101858
return shifted
18111859

@@ -1957,8 +2005,12 @@ cdef class QuarterOffset(SingleConstructorOffset):
19572005

19582006
@apply_index_wraps
19592007
def apply_index(self, dtindex):
2008+
return self._apply_array(dtindex)
2009+
2010+
@apply_array_wraps
2011+
def _apply_array(self, dtarr):
19602012
shifted = shift_quarters(
1961-
dtindex.view("i8"), self.n, self.startingMonth, self._day_opt
2013+
dtarr.view("i8"), self.n, self.startingMonth, self._day_opt
19622014
)
19632015
return shifted
19642016

@@ -2072,7 +2124,11 @@ cdef class MonthOffset(SingleConstructorOffset):
20722124

20732125
@apply_index_wraps
20742126
def apply_index(self, dtindex):
2075-
shifted = shift_months(dtindex.view("i8"), self.n, self._day_opt)
2127+
return self._apply_array(dtindex)
2128+
2129+
@apply_array_wraps
2130+
def _apply_array(self, dtarr):
2131+
shifted = shift_months(dtarr.view("i8"), self.n, self._day_opt)
20762132
return shifted
20772133

20782134
cpdef __setstate__(self, state):
@@ -2209,8 +2265,14 @@ cdef class SemiMonthOffset(SingleConstructorOffset):
22092265
@cython.wraparound(False)
22102266
@cython.boundscheck(False)
22112267
def apply_index(self, dtindex):
2268+
return self._apply_array(dtindex)
2269+
2270+
@apply_array_wraps
2271+
@cython.wraparound(False)
2272+
@cython.boundscheck(False)
2273+
def _apply_array(self, dtarr):
22122274
cdef:
2213-
int64_t[:] i8other = dtindex.view("i8")
2275+
int64_t[:] i8other = dtarr.view("i8")
22142276
Py_ssize_t i, count = len(i8other)
22152277
int64_t val
22162278
int64_t[:] out = np.empty(count, dtype="i8")
@@ -2368,12 +2430,16 @@ cdef class Week(SingleConstructorOffset):
23682430

23692431
@apply_index_wraps
23702432
def apply_index(self, dtindex):
2433+
return self._apply_array(dtindex)
2434+
2435+
@apply_array_wraps
2436+
def _apply_array(self, dtarr):
23712437
if self.weekday is None:
23722438
td = timedelta(days=7 * self.n)
23732439
td64 = np.timedelta64(td, "ns")
2374-
return dtindex + td64
2440+
return dtarr + td64
23752441
else:
2376-
i8other = dtindex.view("i8")
2442+
i8other = dtarr.view("i8")
23772443
return self._end_apply_index(i8other)
23782444

23792445
@cython.wraparound(False)
@@ -3146,6 +3212,9 @@ cdef class CustomBusinessDay(BusinessDay):
31463212
def apply_index(self, dtindex):
31473213
raise NotImplementedError
31483214

3215+
def _apply_array(self, dtarr):
3216+
raise NotImplementedError
3217+
31493218
def is_on_offset(self, dt: datetime) -> bool:
31503219
if self.normalize and not _is_normalized(dt):
31513220
return False

pandas/core/arrays/datetimes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ def _add_offset(self, offset):
683683
values = self.tz_localize(None)
684684
else:
685685
values = self
686-
result = offset.apply_index(values)
686+
result = offset._apply_array(values)
687687
result = DatetimeArray._simple_new(result)
688688
result = result.tz_localize(self.tz)
689689

pandas/tests/tseries/offsets/test_offsets.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -3663,14 +3663,19 @@ def test_offset(self, case):
36633663

36643664
@pytest.mark.parametrize("case", offset_cases)
36653665
def test_apply_index(self, case):
3666+
# https://github.com/pandas-dev/pandas/issues/34580
36663667
offset, cases = case
36673668
s = DatetimeIndex(cases.keys())
3669+
exp = DatetimeIndex(cases.values())
3670+
36683671
with tm.assert_produces_warning(None):
36693672
# GH#22535 check that we don't get a FutureWarning from adding
36703673
# an integer array to PeriodIndex
36713674
result = offset + s
3675+
tm.assert_index_equal(result, exp)
36723676

3673-
exp = DatetimeIndex(cases.values())
3677+
with tm.assert_produces_warning(FutureWarning):
3678+
result = offset.apply_index(s)
36743679
tm.assert_index_equal(result, exp)
36753680

36763681
on_offset_cases = [

0 commit comments

Comments
 (0)