From 52361aec8c5c68f919194fe84c835606938a95e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 27 Apr 2023 10:56:25 -0400 Subject: [PATCH 01/24] Adapt_freq called in training (fix) --- xclim/sdba/_adjustment.py | 41 ++++++++++++++------ xclim/sdba/_processing.py | 81 +++++++++++++++++++++++++++++++++++++++ xclim/sdba/adjustment.py | 4 ++ 3 files changed, 115 insertions(+), 11 deletions(-) diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index 27e6b2d8a..78e603ac6 100644 --- a/xclim/sdba/_adjustment.py +++ b/xclim/sdba/_adjustment.py @@ -10,13 +10,24 @@ import numpy as np import xarray as xr +from xclim.core.units import convert_units_to, infer_context, units from xclim.indices.stats import _fitfunc_1d # noqa from . import nbutils as nbu from . import utils as u +from ._processing import _adapt_freq_s from .base import Grouper, map_blocks, map_groups from .detrending import PolyDetrend -from .processing import escore +from .processing import adapt_freq, escore + + +def _adapt_freq_hist(ds, adapt_freq_thresh): + with units.context(infer_context(ds.ref.attrs.get("standard_name"))): + thresh = convert_units_to(adapt_freq_thresh, ds.ref) + dim = ["time"] + ["window"] * ("window" in ds.hist.dims) + return _adapt_freq_s( + xr.Dataset(dict(sim=ds.hist, ref=ds.ref)), thresh=thresh, dim=dim + ) @map_groups( @@ -24,7 +35,7 @@ hist_q=[Grouper.PROP, "quantiles"], scaling=[Grouper.PROP], ) -def dqm_train(ds, *, dim, kind, quantiles) -> xr.Dataset: +def dqm_train(ds, *, dim, kind, quantiles, adapt_freq_thresh) -> xr.Dataset: """Train step on one group. Notes @@ -33,15 +44,17 @@ def dqm_train(ds, *, dim, kind, quantiles) -> xr.Dataset: ref : training target hist : training data """ + hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist + refn = u.apply_correction(ds.ref, u.invert(ds.ref.mean(dim), kind), kind) - histn = u.apply_correction(ds.hist, u.invert(ds.hist.mean(dim), kind), kind) + histn = u.apply_correction(hist, u.invert(hist.mean(dim), kind), kind) ref_q = nbu.quantile(refn, quantiles, dim) hist_q = nbu.quantile(histn, quantiles, dim) af = u.get_correction(hist_q, ref_q, kind) mu_ref = ds.ref.mean(dim) - mu_hist = ds.hist.mean(dim) + mu_hist = hist.mean(dim) scaling = u.get_correction(mu_hist, mu_ref, kind=kind) return xr.Dataset(data_vars=dict(af=af, hist_q=hist_q, scaling=scaling)) @@ -51,15 +64,17 @@ def dqm_train(ds, *, dim, kind, quantiles) -> xr.Dataset: af=[Grouper.PROP, "quantiles"], hist_q=[Grouper.PROP, "quantiles"], ) -def eqm_train(ds, *, dim, kind, quantiles) -> xr.Dataset: +def eqm_train(ds, *, dim, kind, quantiles, adapt_freq_thresh) -> xr.Dataset: """EQM: Train step on one group. Dataset variables: ref : training target hist : training data """ + hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist + ref_q = nbu.quantile(ds.ref, quantiles, dim) - hist_q = nbu.quantile(ds.hist, quantiles, dim) + hist_q = nbu.quantile(hist, quantiles, dim) af = u.get_correction(hist_q, ref_q, kind) @@ -154,18 +169,20 @@ def qdm_adjust(ds, *, group, interp, extrapolation, kind) -> xr.Dataset: af=[Grouper.PROP], hist_thresh=[Grouper.PROP], ) -def loci_train(ds, *, group, thresh) -> xr.Dataset: +def loci_train(ds, *, group, thresh, adapt_freq_thresh) -> xr.Dataset: """LOCI: Train on one block. Dataset variables: ref : training target hist : training data """ + hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist + s_thresh = group.apply( u.map_cdf, ds.rename(hist="x", ref="y"), y_value=thresh ).isel(x=0) - sth = u.broadcast(s_thresh, ds.hist, group=group) - ws = xr.where(ds.hist >= sth, ds.hist, np.nan) + sth = u.broadcast(s_thresh, hist, group=group) + ws = xr.where(hist >= sth, hist, np.nan) wo = xr.where(ds.ref >= thresh, ds.ref, np.nan) ms = group.apply("mean", ws, skipna=True) @@ -192,14 +209,16 @@ def loci_adjust(ds, *, group, thresh, interp) -> xr.Dataset: @map_groups(af=[Grouper.PROP]) -def scaling_train(ds, *, dim, kind) -> xr.Dataset: +def scaling_train(ds, *, dim, kind, adapt_freq_thresh) -> xr.Dataset: """Scaling: Train on one group. Dataset variables: ref : training target hist : training data """ - mhist = ds.hist.mean(dim) + hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist + + mhist = hist.mean(dim) mref = ds.ref.mean(dim) af = u.get_correction(mhist, mref, kind) return af.rename("af").to_dataset() diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index 8060c97da..14c596f40 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -106,6 +106,87 @@ def _adapt_freq( return xr.Dataset(data_vars={"pth": pth, "dP0": dP0, "sim_ad": sim_ad}) +# The main difference is that this doesnt rely on blocks and works with ranks +# on possibly multiple dimensions (time, window). +# If a ds with dimensions (time, window) is given as input, the same dimensions +# are in the output +def _adapt_freq_s( + ds: xr.Dataset, + *, + dim: Sequence[str], + thresh: float = 0, +) -> xr.Dataset: + r""" + Adapt frequency of values under thresh of `sim`, in order to match ref. + + This is the compute function, see :py:func:`xclim.sdba.processing.adapt_freq` for the user-facing function. + + Parameters + ---------- + ds : xr.Dataset + With variables : "ref", Target/reference data, usually observed data and "sim", Simulated data. + dim : str, or sequence of strings + Dimension name(s). If more than one, the probabilities and quantiles are computed within all the dimensions. + If `window` is in the names, it is removed before the correction + and the final timeseries is corrected along dim[0] only. + group : Union[str, Grouper] + Grouping information, see base.Grouper + thresh : float + Threshold below which values are considered zero. + + Returns + ------- + xr.Dataset, wth the following variables: + + - `sim_adj`: Simulated data with the same frequency of values under threshold than ref. + Adjustment is made group-wise. + - `pth` : For each group, the smallest value of sim that was not frequency-adjusted. All values smaller were + either left as zero values or given a random value between thresh and pth. + NaN where frequency adaptation wasn't needed. + - `dP0` : For each group, the percentage of values that were corrected in sim. + """ + # Compute the probability of finding a value <= thresh + # This is the "dry-day frequency" in the precipitation case + P0_sim = ecdf(ds.sim, thresh, dim=dim) + P0_ref = ecdf(ds.ref, thresh, dim=dim) + + # The proportion of values <= thresh in sim that need to be corrected, compared to ref + dP0 = (P0_sim - P0_ref) / P0_sim + + if dP0.isnull().all(): + # All NaN slice. + pth = dP0.copy() + sim_ad = ds.sim.copy() + else: + # Compute : ecdf_ref^-1( ecdf_sim( thresh ) ) + # The value in ref with the same rank as the first non-zero value in sim. + # pth is meaningless when freq. adaptation is not needed + pth = nbu.vecquantiles(ds.ref, P0_sim, dim).where(dP0 > 0) + + # Probabilities and quantiles computed within all dims, but correction along the first one only. + sim = ds.sim + + temp_dim = "__temp_dim__" + rank = ( + sim.stack(**{temp_dim: dim}) + .rank(temp_dim, pct=True) + .unstack(temp_dim) + .transpose(*sim.dims) + .drop_vars([dim_ for dim_ in dim if dim_ not in sim.coords]) + ) + sim_ad = sim.where( + dP0 < 0, # dP0 < 0 means no-adaptation. + sim.where( + (rank < P0_ref) | (rank > P0_sim), # Preserve current values + # Generate random numbers ~ U[T0, Pth] + (pth.broadcast_like(sim) - thresh) + * np.random.random_sample(size=sim.shape) + + thresh, + ), + ) + return sim_ad + + @map_groups( reduces=[Grouper.DIM, Grouper.PROP], data=[Grouper.DIM], norm=[Grouper.PROP] ) diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 3065c1927..8bdaca14c 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -353,6 +353,7 @@ def _train( nquantiles: int | np.ndarray = 20, kind: str = ADDITIVE, group: str | Grouper = "time", + adapt_freq_thresh: str | None = None, ): if np.isscalar(nquantiles): quantiles = equally_spaced_nodes(nquantiles).astype(ref.dtype) @@ -364,6 +365,7 @@ def _train( group=group, kind=kind, quantiles=quantiles, + adapt_freq_thresh=adapt_freq_thresh, ) ds.af.attrs.update( @@ -446,6 +448,7 @@ def _train( nquantiles: int | np.ndarray = 20, kind: str = ADDITIVE, group: str | Grouper = "time", + adapt_freq_thresh: str | None = None, ): if group.prop not in ["group", "dayofyear"]: warn( @@ -462,6 +465,7 @@ def _train( group=group, quantiles=quantiles, kind=kind, + adapt_freq_thresh=adapt_freq_thresh, ) ds.af.attrs.update( From e096c8f12450dd5a703cf8c27e9efdd63a9b10e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Mon, 8 May 2023 14:28:47 -0400 Subject: [PATCH 02/24] LOCI has adapt_freq_thresh argument --- xclim/sdba/adjustment.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 8bdaca14c..1ebc550d1 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -772,10 +772,14 @@ def _train( *, thresh: str, group: str | Grouper = "time", + adapt_freq_thresh: str | None = None, ): thresh = convert_units_to(thresh, ref) ds = loci_train( - xr.Dataset({"ref": ref, "hist": hist}), group=group, thresh=thresh + xr.Dataset({"ref": ref, "hist": hist}), + group=group, + thresh=thresh, + adapt_freq_thresh=adapt_freq_thresh, ) ds.af.attrs.update(long_name="LOCI adjustment factors") ds.hist_thresh.attrs.update(long_name="Threshold over modeled data") From 58d5b66e3465150cd671c72db4c33f713f221572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Mon, 29 May 2023 15:32:19 -0400 Subject: [PATCH 03/24] adapt_freq_thresh option in Scaling --- xclim/sdba/adjustment.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 1ebc550d1..7e29224b1 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -828,9 +828,13 @@ def _train( *, group: str | Grouper = "time", kind: str = ADDITIVE, + adapt_freq_thresh: str | None = None, ): ds = scaling_train( - xr.Dataset({"ref": ref, "hist": hist}), group=group, kind=kind + xr.Dataset({"ref": ref, "hist": hist}), + group=group, + kind=kind, + adapt_freq_thresh=adapt_freq_thresh, ) ds.af.attrs.update(long_name="Scaling adjustment factors") return ds, {"group": group, "kind": kind} From 1be0d1dfacdfcf09973b3b063c22cb5af2736700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 10:03:12 -0400 Subject: [PATCH 04/24] CHANGES update --- CHANGES.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 46032726f..6ba1535be 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,11 +4,12 @@ Changelog v0.45.0 (unreleased) -------------------- -Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (:user:`Zeitsperre`). +Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (:user:`Zeitsperre`), Éric Dupuis (:user:`coxipi`). New features and enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Added ``ensembles.hawkins_sutton`` method to partition the uncertainty sources in a climate projection ensemble. (:issue:`771`, :pull:`1262`). +* `adapt_freq_thresh` argument added `sdba` training functions, allowing to perform frequency adaptation appropriately in each map block. Internal changes ^^^^^^^^^^^^^^^^ From b304326351f390903c37eb6e3883e9f6ebb19611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 10:15:23 -0400 Subject: [PATCH 05/24] Better description _adapt_freq_s --- xclim/sdba/_processing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index 117ec759f..1c8f47964 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -105,10 +105,10 @@ def _adapt_freq( return xr.Dataset(data_vars={"pth": pth, "dP0": dP0, "sim_ad": sim_ad}) -# The main difference is that this doesnt rely on blocks and works with ranks -# on possibly multiple dimensions (time, window). -# If a ds with dimensions (time, window) is given as input, the same dimensions -# are in the output +# Works with ranks on possibly multiple dimensions (time, window) +# ds with dimensions (time, window) as input: same dimensions as output +# Probably that second part could be done by keeping the explicit map block decorator and calling it +# appropriately? def _adapt_freq_s( ds: xr.Dataset, *, From 3162c32b65e5d62f8c2ef60debe32e9182b97ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 10:31:12 -0400 Subject: [PATCH 06/24] more comments --- xclim/sdba/_processing.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index 1c8f47964..5e364f3b4 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -108,7 +108,14 @@ def _adapt_freq( # Works with ranks on possibly multiple dimensions (time, window) # ds with dimensions (time, window) as input: same dimensions as output # Probably that second part could be done by keeping the explicit map block decorator and calling it -# appropriately? +# appropriately with .func? +# +# My issue with incorporating all this in `_adapt_freq` is +# the [[ if "window" in dim: ]] part above. How does wanting a (window,time) output fit with the idea +# behind the previous function? If I call adapt_freq.func, should I not expect the output to be +# of dimension (window,time)? + + def _adapt_freq_s( ds: xr.Dataset, *, From 55ec6227a8f434f4894e2f664ceb985b343e8847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 10:33:46 -0400 Subject: [PATCH 07/24] pull number --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6ba1535be..f67a5b8bd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,7 +9,7 @@ Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (: New features and enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Added ``ensembles.hawkins_sutton`` method to partition the uncertainty sources in a climate projection ensemble. (:issue:`771`, :pull:`1262`). -* `adapt_freq_thresh` argument added `sdba` training functions, allowing to perform frequency adaptation appropriately in each map block. +* `adapt_freq_thresh` argument added `sdba` training functions, allowing to perform frequency adaptation appropriately in each map block. (:pull:`1407`). Internal changes ^^^^^^^^^^^^^^^^ From f6ce69b2b627032bb9e2a3f2b4638c644bd423fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 10:39:48 -0400 Subject: [PATCH 08/24] Remove adapt_freq_thresh, useless in LOCI and Scaling --- xclim/sdba/_adjustment.py | 14 +++++--------- xclim/sdba/adjustment.py | 4 ---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index fce7ff5e2..9bf24a750 100644 --- a/xclim/sdba/_adjustment.py +++ b/xclim/sdba/_adjustment.py @@ -168,20 +168,18 @@ def qdm_adjust(ds, *, group, interp, extrapolation, kind) -> xr.Dataset: af=[Grouper.PROP], hist_thresh=[Grouper.PROP], ) -def loci_train(ds, *, group, thresh, adapt_freq_thresh) -> xr.Dataset: +def loci_train(ds, *, group, thresh) -> xr.Dataset: """LOCI: Train on one block. Dataset variables: ref : training target hist : training data """ - hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist - s_thresh = group.apply( u.map_cdf, ds.rename(hist="x", ref="y"), y_value=thresh ).isel(x=0) - sth = u.broadcast(s_thresh, hist, group=group) - ws = xr.where(hist >= sth, hist, np.nan) + sth = u.broadcast(s_thresh, ds.hist, group=group) + ws = xr.where(ds.hist >= sth, ds.hist, np.nan) wo = xr.where(ds.ref >= thresh, ds.ref, np.nan) ms = group.apply("mean", ws, skipna=True) @@ -208,16 +206,14 @@ def loci_adjust(ds, *, group, thresh, interp) -> xr.Dataset: @map_groups(af=[Grouper.PROP]) -def scaling_train(ds, *, dim, kind, adapt_freq_thresh) -> xr.Dataset: +def scaling_train(ds, *, dim, kind) -> xr.Dataset: """Scaling: Train on one group. Dataset variables: ref : training target hist : training data """ - hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist - - mhist = hist.mean(dim) + mhist = ds.hist.mean(dim) mref = ds.ref.mean(dim) af = u.get_correction(mhist, mref, kind) return af.rename("af").to_dataset() diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 27d01a2a1..6e225ce52 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -771,14 +771,12 @@ def _train( *, thresh: str, group: str | Grouper = "time", - adapt_freq_thresh: str | None = None, ): thresh = convert_units_to(thresh, ref) ds = loci_train( xr.Dataset({"ref": ref, "hist": hist}), group=group, thresh=thresh, - adapt_freq_thresh=adapt_freq_thresh, ) ds.af.attrs.update(long_name="LOCI adjustment factors") ds.hist_thresh.attrs.update(long_name="Threshold over modeled data") @@ -827,13 +825,11 @@ def _train( *, group: str | Grouper = "time", kind: str = ADDITIVE, - adapt_freq_thresh: str | None = None, ): ds = scaling_train( xr.Dataset({"ref": ref, "hist": hist}), group=group, kind=kind, - adapt_freq_thresh=adapt_freq_thresh, ) ds.af.attrs.update(long_name="Scaling adjustment factors") return ds, {"group": group, "kind": kind} From c95e3c9d7b4c0138eaf645569a51402a6f5f6bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 10:42:04 -0400 Subject: [PATCH 09/24] remove unused import --- xclim/sdba/_adjustment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index 9bf24a750..19443c751 100644 --- a/xclim/sdba/_adjustment.py +++ b/xclim/sdba/_adjustment.py @@ -17,7 +17,7 @@ from ._processing import _adapt_freq_s from .base import Grouper, map_blocks, map_groups from .detrending import PolyDetrend -from .processing import adapt_freq, escore +from .processing import escore def _adapt_freq_hist(ds, adapt_freq_thresh): From 79bdc32b7960caa72dc9dab1e9dae27911550b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 17:07:28 -0400 Subject: [PATCH 10/24] incorporate changes from _adapt_freq_s to _adapt_freq --- xclim/sdba/_adjustment.py | 7 ++- xclim/sdba/_processing.py | 107 ++++---------------------------------- 2 files changed, 13 insertions(+), 101 deletions(-) diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index 19443c751..63c86eca1 100644 --- a/xclim/sdba/_adjustment.py +++ b/xclim/sdba/_adjustment.py @@ -14,7 +14,7 @@ from . import nbutils as nbu from . import utils as u -from ._processing import _adapt_freq_s +from ._processing import _adapt_freq from .base import Grouper, map_blocks, map_groups from .detrending import PolyDetrend from .processing import escore @@ -24,9 +24,9 @@ def _adapt_freq_hist(ds, adapt_freq_thresh): with units.context(infer_context(ds.ref.attrs.get("standard_name"))): thresh = convert_units_to(adapt_freq_thresh, ds.ref) dim = ["time"] + ["window"] * ("window" in ds.hist.dims) - return _adapt_freq_s( + return _adapt_freq.func( xr.Dataset(dict(sim=ds.hist, ref=ds.ref)), thresh=thresh, dim=dim - ) + ).sim_ad @map_groups( @@ -71,7 +71,6 @@ def eqm_train(ds, *, dim, kind, quantiles, adapt_freq_thresh) -> xr.Dataset: hist : training data """ hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist - ref_q = nbu.quantile(ds.ref, quantiles, dim) hist_q = nbu.quantile(hist, quantiles, dim) diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index 5e364f3b4..423574ad2 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -11,6 +11,7 @@ import numpy as np import xarray as xr +from xarray.core.utils import get_temp_dimname from . import nbutils as nbu from .base import Grouper, map_groups @@ -74,16 +75,16 @@ def _adapt_freq( pth = nbu.vecquantiles(ds.ref, P0_sim, dim).where(dP0 > 0) # Probabilities and quantiles computed within all dims, but correction along the first one only. - if "window" in dim: - # P0_sim was computed using the window, but only the original time series is corrected. - # Grouper.apply does this step, but if done here it makes the code faster. - sim = ds.sim.isel(window=(ds.sim.window.size - 1) // 2) - else: - sim = ds.sim - dim = dim[0] - + sim = ds.sim # Get the percentile rank of each value in sim. - rank = sim.rank(dim, pct=True) + temp_dim = get_temp_dimname(sim.dims, "temp") + rank = ( + sim.stack(**{temp_dim: dim}) + .rank(temp_dim, pct=True) + .unstack(temp_dim) + .transpose(*sim.dims) + .drop_vars([dim_ for dim_ in dim if dim_ not in sim.coords]) + ) # Frequency-adapted sim sim_ad = sim.where( @@ -105,94 +106,6 @@ def _adapt_freq( return xr.Dataset(data_vars={"pth": pth, "dP0": dP0, "sim_ad": sim_ad}) -# Works with ranks on possibly multiple dimensions (time, window) -# ds with dimensions (time, window) as input: same dimensions as output -# Probably that second part could be done by keeping the explicit map block decorator and calling it -# appropriately with .func? -# -# My issue with incorporating all this in `_adapt_freq` is -# the [[ if "window" in dim: ]] part above. How does wanting a (window,time) output fit with the idea -# behind the previous function? If I call adapt_freq.func, should I not expect the output to be -# of dimension (window,time)? - - -def _adapt_freq_s( - ds: xr.Dataset, - *, - dim: Sequence[str], - thresh: float = 0, -) -> xr.Dataset: - r""" - Adapt frequency of values under thresh of `sim`, in order to match ref. - - This is the compute function, see :py:func:`xclim.sdba.processing.adapt_freq` for the user-facing function. - - Parameters - ---------- - ds : xr.Dataset - With variables : "ref", Target/reference data, usually observed data and "sim", Simulated data. - dim : str, or sequence of strings - Dimension name(s). If more than one, the probabilities and quantiles are computed within all the dimensions. - If `window` is in the names, it is removed before the correction - and the final timeseries is corrected along dim[0] only. - group : Union[str, Grouper] - Grouping information, see base.Grouper - thresh : float - Threshold below which values are considered zero. - - Returns - ------- - xr.Dataset, wth the following variables: - - - `sim_adj`: Simulated data with the same frequency of values under threshold than ref. - Adjustment is made group-wise. - - `pth` : For each group, the smallest value of sim that was not frequency-adjusted. All values smaller were - either left as zero values or given a random value between thresh and pth. - NaN where frequency adaptation wasn't needed. - - `dP0` : For each group, the percentage of values that were corrected in sim. - """ - # Compute the probability of finding a value <= thresh - # This is the "dry-day frequency" in the precipitation case - P0_sim = ecdf(ds.sim, thresh, dim=dim) - P0_ref = ecdf(ds.ref, thresh, dim=dim) - - # The proportion of values <= thresh in sim that need to be corrected, compared to ref - dP0 = (P0_sim - P0_ref) / P0_sim - - if dP0.isnull().all(): - # All NaN slice. - pth = dP0.copy() - sim_ad = ds.sim.copy() - else: - # Compute : ecdf_ref^-1( ecdf_sim( thresh ) ) - # The value in ref with the same rank as the first non-zero value in sim. - # pth is meaningless when freq. adaptation is not needed - pth = nbu.vecquantiles(ds.ref, P0_sim, dim).where(dP0 > 0) - - # Probabilities and quantiles computed within all dims, but correction along the first one only. - sim = ds.sim - - temp_dim = "__temp_dim__" - rank = ( - sim.stack(**{temp_dim: dim}) - .rank(temp_dim, pct=True) - .unstack(temp_dim) - .transpose(*sim.dims) - .drop_vars([dim_ for dim_ in dim if dim_ not in sim.coords]) - ) - sim_ad = sim.where( - dP0 < 0, # dP0 < 0 means no-adaptation. - sim.where( - (rank < P0_ref) | (rank > P0_sim), # Preserve current values - # Generate random numbers ~ U[T0, Pth] - (pth.broadcast_like(sim) - thresh) - * np.random.random_sample(size=sim.shape) - + thresh, - ), - ) - return sim_ad - - @map_groups( reduces=[Grouper.DIM, Grouper.PROP], data=[Grouper.DIM], norm=[Grouper.PROP] ) From e9a64ae2a354122f03ec12eb9d493902eb098ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 17:12:57 -0400 Subject: [PATCH 11/24] Revert useless formatting changes --- xclim/sdba/adjustment.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 6e225ce52..65f17ac80 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -774,9 +774,7 @@ def _train( ): thresh = convert_units_to(thresh, ref) ds = loci_train( - xr.Dataset({"ref": ref, "hist": hist}), - group=group, - thresh=thresh, + xr.Dataset({"ref": ref, "hist": hist}), group=group, thresh=thresh ) ds.af.attrs.update(long_name="LOCI adjustment factors") ds.hist_thresh.attrs.update(long_name="Threshold over modeled data") @@ -827,9 +825,7 @@ def _train( kind: str = ADDITIVE, ): ds = scaling_train( - xr.Dataset({"ref": ref, "hist": hist}), - group=group, - kind=kind, + xr.Dataset({"ref": ref, "hist": hist}), group=group, kind=kind ) ds.af.attrs.update(long_name="Scaling adjustment factors") return ds, {"group": group, "kind": kind} From 36ef886a5c50630208dbcc136bb0c007e0914d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Thu, 29 Jun 2023 17:18:16 -0400 Subject: [PATCH 12/24] Only used stacked ranking if len(dim)>1 --- xclim/sdba/_processing.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index 423574ad2..d5ebe6417 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -77,15 +77,17 @@ def _adapt_freq( # Probabilities and quantiles computed within all dims, but correction along the first one only. sim = ds.sim # Get the percentile rank of each value in sim. - temp_dim = get_temp_dimname(sim.dims, "temp") - rank = ( - sim.stack(**{temp_dim: dim}) - .rank(temp_dim, pct=True) - .unstack(temp_dim) - .transpose(*sim.dims) - .drop_vars([dim_ for dim_ in dim if dim_ not in sim.coords]) - ) - + if len(dim) > 1: + temp_dim = get_temp_dimname(sim.dims, "temp") + rank = ( + sim.stack(**{temp_dim: dim}) + .rank(temp_dim, pct=True) + .unstack(temp_dim) + .transpose(*sim.dims) + .drop_vars([dim_ for dim_ in dim if dim_ not in sim.coords]) + ) + else: + rank = sim.rank(dim[0], pct=True) # Frequency-adapted sim sim_ad = sim.where( dP0 < 0, # dP0 < 0 means no-adaptation. From 2d118cdbd160b4128d6bdc8b91de06f293a5281e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Wed, 2 Aug 2023 11:33:00 -0400 Subject: [PATCH 13/24] Example explaining use of adapt_freq_thresh --- docs/notebooks/sdba.ipynb | 71 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/sdba.ipynb b/docs/notebooks/sdba.ipynb index effc36d15..7a2ce1a53 100644 --- a/docs/notebooks/sdba.ipynb +++ b/docs/notebooks/sdba.ipynb @@ -293,6 +293,68 @@ "plt.legend()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Corner-case: Using a rolling window\n", + "In cases such as `group=time.dayofyear`, `window=31`, the previous procedure can still have issues. Assuming a no-leap calendar, the first step would combine precipitations 365 overlapping blocks of 31 days * Y years, one block for each day of the year. Each block is adapted, then the 16th day-of-year slice (at the center of the block) is assigned to the corresponding day-of-year in the adapted dataset `sim_ad`. As we proceed to the training, we re-form 31 days * Y years blocks, but this step does not invert the last one: There can still be more zeroes in the simulation than in the reference. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 3.0 with adapt_freq outside training\n", + "group = sdba.Grouper(\"time.dayofyear\", window=31)\n", + "sim_ad, pth, dP0 = sdba.processing.adapt_freq(\n", + " pr_ref, pr_sim, thresh=\"0.05 mm d-1\", group=group\n", + ")\n", + "QM_ad = sdba.EmpiricalQuantileMapping.train(\n", + " pr_ref, sim_ad, nquantiles=15, kind=\"*\", group=group\n", + ")\n", + "scen_ad = QM_ad.adjust(pr_sim)\n", + "\n", + "pr_ref.sel(time=\"2010\").plot(alpha=0.9, label=\"Reference\")\n", + "pr_sim.sel(time=\"2010\").plot(alpha=0.7, label=\"Model - biased\")\n", + "scen_ad.sel(time=\"2010\").plot(alpha=0.6, label=\"Model - adjusted\")\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To alleviate this issue, another way of proceeding is to perform a frequency adaptation on the blocks, and then use the same blocks in the training step. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 3.1 try with adapt_freq during training\n", + "group = sdba.Grouper(\"time.dayofyear\", window=31)\n", + "\n", + "QM_ad = sdba.EmpiricalQuantileMapping.train(\n", + " pr_ref,\n", + " sim_ad,\n", + " nquantiles=15,\n", + " kind=\"*\",\n", + " group=group,\n", + " adapt_freq_thresh=\"0.05 mm d-1\",\n", + ")\n", + "scen_ad = QM_ad.adjust(pr_sim)\n", + "\n", + "pr_ref.sel(time=\"2010\").plot(alpha=0.9, label=\"Reference\")\n", + "pr_sim.sel(time=\"2010\").plot(alpha=0.7, label=\"Model - biased\")\n", + "scen_ad.sel(time=\"2010\").plot(alpha=0.6, label=\"Model - adjusted\")\n", + "plt.legend()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -675,7 +737,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -689,7 +751,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" }, "toc": { "base_numbering": 1, @@ -703,6 +765,11 @@ "toc_position": {}, "toc_section_display": true, "toc_window_display": false + }, + "vscode": { + "interpreter": { + "hash": "7bfb2ff12c8d2ac85e75a42433bff767a429e1b9def8e10da354335359f03dc7" + } } }, "nbformat": 4, From 341e53d87e62aa00aaf100d71904963690129589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Wed, 2 Aug 2023 13:00:41 -0400 Subject: [PATCH 14/24] move rolling_window example the advanced notebook --- docs/notebooks/sdba-advanced.ipynb | 123 ++++++++++++++++++++++++++++- docs/notebooks/sdba.ipynb | 62 --------------- 2 files changed, 120 insertions(+), 65 deletions(-) diff --git a/docs/notebooks/sdba-advanced.ipynb b/docs/notebooks/sdba-advanced.ipynb index 97e2b9bfe..7d6d79ce0 100644 --- a/docs/notebooks/sdba-advanced.ipynb +++ b/docs/notebooks/sdba-advanced.ipynb @@ -692,6 +692,118 @@ "plt.legend()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Frequency adaption with a rolling window" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous example, we performed bias adjustment with a rolling window. Here we show how to include frequency adaptation (see `sdba.ipynb` for the simple case `group=\"time\"`). We first generate the same precipitation dataset used in `sdba.ipynb`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import xarray as xr\n", + "\n", + "t = xr.cftime_range(\"2000-01-01\", \"2030-12-31\", freq=\"D\", calendar=\"noleap\")\n", + "\n", + "vals = np.random.randint(0, 1000, size=(t.size,)) / 100\n", + "vals_ref = (4 ** np.where(vals < 9, vals / 100, vals)) / 3e6\n", + "vals_sim = (\n", + " (1 + 0.1 * np.random.random_sample((t.size,)))\n", + " * (4 ** np.where(vals < 9.5, vals / 100, vals))\n", + " / 3e6\n", + ")\n", + "\n", + "pr_ref = xr.DataArray(\n", + " vals_ref, coords={\"time\": t}, dims=(\"time\",), attrs={\"units\": \"mm/day\"}\n", + ")\n", + "pr_ref = pr_ref.sel(time=slice(\"2000\", \"2015\"))\n", + "pr_sim = xr.DataArray(\n", + " vals_sim, coords={\"time\": t}, dims=(\"time\",), attrs={\"units\": \"mm/day\"}\n", + ")\n", + "pr_hist = pr_sim.sel(time=slice(\"2000\", \"2015\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Bias adjustment on a rolling window can be performed in the same way as shown in `sdba.ipynb`, but instead of being a single string precising the time grouping (e.g. `time.month`), the `group` argument is built with `sdba.Grouper` function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# adapt_freq with a sdba.Grouper\n", + "from xclim import sdba\n", + "\n", + "group = sdba.Grouper(\"time.dayofyear\", window=31)\n", + "sim_ad, pth, dP0 = sdba.processing.adapt_freq(\n", + " pr_ref, pr_sim, thresh=\"0.05 mm d-1\", group=group\n", + ")\n", + "QM_ad = sdba.EmpiricalQuantileMapping.train(\n", + " pr_ref, sim_ad, nquantiles=15, kind=\"*\", group=group\n", + ")\n", + "scen_ad = QM_ad.adjust(pr_sim)\n", + "\n", + "pr_ref.sel(time=\"2010\").plot(alpha=0.9, label=\"Reference\")\n", + "pr_sim.sel(time=\"2010\").plot(alpha=0.7, label=\"Model - biased\")\n", + "scen_ad.sel(time=\"2010\").plot(alpha=0.6, label=\"Model - adjusted\")\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the figure above, `scen` occasionally has small peaks where `sim` is 0, indicating that there are more \"dry days\" (days with almost no precipitation) in `hist` than in `ref`. The frequency-adaptation [Themeßl et al. (2010)](https://doi.org/10.1007/s10584-011-0224-4) performed in the step above only worked partially. \n", + "\n", + "The reason for this is the following. The first step above combines precipitations in 365 overlapping blocks of 31 days * Y years, one block for each day of the year. Each block is adapted, and the 16th day-of-year slice (at the center of the block) is assigned to the corresponding day-of-year in the adapted dataset `sim_ad`. As we proceed to the training, we re-form those 31 days * Y years blocks, but this step does not invert the last one: There can still be more zeroes in the simulation than in the reference. \n", + "\n", + "To alleviate this issue, another way of proceeding is to perform a frequency adaptation on the blocks, and then use the same blocks in the training step, as we show below.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# adapt_freq directly in the training step\n", + "group = sdba.Grouper(\"time.dayofyear\", window=31)\n", + "\n", + "QM_ad = sdba.EmpiricalQuantileMapping.train(\n", + " pr_ref,\n", + " sim_ad,\n", + " nquantiles=15,\n", + " kind=\"*\",\n", + " group=group,\n", + " adapt_freq_thresh=\"0.05 mm d-1\",\n", + ")\n", + "scen_ad = QM_ad.adjust(pr_sim)\n", + "\n", + "pr_ref.sel(time=\"2010\").plot(alpha=0.9, label=\"Reference\")\n", + "pr_sim.sel(time=\"2010\").plot(alpha=0.7, label=\"Model - biased\")\n", + "scen_ad.sel(time=\"2010\").plot(alpha=0.6, label=\"Model - adjusted\")\n", + "plt.legend()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -829,13 +941,13 @@ "fig.suptitle(\n", " \"Bias of the mean of the warm spell length distribution compared to observations\"\n", ")\n", - "plt.tight_layout()\n" + "plt.tight_layout()" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -849,7 +961,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.11.4" }, "toc": { "base_numbering": 1, @@ -863,6 +975,11 @@ "toc_position": {}, "toc_section_display": true, "toc_window_display": false + }, + "vscode": { + "interpreter": { + "hash": "7bfb2ff12c8d2ac85e75a42433bff767a429e1b9def8e10da354335359f03dc7" + } } }, "nbformat": 4, diff --git a/docs/notebooks/sdba.ipynb b/docs/notebooks/sdba.ipynb index 7a2ce1a53..308a2b681 100644 --- a/docs/notebooks/sdba.ipynb +++ b/docs/notebooks/sdba.ipynb @@ -293,68 +293,6 @@ "plt.legend()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Corner-case: Using a rolling window\n", - "In cases such as `group=time.dayofyear`, `window=31`, the previous procedure can still have issues. Assuming a no-leap calendar, the first step would combine precipitations 365 overlapping blocks of 31 days * Y years, one block for each day of the year. Each block is adapted, then the 16th day-of-year slice (at the center of the block) is assigned to the corresponding day-of-year in the adapted dataset `sim_ad`. As we proceed to the training, we re-form 31 days * Y years blocks, but this step does not invert the last one: There can still be more zeroes in the simulation than in the reference. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 3.0 with adapt_freq outside training\n", - "group = sdba.Grouper(\"time.dayofyear\", window=31)\n", - "sim_ad, pth, dP0 = sdba.processing.adapt_freq(\n", - " pr_ref, pr_sim, thresh=\"0.05 mm d-1\", group=group\n", - ")\n", - "QM_ad = sdba.EmpiricalQuantileMapping.train(\n", - " pr_ref, sim_ad, nquantiles=15, kind=\"*\", group=group\n", - ")\n", - "scen_ad = QM_ad.adjust(pr_sim)\n", - "\n", - "pr_ref.sel(time=\"2010\").plot(alpha=0.9, label=\"Reference\")\n", - "pr_sim.sel(time=\"2010\").plot(alpha=0.7, label=\"Model - biased\")\n", - "scen_ad.sel(time=\"2010\").plot(alpha=0.6, label=\"Model - adjusted\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To alleviate this issue, another way of proceeding is to perform a frequency adaptation on the blocks, and then use the same blocks in the training step. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 3.1 try with adapt_freq during training\n", - "group = sdba.Grouper(\"time.dayofyear\", window=31)\n", - "\n", - "QM_ad = sdba.EmpiricalQuantileMapping.train(\n", - " pr_ref,\n", - " sim_ad,\n", - " nquantiles=15,\n", - " kind=\"*\",\n", - " group=group,\n", - " adapt_freq_thresh=\"0.05 mm d-1\",\n", - ")\n", - "scen_ad = QM_ad.adjust(pr_sim)\n", - "\n", - "pr_ref.sel(time=\"2010\").plot(alpha=0.9, label=\"Reference\")\n", - "pr_sim.sel(time=\"2010\").plot(alpha=0.7, label=\"Model - biased\")\n", - "scen_ad.sel(time=\"2010\").plot(alpha=0.6, label=\"Model - adjusted\")\n", - "plt.legend()" - ] - }, { "cell_type": "markdown", "metadata": {}, From 75eee240e0d2a816a8dd63ca882a6aa6a4060a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Mon, 16 Oct 2023 14:44:08 -0400 Subject: [PATCH 15/24] Docstrings for `adapt_freq_thresh` --- xclim/sdba/_adjustment.py | 9 +++++++++ xclim/sdba/adjustment.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/xclim/sdba/_adjustment.py b/xclim/sdba/_adjustment.py index 63c86eca1..a28f9c03a 100644 --- a/xclim/sdba/_adjustment.py +++ b/xclim/sdba/_adjustment.py @@ -21,6 +21,7 @@ def _adapt_freq_hist(ds, adapt_freq_thresh): + """Adapt frequency of null values of `hist` in order to match `ref`.""" with units.context(infer_context(ds.ref.attrs.get("standard_name"))): thresh = convert_units_to(adapt_freq_thresh, ds.ref) dim = ["time"] + ["window"] * ("window" in ds.hist.dims) @@ -42,6 +43,10 @@ def dqm_train(ds, *, dim, kind, quantiles, adapt_freq_thresh) -> xr.Dataset: Dataset must contain the following variables: ref : training target hist : training data + + adapt_freq_thresh : str | None + Threshold for frequency adaptation. See :py:class:`xclim.sdba.processing.adapt_freq` for details. + Default is None, meaning that frequency adaptation is not performed. """ hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist @@ -69,6 +74,10 @@ def eqm_train(ds, *, dim, kind, quantiles, adapt_freq_thresh) -> xr.Dataset: Dataset variables: ref : training target hist : training data + + adapt_freq_thresh : str | None + Threshold for frequency adaptation. See :py:class:`xclim.sdba.processing.adapt_freq` for details. + Default is None, meaning that frequency adaptation is not performed. """ hist = _adapt_freq_hist(ds, adapt_freq_thresh) if adapt_freq_thresh else ds.hist ref_q = nbu.quantile(ds.ref, quantiles, dim) diff --git a/xclim/sdba/adjustment.py b/xclim/sdba/adjustment.py index 601c0598b..edf166b1d 100644 --- a/xclim/sdba/adjustment.py +++ b/xclim/sdba/adjustment.py @@ -328,6 +328,9 @@ class EmpiricalQuantileMapping(TrainAdjust): group : Union[str, Grouper] The grouping information. See :py:class:`xclim.sdba.base.Grouper` for details. Default is "time", meaning an single adjustment group along dimension "time". + adapt_freq_thresh : str | None + Threshold for frequency adaptation. See :py:class:`xclim.sdba.processing.adapt_freq` for details. + Default is None, meaning that frequency adaptation is not performed. Adjust step: @@ -419,6 +422,9 @@ class DetrendedQuantileMapping(TrainAdjust): group : Union[str, Grouper] The grouping information. See :py:class:`xclim.sdba.base.Grouper` for details. Default is "time", meaning a single adjustment group along dimension "time". + adapt_freq_thresh : str | None + Threshold for frequency adaptation. See :py:class:`xclim.sdba.processing.adapt_freq` for details. + Default is None, meaning that frequency adaptation is not performed. Adjust step: From ff9768662d0a7f6fe2c14725322361e9c0d3bbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Mon, 16 Oct 2023 16:12:09 -0400 Subject: [PATCH 16/24] rank now supports multi-dimensional ranking --- xclim/sdba/_processing.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index d5ebe6417..f0cce5e83 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -15,7 +15,7 @@ from . import nbutils as nbu from .base import Grouper, map_groups -from .utils import ADDITIVE, apply_correction, ecdf, invert +from .utils import ADDITIVE, apply_correction, ecdf, invert, rank @map_groups( @@ -77,17 +77,8 @@ def _adapt_freq( # Probabilities and quantiles computed within all dims, but correction along the first one only. sim = ds.sim # Get the percentile rank of each value in sim. - if len(dim) > 1: - temp_dim = get_temp_dimname(sim.dims, "temp") - rank = ( - sim.stack(**{temp_dim: dim}) - .rank(temp_dim, pct=True) - .unstack(temp_dim) - .transpose(*sim.dims) - .drop_vars([dim_ for dim_ in dim if dim_ not in sim.coords]) - ) - else: - rank = sim.rank(dim[0], pct=True) + rank(sim, dim=dim, pct=True) + # Frequency-adapted sim sim_ad = sim.where( dP0 < 0, # dP0 < 0 means no-adaptation. From 94cffa2ed300e8e1740a5177f572f8b889a382ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Mon, 16 Oct 2023 16:15:43 -0400 Subject: [PATCH 17/24] add the modifications of rank (duh) --- xclim/sdba/utils.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/xclim/sdba/utils.py b/xclim/sdba/utils.py index 9eba7ca4f..fbe42d7a0 100644 --- a/xclim/sdba/utils.py +++ b/xclim/sdba/utils.py @@ -14,6 +14,7 @@ from dask import array as dsk from scipy.interpolate import griddata, interp1d from scipy.stats import spearmanr +from xarray.core.utils import get_temp_dimname from xclim.core.calendar import _interpolate_doy_calendar # noqa from xclim.core.utils import ensure_chunk_size @@ -461,7 +462,9 @@ def interp_on_quantiles( ) -def rank(da: xr.DataArray, dim: str = "time", pct: bool = False) -> xr.DataArray: +def rank( + da: xr.DataArray, dim: str | list[str] = "time", pct: bool = False +) -> xr.DataArray: """Ranks data along a dimension. Replicates `xr.DataArray.rank` but as a function usable in a Grouper.apply(). Xarray's docstring is below: @@ -469,12 +472,14 @@ def rank(da: xr.DataArray, dim: str = "time", pct: bool = False) -> xr.DataArray Equal values are assigned a rank that is the average of the ranks that would have been otherwise assigned to all the values within that set. Ranks begin at 1, not 0. If pct, computes percentage ranks, ranging from 0 to 1. + A list of dimensions can be provided and the ranks are then computed separately for each dimension. + Parameters ---------- da: xr.DataArray Source array. - dim : str, hashable - Dimension over which to compute rank. + dim : str | list[str], hashable + Dimension(s) over which to compute rank. pct : bool, optional If True, compute percentage ranks, otherwise compute integer ranks. Percentage ranks range from 0 to 1, in opposition to xarray's implementation, @@ -493,11 +498,26 @@ def rank(da: xr.DataArray, dim: str = "time", pct: bool = False) -> xr.DataArray -------- xarray.DataArray.rank """ - rnk = da.rank(dim, pct=pct) + da_dims, da_coords = da.dims, da.coords + dims = dim if isinstance(dim, list) else [dim] + rnk_dim = dims[0] if len(dims) == 1 else get_temp_dimname(da_dims, "temp") + + # multi-dimensional ranking through stacking + if len(dims) > 1: + da = da.stack(**{rnk_dim: dims}) + rnk = da.rank(rnk_dim, pct=pct) + if pct: - mn = rnk.min(dim) - mx = rnk.max(dim) - return mx * (rnk - mn) / (mx - mn) + mn = rnk.min(rnk_dim) + mx = rnk.max(rnk_dim) + rnk = mx * (rnk - mn) / (mx - mn) + + if len(dims) > 1: + rnk = ( + rnk.unstack(rnk_dim) + .transpose(*da_dims) + .drop_vars([d for d in dims if d not in da_coords]) + ) return rnk From 5aa31da0ada4da1832d9ee17066d6dae1571c1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= <71575674+coxipi@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:19:44 -0400 Subject: [PATCH 18/24] Update CHANGES.rst remove merge artefacts --- CHANGES.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 817642b0e..66a3a7a22 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -47,7 +47,6 @@ Internal changes v0.45.0 (2023-09-05) -------------------- -======= Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (:user:`Zeitsperre`), Pascal Bourgault (:user:`aulemahal`), Juliette Lavoie (:user:`juliettelavoie`), Gabriel Rondeau-Genesse (:user:`RondeauG`), Marco Braun (:user:`vindelico`), Éric Dupuis (:user:`coxipi`). Announcements From 1c84d4af58a716f04c647a222f4281f71526a606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Tue, 17 Oct 2023 15:14:08 -0400 Subject: [PATCH 19/24] rank variable assignment forgotten --- tests/test_sdba/test_processing.py | 4 +++- xclim/sdba/_processing.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_sdba/test_processing.py b/tests/test_sdba/test_processing.py index a59e18313..a93a4eb42 100644 --- a/tests/test_sdba/test_processing.py +++ b/tests/test_sdba/test_processing.py @@ -117,7 +117,9 @@ def test_adapt_freq(use_dask, random): @pytest.mark.parametrize("use_dask", [True, False]) def test_adapt_freq_add_dims(use_dask, random): time = pd.date_range("1990-01-01", "2020-12-31", freq="D") - prvals = random.integers(0, 100, size=(time.size, 3)) + # prvals = random.integers(0, 100, size=(time.size, 3)) + + prvals = np.random.randint(0, 100 + 1, size=(time.size, 3)) pr = xr.DataArray( prvals, coords={"time": time, "lat": [0, 1, 2]}, diff --git a/xclim/sdba/_processing.py b/xclim/sdba/_processing.py index f0cce5e83..164129abe 100644 --- a/xclim/sdba/_processing.py +++ b/xclim/sdba/_processing.py @@ -77,13 +77,13 @@ def _adapt_freq( # Probabilities and quantiles computed within all dims, but correction along the first one only. sim = ds.sim # Get the percentile rank of each value in sim. - rank(sim, dim=dim, pct=True) + rnk = rank(sim, dim=dim, pct=True) # Frequency-adapted sim sim_ad = sim.where( dP0 < 0, # dP0 < 0 means no-adaptation. sim.where( - (rank < P0_ref) | (rank > P0_sim), # Preserve current values + (rnk < P0_ref) | (rnk > P0_sim), # Preserve current values # Generate random numbers ~ U[T0, Pth] (pth.broadcast_like(sim) - thresh) * np.random.random_sample(size=sim.shape) From 944b628b31da25c059f8789f486c789a35493abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Tue, 17 Oct 2023 15:56:49 -0400 Subject: [PATCH 20/24] revert changes in test --- tests/test_sdba/test_processing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_sdba/test_processing.py b/tests/test_sdba/test_processing.py index a93a4eb42..6338bb2a7 100644 --- a/tests/test_sdba/test_processing.py +++ b/tests/test_sdba/test_processing.py @@ -117,8 +117,6 @@ def test_adapt_freq(use_dask, random): @pytest.mark.parametrize("use_dask", [True, False]) def test_adapt_freq_add_dims(use_dask, random): time = pd.date_range("1990-01-01", "2020-12-31", freq="D") - # prvals = random.integers(0, 100, size=(time.size, 3)) - prvals = np.random.randint(0, 100 + 1, size=(time.size, 3)) pr = xr.DataArray( prvals, From 650d118b40f292bef81817888c2c70424563850a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Tue, 17 Oct 2023 16:21:09 -0400 Subject: [PATCH 21/24] clean notebook, strip 'metadata.kernelspec.display_name' in pre-commit --- .pre-commit-config.yaml | 1 + docs/notebooks/analogs.ipynb | 1 - docs/notebooks/cli.ipynb | 1 - docs/notebooks/customize.ipynb | 1 - docs/notebooks/ensembles-advanced.ipynb | 1 - docs/notebooks/ensembles.ipynb | 1 - docs/notebooks/example.ipynb | 6 ------ docs/notebooks/extendxclim.ipynb | 1 - docs/notebooks/frequency_analysis.ipynb | 1 - docs/notebooks/partitioning.ipynb | 1 - docs/notebooks/sdba-advanced.ipynb | 6 ------ docs/notebooks/sdba.ipynb | 6 ------ docs/notebooks/units.ipynb | 1 - docs/notebooks/usage.ipynb | 1 - docs/notebooks/xclim_training/Exercices.ipynb | 1 - .../xclim_training/XARRAY_calcul_moy_saisonniere.ipynb | 1 - docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb | 1 - .../xclim_training/XCLIM_calculate_index-Exemple.ipynb | 1 - docs/notebooks/xclim_training/finch.ipynb | 1 - docs/notebooks/xclim_training/intro_xarray.ipynb | 1 - docs/notebooks/xclim_training/readme.ipynb | 1 - 21 files changed, 1 insertion(+), 35 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f2de0f723..d1f2b5e3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,6 +61,7 @@ repos: hooks: - id: nbstripout files: '.ipynb' + args: [ "--extra-keys", "metadata.kernelspec.display_name" ] - repo: https://github.com/pycqa/pydocstyle rev: 6.3.0 hooks: diff --git a/docs/notebooks/analogs.ipynb b/docs/notebooks/analogs.ipynb index 4c292eea4..d4994023e 100644 --- a/docs/notebooks/analogs.ipynb +++ b/docs/notebooks/analogs.ipynb @@ -249,7 +249,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/cli.ipynb b/docs/notebooks/cli.ipynb index 9027f82e6..5170643fb 100644 --- a/docs/notebooks/cli.ipynb +++ b/docs/notebooks/cli.ipynb @@ -408,7 +408,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/customize.ipynb b/docs/notebooks/customize.ipynb index 506ae4fb0..bea83a216 100644 --- a/docs/notebooks/customize.ipynb +++ b/docs/notebooks/customize.ipynb @@ -207,7 +207,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/ensembles-advanced.ipynb b/docs/notebooks/ensembles-advanced.ipynb index e8a9c24de..ce760cf78 100644 --- a/docs/notebooks/ensembles-advanced.ipynb +++ b/docs/notebooks/ensembles-advanced.ipynb @@ -390,7 +390,6 @@ "metadata": { "celltoolbar": "Edit Metadata", "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/ensembles.ipynb b/docs/notebooks/ensembles.ipynb index 94ddfbc40..ed8c98b73 100644 --- a/docs/notebooks/ensembles.ipynb +++ b/docs/notebooks/ensembles.ipynb @@ -280,7 +280,6 @@ "metadata": { "celltoolbar": "Edit Metadata", "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/example.ipynb b/docs/notebooks/example.ipynb index bf3a075b1..f67b24dae 100644 --- a/docs/notebooks/example.ipynb +++ b/docs/notebooks/example.ipynb @@ -694,7 +694,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -709,11 +708,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.10" - }, - "vscode": { - "interpreter": { - "hash": "978613a455e3ebf412cb0984a5d2225f42b37aab91916c56e2a684ef83795a8e" - } } }, "nbformat": 4, diff --git a/docs/notebooks/extendxclim.ipynb b/docs/notebooks/extendxclim.ipynb index e162781a2..b8e1d32a1 100644 --- a/docs/notebooks/extendxclim.ipynb +++ b/docs/notebooks/extendxclim.ipynb @@ -605,7 +605,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/frequency_analysis.ipynb b/docs/notebooks/frequency_analysis.ipynb index 44a16a1bf..1af689daa 100644 --- a/docs/notebooks/frequency_analysis.ipynb +++ b/docs/notebooks/frequency_analysis.ipynb @@ -177,7 +177,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/partitioning.ipynb b/docs/notebooks/partitioning.ipynb index eeb7311a3..4146d93dc 100644 --- a/docs/notebooks/partitioning.ipynb +++ b/docs/notebooks/partitioning.ipynb @@ -229,7 +229,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/sdba-advanced.ipynb b/docs/notebooks/sdba-advanced.ipynb index 7d6d79ce0..76ce0b56a 100644 --- a/docs/notebooks/sdba-advanced.ipynb +++ b/docs/notebooks/sdba-advanced.ipynb @@ -947,7 +947,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -975,11 +974,6 @@ "toc_position": {}, "toc_section_display": true, "toc_window_display": false - }, - "vscode": { - "interpreter": { - "hash": "7bfb2ff12c8d2ac85e75a42433bff767a429e1b9def8e10da354335359f03dc7" - } } }, "nbformat": 4, diff --git a/docs/notebooks/sdba.ipynb b/docs/notebooks/sdba.ipynb index 3f3982296..c6cb366dc 100644 --- a/docs/notebooks/sdba.ipynb +++ b/docs/notebooks/sdba.ipynb @@ -677,7 +677,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -705,11 +704,6 @@ "toc_position": {}, "toc_section_display": true, "toc_window_display": false - }, - "vscode": { - "interpreter": { - "hash": "7bfb2ff12c8d2ac85e75a42433bff767a429e1b9def8e10da354335359f03dc7" - } } }, "nbformat": 4, diff --git a/docs/notebooks/units.ipynb b/docs/notebooks/units.ipynb index 1273da58a..82badbb44 100644 --- a/docs/notebooks/units.ipynb +++ b/docs/notebooks/units.ipynb @@ -231,7 +231,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/usage.ipynb b/docs/notebooks/usage.ipynb index 0d703c29b..c129f7496 100644 --- a/docs/notebooks/usage.ipynb +++ b/docs/notebooks/usage.ipynb @@ -358,7 +358,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/Exercices.ipynb b/docs/notebooks/xclim_training/Exercices.ipynb index 3eb52a9bf..9ba0fd911 100644 --- a/docs/notebooks/xclim_training/Exercices.ipynb +++ b/docs/notebooks/xclim_training/Exercices.ipynb @@ -86,7 +86,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb b/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb index f52798420..0f0f05dfe 100644 --- a/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb +++ b/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb @@ -228,7 +228,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb b/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb index 4f4be7967..f2c741be6 100644 --- a/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb +++ b/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb @@ -469,7 +469,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb b/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb index 7b3caf952..93c190965 100644 --- a/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb +++ b/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb @@ -499,7 +499,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/finch.ipynb b/docs/notebooks/xclim_training/finch.ipynb index 91f1419c9..c80c22f53 100644 --- a/docs/notebooks/xclim_training/finch.ipynb +++ b/docs/notebooks/xclim_training/finch.ipynb @@ -146,7 +146,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/intro_xarray.ipynb b/docs/notebooks/xclim_training/intro_xarray.ipynb index e07713293..cabc4c753 100644 --- a/docs/notebooks/xclim_training/intro_xarray.ipynb +++ b/docs/notebooks/xclim_training/intro_xarray.ipynb @@ -424,7 +424,6 @@ "metadata": { "celltoolbar": "Slideshow", "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/docs/notebooks/xclim_training/readme.ipynb b/docs/notebooks/xclim_training/readme.ipynb index 9c38191f0..d454e22e2 100644 --- a/docs/notebooks/xclim_training/readme.ipynb +++ b/docs/notebooks/xclim_training/readme.ipynb @@ -100,7 +100,6 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", "language": "python", "name": "python3" }, From f932a9c24931064b82aa07d0816a80a57a7c2fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Tue, 17 Oct 2023 16:24:46 -0400 Subject: [PATCH 22/24] really revert change in test --- tests/test_sdba/test_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sdba/test_processing.py b/tests/test_sdba/test_processing.py index 6338bb2a7..a59e18313 100644 --- a/tests/test_sdba/test_processing.py +++ b/tests/test_sdba/test_processing.py @@ -117,7 +117,7 @@ def test_adapt_freq(use_dask, random): @pytest.mark.parametrize("use_dask", [True, False]) def test_adapt_freq_add_dims(use_dask, random): time = pd.date_range("1990-01-01", "2020-12-31", freq="D") - prvals = np.random.randint(0, 100 + 1, size=(time.size, 3)) + prvals = random.integers(0, 100, size=(time.size, 3)) pr = xr.DataArray( prvals, coords={"time": time, "lat": [0, 1, 2]}, From 13e270209c74d3a0c9f3b19e2a4a1571de0d0f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Tue, 17 Oct 2023 16:44:36 -0400 Subject: [PATCH 23/24] try removing all kernelspec --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d1f2b5e3d..f5d6ff905 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,7 +61,7 @@ repos: hooks: - id: nbstripout files: '.ipynb' - args: [ "--extra-keys", "metadata.kernelspec.display_name" ] + args: [ "--extra-keys", "metadata.kernelspec" ] - repo: https://github.com/pycqa/pydocstyle rev: 6.3.0 hooks: From 2b7debde6fd06dfddbc37969f940cf3b71c4c286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Dupuis?= Date: Wed, 18 Oct 2023 10:08:40 -0400 Subject: [PATCH 24/24] remove kernelspec in nb metadata --- docs/notebooks/analogs.ipynb | 4 ---- docs/notebooks/cli.ipynb | 4 ---- docs/notebooks/customize.ipynb | 4 ---- docs/notebooks/ensembles-advanced.ipynb | 4 ---- docs/notebooks/ensembles.ipynb | 4 ---- docs/notebooks/example.ipynb | 4 ---- docs/notebooks/extendxclim.ipynb | 4 ---- docs/notebooks/frequency_analysis.ipynb | 4 ---- docs/notebooks/partitioning.ipynb | 4 ---- docs/notebooks/sdba-advanced.ipynb | 4 ---- docs/notebooks/sdba.ipynb | 4 ---- docs/notebooks/units.ipynb | 4 ---- docs/notebooks/usage.ipynb | 4 ---- docs/notebooks/xclim_training/Exercices.ipynb | 4 ---- .../xclim_training/XARRAY_calcul_moy_saisonniere.ipynb | 4 ---- docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb | 4 ---- .../xclim_training/XCLIM_calculate_index-Exemple.ipynb | 4 ---- docs/notebooks/xclim_training/finch.ipynb | 4 ---- docs/notebooks/xclim_training/intro_xarray.ipynb | 4 ---- docs/notebooks/xclim_training/readme.ipynb | 4 ---- 20 files changed, 80 deletions(-) diff --git a/docs/notebooks/analogs.ipynb b/docs/notebooks/analogs.ipynb index d4994023e..1cc7aebaf 100644 --- a/docs/notebooks/analogs.ipynb +++ b/docs/notebooks/analogs.ipynb @@ -248,10 +248,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/cli.ipynb b/docs/notebooks/cli.ipynb index 5170643fb..aac26b04e 100644 --- a/docs/notebooks/cli.ipynb +++ b/docs/notebooks/cli.ipynb @@ -407,10 +407,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/customize.ipynb b/docs/notebooks/customize.ipynb index bea83a216..a5f8a9b34 100644 --- a/docs/notebooks/customize.ipynb +++ b/docs/notebooks/customize.ipynb @@ -206,10 +206,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/ensembles-advanced.ipynb b/docs/notebooks/ensembles-advanced.ipynb index ce760cf78..572da5910 100644 --- a/docs/notebooks/ensembles-advanced.ipynb +++ b/docs/notebooks/ensembles-advanced.ipynb @@ -389,10 +389,6 @@ ], "metadata": { "celltoolbar": "Edit Metadata", - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/ensembles.ipynb b/docs/notebooks/ensembles.ipynb index ed8c98b73..0a48b600c 100644 --- a/docs/notebooks/ensembles.ipynb +++ b/docs/notebooks/ensembles.ipynb @@ -279,10 +279,6 @@ ], "metadata": { "celltoolbar": "Edit Metadata", - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/example.ipynb b/docs/notebooks/example.ipynb index f67b24dae..26d8f0b9b 100644 --- a/docs/notebooks/example.ipynb +++ b/docs/notebooks/example.ipynb @@ -693,10 +693,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/extendxclim.ipynb b/docs/notebooks/extendxclim.ipynb index b8e1d32a1..d4e9a4c04 100644 --- a/docs/notebooks/extendxclim.ipynb +++ b/docs/notebooks/extendxclim.ipynb @@ -604,10 +604,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/frequency_analysis.ipynb b/docs/notebooks/frequency_analysis.ipynb index 1af689daa..0e4c0d086 100644 --- a/docs/notebooks/frequency_analysis.ipynb +++ b/docs/notebooks/frequency_analysis.ipynb @@ -176,10 +176,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/partitioning.ipynb b/docs/notebooks/partitioning.ipynb index 4146d93dc..76ccf61f6 100644 --- a/docs/notebooks/partitioning.ipynb +++ b/docs/notebooks/partitioning.ipynb @@ -228,10 +228,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/sdba-advanced.ipynb b/docs/notebooks/sdba-advanced.ipynb index 76ce0b56a..0354a0eb7 100644 --- a/docs/notebooks/sdba-advanced.ipynb +++ b/docs/notebooks/sdba-advanced.ipynb @@ -946,10 +946,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/sdba.ipynb b/docs/notebooks/sdba.ipynb index c6cb366dc..7f0e75f90 100644 --- a/docs/notebooks/sdba.ipynb +++ b/docs/notebooks/sdba.ipynb @@ -676,10 +676,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/units.ipynb b/docs/notebooks/units.ipynb index 82badbb44..5ff1dbe6b 100644 --- a/docs/notebooks/units.ipynb +++ b/docs/notebooks/units.ipynb @@ -230,10 +230,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/usage.ipynb b/docs/notebooks/usage.ipynb index c129f7496..69a01c629 100644 --- a/docs/notebooks/usage.ipynb +++ b/docs/notebooks/usage.ipynb @@ -357,10 +357,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/Exercices.ipynb b/docs/notebooks/xclim_training/Exercices.ipynb index 9ba0fd911..abdd32d61 100644 --- a/docs/notebooks/xclim_training/Exercices.ipynb +++ b/docs/notebooks/xclim_training/Exercices.ipynb @@ -85,10 +85,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb b/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb index 0f0f05dfe..70c21e78b 100644 --- a/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb +++ b/docs/notebooks/xclim_training/XARRAY_calcul_moy_saisonniere.ipynb @@ -227,10 +227,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb b/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb index f2c741be6..e8d1bfd98 100644 --- a/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb +++ b/docs/notebooks/xclim_training/XCLIM Demo - Ensembles.ipynb @@ -468,10 +468,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb b/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb index 93c190965..a5e055d0b 100644 --- a/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb +++ b/docs/notebooks/xclim_training/XCLIM_calculate_index-Exemple.ipynb @@ -498,10 +498,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/finch.ipynb b/docs/notebooks/xclim_training/finch.ipynb index c80c22f53..3b59f3ff8 100644 --- a/docs/notebooks/xclim_training/finch.ipynb +++ b/docs/notebooks/xclim_training/finch.ipynb @@ -145,10 +145,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/intro_xarray.ipynb b/docs/notebooks/xclim_training/intro_xarray.ipynb index cabc4c753..d566d1d72 100644 --- a/docs/notebooks/xclim_training/intro_xarray.ipynb +++ b/docs/notebooks/xclim_training/intro_xarray.ipynb @@ -423,10 +423,6 @@ ], "metadata": { "celltoolbar": "Slideshow", - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/docs/notebooks/xclim_training/readme.ipynb b/docs/notebooks/xclim_training/readme.ipynb index d454e22e2..68b657244 100644 --- a/docs/notebooks/xclim_training/readme.ipynb +++ b/docs/notebooks/xclim_training/readme.ipynb @@ -99,10 +99,6 @@ } ], "metadata": { - "kernelspec": { - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython",