@@ -58,6 +58,7 @@ class CMethods:
58
58
59
59
Distribution-based techniques:
60
60
* Quantile Mapping :func:`cmethods.CMethods.quantile_mapping`
61
+ * Detrended Quantile Mapping :func:`cmethods.CMethods.detrended_quantile_mapping`
61
62
* Quantile Delta Mapping :func:`cmethods.CMethods.quantile_delta_mapping`
62
63
63
64
Except for the Variance Scaling all methods can be applied on both, stochastic and non-stochastic
@@ -73,7 +74,11 @@ class CMethods:
73
74
"""
74
75
75
76
SCALING_METHODS = ["linear_scaling" , "variance_scaling" , "delta_method" ]
76
- DISTRIBUTION_METHODS = ["quantile_mapping" , "quantile_delta_mapping" ]
77
+ DISTRIBUTION_METHODS = [
78
+ "quantile_mapping" ,
79
+ "detrended_quantile_mapping" ,
80
+ "quantile_delta_mapping" ,
81
+ ]
77
82
78
83
CUSTOM_METHODS = SCALING_METHODS + DISTRIBUTION_METHODS
79
84
METHODS = CUSTOM_METHODS
@@ -127,6 +132,8 @@ def get_function(cls, method: str):
127
132
return cls .delta_method
128
133
if method == "quantile_mapping" :
129
134
return cls .quantile_mapping
135
+ if method == "detrended_quantile_mapping" :
136
+ return cls .detrended_quantile_mapping
130
137
if method == "empirical_quantile_mapping" :
131
138
return cls .empirical_quantile_mapping
132
139
if method == "quantile_delta_mapping" :
@@ -717,7 +724,6 @@ def quantile_mapping(
717
724
simp : xr .core .dataarray .DataArray ,
718
725
n_quantiles : int ,
719
726
kind : str = "+" ,
720
- detrended : bool = False ,
721
727
** kwargs ,
722
728
) -> np .array :
723
729
r"""
@@ -735,14 +741,10 @@ def quantile_mapping(
735
741
Precipitation by Quantile Mapping: How Well Do Methods Preserve Changes in Quantiles
736
742
and Extremes?"* (https://doi.org/10.1175/JCLI-D-14-00754.1).
737
743
738
- Since the regular Quantile Mapping is bounded to the value range of the modeled data of
739
- the control period, the *Detrended* Quantile Mapping approach can be used by setting the
740
- ``detrended`` argument to ``True``. Detrending means, that the values of :math:`X_{sim,p}`
741
- are shifted to the value range of :math:`X_{sim,h}` before the Quantile Mapping is applied.
742
- After the Quantile Mapping was applied, the mean is shifted back. Since it does not make sens
743
- to take the whole mean to rescale the data, the month-dependent long-term mean is used.
744
+ The regular Quantile Mapping is bounded to the value range of the modeled data
745
+ of the control period. To avoid this, the Detrended Quantile Mapping can be used.
744
746
745
- In the following the equations of Alex J. Cannon (2015) are shown and explained (without detrending) :
747
+ In the following the equations of Alex J. Cannon (2015) are shown and explained:
746
748
747
749
**Additive**:
748
750
@@ -795,10 +797,7 @@ def quantile_mapping(
795
797
:param kind: The kind of the correction, additive for non-stochastic and multiplicative
796
798
for stochastic climate variables, defaults to ``+``
797
799
:type kind: str, optional
798
- :param detrended: If the extremes should be respected by applying month-dependent
799
- detrending before and after applying the Quantile Mapping
800
- :type detrended: bool, optional
801
- :param val_min: Lower boundary for interpolation (only if ``kind="*"``, default: ``0``)
800
+ :param val_min: Lower boundary for interpolation (only if ``kind="*"``, default: ``0.0``)
802
801
:type val_min: float, optional
803
802
:param val_max: Upper boundary for interpolation (only if ``kind="*"``, default: ``None``)
804
803
:type val_max: float, optional
@@ -827,7 +826,7 @@ def quantile_mapping(
827
826
... n_quantiles=250
828
827
... )
829
828
"""
830
- obs , simh , simp_ = np .array (obs ), np .array (simh ), np .array (simp )
829
+ obs , simh , simp = np .array (obs ), np .array (simh ), np .array (simp )
831
830
832
831
global_max = max (np .amax (obs ), np .amax (simh ))
833
832
global_min = min (np .amin (obs ), np .amin (simh ))
@@ -837,53 +836,13 @@ def quantile_mapping(
837
836
cdf_obs = cls .get_cdf (obs , xbins )
838
837
cdf_simh = cls .get_cdf (simh , xbins )
839
838
840
- if detrended :
841
- # detrended => shift mean of $X_{sim,p}$ to range of $X_{sim,h}$ to adjust extremes
842
- res = np .zeros (len (simp .values ))
843
- for _ , idxs in simp .groupby ("time.month" ).groups .items ():
844
- # detrended by long-term month
845
- m_simh , m_simp = [], []
846
- for idx in idxs :
847
- m_simh .append (simh [idx ])
848
- m_simp .append (simp [idx ])
849
-
850
- m_simh = np .array (m_simh )
851
- m_simp = np .array (m_simp )
852
- m_simh_mean = np .nanmean (m_simh )
853
- m_simp_mean = np .nanmean (m_simp )
854
-
855
- if kind in cls .ADDITIVE :
856
- epsilon = np .interp (
857
- m_simp - m_simp_mean + m_simh_mean , xbins , cdf_simh
858
- ) # Eq. 1
859
- X = (
860
- cls .get_inverse_of_cdf (cdf_obs , epsilon , xbins )
861
- + m_simp_mean
862
- - m_simh_mean
863
- ) # Eq. 1
864
-
865
- elif kind in cls .MULTIPLICATIVE :
866
- epsilon = np .interp ( # Eq. 2
867
- cls .ensure_devidable ((m_simh_mean * m_simp ), m_simp_mean ),
868
- xbins ,
869
- cdf_simh ,
870
- left = kwargs .get ("val_min" , 0.0 ),
871
- right = kwargs .get ("val_max" , None ),
872
- )
873
- X = np .interp (epsilon , cdf_obs , xbins ) * (
874
- cls .ensure_devidable (m_simp_mean , m_simh_mean )
875
- ) # Eq. 2
876
- for i , idx in enumerate (idxs ):
877
- res [idx ] = X [i ]
878
- return res
879
-
880
839
if kind in cls .ADDITIVE :
881
- epsilon = np .interp (simp_ , xbins , cdf_simh ) # Eq. 1
840
+ epsilon = np .interp (simp , xbins , cdf_simh ) # Eq. 1
882
841
return cls .get_inverse_of_cdf (cdf_obs , epsilon , xbins ) # Eq. 1
883
842
884
843
if kind in cls .MULTIPLICATIVE :
885
844
epsilon = np .interp ( # Eq. 2
886
- simp_ ,
845
+ simp ,
887
846
xbins ,
888
847
cdf_simh ,
889
848
left = kwargs .get ("val_min" , 0.0 ),
@@ -895,6 +854,145 @@ def quantile_mapping(
895
854
f"{ kind } for quantile_mapping is not available. Use '+' or '*' instead."
896
855
)
897
856
857
+ @classmethod
858
+ def detrended_quantile_mapping (
859
+ cls ,
860
+ obs : xr .core .dataarray .DataArray ,
861
+ simh : xr .core .dataarray .DataArray ,
862
+ simp : xr .core .dataarray .DataArray ,
863
+ n_quantiles : int ,
864
+ kind : str = "+" ,
865
+ ** kwargs ,
866
+ ) -> np .array :
867
+ r"""
868
+ The Detrended Quantile Mapping bias correction technique can be used to minimize distributional
869
+ biases between modeled and observed time-series climate data like the regular Quantile Mapping.
870
+ Detrending means, that the values of :math:`X_{sim,p}` are shifted to the value range of
871
+ :math:`X_{sim,h}` before the regular Quantile Mapping is applied.
872
+ After the Quantile Mapping was applied, the mean is shifted back. Since it does not make sens
873
+ to take the whole mean to rescale the data, the month-dependent long-term mean is used.
874
+
875
+ This method must be applied on a 1-dimensional data set i.e., there is only one
876
+ time-series passed for each of ``obs``, ``simh``, and ``simp``. Also this method requires
877
+ that the time series are groupable by ``time.month``.
878
+
879
+ The Detrended Quantile Mapping technique implemented here is based on the equations of
880
+ Alex J. Cannon and Stephen R. Sobie and Trevor Q. Murdock (2015) *"Bias Correction of GCM
881
+ Precipitation by Quantile Mapping: How Well Do Methods Preserve Changes in Quantiles
882
+ and Extremes?"* (https://doi.org/10.1175/JCLI-D-14-00754.1).
883
+
884
+ In the following the equations of Alex J. Cannon (2015) are shown and explained (without detrending):
885
+
886
+ **Additive**:
887
+
888
+ .. math::
889
+
890
+ X^{*QM}_{sim,p}(i) = F^{-1}_{obs,h} \left\{F_{sim,h}\left[X_{sim,p}(i)\right]\right\}
891
+
892
+
893
+ **Multiplicative**:
894
+
895
+ .. math::
896
+
897
+ X^{*QM}_{sim,p}(i) = F^{-1}_{obs,h}\Biggl\{F_{sim,h}\left[\frac{\mu{X_{sim,h}} \cdot \mu{X_{sim,p}(i)}}{\mu{X_{sim,p}(i)}}\right]\Biggr\}\frac{\mu{X_{sim,p}(i)}}{\mu{X_{sim,h}}}
898
+
899
+
900
+ :param obs: The reference data set of the control period
901
+ (in most cases the observational data)
902
+ :type obs: xr.core.dataarray.DataArray
903
+ :param simh: The modeled data of the control period
904
+ :type simh: xr.core.dataarray.DataArray
905
+ :param simp: The modeled data of the scenario period (this is the data set
906
+ on which the bias correction takes action)
907
+ :type simp: xr.core.dataarray.DataArray
908
+ :param n_quantiles: Number of quantiles to respect/use
909
+ :type n_quantiles: int
910
+ :param kind: The kind of the correction, additive for non-stochastic and multiplicative
911
+ for stochastic climate variables, defaults to ``+``
912
+ :type kind: str, optional
913
+ :param val_min: Lower boundary for interpolation (only if ``kind="*"``, default: ``0.0``)
914
+ :type val_min: float, optional
915
+ :param val_max: Upper boundary for interpolation (only if ``kind="*"``, default: ``None``)
916
+ :type val_max: float, optional
917
+ :raises NotImplementedError: If the kind is not in (``+``, ``*``, ``add``, ``mult``)
918
+ :return: The bias-corrected time series
919
+ :rtype: np.array
920
+
921
+ .. code-block:: python
922
+ :linenos:
923
+ :caption: Example: Quantile Mapping
924
+
925
+ >>> import xarray as xr
926
+ >>> from cmethods import CMethods as cm
927
+
928
+ >>> # Note: The data sets must contain the dimension "time"
929
+ >>> # for the respective variable.
930
+ >>> obsh = xr.open_dataset("path/to/reference_data-control_period.nc")
931
+ >>> simh = xr.open_dataset("path/to/modeled_data-control_period.nc")
932
+ >>> simp = xr.open_dataset("path/to/the_dataset_to_adjust-scenario_period.nc")
933
+ >>> variable = "tas" # temperatures
934
+
935
+ >>> qm_adjusted = cm.detrended_quantile_mapping(
936
+ ... obs=obs[variable],
937
+ ... simh=simh[variable],
938
+ ... simp=simp[variable],
939
+ ... n_quantiles=250
940
+ ... )
941
+ """
942
+ if kind not in cls .MULTIPLICATIVE and kind not in cls .ADDITIVE :
943
+ raise NotImplementedError (
944
+ f"{ kind } for detrended_quantile_mapping is not available. Use '+' or '*' instead."
945
+ )
946
+
947
+ obs , simh = np .array (obs ), np .array (simh )
948
+
949
+ global_max = max (np .amax (obs ), np .amax (simh ))
950
+ global_min = min (np .amin (obs ), np .amin (simh ))
951
+ wide = abs (global_max - global_min ) / n_quantiles
952
+ xbins = np .arange (global_min , global_max + wide , wide )
953
+
954
+ cdf_obs = cls .get_cdf (obs , xbins )
955
+ cdf_simh = cls .get_cdf (simh , xbins )
956
+
957
+ # detrended => shift mean of $X_{sim,p}$ to range of $X_{sim,h}$ to adjust extremes
958
+ res = np .zeros (len (simp .values ))
959
+ for _ , idxs in simp .groupby ("time.month" ).groups .items ():
960
+ # detrended by long-term month
961
+ m_simh , m_simp = [], []
962
+ for idx in idxs :
963
+ m_simh .append (simh [idx ])
964
+ m_simp .append (simp [idx ])
965
+
966
+ m_simh = np .array (m_simh )
967
+ m_simp = np .array (m_simp )
968
+ m_simh_mean = np .nanmean (m_simh )
969
+ m_simp_mean = np .nanmean (m_simp )
970
+
971
+ if kind in cls .ADDITIVE :
972
+ epsilon = np .interp (
973
+ m_simp - m_simp_mean + m_simh_mean , xbins , cdf_simh
974
+ ) # Eq. 1
975
+ X = (
976
+ cls .get_inverse_of_cdf (cdf_obs , epsilon , xbins )
977
+ + m_simp_mean
978
+ - m_simh_mean
979
+ ) # Eq. 1
980
+
981
+ elif kind in cls .MULTIPLICATIVE :
982
+ epsilon = np .interp ( # Eq. 2
983
+ cls .ensure_devidable ((m_simh_mean * m_simp ), m_simp_mean ),
984
+ xbins ,
985
+ cdf_simh ,
986
+ left = kwargs .get ("val_min" , 0.0 ),
987
+ right = kwargs .get ("val_max" , None ),
988
+ )
989
+ X = np .interp (epsilon , cdf_obs , xbins ) * cls .ensure_devidable (
990
+ m_simp_mean , m_simh_mean
991
+ ) # Eq. 2
992
+ for i , idx in enumerate (idxs ):
993
+ res [idx ] = X [i ]
994
+ return res
995
+
898
996
# ? -----========= E M P I R I C A L - Q U A N T I L E - M A P P I N G =========------
899
997
@classmethod
900
998
def empirical_quantile_mapping (
0 commit comments