8000 API: No timedelta/datetimes are not integers and do not promote by seberg · Pull Request #22568 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

API: No timedelta/datetimes are not integers and do not promote #22568

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

seberg
Copy link
Member
@seberg seberg commented Nov 10, 2022

Draft: But only because it depends on gh-22566

What is the fallout of this? No idea, lets find out! The main thing that magically stops working, are comparisons like timedelta64(3, "s") > 0. Of course it seems they didn't work for 0-D scalars anyway most of the time.
(My 1.19.5 NumPy cannot do it for timdelta's if there is a unit attached. Somewhere along the way I fixed the promotion to not drop the unit and made it work.)

Now, astropy's units do allow explicitly comparisons to 0, because can be somewhat defined. But that isn't really plausible at this time (it might be in a NEP 50 future).

Is this the right thing to do? Yes, most definitely. Can we do this? I guess we see what astropy and pandas notice, but I guess it has to wait until after branching :(.

@seberg seberg added the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label Nov 10, 2022
@seberg
Copy link
Member Author
seberg commented Nov 10, 2022

Actually, at least pandas doesn't see to notice really.

@seberg seberg force-pushed the strict-time-promotion branch from b054f57 to d6d8c55 Compare November 10, 2022 16:08
@seberg seberg marked this pull request as ready for review November 11, 2022 10:08
@seberg seberg force-pushed the st 8000 rict-time-promotion branch from 739faf8 to c127d12 Compare November 11, 2022 10:09
@seberg
Copy link
Member Author
seberg commented Nov 11, 2022

I am tempted to say that this is best to "just do", rather than try to deprecate (yes, its not quite finished).

There are some issues though, i.e. timedelta64(0) == 0 could change behavior in certain cases. We could explicitly define to warn about that. The difficult part (as typically) is to figure out what exactly to do here.

The other issue is the infinite deprecation/futurewarning that we have on comparisons, and I am considering looking into that a bit...

@charris
Copy link
Member
charris commented Dec 12, 2022

close/reopen

@charris charris closed this Dec 12, 2022
@charris charris reopened this Dec 12, 2022
@seberg seberg force-pushed the strict-time-promotion branch from e9b98d6 to de6293d Compare December 13, 2022 15:47
@seberg
Copy link
Member Author
seberg commented Dec 13, 2022

Fixing up the typing is proving more complicated and probably needs another approach (and is not finished yet).

I added a release note, the two biggest changes are:

  • Returning an object dtype array for np.array([timedelta, number])
  • Returning False for timedelta64(0, "s") == 0

So I am wondering if we should go through a deprecation tedious or not? The problem with a FutureWarning is that it may be a bit noisy.

Considering that warning filters are annoying, I guess the best thing might be to have a helper context var to enable with _use_future_time_promotion:? (Users could use the contextlib.nullcontext to be future-proof to some degree while opting in to the new behavior.)

@seberg seberg added the triage review Issue/PR to be discussed at the next triage meeting label Dec 13, 2022
@mhvk
Copy link
Contributor
mhvk commented Dec 13, 2022

Just a comment from the sidelines: astropy's choice to treat 0 (and inf and nan) as promotable to any unit was, I think, a decent choice (and one of my first PRs to astropy!), but it certainly also makes sense not to do it. I do worry, however, that timedelta == 0 giving False indepedent of the value of timedelta is just asking for bugs (however logical the implementation). A warning or even exception for that would seem wise.

@seberg
Copy link
Member Author
seberg commented Dec 13, 2022

Yeah, I don't really like the implication either. Generalizing that 0/0.0/inf/nan special handling choice seems really tricky unfortunately.

Some random thoughts:

  • Yes, we can certainly just make all time comparisons fail explicitly rather than returning not-equal.
  • Special casing 0, 0.0, nan, and inf seems super tricky. I could make their cast-safety value-dependent (yes, this is probably surprisingly fine). But I am not convinced it would help much for times. It might however help a bit for a UnitDType which normally does not allow any casts.
  • Special casing promotion could maybe be done, but would require some sort of complicated special casing and channeling in the value, I have tried to avoid this... OF course you could have DTypes just for this purpose, but can we stop at those 4 values?
  • There might be weirder solutions possible, like promoting to a timdelta[no-unit] which only succeeds for special values. But not sure that works, and its probably too wonky anyway.

@mhvk
Copy link
Contributor
mhvk commented Dec 13, 2022

Yes, it is tricky... Probably good to start strict, as it is much easier to change after. But definitely have a warning for timedelta==0 and timedelta!=0!

@seberg
Copy link
Member Author
seberg commented Jan 20, 2023

I think I may implement the warning there (unfortunately), and then revive it as a "2.0 PR" i.e. only active with opt-in... Maybe we could pull it off otherwise, but I am not sure I want to deal with all that complexity (but can be convinced if anyone wants to). This could be a trial baloon for it.

(We might yet get away with doing the "right" thing and just erroring out for != 0 there with NEP 50 in-place)

@mattip
Copy link
Member
mattip commented Feb 15, 2023

This has merge conflicts and has the "needs a release note" label.

@seberg
Copy link
Member Author
seberg commented Feb 15, 2023

This mainly needs a decision of whether we can get away with this. Otherwise, I will try to do this soonish, but hide it behind "numpy 2.0"...

@charris
Copy link
Member
charris commented Feb 19, 2023

Needs rebase.

@InessaPawson InessaPawson added triaged Issue/PR that was discussed in a triage meeting and removed triage review Issue/PR to be discussed at the next triage meeting labels Mar 22, 2023
@jbrockmendel
Copy link
Contributor

FWIW both the stdlib timedelta and pandas.Timedelta do not promote

from datetime import timedelta
import pandas as pd

>>> timedelta(0) == 0
False
>>> pd.Timedelta(0) == 0
False

seberg added 4 commits May 15, 2023 15:14
None of that makes sense, so lets get rid of it!
And yes, the printing elif logic thought it was...
… for floats

Sure, it barely makes sense there as well, but at least this way we can
bypass it and only do the "nonzero" check, which also makes sense for
timedelta64.

And we need timedelta64 to work, because `np.nanmedian` uses the Masked
median internally, and that ends up here 🎉 (sarcasm).
@seberg seberg force-pushed the strict-time-promotion branch from de6293d to 981d3a7 Compare May 15, 2023 14:45
_SupportsArray["dtype[timedelta64]"],
_NestedSequence[_SupportsArray["dtype[timedelta64]"]],
]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased (no version switch). I wouldn't mind pushing this forward before branching, but I will doubt that it is meant to be. In either case, need to figure out the typing...

@BvB93 do you have quick insight? The typing changes seem to make the failure test(s):

AR_f: np.ndarray[Any, np.dtype[np.float64]]
AR_m: np.ndarray[Any, np.dtype[np.timedelta64]]

AR_f > AR_m  # E: Unsupported operand types
```
pass, when floating point comparisons never worked.  It is not clear to me what change could have accidentally allowed comparisons of floats/numbers with timedelta?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a _ArrayLikeTD64_comp_co re-export is missing to the numpy._typing namespace; that's all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Do I see it right that the comparisons are typed so broadly for scalars that at the moment they will simply never fail?

Copy link
Member
@BvB93 BvB93 May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Do I see it right that the comparisons are typed so broadly for scalars that at the moment they will simply never fail?
Only for __eq__/__ne__; for the others they, in the example of timedelta64, accepts one of four types:

  • A numeric scalar (the first parameter)
  • A numeric array-like (second parameter)
  • An object implementing __lt__ or __gt__ (hard coded in _ComparisonOp)
  • A (nested) sequence whose elements implement __lt__ or __gt__ (also hard coded in _ComparisonOp)

The __lt__ / __gt__ escape hatch here is rather broad, though it doesn't quite allow any arbitrary type. Unfortunately it's very difficult to narrow the types down to something more specific, and we need these ones in particular for certain object-based comparison paths (e.g. those involving fractions.Fraction or decimal.Decimal; xref #21939).

    __lt__: _ComparisonOp[_NumberLike_co, _ArrayLikeNumber_co]
    __le__: _ComparisonOp[_NumberLike_co, _ArrayLikeNumber_co]
    __gt__: _ComparisonOp[_NumberLike_co, _ArrayLikeNumber_co]
    __ge__: _ComparisonOp[_NumberLike_co, _ArrayLikeNumber_co]

@jorenham
Copy link
Member
jorenham commented Jun 13, 2025

Any chance this could be resurrected, @seberg?

I wouldn't mind taking care of the typing stuff, if that helps.

@seberg
Copy link
Member Author
seberg commented Jun 14, 2025

@jorenham I would like this. The code changes really should still be practically good, it's just that _core rename...

The problem here is a decision problem and maybe checking for fallout (or just seeing what happens, since the astropy/pandas as the most interesting projects test in nightly CI anyway).
I think the discussion about comparison with int's toned down my immediate will to push it back in the day.

Unlike fixing string promotion, I suppose this might have fewer things that lead to weird stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
30 - API 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes triaged Issue/PR that was discussed in a triage meeting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants
0