Source code for xclim.testing.diagnostics
# pylint: disable=no-member,missing-kwoa
"""
SDBA Diagnostic Testing Module
==============================
This module is meant to compare results with those expected from papers, or create figures illustrating the
behavior of sdba methods and utilities.
"""
from __future__ import annotations
import warnings
import numpy as np
from scipy.stats import gaussian_kde, scoreatpercentile
from xclim.sdba.adjustment import (
DetrendedQuantileMapping,
EmpiricalQuantileMapping,
QuantileDeltaMapping,
)
from xclim.sdba.processing import adapt_freq
from xclim.testing.sdba_utils import cannon_2015_rvs, series
try:
from matplotlib import pyplot as plt
except ModuleNotFoundError:
warnings.warn("Matplotlib not found, plot-generating functions will not work.")
__all__ = ["adapt_freq_graph", "cannon_2015_figure_2", "synth_rainfall"]
[docs]
def synth_rainfall(shape, scale=1, wet_freq=0.25, size=1):
r"""Return gamma distributed rainfall values for wet days.
Notes
-----
The probability density for the Gamma distribution is:
.. math::
p(x) = x^{k-1}\frac{e^{-x/\theta}}{\theta^k\Gamma(k)},
where :math:`k` is the shape and :math:`\theta` the scale, and :math:`\Gamma` is the Gamma function.
"""
is_wet = np.random.binomial(1, p=wet_freq, size=size)
wet_intensity = np.random.gamma(shape, scale, size)
return np.where(is_wet, wet_intensity, 0)
[docs]
def cannon_2015_figure_2():
"""Create a graphic similar to figure 2 of Cannon et al. 2015."""
# noqa: D103
n = 10000
ref, hist, sim = cannon_2015_rvs(n, random=False)
QM = EmpiricalQuantileMapping(kind="*", group="time", interp="linear")
QM.train(ref, hist)
sim_eqm = QM.predict(sim)
DQM = DetrendedQuantileMapping(kind="*", group="time", interp="linear")
DQM.train(ref, hist)
sim_dqm = DQM.predict(sim, degree=0)
QDM = QuantileDeltaMapping(kind="*", group="time", interp="linear")
QDM.train(ref, hist)
sim_qdm = QDM.predict(sim)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
x = np.linspace(0, 105, 50)
ax1.plot(x, gaussian_kde(ref)(x), color="r", label="Obs hist")
ax1.plot(x, gaussian_kde(hist)(x), color="k", label="GCM hist")
ax1.plot(x, gaussian_kde(sim)(x), color="blue", label="GCM simure")
ax1.plot(x, gaussian_kde(sim_qdm)(x), color="lime", label="QDM future")
ax1.plot(x, gaussian_kde(sim_eqm)(x), color="darkgreen", ls="--", label="QM future")
ax1.plot(x, gaussian_kde(sim_dqm)(x), color="lime", ls=":", label="DQM future")
ax1.legend(frameon=False)
ax1.set_xlabel("Value")
ax1.set_ylabel("Density")
tau = np.array([0.25, 0.5, 0.75, 0.95, 0.99]) * 100
bc_gcm = (
scoreatpercentile(sim, tau) - scoreatpercentile(hist, tau)
) / scoreatpercentile(hist, tau)
bc_qdm = (
scoreatpercentile(sim_qdm, tau) - scoreatpercentile(ref, tau)
) / scoreatpercentile(ref, tau)
bc_eqm = (
scoreatpercentile(sim_eqm, tau) - scoreatpercentile(ref, tau)
) / scoreatpercentile(ref, tau)
bc_dqm = (
scoreatpercentile(sim_dqm, tau) - scoreatpercentile(ref, tau)
) / scoreatpercentile(ref, tau)
ax2.plot([0, 1], [0, 1], ls=":", color="blue")
ax2.plot(bc_gcm, bc_gcm, "-", color="blue", label="GCM")
ax2.plot(bc_gcm, bc_qdm, marker="o", mfc="lime", label="QDM")
ax2.plot(
bc_gcm,
bc_eqm,
marker="o",
mfc="darkgreen",
ls=":",
color="darkgreen",
label="QM",
)
ax2.plot(
bc_gcm,
bc_dqm,
marker="s",
mec="lime",
mfc="w",
ls="--",
color="lime",
label="DQM",
)
for i, s in enumerate(tau / 100):
ax2.text(bc_gcm[i], bc_eqm[i], f"{s} ", ha="right", va="center", fontsize=9)
ax2.set_xlabel("GCM relative change")
ax2.set_ylabel("Bias adjusted relative change")
ax2.legend(loc="upper left", frameon=False)
ax2.set_aspect("equal")
plt.tight_layout()
return fig
[docs]
def adapt_freq_graph():
"""Create a graphic with the additive adjustment factors estimated after applying the adapt_freq method."""
n = 10000
x = series(synth_rainfall(2, 2, wet_freq=0.25, size=n), "pr") # sim
y = series(synth_rainfall(2, 2, wet_freq=0.5, size=n), "pr") # ref
xp = adapt_freq(x, y, thresh=0).sim_ad # noqa
fig, (ax1, ax2) = plt.subplots(2, 1)
sx = x.sortby(x)
sy = y.sortby(y)
sxp = xp.sortby(xp)
# Original and corrected series
ax1.plot(sx.values, color="blue", lw=1.5, label="x : sim")
ax1.plot(sxp.values, color="pink", label="xp : sim corrected")
ax1.plot(sy.values, color="k", label="y : ref")
ax1.legend()
# Compute qm factors
qm_add = QuantileDeltaMapping(kind="+", group="time").train(y, x).ds
qm_mul = QuantileDeltaMapping(kind="*", group="time").train(y, x).ds
qm_add_p = QuantileDeltaMapping(kind="+", group="time").train(y, xp).ds
qm_mul_p = QuantileDeltaMapping(kind="*", group="time").train(y, xp).ds
qm_add.cf.plot(ax=ax2, color="cyan", ls="--", label="+: y-x")
qm_add_p.cf.plot(ax=ax2, color="cyan", label="+: y-xp")
qm_mul.cf.plot(ax=ax2, color="brown", ls="--", label="*: y/x")
qm_mul_p.cf.plot(ax=ax2, color="brown", label="*: y/xp")
ax2.legend(loc="upper left", frameon=False)
return fig