-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
REF: simplify Timedelta arithmetic methods #33978
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 3 commits
1de6eb8
d771ce8
bc65058
22ac75f
4a5b057
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 |
---|---|---|
|
@@ -18,7 +18,8 @@ PyDateTime_IMPORT | |
cimport pandas._libs.tslibs.util as util | ||
from pandas._libs.tslibs.util cimport ( | ||
is_timedelta64_object, is_datetime64_object, is_integer_object, | ||
is_float_object) | ||
is_float_object, is_array | ||
) | ||
|
||
from pandas._libs.tslibs.c_timestamp cimport _Timestamp | ||
|
||
|
@@ -606,7 +607,7 @@ def _binary_op_method_timedeltalike(op, name): | |
# We are implicitly requiring the canonical behavior to be | ||
# defined by Timestamp methods. | ||
|
||
elif hasattr(other, 'dtype'): | ||
elif is_array(other): | ||
# nd-array like | ||
if other.dtype.kind in ['m', 'M']: | ||
return op(self.to_timedelta64(), other) | ||
|
@@ -1347,113 +1348,64 @@ class Timedelta(_Timedelta): | |
__rsub__ = _binary_op_method_timedeltalike(lambda x, y: y - x, '__rsub__') | ||
|
||
def __mul__(self, other): | ||
if hasattr(other, '_typ'): | ||
# Series, DataFrame, ... | ||
if other._typ == 'dateoffset' and hasattr(other, 'delta'): | ||
# Tick offset; this op will raise TypeError | ||
return other.delta * self | ||
return NotImplemented | ||
if is_integer_object(other) or is_float_object(other): | ||
return Timedelta(other * self.value, unit='ns') | ||
|
||
elif util.is_nan(other): | ||
# i.e. np.nan, but also catch np.float64("NaN") which would | ||
# otherwise get caught by the hasattr(other, "dtype") branch | ||
# incorrectly return a np.timedelta64 object. | ||
return NaT | ||
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. not checking this, does that give a change in behaviour? 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 util.is_nan case is subsumed by the is_float_object case on L1351 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, I see! |
||
|
||
elif hasattr(other, 'dtype'): | ||
elif is_array(other): | ||
# ndarray-like | ||
return other * self.to_timedelta64() | ||
|
||
elif other is NaT: | ||
raise TypeError('Cannot multiply Timedelta with NaT') | ||
|
||
elif not (is_integer_object(other) or is_float_object(other)): | ||
# only integers and floats allowed | ||
return NotImplemented | ||
|
||
return Timedelta(other * self.value, unit='ns') | ||
return NotImplemented | ||
|
||
__rmul__ = __mul__ | ||
|
||
def __truediv__(self, other): | ||
if hasattr(other, '_typ'): | ||
# Series, DataFrame, ... | ||
if other._typ == 'dateoffset' and hasattr(other, 'delta'): | ||
# Tick offset | ||
return self / other.delta | ||
return NotImplemented | ||
|
||
elif is_timedelta64_object(other): | ||
# convert to Timedelta below | ||
pass | ||
|
||
elif util.is_nan(other): | ||
# i.e. np.nan, but also catch np.float64("NaN") which would | ||
# otherwise get caught by the hasattr(other, "dtype") branch | ||
# incorrectly return a np.timedelta64 object. | ||
return NaT | ||
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. same here 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. same here, subsumed by the is_float_object case on L1370 |
||
|
||
elif hasattr(other, 'dtype'): | ||
return self.to_timedelta64() / other | ||
if _should_cast_to_timedelta(other): | ||
# We interpret NaT as timedelta64("NaT") | ||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return self.value / float(other.value) | ||
|
||
elif is_integer_object(other) or is_float_object(other): | ||
# integers or floats | ||
return Timedelta(self.value / other, unit='ns') | ||
|
||
elif not _validate_ops_compat(other): | ||
return NotImplemented | ||
elif is_array(other): | ||
return self.to_timedelta64() / other | ||
|
||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return self.value / float(other.value) | ||
return NotImplemented | ||
|
||
def __rtruediv__(self, other): | ||
if hasattr(other, '_typ'): | ||
# Series, DataFrame, ... | ||
if other._typ == 'dateoffset' and hasattr(other, 'delta'): | ||
# Tick offset | ||
return other.delta / self | ||
return NotImplemented | ||
|
||
elif is_timedelta64_object(other): | ||
# convert to Timedelta below | ||
pass | ||
|
||
elif util.is_nan(other): | ||
# i.e. np.nan or np.float64("NaN") | ||
raise TypeError("Cannot divide float by Timedelta") | ||
if _should_cast_to_timedelta(other): | ||
# We interpret NaT as timedelta64("NaT") | ||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return float(other.value) / self.value | ||
|
||
elif hasattr(other, 'dtype'): | ||
elif is_array(other): | ||
if other.dtype.kind == "O": | ||
# GH#31869 | ||
return np.array([x / self for x in other]) | ||
return other / self.to_timedelta64() | ||
|
||
elif not _validate_ops_compat(other): | ||
return NotImplemented | ||
|
||
other = Timedelta(other) | ||
if other is NaT: | ||
# In this context we treat NaT as timedelta-like | ||
return np.nan | ||
return float(other.value) / self.value | ||
return NotImplemented | ||
|
||
def __floordiv__(self, other): | ||
# numpy does not implement floordiv for timedelta64 dtype, so we cannot | ||
# just defer | ||
if hasattr(other, '_typ'): | ||
# Series, DataFrame, ... | ||
if other._typ == 'dateoffset' and hasattr(other, 'delta'): | ||
# Tick offset | ||
return self // other.delta | ||
return NotImplemented | ||
if _should_cast_to_timedelta(other): | ||
# We interpret NaT as timedelta64("NaT") | ||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return self.value // other.value | ||
|
||
elif is_timedelta64_object(other): | ||
# convert to Timedelta below | ||
pass | ||
elif is_integer_object(other) or is_float_object(other): | ||
return Timedelta(self.value // other, unit='ns') | ||
|
||
elif hasattr(other, 'dtype'): | ||
elif is_array(other): | ||
if other.dtype.kind == 'm': | ||
# also timedelta-like | ||
return _broadcast_floordiv_td64(self.value, other, _floordiv) | ||
|
@@ -1465,50 +1417,27 @@ class Timedelta(_Timedelta): | |
|
||
raise TypeError(f'Invalid dtype {other.dtype} for __floordiv__') | ||
|
||
elif is_integer_object(other) or is_float_object(other): | ||
return Timedelta(self.value // other, unit='ns') | ||
|
||
elif not _validate_ops_compat(other): | ||
return NotImplemented | ||
|
||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return self.value // other.value | ||
return NotImplemented | ||
|
||
def __rfloordiv__(self, other): | ||
# numpy does not implement floordiv for timedelta64 dtype, so we cannot | ||
# just defer | ||
if hasattr(other, '_typ'): | ||
# Series, DataFrame, ... | ||
if other._typ == 'dateoffset' and hasattr(other, 'delta'): | ||
# Tick offset | ||
return other.delta // self | ||
return NotImplemented | ||
|
||
elif is_timedelta64_object(other): | ||
# convert to Timedelta below | ||
pass | ||
if _should_cast_to_timedelta(other): | ||
# We interpret NaT as timedelta64("NaT") | ||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return other.value // self.value | ||
|
||
elif hasattr(other, 'dtype'): | ||
elif is_array(other): | ||
if other.dtype.kind == 'm': | ||
# also timedelta-like | ||
return _broadcast_floordiv_td64(self.value, other, _rfloordiv) | ||
|
||
# Includes integer array // Timedelta, disallowed in GH#19761 | ||
raise TypeError(f'Invalid dtype {other.dtype} for __floordiv__') | ||
|
||
elif is_float_object(other) and util.is_nan(other): | ||
# i.e. np.nan | ||
return NotImplemented | ||
|
||
elif not _validate_ops_compat(other): | ||
return NotImplemented | ||
|
||
other = Timedelta(other) | ||
if other is NaT: | ||
return np.nan | ||
return other.value // self.value | ||
return NotImplemented | ||
|
||
def __mod__(self, other): | ||
# Naive implementation, room for optimization | ||
|
@@ -1529,6 +1458,21 @@ class Timedelta(_Timedelta): | |
return div, other - div * self | ||
|
||
|
||
cdef bint is_any_td_scalar(object obj): | ||
return ( | ||
PyDelta_Check(obj) or is_timedelta64_object(obj) or isinstance(obj, Tick) | ||
) | ||
|
||
|
||
cdef bint _should_cast_to_timedelta(object obj): | ||
""" | ||
Should we treat this object as a Timedelta for the purpose of a binary op | ||
""" | ||
return ( | ||
is_any_td_scalar(obj) or obj is None or obj is NaT or isinstance(obj, str) | ||
) | ||
|
||
|
||
cdef _floordiv(int64_t value, right): | ||
return value // right | ||
|
||
|
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.
This part is covered by
rmul
of DateOffset?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.
It will go through
DateOffset.__rmul__
which will raise TypeError (correctly), yes.