Skip to content

Commit cfa5ea6

Browse files
jbrockmendeljreback
authored andcommitted
BUG: fix Series[timedelta64] arithmetic with Timedelta scalars (#18831)
1 parent beb1e69 commit cfa5ea6

File tree

4 files changed

+56
-9
lines changed

4 files changed

+56
-9
lines changed

doc/source/timedeltas.rst

+8
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,14 @@ yields another ``timedelta64[ns]`` dtypes Series.
267267
td * -1
268268
td * pd.Series([1, 2, 3, 4])
269269
270+
Rounded division (floor-division) of a ``timedelta64[ns]`` Series by a scalar
271+
``Timedelta`` gives a series of integers.
272+
273+
.. ipython:: python
274+
275+
td // pd.Timedelta(days=3, hours=4)
276+
pd.Timedelta(days=3, hours=4) // td
277+
270278
Attributes
271279
----------
272280

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ Conversion
297297
- Bug in :meth:`DatetimeIndex.astype` when converting between timezone aware dtypes, and converting from timezone aware to naive (:issue:`18951`)
298298
- Bug in :class:`FY5253` where ``datetime`` addition and subtraction incremented incorrectly for dates on the year-end but not normalized to midnight (:issue:`18854`)
299299
- Bug in :class:`DatetimeIndex` where adding or subtracting an array-like of ``DateOffset`` objects either raised (``np.array``, ``pd.Index``) or broadcast incorrectly (``pd.Series``) (:issue:`18849`)
300+
- Bug in :class:`Series` floor-division where operating on a scalar ``timedelta`` raises an exception (:issue:`18846`)
300301

301302

302303
Indexing

pandas/core/ops.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def _validate_timedelta(self, name):
425425
# 2 timedeltas
426426
if name not in ('__div__', '__rdiv__', '__truediv__',
427427
'__rtruediv__', '__add__', '__radd__', '__sub__',
428-
'__rsub__'):
428+
'__rsub__', '__floordiv__', '__rfloordiv__'):
429429
raise TypeError("can only operate on a timedeltas for addition"
430430
", subtraction, and division, but the operator"
431431
" [{name}] was passed".format(name=name))
@@ -629,7 +629,9 @@ def _offset(lvalues, rvalues):
629629
# integer gets converted to timedelta in np < 1.6
630630
if ((self.is_timedelta_lhs and self.is_timedelta_rhs) and
631631
not self.is_integer_rhs and not self.is_integer_lhs and
632-
self.name in ('__div__', '__truediv__')):
632+
self.name in ('__div__', '__rdiv__',
633+
'__truediv__', '__rtruediv__',
634+
'__floordiv__', '__rfloordiv__')):
633635
self.dtype = 'float64'
634636
self.fill_value = np.nan
635637
lvalues = lvalues.astype(np.float64)

pandas/tests/series/test_operators.py

+43-7
Original file line numberDiff line numberDiff line change
@@ -977,9 +977,7 @@ def test_operators_timedelta64_with_timedelta(self, scalar_td):
977977

978978
@pytest.mark.parametrize('scalar_td', [
979979
timedelta(minutes=5, seconds=4),
980-
pytest.param(Timedelta('5m4s'),
981-
marks=pytest.mark.xfail(reason="Timedelta.__floordiv__ "
982-
"bug GH#18846")),
980+
Timedelta('5m4s'),
983981
Timedelta('5m4s').to_timedelta64()])
984982
def test_operators_timedelta64_with_timedelta_invalid(self, scalar_td):
985983
td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
@@ -993,15 +991,53 @@ def test_operators_timedelta64_with_timedelta_invalid(self, scalar_td):
993991
td1 * scalar_td
994992
with tm.assert_raises_regex(TypeError, pattern):
995993
scalar_td * td1
996-
with tm.assert_raises_regex(TypeError, pattern):
997-
td1 // scalar_td
998-
with tm.assert_raises_regex(TypeError, pattern):
999-
scalar_td // td1
1000994
with tm.assert_raises_regex(TypeError, pattern):
1001995
scalar_td ** td1
1002996
with tm.assert_raises_regex(TypeError, pattern):
1003997
td1 ** scalar_td
1004998

999+
@pytest.mark.parametrize('scalar_td', [
1000+
timedelta(minutes=5, seconds=4),
1001+
pytest.param(Timedelta('5m4s'),
1002+
marks=pytest.mark.xfail(reason="Timedelta.__floordiv__ "
1003+
"bug GH#18846")),
1004+
Timedelta('5m4s').to_timedelta64()])
1005+
def test_timedelta_rfloordiv(self, scalar_td):
1006+
# GH#18831
1007+
td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
1008+
td1.iloc[2] = np.nan
1009+
result = scalar_td // td1
1010+
expected = Series([1, 1, np.nan])
1011+
tm.assert_series_equal(result, expected)
1012+
1013+
@pytest.mark.parametrize('scalar_td', [
1014+
timedelta(minutes=5, seconds=4),
1015+
Timedelta('5m4s'),
1016+
Timedelta('5m4s').to_timedelta64()])
1017+
def test_timedelta_rfloordiv_explicit(self, scalar_td):
1018+
# GH#18831
1019+
td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
1020+
td1.iloc[2] = np.nan
1021+
1022+
# We can test __rfloordiv__ using this syntax,
1023+
# see `test_timedelta_rfloordiv`
1024+
result = td1.__rfloordiv__(scalar_td)
1025+
expected = Series([1, 1, np.nan])
1026+
tm.assert_series_equal(result, expected)
1027+
1028+
@pytest.mark.parametrize('scalar_td', [
1029+
timedelta(minutes=5, seconds=4),
1030+
Timedelta('5m4s'),
1031+
Timedelta('5m4s').to_timedelta64()])
1032+
def test_timedelta_floordiv(self, scalar_td):
1033+
# GH#18831
1034+
td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
1035+
td1.iloc[2] = np.nan
1036+
1037+
result = td1 // scalar_td
1038+
expected = Series([0, 0, np.nan])
1039+
tm.assert_series_equal(result, expected)
1040+
10051041

10061042
class TestDatetimeSeriesArithmetic(object):
10071043
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)