-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
ENH: implement Timedelta.__mod__ and __divmod__ #19755
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 2 commits
48cb394
5782463
d6ac27d
c252eff
c78ed1b
3e8529e
aefa6d6
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 |
---|---|---|
|
@@ -117,6 +117,20 @@ resetting indexes. See the :ref:`Sorting by Indexes and Values | |
# Sort by 'second' (index) and 'A' (column) | ||
df_multi.sort_values(by=['second', 'A']) | ||
|
||
.. _whatsnew_0230.enhancements.timedelta_mod | ||
|
||
Timedelta mod method | ||
^^^^^^^^^^^^^^^^^^^^ | ||
|
||
``mod`` (%) and ``divmod`` operations are now defined on ``Timedelta`` objects | ||
when operating with either timedelta-like or with numeric arguments. | ||
See the :ref:`<_timedeltas.mod_divmod>` documentation section. (:issue:`19365`) | ||
|
||
.. ipython:: python | ||
|
||
td = pd.Timedelta(hours=37) | ||
td % pd.Timedelta(minutes=45) | ||
|
||
.. _whatsnew_0230.enhancements.ran_inf: | ||
|
||
``.rank()`` handles ``inf`` values when ``NaN`` are present | ||
|
@@ -571,6 +585,7 @@ Other API Changes | |
- Set operations (union, difference...) on :class:`IntervalIndex` with incompatible index types will now raise a ``TypeError`` rather than a ``ValueError`` (:issue:`19329`) | ||
- :class:`DateOffset` objects render more simply, e.g. "<DateOffset: days=1>" instead of "<DateOffset: kwds={'days': 1}>" (:issue:`19403`) | ||
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`) | ||
- :func:`Timedelta.__mod__`, :func:`Timedelta.__divmod__` now accept timedelta-like and numeric arguments instead of raising ``TypeError`` (:issue:`19365`) | ||
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 the same as above? Isn't it enough to mention it once? 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 an attempt to follow the instructions in #19365, some of which were unclear. I'm open to suggestions. 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. My suggestion would be to just remove this one, but I don't know the reason for the previous instruction 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. @JRBACK is there consensus on this suggestion? 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. oh i guess I wasn't clear. if you make a sub-section then no reason to repeat things, so kill this entry as it dupes the sub-section. |
||
|
||
.. _whatsnew_0230.deprecations: | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -254,6 +254,186 @@ def test_rfloordiv(self): | |
with pytest.raises(TypeError): | ||
ser // td | ||
|
||
def test_td_mod_timedeltalike(self): | ||
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. Like 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. ok |
||
# GH#19365 | ||
td = Timedelta(hours=37) | ||
|
||
# Timedelta-like others | ||
result = td % Timedelta(hours=6) | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(hours=1) | ||
|
||
result = td % timedelta(minutes=60) | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(0) | ||
|
||
result = td % NaT | ||
assert result is NaT | ||
|
||
@pytest.mark.xfail(reason='GH#19378 floordiv td64 returns td64') | ||
def test_td_mod_timedelta64_nat(self): | ||
# GH#19365 | ||
td = Timedelta(hours=37) | ||
|
||
result = td % np.timedelta64('NaT', 'ns') | ||
assert result is NaT | ||
|
||
@pytest.mark.xfail(reason='GH#19378 floordiv td64 returns td64') | ||
def test_td_mod_timedelta64(self): | ||
# GH#19365 | ||
td = Timedelta(hours=37) | ||
|
||
result = td % np.timedelta64(2, 'h') | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(hours=1) | ||
|
||
@pytest.mark.xfail(reason='GH#19378 floordiv by Tick not implemented') | ||
def test_td_mod_offset(self): | ||
# GH#19365 | ||
td = Timedelta(hours=37) | ||
|
||
result = td % pd.offsets.Hour(5) | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(hours=2) | ||
|
||
def test_td_mod_numeric(self): | ||
# GH#19365 | ||
td = Timedelta(hours=37) | ||
|
||
# Numeric Others | ||
result = td % 2 | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(0) | ||
|
||
result = td % 1e12 | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(minutes=3, seconds=20) | ||
|
||
result = td % int(1e12) | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(minutes=3, seconds=20) | ||
|
||
def test_td_mod_invalid(self): | ||
# GH#19365 | ||
td = Timedelta(hours=37) | ||
|
||
with pytest.raises(TypeError): | ||
td % pd.Timestamp('2018-01-22') | ||
|
||
with pytest.raises(TypeError): | ||
td % [] | ||
|
||
def test_td_rmod_pytimedelta(self): | ||
# GH#19365 | ||
td = Timedelta(minutes=3) | ||
|
||
result = timedelta(minutes=4) % td | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(minutes=1) | ||
|
||
@pytest.mark.xfail(reason='GH#19378 floordiv by Tick not implemented') | ||
def test_td_rmod_timedelta64(self): | ||
# GH#19365 | ||
td = Timedelta(minutes=3) | ||
result = np.timedelta64(5, 'm') % td | ||
assert isinstance(result, Timedelta) | ||
assert result == Timedelta(minutes=2) | ||
|
||
def test_td_rmod_invalid(self): | ||
# GH#19365 | ||
td = Timedelta(minutes=3) | ||
|
||
with pytest.raises(TypeError): | ||
pd.Timestamp('2018-01-22') % td | ||
|
||
with pytest.raises(TypeError): | ||
15 % td | ||
|
||
with pytest.raises(TypeError): | ||
16.0 % td | ||
|
||
with pytest.raises(TypeError): | ||
np.array([22, 24]) % td | ||
|
||
def test_td_divmod_numeric(self): | ||
# GH#19365 | ||
td = Timedelta(days=2, hours=6) | ||
|
||
result = divmod(td, 53 * 3600 * 1e9) | ||
assert result[0] == Timedelta(1, unit='ns') | ||
assert isinstance(result[1], Timedelta) | ||
assert result[1] == Timedelta(hours=1) | ||
|
||
assert result | ||
result = divmod(td, np.nan) | ||
assert result[0] is pd.NaT | ||
assert result[1] is pd.NaT | ||
|
||
def test_td_divmod(self): | ||
# GH#19365 | ||
td = Timedelta(days=2, hours=6) | ||
|
||
result = divmod(td, timedelta(days=1)) | ||
assert result[0] == 2 | ||
assert isinstance(result[1], Timedelta) | ||
assert result[1] == Timedelta(hours=6) | ||
|
||
result = divmod(td, 54) | ||
assert result[0] == Timedelta(hours=1) | ||
assert isinstance(result[1], Timedelta) | ||
assert result[1] == Timedelta(0) | ||
|
||
result = divmod(td, pd.NaT) | ||
assert np.isnan(result[0]) | ||
assert result[1] is pd.NaT | ||
|
||
@pytest.mark.xfail(reason='GH#19378 floordiv by Tick not implemented') | ||
def test_td_divmod_offset(self): | ||
# GH#19365 | ||
td = Timedelta(days=2, hours=6) | ||
|
||
result = divmod(td, pd.offsets.Hour(-4)) | ||
assert result[0] == -14 | ||
assert isinstance(result[1], Timedelta) | ||
assert result[1] == Timedelta(hours=-2) | ||
|
||
def test_td_divmod_invalid(self): | ||
# GH#19365 | ||
td = Timedelta(days=2, hours=6) | ||
|
||
with pytest.raises(TypeError): | ||
divmod(td, pd.Timestamp('2018-01-22')) | ||
|
||
def test_td_rdivmod_pytimedelta(self): | ||
# GH#19365 | ||
result = divmod(timedelta(days=2, hours=6), Timedelta(days=1)) | ||
assert result[0] == 2 | ||
assert isinstance(result[1], Timedelta) | ||
assert result[1] == Timedelta(hours=6) | ||
|
||
@pytest.mark.xfail(reason='GH#19378 floordiv by Tick not implemented') | ||
def test_td_rdivmod_offset(self): | ||
result = divmod(pd.offsets.Hour(54), Timedelta(hours=-4)) | ||
assert result[0] == -14 | ||
assert isinstance(result[1], Timedelta) | ||
assert result[1] == Timedelta(hours=-2) | ||
|
||
def test_td_rdivmod_invalid(self): | ||
# GH#19365 | ||
td = Timedelta(minutes=3) | ||
|
||
with pytest.raises(TypeError): | ||
divmod(pd.Timestamp('2018-01-22'), td) | ||
|
||
with pytest.raises(TypeError): | ||
divmod(15, td) | ||
|
||
with pytest.raises(TypeError): | ||
divmod(16.0, td) | ||
|
||
with pytest.raises(TypeError): | ||
divmod(np.array([22, 24]), td) | ||
|
||
|
||
class TestTimedeltaComparison(object): | ||
def test_comparison_object_array(self): | ||
|
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.
nice!