You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
BUG np.where half-initializes subclass of output #5095
In numpy 1.9, the behaviour of np.where(condition, first, second) changed, in that the output no longer always is an ndarray instance but an instance of the subclass of first. However, __array_finalize__(self, obj) is called with obj=None, and hence the subclass can not properly initialize itself (according to the documentation [1], None is reserved for the case where a new instance is created via __new__). This change in behaviour has caused some problems (e.g., astropy/astropy#2958, also discussion in astropy/astropy#1274).
I should add that I do not necessarily believe the old behaviour was preferable, but it would be good if the current behaviour is documented.
Going forward, an option which I think would be ideal (though not necessarily practical...), is if np.where would do the equivalent of
out = first.copy()
out[condition] = second[condition]
In this way, subclasses can check if the second argument is consistent with the first (e.g., for our Quantity class, check the units and possibly convert). This might be done most easily if, somehow, np.where could be turned into a ufunc and passed through the __numpy_ufunc__ mechanism.
where now uses the iterator to allocate the output, but when it allocates the result it propagates the subtype but calls the constructor without an object, like a direct __new__ call
hacking it in using the input object with the hightest priority should fix this and the testsuite succeeds.
But I am a skeptical if this is safe for all cases, our testsuite is leaky in regards to these.
Do we need a new iterator flag to call PyArray_NewFromDescr with non-null obj argument from the iterator output allocation?
on second though where is currently completely subclass unsafe as there is no way to preserve extra information like masks.
should we revert back to 1.8 dropping of subclasses? or change it to use indexing so __getitem____setitem__ are used as suggested?
@juliantaylor - I agree that it would remain unsafe for subclasses. I wondered whether my suggestion was much slower, but it turns out it is not too bad, at least for large arrays:
import numpy as np
def my_where(condition, first, second):
condition, first, second = np.broadcast_arrays(condition, first, second)
condition = ~condition
result = first.copy()
result[condition] = second[condition]
return result
a = np.random.uniform(size=100000)
condition = a > 0.5
%timeit np.where(condition, a, 0.)
# 100 loops, best of 3: 2.07 ms per loop
%timeit my_where(condition, a, 0.)
# 100 loops, best of 3: 2.36 ms per loop
For smaller arrays, it is worse. E.g., for size=100, one becomes dominated by all the initialisation: 13.5 and 96.7 micro-sec, respectively.
where can be double as fast as indexing if the condition is more friendly to branch prediction (dense or sparse) but that should not be an issue, we can add a wrapper python function that does indexing on subclasses and calls to the existing C function for ndarrays
for 1.9.1 we should probably just disable the broken subclass keeping as it brakes code.
@juliantaylor - I saw in #5105 that you hope to produce a version that handles subclasses correctly. Let me know if I can help in any way. Also, shall I raise a new issue reminding us of this?
np.where works like a ufunc, though I know it isn't implemented that way internally. So I think the right solution to this is probably to simply use __numpy_ufunc__ and let subclasses/duck-types override the functionality that way.
@mhvk's proposed solution is interesting, but it won't work for all duck-array types:
out = first.copy()
out[condition] = second[condition]
The problem is that some of the duck-array types for which where is most useful are actually immutable (e.g., dask.array). These types need their own solution, which they can implement with __numpy_ufunc__.
I should also point out that my code example used to work because the condition was a masked array. Depending on the type of first would have been misleading in my case as my example has just an integer there.
...and looking over my company's internal bug tracker, this may be the source of another bug that was customer-facing, causing our shipped netCDF files to have very negative values in places that should have been masked, and our contours to look a bit silly. We never figured out the exact cause of the issue, but I am now going to see if an np.where() was used.
I think I would be more comfortable with a warning. The sneaky nature of
this problem indicates that the results are more like a degraded result
rather than "wrong", especially in the face of duck-typing elsewhere. A
warning would have pulled me to this spot in the code faster, and I would
have reported it sooner as such. It is quite possible that other people
have encountered similar problems and merely shrugged and worked around it,
or didn't figure out the source of it.
On Wed, Jul 29, 2015 at 4:00 PM, Charles Harris [email protected]
wrote:
Activity
juliantaylor commentedon Sep 22, 2014
wherenow uses the iterator to allocate the output, but when it allocates the result it propagates the subtype but calls the constructor without an object, like a direct__new__callhacking it in using the input object with the hightest priority should fix this and the testsuite succeeds.
But I am a skeptical if this is safe for all cases, our testsuite is leaky in regards to these.
Do we need a new iterator flag to call
PyArray_NewFromDescrwith non-nullobjargument from the iterator output allocation?juliantaylor commentedon Sep 22, 2014
branch with the changes:
https://github.com/juliantaylor/numpy/tree/iter-subobj
@mhvk can you try if that branch fixes your issues?
juliantaylor commentedon Sep 22, 2014
on second though
whereis currently completely subclass unsafe as there is no way to preserve extra information like masks.should we revert back to 1.8 dropping of subclasses? or change it to use indexing so
__getitem____setitem__are used as suggested?mhvk commentedon Sep 22, 2014
@juliantaylor - I agree that it would remain unsafe for subclasses. I wondered whether my suggestion was much slower, but it turns out it is not too bad, at least for large arrays:
For smaller arrays, it is worse. E.g., for size=100, one becomes dominated by all the initialisation: 13.5 and 96.7 micro-sec, respectively.
njsmith commentedon Sep 22, 2014
Note that implementing where() via indexing requires a special case for 0d
inputs.
where(True, 5, 10)
works, but
result[condition] = ...
will fail because of how 0d indexing works.
juliantaylor commentedon Sep 23, 2014
wherecan be double as fast as indexing if the condition is more friendly to branch prediction (dense or sparse) but that should not be an issue, we can add a wrapper python function that does indexing on subclasses and calls to the existing C function for ndarraysfor 1.9.1 we should probably just disable the broken subclass keeping as it brakes code.
seberg commentedon Sep 23, 2014
result[np.asarray(condition)]is a workaround until our deprecation is all done and we can change it in master.mhvk commentedon Sep 23, 2014
@juliantaylor - I saw in #5105 that you hope to produce a version that handles subclasses correctly. Let me know if I can help in any way. Also, shall I raise a new issue reminding us of this?
juliantaylor commentedon Sep 23, 2014
too bad
broadcast_arraysdoesn't preserve subtypes, else this would probably already be it:I wonder if thats fixable
mhvk commentedon Sep 23, 2014
See #4622! (about to be merged by @seberg)
mhvk commentedon Sep 23, 2014
p.s. This is indeed why I thought of my example. Note that
broadcast_arraysdoes upgrade to an array, so I think one could even omit theasanyarray.7 remaining items
shoyer commentedon Jul 29, 2015
np.whereworks like a ufunc, though I know it isn't implemented that way internally. So I think the right solution to this is probably to simply use__numpy_ufunc__and let subclasses/duck-types override the functionality that way.@mhvk's proposed solution is interesting, but it won't work for all duck-array types:
The problem is that some of the duck-array types for which
whereis most useful are actually immutable (e.g.,dask.array). These types need their own solution, which they can implement with__numpy_ufunc__.WeatherGod commentedon Jul 29, 2015
I should also point out that my code example used to work because the condition was a masked array. Depending on the type of
firstwould have been misleading in my case as my example has just an integer there.WeatherGod commentedon Jul 29, 2015
...and looking over my company's internal bug tracker, this may be the source of another bug that was customer-facing, causing our shipped netCDF files to have very negative values in places that should have been masked, and our contours to look a bit silly. We never figured out the exact cause of the issue, but I am now going to see if an np.where() was used.
charris commentedon Jul 29, 2015
Perhaps we should raise an error until the subclass issue is fixed. @juliantaylor Have you had any time to pursue your solution?
WeatherGod commentedon Jul 29, 2015
I think I would be more comfortable with a warning. The sneaky nature of
this problem indicates that the results are more like a degraded result
rather than "wrong", especially in the face of duck-typing elsewhere. A
warning would have pulled me to this spot in the code faster, and I would
have reported it sooner as such. It is quite possible that other people
have encountered similar problems and merely shrugged and worked around it,
or didn't figure out the source of it.
On Wed, Jul 29, 2015 at 4:00 PM, Charles Harris [email protected]
wrote:
charris commentedon Jan 21, 2016
So this is still up in the air. Pushing off to 1.12.
mhvk commentedon Jun 17, 2019
Closing since one can now override
np.wherewith__array_function__. Hopefully, eventuallywherebecomes a proper ufunc.