Skip to content

Cannot cast one-element object array of empty string to string dtype on 1.21.0rc1 #19085

Closed

Description

@adeak

There's an admittedly edge case that changed behaviour, probably due to the type promotion changes:

>>> import numpy as np
>>> np.array([''], dtype=object).astype('U')

On 1.20.3 this returns

array([''], dtype='<U1')

whereas on the 1.21.0rc1 release candidate this is an error:

>>> np.array([''], dtype=object).astype('U')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Cannot cast array data from dtype('O') to dtype('<U') according to the rule 'unsafe'

I note that casting scalar strings or non-empty arrays still works:

>>> np.array('', dtype=object).astype('U')
array('', dtype='<U1')
>>> np.array(['a'], dtype=object).astype('U')
array(['a'], dtype='<U1')

Is this new error expected?

Activity

  1. added this to the 1.21.0 release milestone on May 24, 2021
  2. seberg commented on May 24, 2021

    @seberg
    Member

    I am not immediately sure why this is happening, but it must indeed be an error in the promotion changes (this is very recent). Astype should see the U and then infer the correct string size from the object array as if you did dtype="U" directly.

    EDIT: OK, I tracked down the issue, but still have to figur out the correct solution... There are some corner cases, that casting to "U" has to figure out the size (but cannot normally, although astype can). So np.can_cast("O", "U", casting="unsafe") is maybe OK to fail, but .astype("U") is not. The fix depends a bit on whether the first should stay failing, or that should also be taped over.

  3. shoyer commented on May 26, 2021

    @shoyer
    Member

    We hit this in Xarray's test suite as well.

  4. seberg commented on May 26, 2021

    @seberg
    Member

    I brought it up briefly on the meeting today. My preference is to not worry about np.can_cast("O", "U", casting="unsafe") (in theory, this is different from np.can_cast("O", "U0", casting="unsafe"), in practice, we are not there yet.

    There may be another solution available, though: I do have a "default" casting level stored (that could be checked without figuring out the actual string length). I can possibly assume that if the "U" (without a length) is given, it is sufficient to compare the default for np.can_cast, because the resolver has the freedom to pick a string that matches (normally, in the case of object to string it will bail, but not for arr.astype("U")!).
    (I have to think this through, in theory its simple, but in generaly it probably does not work, because the first argument may also be the parametric one. E.g. casting an arbitrary precision floating point dtype to a double must enter the "resolution" step to check the length of the arbitrary precision float (if it is high precision, its "safe", if its short it is "same kind"). We could return a "minimum" casting safety, but that may just shift the problem and the "default" was also useful for some other legacy work-arounds I think – and that could collide.)

    Problem recap: np.can_cast("O", "U", casting="unsafe") should maybe return True, since no matter what string length you would use, it is considered "unsafe". But, NumPy has no way to pick a string length (it could guess something, it used to guess 64-bytes sometimes). Since we would have to guess the correct string length Not being able to pick a string length, an error is raised (internally), which then is suppressed and the cast is considered as impossible.

    This is problematic, because we don't (yet) distinguish cleanly between "U0" (zero length) and "U" (undefined length).

  5. mattip commented on May 27, 2021

    @mattip
    Member

    What about putting a shim into PyArray_CanCastArrayTo before this line to explicitly handle this case, with a test and/or perhaps a deprecation warning?

  6. seberg commented on Jun 5, 2021

    @seberg
    Member

    Well, hopefully I got a complete solution mostly, but not quite yet :/.

    My first try, was creating a new set of function PyArray_CanCastArrayToDescr which doesn't do the special casing for S0 to mean S. That works for arr.astype(), but I thought I can be more minimal. (Although we should change everything to such new functions anyway.)

    A second try, trying to be minimal was to rearrange arr.astype("S") a bit, so that it replaces the S0 with S1 before checking the casting safety...

    Both of these fail to solve np.can_cast(arr, "S", casting="unsafe"). My previous thought was that can_cast is maybe not important, but I was thinking of the np.can_cast("O", "S", casting="unsafe") signature. The np.can_cast(arr, "S", casting='unsafe') has the same issue and seems more annoying to keep broken and unaligned with arr.astype()?

    My current try, is the idea of using a "minimal" casting that can be checked before the actual resolution (which fails here). That is, defining castingimpl->casting to be set to a minimal casting level (i.e. no matter what is passed as the to descriptor). That should work more or less:

    • For strings, it gives the "correct" result – assuming str(object) never fails. (For arbitrary user dtypes it would not necessarily align np.can_cast("O", DType) with object_arr.astype(DType), I think)
    • It means fixing up NEP 42 a bit. (That is OK, the tricky part is about user dtypes, but they are never parametric, so I think that is no problem.)

    That is more extensive than I expected, but it is probably the best solution. This seems to work fine, but I have some incorrect sanity checks in my code to track down and one test is still failing...

    @charris, hopefully, I will just have a good solution tomorrow. Unless we go the "minimal" route, maybe it would make sense to publish the bi-weekly wheels for downstream testing before the rc?

  7. charris commented on Jun 5, 2021

    @charris
    Member

    bi-weekly wheels for downstream testing before the rc

    Maybe. Things still feel a bit up in the air.

  8. charris commented on Jun 7, 2021

    @charris
    Member

    Let's wait to close this until we get some feedback from downstream.

  9. adeak commented on Jun 7, 2021

    @adeak
    ContributorAuthor

    Sorry, I was only following emails so I didn't realize that you've pushed a fix.

    I can confirm that current numpy main doesn't raise this error in our test suite. Thank you for the fix :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions