Repository navigation
Fix cumulative argmax/argmin returning window-local indices (GH#11336) - #11412
C1-BA-B1-F3 wants to merge 1 commit into
Conversation
When calling argmax/argmin on a cumulative rolling window, the result returned window-local indices (positions within the NaN-padded window) instead of absolute indices in the original array. The fix detects cumulative rolling (window size == dimension size, not centered) and adjusts the returned indices by subtracting the NaN padding offset: absolute = local - (window_size - 1 - position). Regression tests included for 1D and 2D cases.
headtr1ck
left a comment
There was a problem hiding this comment.
Thanks for working on this! Unfortunately I think this patches the symptom rather than the cause, and it introduces a regression:
Dataset rolling argmax/argmin is broken. _reduce_method is shared by DataArrayRolling and DatasetRolling, but _adjust_argminmax_result only exists on DataArrayRolling:
ds = xr.Dataset({"a": ("t", [1.0, 3, 2, 5])})
ds.rolling(t=2).argmax()
# main: a = [nan, 1, 0, 1]
# this PR: AttributeError: 'DatasetRolling' object has no attribute '_adjust_argminmax_result'This affects every Dataset.rolling(...).argmax(), not just cumulative ones.
_is_cumulative() is a heuristic. window == size and not center also matches da.rolling(time=da.sizes["time"], min_periods=1), which now returns absolute indices while rolling(time=size - 1) still returns window-local ones. The meaning of the result shouldn't depend on whether the window happens to equal the dimension length.
The root cause isn't specific to cumulative. Rolling argmax/argmin return the position in the padded window (the padding is filled with ∓inf). cumulative is just a rolling window of the full dimension length, so almost the entire window is padding. Regular rolling has the same problem at the edges, e.g. for [1, 2, 1.5, 3.5, 4]:
da.rolling(time=3, center=True, min_periods=1).argmax()
# [2, 1, 2, 2, 1] -> at i=0 the window is [pad, 1, 2], so 2 is the position of the value 2,
# counted from the start of the padded window instead of the dataA fix at the root would be to subtract the left padding from the result in _array_reduce (after all of the numpy/bottleneck/numbagg/dask paths), i.e. return the position within the valid part of the window. Interior windows are unchanged, edge windows no longer point into the padding, and cumulative naturally returns absolute indices since its window always starts at 0, without having to detect it. It also works for Dataset and center=True.
Multiple rolling dimensions already fail on main (da.cumulative(["x", "y"]).argmax() raises TypeError: 'tuple' object cannot be interpreted as an integer), so the loop over self.dim here is never exercised; test_cumulative_argmax_2d only rolls over a single dimension.
Finally, what #11336 actually asks for ("fetch the time for the largest value") is idxmax, which rolling objects don't have. Adding idxmin/idxmax to rolling (absolute index → coordinate label) would give users an unambiguous way to get labels without changing the meaning of argmax. That could be a follow-up though.
|
superseeded by #11696 |
Problem
When calling argmax() or argmin() on a cumulative rolling window, the result returns window-local indices (positions within the NaN-padded window) instead of absolute indices in the original array.
For example, with data [1, 2, 1.5, 3.5, 4]:
Root Cause
Cumulative rolling constructs a window with NaN padding on the left. argmax/argmin returns the position within this padded window, not the absolute index in the original array.
For example, at position 2:
Fix
The fix detects cumulative rolling (window size == dimension size, not centered) and adjusts the returned indices by subtracting the NaN padding offset:
absolute = local - (window_size - 1 - position)
Regression Tests
Closes #11336