@@ -374,3 +374,56 @@ def lor(a, b):
374374 final_result = lor (reduce (lor , results ), sections [- 1 ]) - 1
375375
376376 return final_result
377+
378+
379+ def windowed_rank (
380+ array : NDArray [Any ], window_size : Tuple [int , ...], rank : int
381+ ) -> NDArray [Any ]:
382+ """
383+ Compute the windowed rank order filter of an array.
384+ Input will be coerced to a numpy array.
385+
386+ Parameters
387+ ----------
388+ array: Array-like, e.g. Numpy array, Dask array
389+ The array to be downscaled. The array must have a ``reshape``
390+ method.
391+
392+ window_size: Tuple of ints
393+ The window to use for aggregation. The array is partitioned into
394+ non-overlapping regions with size equal to ``window_size``, and the
395+ values in each window are sorted to generate the result.
396+
397+ rank: int
398+ The index to take from the sorted values in each window. Must be
399+ between 0 and the product of the elements of ``window_size``, minus one.
400+ Use negative indices to count from the end of the sorted window.
401+
402+ Returns
403+ -------
404+ Numpy array
405+ The result of the windowed rank filter. The length of each axis of this array
406+ will be a fraction of the input.
407+
408+ Examples
409+ --------
410+ >>> import numpy as np
411+ >>> from xarray_multiscale.reducers import windowed_rank
412+ >>> data = np.arange(16).reshape(4, 4)
413+ >>> windowed_rank(data, (2, 2), -2)
414+ array([[ 4 6]
415+ [12 14]])
416+ """
417+ max_rank = np .prod (window_size ) - 1
418+ if rank > max_rank or rank < - max_rank - 1 :
419+ raise ValueError (
420+ f"Rank must be between either -1 and { - max_rank - 1 } or 0 and { max_rank } for window_size { window_size } ."
421+ )
422+ reshaped = reshape_windowed (array , window_size )
423+ transposed_shape = tuple (range (0 , reshaped .ndim , 2 )) + tuple (
424+ range (1 , reshaped .ndim , 2 )
425+ )
426+ transposed = reshaped .transpose (transposed_shape )
427+ collapsed = transposed .reshape (tuple (reshaped .shape [slice (0 , None , 2 )]) + (- 1 ,))
428+ result = np .take (np .sort (collapsed , axis = - 1 ), rank , axis = - 1 )
429+ return result
0 commit comments