Skip to content

Commit 33199e1

Browse files
authored
BUG: inconsistent behavior of DateOffset #47953 (#53681)
* BUG: Fixed inconsistent multiplication #47953 * Fixed release note * Fixed attribute name * Changed offset apply logic * Addressed #46877 re-occurence * Removed dulicate code * Addressed comments * Added unit test cases * Added mistakenly removed comment
1 parent ef95005 commit 33199e1

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ Categorical
361361
Datetimelike
362362
^^^^^^^^^^^^
363363
- :meth:`DatetimeIndex.map` with ``na_action="ignore"`` now works as expected. (:issue:`51644`)
364+
- Bug in :class:`DateOffset` which had inconsistent behavior when multiplying a :class:`DateOffset` object by a constant (:issue:`47953`)
364365
- Bug in :func:`date_range` when ``freq`` was a :class:`DateOffset` with ``nanoseconds`` (:issue:`46877`)
365366
- Bug in :meth:`Timestamp.date`, :meth:`Timestamp.isocalendar`, :meth:`Timestamp.timetuple`, and :meth:`Timestamp.toordinal` were returning incorrect results for inputs outside those supported by the Python standard library's datetime module (:issue:`53668`)
366367
- Bug in :meth:`Timestamp.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsDatetime`` (:issue:`51494`)

pandas/_libs/tslibs/offsets.pyx

+1-6
Original file line numberDiff line numberDiff line change
@@ -1221,12 +1221,7 @@ cdef class RelativeDeltaOffset(BaseOffset):
12211221
# perform calculation in UTC
12221222
other = other.replace(tzinfo=None)
12231223

1224-
if self.n > 0:
1225-
for i in range(self.n):
1226-
other = other + self._offset
1227-
else:
1228-
for i in range(-self.n):
1229-
other = other - self._offset
1224+
other = other + (self._offset * self.n)
12301225

12311226
if hasattr(self, "nanoseconds"):
12321227
other = self.n * Timedelta(nanoseconds=self.nanoseconds) + other

pandas/tests/tseries/offsets/test_offsets.py

+49
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pandas.errors import PerformanceWarning
2828

2929
from pandas import (
30+
DataFrame,
3031
DatetimeIndex,
3132
Series,
3233
date_range,
@@ -1067,3 +1068,51 @@ def test_dateoffset_add_sub_timestamp_series_with_nano(offset, expected):
10671068
assert testseries[0] == teststamp
10681069
testseries = offset + testseries
10691070
assert testseries[0] == expected
1071+
1072+
1073+
@pytest.mark.parametrize(
1074+
"n_months, scaling_factor, start_timestamp, expected_timestamp",
1075+
[
1076+
(1, 2, "2020-01-30", "2020-03-30"),
1077+
(2, 1, "2020-01-30", "2020-03-30"),
1078+
(1, 0, "2020-01-30", "2020-01-30"),
1079+
(2, 0, "2020-01-30", "2020-01-30"),
1080+
(1, -1, "2020-01-30", "2019-12-30"),
1081+
(2, -1, "2020-01-30", "2019-11-30"),
1082+
],
1083+
)
1084+
def test_offset_multiplication(
1085+
n_months, scaling_factor, start_timestamp, expected_timestamp
1086+
):
1087+
# GH 47953
1088+
mo1 = DateOffset(months=n_months)
1089+
1090+
startscalar = Timestamp(start_timestamp)
1091+
startarray = Series([startscalar])
1092+
1093+
resultscalar = startscalar + (mo1 * scaling_factor)
1094+
resultarray = startarray + (mo1 * scaling_factor)
1095+
1096+
expectedscalar = Timestamp(expected_timestamp)
1097+
expectedarray = Series([expectedscalar])
1098+
assert resultscalar == expectedscalar
1099+
1100+
tm.assert_series_equal(resultarray, expectedarray)
1101+
1102+
1103+
def test_dateoffset_operations_on_dataframes():
1104+
# GH 47953
1105+
df = DataFrame({"T": [Timestamp("2019-04-30")], "D": [DateOffset(months=1)]})
1106+
frameresult1 = df["T"] + 26 * df["D"]
1107+
df2 = DataFrame(
1108+
{
1109+
"T": [Timestamp("2019-04-30"), Timestamp("2019-04-30")],
1110+
"D": [DateOffset(months=1), DateOffset(months=1)],
1111+
}
1112+
)
1113+
expecteddate = Timestamp("2021-06-30")
1114+
with tm.assert_produces_warning(PerformanceWarning):
1115+
frameresult2 = df2["T"] + 26 * df2["D"]
1116+
1117+
assert frameresult1[0] == expecteddate
1118+
assert frameresult2[0] == expecteddate

0 commit comments

Comments
 (0)