From bab1995c0b73c868f210a3725603583bf7253a00 Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Mon, 8 Aug 2022 10:50:05 -0700 Subject: [PATCH 1/6] BUG: Fixed ignoring of nanoseconds when adding to series #47856 --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/_libs/tslibs/offsets.pyx | 8 +++--- pandas/tests/tseries/offsets/test_offsets.py | 26 +++++++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 0b450fab53137..b93b3bd29442b 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -824,6 +824,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 :class:`RelativeDeltaOffset` which caused adding a ``DateOffset`` to a ``Series`` to not add the ``nanoseconds`` field (:issue:`47856`) - Timedelta diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 81b59db6f0e18..460c615d265bc 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -297,8 +297,8 @@ _relativedelta_kwds = {"years", "months", "weeks", "days", "year", "month", cdef _determine_offset(kwds): # timedelta is used for sub-daily plural offsets and all singular - # offsets relativedelta is used for plural offsets of daily length or - # more nanosecond(s) are handled by apply_wraps + # offsets, relativedelta is used for plural offsets of daily length or + # more, nanosecond(s) are handled by apply_wraps kwds_no_nanos = dict( (k, v) for k, v in kwds.items() if k not in ('nanosecond', 'nanoseconds') @@ -1157,7 +1157,9 @@ cdef class RelativeDeltaOffset(BaseOffset): return dt64other elif not self._use_relativedelta and hasattr(self, "_offset"): # timedelta - delta = Timedelta(self._offset * self.n) + num_nano = getattr(self, "nanoseconds", 0) + rem_nano = Timedelta(nanoseconds=num_nano) + delta = Timedelta((self._offset + rem_nano) * self.n) td = (<_Timedelta>delta)._as_reso(reso) return dt64other + td else: diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 49661fe1ec8ce..ebdf89de56930 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -33,6 +33,7 @@ from pandas import ( DatetimeIndex, + Series, date_range, ) import pandas._testing as tm @@ -987,7 +988,7 @@ def test_dateoffset_add_sub(offset_kwargs, expected_arg): assert result == expected -def test_dataoffset_add_sub_timestamp_with_nano(): +def test_dateoffset_add_sub_timestamp_with_nano(): offset = DateOffset(minutes=2, nanoseconds=9) ts = Timestamp(4) result = ts + offset @@ -1032,3 +1033,26 @@ 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 + + +@pytest.mark.parametrize( + "offset, expected", + [ + ( + DateOffset(minutes=7, nanoseconds=18), + Timestamp("2022-01-01 00:07:00.000000018"), + ), + (DateOffset(nanoseconds=3), Timestamp("2022-01-01 00:00:00.000000003")), + ], +) +def test_dateoffset_add_sub_timestamp_series_with_nano(offset, expected): + # GH 47856 + t = Timestamp("2022-01-01") + teststamp = t + s = Series([t]) + s = s + offset + assert s[0] == expected + s -= offset + assert s[0] == teststamp + s = offset + s + assert s[0] == expected From c62dde39b08699605680a71551040ba0dfdeab18 Mon Sep 17 00:00:00 2001 From: srotondo <97266896+srotondo@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:14:46 -0700 Subject: [PATCH 2/6] Update doc/source/whatsnew/v1.5.0.rst Co-authored-by: Matthew Roeschke --- 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 b93b3bd29442b..aece8caef65f1 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -824,7 +824,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 :class:`RelativeDeltaOffset` which caused adding a ``DateOffset`` to a ``Series`` to not add the ``nanoseconds`` field (:issue:`47856`) +- Bug when adding a :class:`DateOffset` to a :class:`Series` would not add the ``nanoseconds`` field (:issue:`47856`) - Timedelta From ee8752f8ee1cd2180b7532d4b059101246f9bbf0 Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Mon, 8 Aug 2022 14:20:05 -0700 Subject: [PATCH 3/6] BUG: Renamed test variables #47856 --- pandas/tests/tseries/offsets/test_offsets.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index ebdf89de56930..bca4ba98f37b7 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -1047,12 +1047,12 @@ def test_construct_int_arg_no_kwargs_assumed_days(n): ) def test_dateoffset_add_sub_timestamp_series_with_nano(offset, expected): # GH 47856 - t = Timestamp("2022-01-01") - teststamp = t - s = Series([t]) - s = s + offset - assert s[0] == expected - s -= offset - assert s[0] == teststamp - s = offset + s - assert s[0] == expected + start_time = Timestamp("2022-01-01") + teststamp = start_time + testseries = Series([start_time]) + testseries = testseries + offset + assert testseries[0] == expected + testseries -= offset + assert testseries[0] == teststamp + testseries = offset + testseries + assert testseries[0] == expected From f7e7dcec60474a8ee4e86b772d82e6f5cf6fabbd Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Tue, 9 Aug 2022 09:56:54 -0700 Subject: [PATCH 4/6] BUG: Changed added if-else for performance #47856 --- pandas/_libs/tslibs/offsets.pyx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 460c615d265bc..9335209b72100 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1157,9 +1157,11 @@ cdef class RelativeDeltaOffset(BaseOffset): return dt64other elif not self._use_relativedelta and hasattr(self, "_offset"): # timedelta - num_nano = getattr(self, "nanoseconds", 0) - rem_nano = Timedelta(nanoseconds=num_nano) - delta = Timedelta((self._offset + rem_nano) * self.n) + if hasattr(self, "nanoseconds"): + rem_nano = Timedelta(nanoseconds=self.nanoseconds) + delta = Timedelta((self._offset + rem_nano) * self.n) + else: + delta = Timedelta((self._offset) * self.n) td = (<_Timedelta>delta)._as_reso(reso) return dt64other + td else: From ca4bb65118bd26239e4ea2dce3bd57a45f9d47b8 Mon Sep 17 00:00:00 2001 From: Steven Rotondo <97266896+srotondo@users.noreply.github.com> Date: Tue, 9 Aug 2022 12:42:18 -0700 Subject: [PATCH 5/6] Update pandas/_libs/tslibs/offsets.pyx Co-authored-by: Matthew Roeschke --- pandas/_libs/tslibs/offsets.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 9335209b72100..8e204ba2201a4 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1161,7 +1161,7 @@ cdef class RelativeDeltaOffset(BaseOffset): rem_nano = Timedelta(nanoseconds=self.nanoseconds) delta = Timedelta((self._offset + rem_nano) * self.n) else: - delta = Timedelta((self._offset) * self.n) + delta = Timedelta(self._offset * self.n) td = (<_Timedelta>delta)._as_reso(reso) return dt64other + td else: From 656a00d226489e7d7d3dcaad00f5289cdf60be5a Mon Sep 17 00:00:00 2001 From: Steven Rotondo Date: Tue, 9 Aug 2022 13:40:19 -0700 Subject: [PATCH 6/6] BUG: Used getattr for nano check #47856 --- pandas/_libs/tslibs/offsets.pyx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 9335209b72100..a385caadb12e0 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1157,8 +1157,9 @@ cdef class RelativeDeltaOffset(BaseOffset): return dt64other elif not self._use_relativedelta and hasattr(self, "_offset"): # timedelta - if hasattr(self, "nanoseconds"): - rem_nano = Timedelta(nanoseconds=self.nanoseconds) + num_nano = getattr(self, "nanoseconds", 0) + if num_nano != 0: + rem_nano = Timedelta(nanoseconds=num_nano) delta = Timedelta((self._offset + rem_nano) * self.n) else: delta = Timedelta((self._offset) * self.n)