Skip to content

Commit d887927

Browse files
jbrockmendeljreback
authored andcommitted
implement truediv, rtruediv directly in TimedeltaArray; tests (#23829)
1 parent b219bf9 commit d887927

File tree

5 files changed

+249
-22
lines changed

5 files changed

+249
-22
lines changed

doc/source/whatsnew/v0.24.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,7 @@ Timedelta
12561256
- Bug in :class:`TimedeltaIndex` where adding a timezone-aware datetime scalar incorrectly returned a timezone-naive :class:`DatetimeIndex` (:issue:`23215`)
12571257
- Bug in :class:`TimedeltaIndex` where adding ``np.timedelta64('NaT')`` incorrectly returned an all-`NaT` :class:`DatetimeIndex` instead of an all-`NaT` :class:`TimedeltaIndex` (:issue:`23215`)
12581258
- Bug in :class:`Timedelta` and :func:`to_timedelta()` have inconsistencies in supported unit string (:issue:`21762`)
1259-
1259+
- Bug in :class:`TimedeltaIndex` division where dividing by another :class:`TimedeltaIndex` raised ``TypeError`` instead of returning a :class:`Float64Index` (:issue:`23829`, :issue:`22631`)
12601260

12611261
Timezones
12621262
^^^^^^^^^

pandas/core/arrays/timedeltas.py

+99-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import numpy as np
99

10-
from pandas._libs import algos, tslibs
10+
from pandas._libs import algos, lib, tslibs
1111
from pandas._libs.tslibs import NaT, Timedelta, Timestamp, iNaT
1212
from pandas._libs.tslibs.fields import get_timedelta_field
1313
from pandas._libs.tslibs.timedeltas import (
@@ -177,7 +177,7 @@ def __new__(cls, values, freq=None, dtype=_TD_DTYPE, copy=False):
177177
passed=freq.freqstr))
178178
elif freq is None:
179179
freq = inferred_freq
180-
freq_infer = False
180+
freq_infer = False
181181

182182
result = cls._simple_new(values, freq=freq)
183183
# check that we are matching freqs
@@ -355,12 +355,108 @@ def _evaluate_with_timedelta_like(self, other, op):
355355

356356
__mul__ = _wrap_tdi_op(operator.mul)
357357
__rmul__ = __mul__
358-
__truediv__ = _wrap_tdi_op(operator.truediv)
359358
__floordiv__ = _wrap_tdi_op(operator.floordiv)
360359
__rfloordiv__ = _wrap_tdi_op(ops.rfloordiv)
361360

361+
def __truediv__(self, other):
362+
# timedelta / X is well-defined for timedelta-like or numeric X
363+
other = lib.item_from_zerodim(other)
364+
365+
if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)):
366+
return NotImplemented
367+
368+
if isinstance(other, (timedelta, np.timedelta64, Tick)):
369+
other = Timedelta(other)
370+
if other is NaT:
371+
# specifically timedelta64-NaT
372+
result = np.empty(self.shape, dtype=np.float64)
373+
result.fill(np.nan)
374+
return result
375+
376+
# otherwise, dispatch to Timedelta implementation
377+
return self._data / other
378+
379+
elif lib.is_scalar(other):
380+
# assume it is numeric
381+
result = self._data / other
382+
freq = None
383+
if self.freq is not None:
384+
# Tick division is not implemented, so operate on Timedelta
385+
freq = self.freq.delta / other
386+
return type(self)(result, freq=freq)
387+
388+
if not hasattr(other, "dtype"):
389+
# e.g. list, tuple
390+
other = np.array(other)
391+
392+
if len(other) != len(self):
393+
raise ValueError("Cannot divide vectors with unequal lengths")
394+
395+
elif is_timedelta64_dtype(other):
396+
# let numpy handle it
397+
return self._data / other
398+
399+
elif is_object_dtype(other):
400+
# Note: we do not do type inference on the result, so either
401+
# an object array or numeric-dtyped (if numpy does inference)
402+
# will be returned. GH#23829
403+
result = [self[n] / other[n] for n in range(len(self))]
404+
result = np.array(result)
405+
return result
406+
407+
else:
408+
result = self._data / other
409+
return type(self)(result)
410+
411+
def __rtruediv__(self, other):
412+
# X / timedelta is defined only for timedelta-like X
413+
other = lib.item_from_zerodim(other)
414+
415+
if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)):
416+
return NotImplemented
417+
418+
if isinstance(other, (timedelta, np.timedelta64, Tick)):
419+
other = Timedelta(other)
420+
if other is NaT:
421+
# specifically timedelta64-NaT
422+
result = np.empty(self.shape, dtype=np.float64)
423+
result.fill(np.nan)
424+
return result
425+
426+
# otherwise, dispatch to Timedelta implementation
427+
return other / self._data
428+
429+
elif lib.is_scalar(other):
430+
raise TypeError("Cannot divide {typ} by {cls}"
431+
.format(typ=type(other).__name__,
432+
cls=type(self).__name__))
433+
434+
if not hasattr(other, "dtype"):
435+
# e.g. list, tuple
436+
other = np.array(other)
437+
438+
if len(other) != len(self):
439+
raise ValueError("Cannot divide vectors with unequal lengths")
440+
441+
elif is_timedelta64_dtype(other):
442+
# let numpy handle it
443+
return other / self._data
444+
445+
elif is_object_dtype(other):
446+
# Note: unlike in __truediv__, we do not _need_ to do type#
447+
# inference on the result. It does not raise, a numeric array
448+
# is returned. GH#23829
449+
result = [other[n] / self[n] for n in range(len(self))]
450+
return np.array(result)
451+
452+
else:
453+
raise TypeError("Cannot divide {dtype} data by {cls}"
454+
.format(dtype=other.dtype,
455+
cls=type(self).__name__))
456+
362457
if compat.PY2:
363458
__div__ = __truediv__
459+
__rdiv__ = __rtruediv__
364460

