diff --git a/CHANGES.rst b/CHANGES.rst index df1720495..b6be64884 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,7 +25,8 @@ New features and enhancements * Added option ``never_reached`` to ``degree_days_exceedance_date`` to assign a custom value when the sum threshold is never reached. (:issue:`1459`, :pull:`1647`). * Added option ``min_members`` to ensemble statistics to mask elements when the number of valid members is under a threshold. (:issue:`1459`, :pull:`1647`). * Distribution instances can now be passed to the ``dist`` argument of most statistical indices. (:pull:`1644`). -* Added a new `xclim.indices.generic.select_rolling_resample_op` function to allow for computing rolling statistics. (:issue:`1480`, :pull:`1643`). +* Added a new ``xclim.indices.generic.select_rolling_resample_op`` function to allow for computing rolling statistics. (:issue:`1480`, :pull:`1643`). +* Add the possibility to use a group with a window in ``xc.sdba.processing.reordering``. (:pull:`1566`). Breaking changes ^^^^^^^^^^^^^^^^ diff --git a/tests/test_sdba/test_processing.py b/tests/test_sdba/test_processing.py index 294ef980c..0dca69583 100644 --- a/tests/test_sdba/test_processing.py +++ b/tests/test_sdba/test_processing.py @@ -5,6 +5,7 @@ import pytest import xarray as xr +from xclim.core.calendar import date_range from xclim.core.units import units from xclim.sdba.adjustment import EmpiricalQuantileMapping from xclim.sdba.base import Grouper @@ -185,6 +186,31 @@ def test_reordering(): assert out.attrs == y.attrs +def test_reordering_with_window(): + time = list( + date_range("2000-01-01", "2000-01-04", freq="D", calendar="noleap") + ) + list(date_range("2001-01-01", "2001-01-04", freq="D", calendar="noleap")) + + x = xr.DataArray( + np.arange(1, 9, 1), + dims=("time"), + coords={"time": time}, + ) + + y = xr.DataArray( + np.arange(8, 0, -1), + dims=("time"), + coords={"time": time}, + ) + + group = Grouper(group="time.dayofyear", window=3) + out = reordering(x, y, group=group) + + np.testing.assert_array_equal(out, [3.0, 3.0, 2.0, 2.0, 7.0, 7.0, 6.0, 6.0]) + out.attrs.pop("history") + assert out.attrs == y.attrs + + def test_to_additive(pr_series, hurs_series): # log pr = pr_series(np.array([0, 1e-5, 1, np.e**10])) diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index 41cc804fe..c6cf7f486 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -142,7 +142,7 @@ def _normalize( ) -@map_groups(reordered=[Grouper.DIM], main_only=True) +@map_groups(reordered=[Grouper.DIM], main_only=False) def _reordering(ds, *, dim): """Group-wise reordering. @@ -159,17 +159,47 @@ def _reordering(ds, *, dim): def _reordering_1d(data, ordr): return np.sort(data)[np.argsort(np.argsort(ordr))] - return ( - xr.apply_ufunc( - _reordering_1d, - ds.sim, - ds.ref, - input_core_dims=[[dim], [dim]], - output_core_dims=[[dim]], - vectorize=True, - dask="parallelized", - output_dtypes=[ds.sim.dtype], + def _reordering_2d(data, ordr): + data_r = data.ravel() + ordr_r = ordr.ravel() + reorder = np.sort(data_r)[np.argsort(np.argsort(ordr_r))] + return reorder.reshape(data.shape)[ + :, int(data.shape[1] / 2) + ] # pick the middle of the window + + if {"window", "time"} == set(dim): + return ( + xr.apply_ufunc( + _reordering_2d, + ds.sim, + ds.ref, + input_core_dims=[["time", "window"], ["time", "window"]], + output_core_dims=[["time"]], + vectorize=True, + dask="parallelized", + output_dtypes=[ds.sim.dtype], + ) + .rename("reordered") + .to_dataset() + ) + elif len(dim) == 1: + return ( + xr.apply_ufunc( + _reordering_1d, + ds.sim, + ds.ref, + input_core_dims=[dim, dim], + output_core_dims=[dim], + vectorize=True, + dask="parallelized", + output_dtypes=[ds.sim.dtype], + ) + .rename("reordered") + .to_dataset() + ) + else: + raise ValueError( + f"Reordering can only be done along one dimension." + f" If there is more than one, they should be `window` and `time`." + f" The dimensions are {dim}." ) - .rename("reordered") - .to_dataset() - )