-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Fix Timedelta.__floordiv__, __rfloordiv__ #18961
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
Conversation
assert (2 * td) // scalar.to_timedelta64() == 2 | ||
|
||
assert td // np.nan is pd.NaT | ||
assert np.isnan(td // pd.NaT) |
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.
these should prob be in test_nat with other nat tests
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.
TestTimedeltaArithmetic doesn't have a dedicated test/section for nat tests.
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.
pandas/tests/scalar/test_nat is where the nat tests go (i don't mind them here), but should have some (even if slightly duplicative)
pandas/_libs/tslibs/timedeltas.pyx
Outdated
# also timedelta-like | ||
# We need to watch out for np.timedelta64('NaT') | ||
if np.ndim(other) == 0: | ||
if np.isnat(other): |
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.
pls try to be DRY
don’t use np.ndim ; we don’t use it anywhere else
don’t use np.isnat
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.
don’t use np.isnat
This is specifically trying to catch np.timedelta64('NaT')
and because of np.NaT rules we can't use is timedelta64('NaT')
and we can't use == timedelta64('NaT')
pandas/_libs/tslibs/timedeltas.pyx
Outdated
|
||
if isinstance(other, float) and np.isnan(other): |
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.
huh?
why are you not just checking isna
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.
Because np.nan
(which can't be detected with other is np.nan
) gets treated differently from pd.NaT
.
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.
why? nan is equiv of nat here
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.
why? nan is equiv of nat here
That's slightly ambiguous, but I lean towards "no it isn't". Timedelta(nan)
does return NaT
, but Timedelta / float
is meaningful on its own, so we should treat nan
as a float for the purposes of this operation.
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.
checknull(other) and is_float_object(other) is more clear here (and add a comment)
elif not _validate_ops_compat(other): | ||
return NotImplemented | ||
|
||
other = Timedelta(other) | ||
if other is NaT: | ||
return NaT | ||
return np.nan |
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.
is this what we do for division?
is it not tested now?
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.
is this what we do for division?
Yes.
The status quo is... weird.
def __floordiv__(self, other):
if hasattr(other, 'dtype'):
# work with i8
other = other.astype('m8[ns]').astype('i8')
return self.value // other
elif is_integer_object(other):
# integers only
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
def __rfloordiv__(self, other):
if hasattr(other, 'dtype'):
# work with i8
other = other.astype('m8[ns]').astype('i8')
return other // self.value
elif not _validate_ops_compat(other):
return NotImplemented
other = Timedelta(other)
if other is NaT:
return NaT
return other.value // self.value
is it not tested now?
Not that I'm aware of.
pandas/_libs/tslibs/timedeltas.pyx
Outdated
other = other.astype('m8[ns]').astype('i8') | ||
return other // self.value | ||
if other.dtype.kind == 'm': | ||
# also timedelta-like |
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.
iirc for division we defer to. TDI to handle his logic
does this code path follow?
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.
iirc for division we defer to. TDI to handle his logic
I would find that design choice surprising.
def __truediv__(self, other):
if hasattr(other, 'dtype'):
return self.to_timedelta64() / other
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
other = Timedelta(other)
if other is NaT:
return np.nan
return self.value / float(other.value)
I think the relevant difference here is for cases where other
is an array this returns NotImplemented
, but that isn't a viable option for floordiv because numpy doesn't implement floordiv for timedelta64.
Codecov Report
@@ Coverage Diff @@
## master #18961 +/- ##
===========================================
- Coverage 91.53% 41.61% -49.92%
===========================================
Files 148 148
Lines 48688 48754 +66
===========================================
- Hits 44566 20289 -24277
- Misses 4122 28465 +24343
Continue to review full report at Codecov.
|
The linting doesn't like |
we have banned all numpy functions, e.g. |
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.
pls acknowledge comments individually (generally)
i don’t want to repeat myself
pandas/_libs/tslibs/timedeltas.pyx
Outdated
|
||
if isinstance(other, float) and np.isnan(other): |
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.
why? nan is equiv of nat here
pandas/_libs/tslibs/timedeltas.pyx
Outdated
|
||
# We need to watch out for np.timedelta64('NaT') | ||
if ndim == 0: | ||
if np.isnat(other): |
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.
use isna
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 looks like the Travis build with numpy 1.10.4 is failing because there is no np.isnat, so this is necessary even aside from matters of taste/convention.
I'm making the suggested change now, but is there an alternative that doesn't involve reaching into non-cython pandas? I have a strong preference for keeping the dependency graph DAG.
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.
you mean like missing.pyx/is_null_datetimelike
pandas/_libs/tslibs/timedeltas.pyx
Outdated
return operation(value, other.astype('m8[ns]').astype('i8')) | ||
|
||
else: | ||
mask = np.isnat(other) |
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.
same
pandas/_libs/tslibs/timedeltas.pyx
Outdated
else: | ||
return self.to_timedelta64() // other | ||
|
||
else: |
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.
just raise you don't need an else
pandas/_libs/tslibs/timedeltas.pyx
Outdated
elif other.dtype.kind in ['i', 'u', 'f']: | ||
if np.ndim(other) == 0: | ||
return Timedelta(self.value // other) | ||
else: |
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.
you don't need the else
also pls use other.ndim
pandas/_libs/tslibs/timedeltas.pyx
Outdated
if other.dtype.kind == 'm': | ||
# also timedelta-like | ||
return _broadcast_floordiv_td64(self.value, other, _rfloordiv) | ||
else: |
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.
same
pandas/_libs/tslibs/timedeltas.pyx
Outdated
|
||
if isinstance(other, float) and np.isnan(other): |
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.
checknull(other) and is_float_object(other) is more clear here (and add a comment)
pandas/_libs/tslibs/timedeltas.pyx
Outdated
|
||
# We need to watch out for np.timedelta64('NaT'). | ||
mask = other.view('i8') == NPY_NAT | ||
# equiv np.isnat, which does not exist in some supported np versions. |
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.
remove this comment, we DO NOT use np.isnat
pandas/_libs/tslibs/timedeltas.pyx
Outdated
if ndim == 0: | ||
if mask: | ||
return np.nan | ||
else: |
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.
no else
pandas/_libs/tslibs/timedeltas.pyx
Outdated
else: | ||
return operation(value, other.astype('m8[ns]').astype('i8')) | ||
|
||
else: |
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.
no else
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.
Will change, but curious why you have such a strong preference here. I tend to like making these else
s explicit (you've no doubt noticed).
assert (2 * td) // scalar.to_timedelta64() == 2 | ||
|
||
assert td // np.nan is pd.NaT | ||
assert np.isnan(td // pd.NaT) |
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.
pandas/tests/scalar/test_nat is where the nat tests go (i don't mind them here), but should have some (even if slightly duplicative)
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.
test comments, lgtm, ping on green.
|
||
dt64 = np.datetime64('2016-01-01', dtype='datetime64[us]') | ||
with pytest.raises(TypeError): | ||
td.__rfloordiv__(dt64) |
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.
adding a bit more comments here enumerating the cases would be useful
Ping |
thanks! |
It would not at all surprise me to learn that there are more corner cases that this misses. Needs some eyeballs.
git diff upstream/master -u -- "*.py" | flake8 --diff