@@ -378,3 +378,60 @@ def windowed_mode_countless(
378378 final_result : npt .NDArray [Any ] = _lor (reduce (_lor , results ), sections [- 1 ]) - 1 # type: ignore[arg-type]
379379
380380 return final_result
381+
382+
383+ def windowed_rank (
384+ array : npt .NDArray [Any ], window_size : tuple [int , ...], rank : int = - 1
385+ ) -> npt .NDArray [Any ]:
386+ """
387+ Compute the windowed rank order filter of an array.
388+ Input will be coerced to a numpy array.
389+
390+ Parameters
391+ ----------
392+ array: Array-like, e.g. Numpy array, Dask array
393+ The array to be downscaled. The array must have a ``reshape``
394+ method.
395+
396+ window_size: tuple[int, ...]
397+ The window to use for aggregation. The array is partitioned into
398+ non-overlapping regions with size equal to ``window_size``, and the
399+ values in each window are sorted to generate the result.
400+
401+ rank: int, default=-1
402+ The index to take from the sorted values in each window. If non-negative, then
403+ rank must be between 0 and the product of the elements of ``window_size`` minus one,
404+ (inclusive).
405+ Rank may be negative, in which case it denotes an index relative to the end of the sorted
406+ values following normal python indexing rules.
407+ E.g., when rank is -1 (the default), this takes the maxmum value of each window.
408+
409+ Returns
410+ -------
411+ Numpy array
412+ The result of the windowed rank filter. The length of each axis of this array
413+ will be a fraction of the input.
414+
415+ Examples
416+ --------
417+ >>> import numpy as np
418+ >>> from xarray_multiscale.reducers import windowed_rank
419+ >>> data = np.arange(16).reshape(4, 4)
420+ >>> windowed_rank(data, (2, 2), -2)
421+ array([[ 4 6]
422+ [12 14]])
423+ """
424+ max_rank = np .prod (window_size ) - 1
425+ if rank > max_rank or rank < - max_rank - 1 :
426+ msg = (
427+ f"Invalid rank: { rank } for window_size: { window_size } " ,
428+ f"If rank is negative then between either -1 and { - max_rank - 1 } , inclusive" ,
429+ f"If rank is non-negtaive, then it must be between 0 and { max_rank } , inclusive." ,
430+ )
431+ raise ValueError (msg )
432+ reshaped = reshape_windowed (array , window_size )
433+ transposed_shape = tuple (range (0 , reshaped .ndim , 2 )) + tuple (range (1 , reshaped .ndim , 2 ))
434+ transposed = reshaped .transpose (transposed_shape )
435+ collapsed = transposed .reshape (tuple (reshaped .shape [slice (0 , None , 2 )]) + (- 1 ,))
436+ result : npt .NDArray [Any ] = np .take (np .sort (collapsed , axis = - 1 ), rank , axis = - 1 )
437+ return result
0 commit comments