Uh oh!
There was an error while loading. Please reload this page.
Feature/weighted - #2922
Conversation
mathause
commented
Apr 30, 2019
I updated the PR
Before I continue, it would be nice to get some feedback.
As mentioned by @aaronspring, esmlab already implemented weighted statistic functions. Similarly, statsmodels for 1D data without handling of NaNs (docs / code). Thus it should be feasible to implement further statistics here (weighted |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| """ | ||
| # we need to mask DATA values that are nan; else the weights are wrong | ||
| masked_weights = self.weights.where(self.obj.notnull()) |
There was a problem hiding this comment.
If weights has an additional dimension to those of self.obj, masked_weight may consume large memory.
Can we avoid this for example by separating mask from weights?
mask=xr.where(self.obj.notnull(), 1, 0) # binary masksum_of_weights=xr.dot(mask, weights)Do you think if the above is worth doing?
There was a problem hiding this comment.
done - I do not have any performance tests (memory or speed)
| # calculate weighted sum | ||
| return (self.obj * self.weights).sum(dim, axis=axis, skipna=skipna, | ||
| **kwargs) |
There was a problem hiding this comment.
For the same reason above,
xr.where(self.obj, self.obj, 0).dot(self.weights)may work if skipna is True?
There was a problem hiding this comment.
done - I do not have any performance tests (memory or speed)
rabernat
commented
Jul 12, 2019
Hi @mathause - We really appreciate your contribution. Sorry your PR has stalled! Do you think you can respond to @fujiisoup's review and add documentation? Then we can get this merged. |
mathause
commented
Jul 17, 2019
Thanks, I am still very interested to get this in. I don't think I'll manage before my holidays, though. |
I finally made some time to work on this - altough I feel far from finished...
Questions:
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| axis : int or sequence of int, optional | ||
| Axis(es) over which to apply the weighted `{fcn}`. Only one of the | ||
| 'dim' and 'axis' arguments can be supplied. If neither are supplied, | ||
| then the weighted `{fcn}` is calculated over all axes. |
There was a problem hiding this comment.
maybe remove as xr.dot does not provide this
There was a problem hiding this comment.
Agree re removing (or am I missing something—why was this here?)
There was a problem hiding this comment.
For consistency it would be nice to offer axis here. Originally sum was implemented as (weights * da).sum(...) and we got axis for free. With dot it is not as straightforward any more. Honestly, I never use axis with xarray, so I suppose it is fine to only implement it if anyone would ever request it...
There was a problem hiding this comment.
OK. Unless I'm missing something, let's remove axis
Uh oh!
There was an error while loading. Please reload this page.
max-sixty
left a comment
There was a problem hiding this comment.
Thanks for making this progress! I left a few comments & questions
| return sum_of_weights.where(valid_weights) | ||
| def _weighted_sum( |
There was a problem hiding this comment.
Should we just put these on the Weighted object? (not a huge deal, trying to reduce indirection if possible though)
There was a problem hiding this comment.
Do you mean like so?
classWeighted
...
def_weighted_sum(self, da, dims, skipna, **kwargs)
passYes good idea, this might actually even work better as this avoids the hassle with the non-sanitized weights
| # need to mask invalid DATA as dot does not implement skipna | ||
| if skipna or skipna is None: | ||
| return where(da.isnull(), 0.0, da).dot(weights, dims=dims) |
There was a problem hiding this comment.
| returnwhere(da.isnull(), 0.0, da).dot(weights, dims=dims) | |
| returnda.fillna(0.0).dot(weights, dims=dims) |
?
| axis : int or sequence of int, optional | ||
| Axis(es) over which to apply the weighted `{fcn}`. Only one of the | ||
| 'dim' and 'axis' arguments can be supplied. If neither are supplied, | ||
| then the weighted `{fcn}` is calculated over all axes. |
There was a problem hiding this comment.
Agree re removing (or am I missing something—why was this here?)
Uh oh!
There was an error while loading. Please reload this page.
| """ Calcualte the sum of weights, accounting for missing values """ | ||
| # we need to mask DATA values that are nan; else the weights are wrong | ||
| mask = where(da.notnull(), 1, 0) # binary mask |
There was a problem hiding this comment.
| mask=where(da.notnull(), 1, 0) # binary mask | |
| mask=da.isnull() |
bool works with dot from my limited checks
| class DatasetWeighted(Weighted): | ||
| def _dataset_implementation(self, func, **kwargs) -> "Dataset": |
There was a problem hiding this comment.
Not for this PR, but we do this lots of places; we should find a common approach at some point!
| weighted[key] = func(da, self.weights, **kwargs) | ||
| return Dataset(weighted, coords=self.obj.coords) |
There was a problem hiding this comment.
Will this work if coords are on dimensions that are reduced / removed by the function?
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
dcherian
commented
Mar 19, 2020
This is going in. Thanks @mathause. This is a major contribution! |
mathause
commented
Mar 19, 2020
Great! Thanks for all the feedback and support! |
max-sixty
commented
Mar 19, 2020
Thanks @mathause ! |
jhamman
commented
Mar 19, 2020
max-sixty
commented
Mar 19, 2020
I realize this is a bit late, but I'm still concerned about memory usage, specifically in https://github.com/pydata/xarray/blob/master/xarray/core/weighted.py#L130 and https://github.com/pydata/xarray/blob/master/xarray/core/weighted.py#L143. I would have implemented this using |
max-sixty
commented
Mar 20, 2020
We do those sorts of operations fairly frequently, so it's not unique here. Generally users should expect to have available ~3x the memory of an array for most operations. @seth-p it's great you've taken an interest in the project! Is there any chance we could harness that into some contributions? 😄 |
tldr: if someone knows how to do memory profiling with reasonable effort this can still be changed It's certainly not too late to change the "backend" of the weighting functions. I once tried to profile the memory usage but I gave up at some point (I think I would have needed to annotate a ton of functions, also in numpy). @fujiisoup suggested using Also It think it should not be very difficult to write something that can be passed to xarray/xarray/tests/test_weighted.py Line 161 in e8a284f So there would be three possibilities: (1) the current implementation (using |
seth-p
commented
Mar 20, 2020
@max-sixty, I wish I could, but I'm afraid that I cannot submit code due to employer limitations. |
@mathause, ideally
Either way, this only addresses the Also, perhaps the test Maybe I'm more sensitive to this than others, but I regularly deal with 10-100GB arrays. |
@mathause, have you considered using these functions?
|
mathause
commented
Mar 20, 2020
There is some stuff I can do to reduce the memory footprint if
Yes, this would be nice. What could be done, though is to only do
I assume so. I don't know what kind of temporary variables
Again this could be avoided if
Do you want to leave it away for performance reasons? Because it was a deliberate decision to not support
No it's important to make sure this stuff works for large arrays. However, using
None of your suggested functions support I am all in to support more functions, but currently I am happy we got a weighted sum and mean into xarray after 5(!) years! Further libraries that support weighted operations:
|
All good points:
Good idea, though I don't know what the performance hit would be of the extra check (in the case that da does contain NaNs, so the check is for naught).
Well,
Yes. You can continue not supporting NaNs in the weights, yet not explicitly check that there are no NaNs (optionally, if the caller assures you that there are no NaNs).
Correct. These have nothing to do with the NaNs issue. For profiling memory usage, I use |
* upstream/master: add spacing in the versions section of the issue report (pydata#3876) map_blocks: allow user function to add new unindexed dimension. (pydata#3817) Delete associated indexes when deleting coordinate variables. (pydata#3840) remove macos build while waiting for libwebp fix (pydata#3875) Fix html repr on non-str keys (pydata#3870) Allow ellipsis to be used in stack (pydata#3826) Improve where docstring (pydata#3836) Add DataArray.pad, Dataset.pad, Variable.pad (pydata#3596) Fix some warnings (pydata#3864) Feature/weighted (pydata#2922) Fix recombination in groupby when changing size along the grouped dimension (pydata#3807) Blacken the doctest code in docstrings (pydata#3857) Fix multi-index with categorical values. (pydata#3860) Fix interp bug when indexer shares coordinates with array (pydata#3758) Fix alignment with join="override" when some dims are unindexed (pydata#3839)
whats-new.rstfor all changes andapi.rstfor new APII took a shot at the weighted function - I added a
DataArrayWeightedclass, that currently only implementsmean. So, there is still quite a bit missing (e.g.DatasetWeighted), but let me know what you think.There are quite a number of difficult edge cases with invalid data, that can be discussed.
NaNin theweightswith0.0it returnsNaN(and notinf)NaN(could be1)It could be good to add all edge-case logic to a separate function but I am not sure if this is possible...