-
-
Notifications
You must be signed in to change notification settings - Fork 18.5k
ENH: Arithmetic with Timestamp-based intervals #36001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
fea2974
da1ccc1
4bbdc6a
e4cae40
bb52809
1d3dd12
ed76752
5e3f616
f3fe17f
f10ed85
67e1ef8
d3a3492
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,42 @@ 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 | ||
souris-dev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 `+`): | ||
souris-dev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. 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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this? Timestamps can't be added/subtracted even if they're not the bounds of an interval There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Timestamps can be subtracted but not added (addition makes no sense). Subtracting Timestamps give Timedeltas. |
||
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 | ||
souris-dev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:class:`TypeError`. | ||
|
||
.. _whatsnew_120.enhancements.other: | ||
|
||
Other enhancements | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is covered by L397 |
||
): | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the PyDelta_Check call makes the _Timedelta check extraneous. The remaining isinstance checks can be combined. |
||
): | ||
return Interval(self.left - y, self.right - y, closed=self.closed) | ||
return NotImplemented | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd name this |
||
# 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
# 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason for all the spaces between backtick and "s"?