From f346b80b3b429e7342b5cc842720e8d169d8723a Mon Sep 17 00:00:00 2001 From: saschahofmann Date: Thu, 18 Apr 2024 15:57:22 +0200 Subject: [PATCH 1/3] Add failing test for QDM with seasonal group --- tests/test_sdba/test_adjustment.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index 7543ae9d7..9776c6285 100644 --- a/tests/test_sdba/test_adjustment.py +++ b/tests/test_sdba/test_adjustment.py @@ -378,6 +378,37 @@ def test_mon_U( # Test predict np.testing.assert_allclose(p, ref.transpose(*p.dims), rtol=0.1, atol=0.2) + def test_seasonal(self, series, random): + u = random.random(10000) + kind = '+' + name = 'tas' + # Define distributions + xd = uniform(loc=1, scale=1) + yd = uniform(loc=2, scale=4) + + # Generate random numbers with u so we get exact results for comparison + x = xd.ppf(u) + y = yd.ppf(u) + + # Test train + hist = sim = series(x, name) + ref = series(y, name) + + QDM = QuantileDeltaMapping.train( + ref.astype("float32"), + hist.astype("float32"), + kind=kind, + group="time.season", + nquantiles=10, + ) + p = QDM.adjust(sim.astype("float32"), interp="linear") + + # Test predict + # Accept discrepancies near extremes + middle = (u > 1e-2) * (u < 0.99) + np.testing.assert_array_almost_equal(p[middle], ref[middle], 1) + + def test_cannon_and_diagnostics(self, cannon_2015_dist, cannon_2015_rvs): ref, hist, sim = cannon_2015_rvs(15000, random=False) From 8e81861fa789b746048c3e11165494bf8d87e504 Mon Sep 17 00:00:00 2001 From: saschahofmann Date: Thu, 18 Apr 2024 16:39:09 +0200 Subject: [PATCH 2/3] Map seasons to integers in interp_on_quanitles --- tests/test_sdba/test_adjustment.py | 5 ++--- xclim/sdba/base.py | 2 ++ xclim/sdba/utils.py | 12 +++++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index 9776c6285..3cdc86ff9 100644 --- a/tests/test_sdba/test_adjustment.py +++ b/tests/test_sdba/test_adjustment.py @@ -380,8 +380,8 @@ def test_mon_U( def test_seasonal(self, series, random): u = random.random(10000) - kind = '+' - name = 'tas' + kind = "+" + name = "tas" # Define distributions xd = uniform(loc=1, scale=1) yd = uniform(loc=2, scale=4) @@ -408,7 +408,6 @@ def test_seasonal(self, series, random): middle = (u > 1e-2) * (u < 0.99) np.testing.assert_array_almost_equal(p[middle], ref[middle], 1) - def test_cannon_and_diagnostics(self, cannon_2015_dist, cannon_2015_rvs): ref, hist, sim = cannon_2015_rvs(15000, random=False) diff --git a/xclim/sdba/base.py b/xclim/sdba/base.py index 0d40e716e..6590601ba 100644 --- a/xclim/sdba/base.py +++ b/xclim/sdba/base.py @@ -295,6 +295,8 @@ def get_index( ind = da.indexes[self.dim] if self.prop == "week": i = da[self.dim].copy(data=ind.isocalendar().week).astype(int) + elif self.prop == "season": + i = da[self.dim].copy(data=ind.month % 12 // 3) else: i = getattr(ind, self.prop) diff --git a/xclim/sdba/utils.py b/xclim/sdba/utils.py index c36cb247a..09a3129af 100644 --- a/xclim/sdba/utils.py +++ b/xclim/sdba/utils.py @@ -360,6 +360,11 @@ def _interp_on_quantiles_2D(newx, newg, oldx, oldy, oldg, method, extrap): # no return out +SEASON_MAP = {"DJF": 0, "MAM": 1, "JJA": 2, "SON": 3} + +map_season_to_int = np.vectorize(SEASON_MAP.get) + + @parse_group def interp_on_quantiles( newx: xr.DataArray, @@ -429,12 +434,17 @@ def interp_on_quantiles( ) return out - # else: if prop not in xq.dims: xq = xq.expand_dims({prop: group.get_coordinate()}) if prop not in yq.dims: yq = yq.expand_dims({prop: group.get_coordinate()}) + # Adding the cyclic bounds fails for string coordinates like seasons + # That's why we map the seasons to integers + if prop == "season": + xq = xq.assign_coords(season=map_season_to_int(xq.season)) + yq = yq.assign_coords(season=map_season_to_int(yq.season)) + xq = add_cyclic_bounds(xq, prop, cyclic_coords=False) yq = add_cyclic_bounds(yq, prop, cyclic_coords=False) newg = group.get_index(newx, interp=method != "nearest") From 6c840a282a0aaaeb19610449544920d178637a13 Mon Sep 17 00:00:00 2001 From: saschahofmann Date: Thu, 18 Apr 2024 16:47:10 +0200 Subject: [PATCH 3/3] Update CHANGE.rst --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5f953b2b7..817d8f0e8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,6 +17,7 @@ Bug fixes * Fixed bug with loess smoothing for an array full of NaNs. (:pull:`1699`). * Fixed and adapted ``time_bnds`` to the newest xarray. (:pull:`1700`). * Fixed "agreement fraction" in ``robustness_fractions`` to distinguish between negative change and no change. Added "negative" and "changed negative" fractions (:issue:`1690`, :pull:`1711`). +* Fixed bug QuantileDeltaMapping adjustment not working for seasonal grouping (:issue:`1704`, :pull:`1716`). Internal changes ^^^^^^^^^^^^^^^^