365461
# Note: TimedeltaIndex overrides this in call to cls._add_numeric_methods
366462
def __neg__(self):

pandas/core/indexes/base.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -5021,11 +5021,14 @@ def _add_numeric_methods_binary(cls):
50215021
cls.__mod__ = _make_arithmetic_op(operator.mod, cls)
50225022
cls.__floordiv__ = _make_arithmetic_op(operator.floordiv, cls)
50235023
cls.__rfloordiv__ = _make_arithmetic_op(ops.rfloordiv, cls)
5024-
cls.__truediv__ = _make_arithmetic_op(operator.truediv, cls)
5025-
cls.__rtruediv__ = _make_arithmetic_op(ops.rtruediv, cls)
5026-
if not compat.PY3:
5027-
cls.__div__ = _make_arithmetic_op(operator.div, cls)
5028-
cls.__rdiv__ = _make_arithmetic_op(ops.rdiv, cls)
5024+
5025+
if not issubclass(cls, ABCTimedeltaIndex):
5026+
# GH#23829 TimedeltaIndex defines these directly
5027+
cls.__truediv__ = _make_arithmetic_op(operator.truediv, cls)
5028+
cls.__rtruediv__ = _make_arithmetic_op(ops.rtruediv, cls)
5029+
if not compat.PY3:
5030+
cls.__div__ = _make_arithmetic_op(operator.div, cls)
5031+
cls.__rdiv__ = _make_arithmetic_op(ops.rdiv, cls)
50295032

50305033
cls.__divmod__ = _make_arithmetic_op(divmod, cls)
50315034

pandas/core/indexes/timedeltas.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,8 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs):
248248

249249
__mul__ = Index.__mul__
250250
__rmul__ = Index.__rmul__
251-
__truediv__ = Index.__truediv__
252251
__floordiv__ = Index.__floordiv__
253252
__rfloordiv__ = Index.__rfloordiv__
254-
if compat.PY2:
255-
__div__ = Index.__div__
256253

257254
days = wrap_field_accessor(TimedeltaArray.days)
258255
seconds = wrap_field_accessor(TimedeltaArray.seconds)
@@ -261,6 +258,26 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs):
261258

262259
total_seconds = wrap_array_method(TimedeltaArray.total_seconds, True)
263260

261+
def __truediv__(self, other):
262+
oth = other
263+
if isinstance(other, Index):
264+
# TimedeltaArray defers, so we need to unwrap
265+
oth = other._values
266+
result = TimedeltaArray.__truediv__(self, oth)
267+
return wrap_arithmetic_op(self, other, result)
268+
269+
def __rtruediv__(self, other):
270+
oth = other
271+
if isinstance(other, Index):
272+
# TimedeltaArray defers, so we need to unwrap
273+
oth = other._values
274+
result = TimedeltaArray.__rtruediv__(self, oth)
275+
return wrap_arithmetic_op(self, other, result)
276+
277+
if compat.PY2:
278+
__div__ = __truediv__
279+
__rdiv__ = __rtruediv__
280+
264281
# Compat for frequency inference, see GH#23789
265282
_is_monotonic_increasing = Index.is_monotonic_increasing
266283
_is_monotonic_decreasing = Index.is_monotonic_decreasing

0 commit comments

Comments
 (0)