-
-
Notifications
You must be signed in to change notification settings - Fork 19.1k
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`) | ||
|
||
|
||
.. _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): | ||
|
||
# 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!