From e4cae4017d51287b176868cfc0f103adc7a1f031 Mon Sep 17 00:00:00 2001 From: souris-dev Date: Mon, 31 Aug 2020 08:58:23 +0530 Subject: [PATCH 1/6] ENH: Arithmetic with timestamp and timedelta intervals --- doc/source/whatsnew/v1.2.0.rst | 39 ++++ pandas/_libs/interval.pyx | 4 + pandas/tests/scalar/interval/test_interval.py | 178 ++++++++++++++++++ 3 files changed, 221 insertions(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 55570341cf4e8..c3046af25f7c4 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -49,6 +49,45 @@ For example: buffer = io.BytesIO() data.to_csv(buffer, mode="w+b", encoding="utf-8", compression="gzip") +Arithmetic with Timestamp and Timedelta-based Intervals +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Arithmetic can now be performed on :class:`Interval`s having their left and right +ends as :class:`Timestamp`s or :class:`Timedelta`s, like what would be possible +if the ends were numeric (:issue:`35908`). +Before this change, it could be performed in an indirect way like this: + +.. ipython:: python + + interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) + pd.Interval( + interval.left - pd.Timestamp("1900-01-01"), + interval.right - pd.Timstamp("1900-01-01") + ) + +Now, this can be performed by directly using arithmetic operators (`-` or `+`): + +.. ipython:: python + + interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) + interval - pd.Timestamp("1900-01-01") + +This is valid for addition using `+`, and also when the ends are :class:`Timedelta`s. +Intervals having ends as Timestamps can also get "added to" or get "subtracted from", +with :class:`Timedelta`s. + +Example: + +.. ipython:: python + + interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) + interval + pd.Timedelta("1 days 00:00:00") + interval - pd.Timedelta("1 days 00:00:00") + +However, it should be noted that :class:`Timestamp`s cannot be added, and a :class:`Timestamp` +cannot be subtracted from a :class:`Timedelta`. Performing these would result in a +:class:`TypeError`. + .. _whatsnew_120.enhancements.other: Other enhancements diff --git a/pandas/_libs/interval.pyx b/pandas/_libs/interval.pyx index 6867e8aba7411..393fecf1259bc 100644 --- a/pandas/_libs/interval.pyx +++ b/pandas/_libs/interval.pyx @@ -395,6 +395,8 @@ cdef class Interval(IntervalMixin): isinstance(y, numbers.Number) or PyDelta_Check(y) or is_timedelta64_object(y) + or isinstance(y, _Timestamp) + or isinstance(y, _Timedelta) ): return Interval(self.left + y, self.right + y, closed=self.closed) elif ( @@ -413,6 +415,8 @@ cdef class Interval(IntervalMixin): isinstance(y, numbers.Number) or PyDelta_Check(y) or is_timedelta64_object(y) + or isinstance(y, _Timestamp) + or isinstance(y, _Timedelta) ): return Interval(self.left - y, self.right - y, closed=self.closed) return NotImplemented diff --git a/pandas/tests/scalar/interval/test_interval.py b/pandas/tests/scalar/interval/test_interval.py index a0151bb9ac7bf..5fd3d34a84f20 100644 --- a/pandas/tests/scalar/interval/test_interval.py +++ b/pandas/tests/scalar/interval/test_interval.py @@ -184,6 +184,184 @@ def test_math_sub(self, closed): with pytest.raises(TypeError, match=msg): interval - "foo" + def test_math_sub_interval_timestamp_timestamp(self, closed): + # Tests for interval of timestamp - timestamp + interval = Interval( + Timestamp("1900-01-01"), Timestamp("1900-01-02"), closed=closed + ) + expected = Interval( + Timedelta("0 days 00:00:00"), Timedelta("1 days 00:00:00"), closed=closed + ) + + result = interval - Timestamp("1900-01-01") + assert result == expected + + expected = Interval( + interval.left - Timestamp("1900-01-01"), + interval.right - Timestamp("1900-01-01"), + closed=closed, + ) + assert result == expected + + result = interval + result -= Timestamp("1900-01-01") + + expected = Interval( + Timedelta("0 days 00:00:00"), Timedelta("1 days 00:00:00"), closed=closed + ) + assert result == expected + + expected = Interval( + interval.left - Timestamp("1900-01-01"), + interval.right - Timestamp("1900-01-01"), + closed=closed, + ) + assert result == expected + + def test_math_sub_interval_timestamp_timedelta(self, closed): + # Tests for interval of timestamps - timedelta + interval = Interval( + Timestamp("1900-01-01"), Timestamp("1900-01-02"), closed=closed + ) + expected = Interval( + Timestamp("1899-12-31"), Timestamp("1900-01-01"), closed=closed + ) + + result = interval - Timedelta("1 days 00:00:00") + assert result == expected + + expected = Interval( + interval.left - Timedelta("1 days 00:00:00"), + interval.right - Timedelta("1 days 00:00:00"), + closed=closed, + ) + assert result == expected + + result = interval + result -= Timedelta("1 days 00:00:00") + + expected = Interval( + Timestamp("1899-12-31"), Timestamp("1900-01-01"), closed=closed + ) + assert result == expected + + expected = Interval( + interval.left - Timedelta("1 days 00:00:00"), + interval.right - Timedelta("1 days 00:00:00"), + closed=closed, + ) + assert result == expected + + def test_math_add_interval_timestamp_timedelta(self, closed): + interval = Interval( + Timestamp("1900-01-01"), Timestamp("1900-01-02"), closed=closed + ) + expected = Interval( + Timestamp("1900-01-02"), Timestamp("1900-01-03"), closed=closed + ) + + result = interval + Timedelta("1 days 00:00:00") + assert result == expected + + result = interval + result += Timedelta("1 days 00:00:00") + assert result == expected + + expected = Interval( + interval.left + Timedelta("1 days 00:00:00"), + interval.right + Timedelta("1 days 00:00:00"), + closed=closed, + ) + + result = interval + Timedelta("1 days 00:00:00") + assert result == expected + + result = interval + result += Timedelta("1 days 00:00:00") + assert result == expected + + def test_math_add_interval_timedelta_timedelta(self, closed): + interval = Interval( + Timedelta("1 days 00:00:00"), Timedelta("2 days 00:00:00"), closed=closed + ) + expected = Interval( + Timedelta("4 days 01:00:00"), Timedelta("5 days 01:00:00"), closed=closed + ) + + result = interval + Timedelta("3 days 01:00:00") + assert result == expected + + result = interval + result += Timedelta("3 days 01:00:00") + assert result == expected + + expected = Interval( + interval.left + Timedelta("3 days 01:00:00"), + interval.right + Timedelta("3 days 01:00:00"), + closed=closed, + ) + + result = interval + Timedelta("3 days 01:00:00") + assert result == expected + + result = interval + result += Timedelta("3 days 01:00:00") + assert result == expected + + def test_sub_interval_imedelta_timedelta(self, closed): + interval = Interval( + Timedelta("1 days 00:00:00"), Timedelta("2 days 00:00:00"), closed=closed + ) + expected = Interval( + Timedelta("-3 days +23:00:00"), + Timedelta("-2 days +23:00:00"), + closed=closed, + ) + + result = interval - Timedelta("3 days 01:00:00") + assert result == expected + + result = interval + result -= Timedelta("3 days 01:00:00") + assert result == expected + + expected = Interval( + interval.left - Timedelta("3 days 01:00:00"), + interval.right - Timedelta("3 days 01:00:00"), + closed=closed, + ) + + result = interval - Timedelta("3 days 01:00:00") + assert result == expected + + result = interval + result -= Timedelta("3 days 01:00:00") + assert result == expected + + def test_math_add_interval_timestamp_timestamp(self, closed): + interval = Interval( + Timestamp("1900-01-01"), Timestamp("1900-01-02"), closed=closed + ) + + msg = r"unsupported operand type\(s\) for \+" + with pytest.raises(TypeError, match=msg): + interval = interval + Timestamp("2002-01-08") + + with pytest.raises(TypeError, match=msg): + interval += Timestamp("2002-01-08") + + def test_math_sub_interval_timedelta_timestamp(self, closed): + interval = Interval( + Timedelta("1 days 00:00:00"), Timedelta("3 days 00:00:00"), closed=closed + ) + + msg = r"unsupported operand type\(s\) for \-" + with pytest.raises(TypeError, match=msg): + interval = interval - Timestamp("1900-01-01") + + with pytest.raises(TypeError, match=msg): + interval -= Timestamp("1900-01-01") + def test_math_mult(self, closed): interval = Interval(0, 1, closed=closed) expected = Interval(0, 2, closed=closed) From ed767523218bb742a30692b9761727c17f9c4e00 Mon Sep 17 00:00:00 2001 From: souris-dev Date: Mon, 31 Aug 2020 09:38:19 +0530 Subject: [PATCH 2/6] Fixed formatting in whatsnew --- doc/source/whatsnew/v1.2.0.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index c3046af25f7c4..85e218377e978 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -52,18 +52,15 @@ For example: Arithmetic with Timestamp and Timedelta-based Intervals ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Arithmetic can now be performed on :class:`Interval`s having their left and right -ends as :class:`Timestamp`s or :class:`Timedelta`s, like what would be possible +Arithmetic can now be performed on :class:`Interval` s having their left and right +ends as :class:`Timestamp`s or :class:`Timedelta` s, like what would be possible if the ends were numeric (:issue:`35908`). Before this change, it could be performed in an indirect way like this: .. ipython:: python interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) - pd.Interval( - interval.left - pd.Timestamp("1900-01-01"), - interval.right - pd.Timstamp("1900-01-01") - ) + pd.Interval(interval.left - pd.Timestamp("1900-01-01"), interval.right - pd.Timstamp("1900-01-01")) Now, this can be performed by directly using arithmetic operators (`-` or `+`): @@ -72,9 +69,9 @@ Now, this can be performed by directly using arithmetic operators (`-` or `+`): interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) interval - pd.Timestamp("1900-01-01") -This is valid for addition using `+`, and also when the ends are :class:`Timedelta`s. +This is valid for addition using `+`, and also when the ends are :class:`Timedelta` s. Intervals having ends as Timestamps can also get "added to" or get "subtracted from", -with :class:`Timedelta`s. +with :class:`Timedelta` s. Example: @@ -84,7 +81,7 @@ Example: interval + pd.Timedelta("1 days 00:00:00") interval - pd.Timedelta("1 days 00:00:00") -However, it should be noted that :class:`Timestamp`s cannot be added, and a :class:`Timestamp` +However, it should be noted that :class:`Timestamp` s cannot be added, and a :class:`Timestamp` cannot be subtracted from a :class:`Timedelta`. Performing these would result in a :class:`TypeError`. From 5e3f6165f57a0d822fc38183635f68a628cc4aae Mon Sep 17 00:00:00 2001 From: souris-dev Date: Mon, 31 Aug 2020 09:42:16 +0530 Subject: [PATCH 3/6] Fixed formatting in whatsnew --- doc/source/whatsnew/v1.2.0.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index c3046af25f7c4..00fa637ce1bcc 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -52,18 +52,15 @@ For example: Arithmetic with Timestamp and Timedelta-based Intervals ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Arithmetic can now be performed on :class:`Interval`s having their left and right -ends as :class:`Timestamp`s or :class:`Timedelta`s, like what would be possible +Arithmetic can now be performed on :class:`Interval` s having their left and right +ends as :class:`Timestamp` s or :class:`Timedelta` s, like what would be possible if the ends were numeric (:issue:`35908`). Before this change, it could be performed in an indirect way like this: .. ipython:: python interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) - pd.Interval( - interval.left - pd.Timestamp("1900-01-01"), - interval.right - pd.Timstamp("1900-01-01") - ) + pd.Interval(interval.left - pd.Timestamp("1900-01-01"), interval.right - pd.Timstamp("1900-01-01")) Now, this can be performed by directly using arithmetic operators (`-` or `+`): @@ -72,9 +69,9 @@ Now, this can be performed by directly using arithmetic operators (`-` or `+`): interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) interval - pd.Timestamp("1900-01-01") -This is valid for addition using `+`, and also when the ends are :class:`Timedelta`s. +This is valid for addition using `+`, and also when the ends are :class:`Timedelta` s. Intervals having ends as Timestamps can also get "added to" or get "subtracted from", -with :class:`Timedelta`s. +with :class:`Timedelta` s. Example: @@ -84,7 +81,7 @@ Example: interval + pd.Timedelta("1 days 00:00:00") interval - pd.Timedelta("1 days 00:00:00") -However, it should be noted that :class:`Timestamp`s cannot be added, and a :class:`Timestamp` +However, it should be noted that :class:`Timestamp` s cannot be added, and a :class:`Timestamp` cannot be subtracted from a :class:`Timedelta`. Performing these would result in a :class:`TypeError`. From f10ed85a91bb50c6947f75b3182d73433344d272 Mon Sep 17 00:00:00 2001 From: souris-dev Date: Mon, 31 Aug 2020 09:46:53 +0530 Subject: [PATCH 4/6] Resloved merge conflicts --- doc/source/whatsnew/v1.2.0.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 28d0824139252..00fa637ce1bcc 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -53,11 +53,7 @@ Arithmetic with Timestamp and Timedelta-based Intervals ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Arithmetic can now be performed on :class:`Interval` s having their left and right -<<<<<<< HEAD ends as :class:`Timestamp` s or :class:`Timedelta` s, like what would be possible -======= -ends as :class:`Timestamp`s or :class:`Timedelta` s, like what would be possible ->>>>>>> ed767523218bb742a30692b9761727c17f9c4e00 if the ends were numeric (:issue:`35908`). Before this change, it could be performed in an indirect way like this: From 67e1ef8aa4996eb28568f915980304acf83e5ce2 Mon Sep 17 00:00:00 2001 From: souris-dev Date: Mon, 31 Aug 2020 09:51:23 +0530 Subject: [PATCH 5/6] Removed old method from what's new --- doc/source/whatsnew/v1.2.0.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 00fa637ce1bcc..a95f17253cd1d 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -55,14 +55,9 @@ Arithmetic with Timestamp and Timedelta-based Intervals Arithmetic can now be performed on :class:`Interval` s having their left and right ends as :class:`Timestamp` s or :class:`Timedelta` s, like what would be possible if the ends were numeric (:issue:`35908`). -Before this change, it could be performed in an indirect way like this: -.. ipython:: python - - interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) - pd.Interval(interval.left - pd.Timestamp("1900-01-01"), interval.right - pd.Timstamp("1900-01-01")) - -Now, this can be performed by directly using arithmetic operators (`-` or `+`): +Arithmetic can be performed by directly using arithmetic operators (`-` or `+`), +so something like this will work: .. ipython:: python From d3a349255cd441240e034fe8c32ac095cd16caa5 Mon Sep 17 00:00:00 2001 From: souris-dev Date: Mon, 31 Aug 2020 09:58:31 +0530 Subject: [PATCH 6/6] What's new fixes --- doc/source/whatsnew/v1.2.0.rst | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index a95f17253cd1d..247f2a7515705 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -64,21 +64,10 @@ so something like this will work: interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) interval - pd.Timestamp("1900-01-01") -This is valid for addition using `+`, and also when the ends are :class:`Timedelta` s. -Intervals having ends as Timestamps can also get "added to" or get "subtracted from", -with :class:`Timedelta` s. +This works when endpoints are :class:`Timestamp` s or :class:`Timedelta` s. -Example: - -.. ipython:: python - - interval = pd.Interval(pd.Timestamp("1900-01-01"), pd.Timestamp("1900-01-02")) - interval + pd.Timedelta("1 days 00:00:00") - interval - pd.Timedelta("1 days 00:00:00") - -However, it should be noted that :class:`Timestamp` s cannot be added, and a :class:`Timestamp` -cannot be subtracted from a :class:`Timedelta`. Performing these would result in a -:class:`TypeError`. +However, it should be noted that adding :class:`Timestamp` s , and subtracting :class:`Timestamp` +from a :class:`Timedelta` is illegal. .. _whatsnew_120.enhancements.other: