From 4e4f72eeec5b81e30db1e90d01784dd0f59ce22a Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Fri, 26 Aug 2022 10:37:13 -0700 Subject: [PATCH 1/3] BUG: Fixed inconsistent multiplication behavior #47953 --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/_libs/tslibs/offsets.pyx | 7 +--- pandas/tests/tseries/offsets/test_offsets.py | 34 ++++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 16fcb34fdb7d1..eb6f6d6acb3af 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -827,6 +827,7 @@ Datetimelike - Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`) - Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`47266`) - Bug in :class:`.DatetimeArray` construction when passed another :class:`.DatetimeArray` and ``freq=None`` incorrectly inferring the freq from the given array (:issue:`47296`) +- Bug in :meth:`RelativeDeltaOffset._apply` which caused inconsistent behavior upon multiplying a :class:`DateOffset` (:issue:`47953`) - Timedelta diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 5f4f6b998a60a..23e2f5fe93e19 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1109,12 +1109,7 @@ cdef class RelativeDeltaOffset(BaseOffset): else: td_nano = Timedelta(0) - if self.n > 0: - for i in range(self.n): - other = other + self._offset + td_nano - else: - for i in range(-self.n): - other = other - self._offset - td_nano + other = other + ((self._offset + td_nano) * self.n) if tzinfo is not None and self._use_relativedelta: # bring tz back from UTC calculation diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 49661fe1ec8ce..e8149a647e405 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -32,7 +32,9 @@ from pandas.errors import PerformanceWarning from pandas import ( + DataFrame, DatetimeIndex, + Series, date_range, ) import pandas._testing as tm @@ -1032,3 +1034,35 @@ def test_construct_int_arg_no_kwargs_assumed_days(n): result = Timestamp(2022, 1, 2) + offset expected = Timestamp(2022, 1, 2 + n) assert result == expected + + +def test_offset_multiplication(): + # GH#47953 + mo1 = DateOffset(months=1) + mo2 = DateOffset(months=2) + + startscalar = Timestamp("2020-01-30") + startarray = Series([Timestamp("2020-01-30")]) + + resultscalar1 = startscalar + (mo1 * 2) + resultscalar2 = startscalar + mo2 + resultarray1 = startarray + (mo1 * 2) + resultarray2 = startarray + mo2 + + expectedscalar = Timestamp("2020-03-30") + expectedarray = Series([Timestamp("2020-03-30")]) + assert resultscalar1 == expectedscalar + assert resultscalar2 == expectedscalar + tm.assert_series_equal(resultarray1, expectedarray) + tm.assert_series_equal(resultarray2, expectedarray) + + df = DataFrame({"T": [Timestamp("2019-04-30")], "D": [DateOffset(months=1)]}) + frameresult1 = df["T"] + 26 * df["D"] + df2 = DataFrame( + { + "T": [Timestamp("2019-04-30"), Timestamp("2019-04-30")], + "D": [DateOffset(months=1), DateOffset(months=1)], + } + ) + frameresult2 = df2["T"] + 26 * df2["D"] + assert frameresult1[0] == frameresult2[0] From bb8856e92da21bf2a4becc31bcc414d32208b25b Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Mon, 29 Aug 2022 10:34:40 -0700 Subject: [PATCH 2/3] BUG: Fixed release note #47953 --- doc/source/whatsnew/v1.5.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index eb6f6d6acb3af..fb0bce83dbbb5 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -827,7 +827,7 @@ Datetimelike - Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`) - Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`47266`) - Bug in :class:`.DatetimeArray` construction when passed another :class:`.DatetimeArray` and ``freq=None`` incorrectly inferring the freq from the given array (:issue:`47296`) -- Bug in :meth:`RelativeDeltaOffset._apply` which caused inconsistent behavior upon multiplying a :class:`DateOffset` (:issue:`47953`) +- Bug in :class:`RelativeDeltaOffset` which caused inconsistent behavior upon multiplying a :class:`DateOffset` (:issue:`47953`) - Timedelta From 74f171bb73d46e9750223eec9f42e7c0d2f40b13 Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Fri, 14 Oct 2022 11:51:18 -0700 Subject: [PATCH 3/3] BUG: Fixed mistake in release note #47953 --- doc/source/whatsnew/v1.5.0.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index dcd47b268e5ef..8faa11c047af8 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -993,7 +993,6 @@ Datetimelike - Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`47266`) - Bug in :class:`.DatetimeArray` construction when passed another :class:`.DatetimeArray` and ``freq=None`` incorrectly inferring the freq from the given array (:issue:`47296`) - Bug in :class:`RelativeDeltaOffset` which caused inconsistent behavior upon multiplying a :class:`DateOffset` (:issue:`47953`) -- Bug in :meth:`RelativeDeltaOffset._apply` which caused inconsistent behavior upon multiplying a :class:`DateOffset` (:issue:`47953`) - Bug in :func:`to_datetime` where ``OutOfBoundsDatetime`` would be thrown even if ``errors=coerce`` if there were more than 50 rows (:issue:`45319`) - Bug when adding a :class:`DateOffset` to a :class:`Series` would not add the ``nanoseconds`` field (:issue:`47856`) -