Conversation
Signed-off-by: Iason Krommydas <[email protected]>
|
cc @seberg |
Signed-off-by: Iason Krommydas <[email protected]>
np.histogramdd
| @@ -0,0 +1 @@ | |||
| * `np.histogramdd` now converts and returns a plain ndarray as its docstring promises in all cases. Previously it only did that when it was handled a sequence of arrays. | |||
There was a problem hiding this comment.
Well, "as it's docstring promises" is a stretch, all docstrings promise that :).
There was a problem hiding this comment.
how about "as the other histogramming functions do"?
| # Sample is a sequence of 1D arrays. | ||
| sample = np.atleast_2d(sample).T | ||
| N, D = sample.shape | ||
| sample = np.asarray(sample) |
There was a problem hiding this comment.
Hmmm, this is awkward, so an ndarray is anything that has a shape attribute, here...
Maybe add a comment that this intentionally drops subclasses? The squence input is awkward anyway. The main reason for this I can see is because other histogram functions do it as well. It could be a regression (although histogramdd is maybe not super common).
Ping @mhvk since you care about subclasses. But astropy uses __array_function__ of course.
There was a problem hiding this comment.
Yeah it is awkward and in the second branch np.atleast_2d(sample) drops the subclasses when handed an iterable of ndarray subclasses....
Random comment just in case Marten cares, but astropy doesn't feel fully consistent here either. Whether the counts will be wrapped in the subclass seems to depend on the other arguments. Don't know if there's a logic behind it but just saying. But I did check astropy and unyt, they don't rely on numpy preserving the subclass. Their overrides rewrap.
In [17]: from astropy import units as u
In [18]: x = np.array([1,2,3]) * u.m
In [19]: x
Out[19]: <Quantity [1., 2., 3.] m>
In [20]: np.histogram(x, weights=[1,2,3])
Out[20]:
(<Quantity [1., 0., 0., 0., 0., 2., 0., 0., 0., 3.]>,
<Quantity [1. , 1.2, 1.4, 1.6, 1.8, 2. , 2.2, 2.4, 2.6, 2.8, 3. ] m>)
In [21]: np.histogram(x)
Out[21]:
(array([1, 0, 0, 0, 0, 1, 0, 0, 0, 1]),
<Quantity [1. , 1.2, 1.4, 1.6, 1.8, 2. , 2.2, 2.4, 2.6, 2.8, 3. ] m>)
In [22]: np.histogramdd(x)
Out[22]:
(array([1., 0., 0., 0., 0., 1., 0., 0., 0., 1.]),
[<Quantity [1. , 1.2, 1.4, 1.6, 1.8, 2. , 2.2, 2.4, 2.6, 2.8, 3. ] m>])
In [23]: np.histogramdd(x, weights=[1,2,3])
Out[23]:
(<Quantity [1., 0., 0., 0., 0., 2., 0., 0., 0., 3.]>,
[<Quantity [1. , 1.2, 1.4, 1.6, 1.8, 2. , 2.2, 2.4, 2.6, 2.8, 3. ] m>])
There was a problem hiding this comment.
The logic of astropy is that if there are no weights, the functions produce counts which should be integer arrays and thus are illogical as quantities (histogramdd appears to be inconsistent in producing float, but that is outside of astropy's control), while if there are weights, the counts get multiplied by it, so it should get the unit of the weight. The choice made is to not specifically check whether the weight itself is in fact a quantity; for float, this does not really matter, since a dimensionless quantity and float ndarray behave the same, but for integer weights arguably it is not quite right, in that one would like to get an integer array out.
My logic here was not to do this for for the sake of minmax, but because it feels inconsistent to me and like it just works by chance. It only preserves the subclass only in the single array input case of |
|
There is another argument for doing this and it has to do with masked arrays. Right now because the function uses You get 3 values in the histogram but the edges are constructed from 2 values. All the other histogramming functions just ignore the mask, period, and there's an open issue for masked histogramming support. |
Signed-off-by: Iason Krommydas <[email protected]>
mhvk
left a comment
There was a problem hiding this comment.
I think it is good to get the histogram functions to behave the same way, although I'd perhaps argue that it would be better to go in the other direction, preserve subclasses also for the other routines (though I haven't checked that this would allow astropy to remove the overrides; note that in principle we should have overrides also on our Masked class but I never got around to implementing those). It may well be that using asanyarray causes more problems, though, since np.histogram is much more used. So, that argues perhaps more for asarray here.
Anyway, regardless of which way one goes, I think it would make sense to try to actually unify the checking code, maybe by splitting out a _asarray_and_check_weights from _ravel_and_check_weights and then use the former in the latter, adding only the .ravel(), as well as in histogramdd.
| # Sample is a sequence of 1D arrays. | ||
| sample = np.atleast_2d(sample).T | ||
| N, D = sample.shape | ||
| sample = np.asarray(sample) |
There was a problem hiding this comment.
The logic of astropy is that if there are no weights, the functions produce counts which should be integer arrays and thus are illogical as quantities (histogramdd appears to be inconsistent in producing float, but that is outside of astropy's control), while if there are weights, the counts get multiplied by it, so it should get the unit of the weight. The choice made is to not specifically check whether the weight itself is in fact a quantity; for float, this does not really matter, since a dimensionless quantity and float ndarray behave the same, but for integer weights arguably it is not quite right, in that one would like to get an integer array out.
|
I don't think histogramming should preserve subclasses in general. Histogramming returns something fundamentally of a different type. Perhaps for the edges maybe it could but for the counts definitely not. Making counts also preserve subclasses would mean making However, personally think working with base ndarrays is the right call overall. I'm not sure preserving the subclass for the edges is the right general call. Also going towards preserving subclasses would also probably mean we need a mask-aware implementation which is a feature request on an open issue for many years. In any case, I'm a fan of base ndarrays for histogramming and letting subclass implementers (like astropy and unyt) override so I'd probably want to go towards just refactoring out the input parsing into a helper for all the histogramming functions although I'm also okay with the 1-line change I have already. |
I have to think about it once more. But one can see this in all directions. If And there is a plausible middle path here, where we use |
|
I hope we do agree that the counts have to be base ndarray though. |
Only if there are no weights, otherwise they could reasonably get the subclass of the weights (as they do for Quantity), since the counts are multiplied by those. But that is all "in principle" -- there is also "refuse the temptation to guess". I do still think that it is OK to follow your original logic and just make |
|
I think for Quantity I'd get unitless Quantity counts with just plain python list weights though. Perhaps that makes sense. Anyways, I'm not fully thinking of edge cases here but there's potentially an argument to be made that you don't always know what the output type is if you use weights and maybe there's edge cases where using the output type of the weights is wrong but sure. I marked this as "needs decision". |
|
Weights can have units. E.g., if one is histogramming measurements, then least-squares weights would be 1/sigma**2, where sigma is the uncertainty which would have the unit of whatever is being measured. |
I agree with this, I'm OK with a bigger diff if it unifies the code paths than handle this conversion. |
|
The concensus from the community meeting was that we should probably go with base ndarrays. It would be nice to preserve units some times but it doesn't always work. For example if we wrap an |
|
@ikrommyd - yes, makes sense. For astropy, as I suspect most other projects where |
|
I don't understand why histogram2d lives in |
Signed-off-by: Iason Krommydas <[email protected]>
okay I did not do that because it would be a large diff and I found an old comment (2018) from Eric where this wasn't done just because hisrogram2d is just a very very thin wrapper. |
| if N not in {1, 2}: | ||
| xedges = yedges = asarray(bins) | ||
| bins = [xedges, yedges] | ||
| bins = [bins, bins] |
There was a problem hiding this comment.
This should be fine I think as right below histogramdd will run asarray on whatever its given.
|
Let me know of what you think of the structure now with the helpers. I also added tests for subclasses. |
PR summary
Found as something annoying for #32577.
I do believe this is an oversight as histogram and histogram2d already convert to plain ndarrays and do not preserve subclasses and histogramdd also converts to plain ndarrays in the case it gets handed an iterable of ndarray subclasses. It's docstring also says that it should return a plain ndarray.
AI Disclosure
AI wrote the tests