diff --git a/CHANGES.rst b/CHANGES.rst index f6ad34277..ab6faa2ca 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,7 +15,8 @@ Bug fixes * Fixed an bug in sdba's ``map_groups`` that prevented passing DataArrays with cftime coordinates if the ``sdba_encode_cf`` option was True. (:issue:`1673`, :pull:`1674`). * Fixed bug (:issue:`1678`, :pull:`1679`) in sdba where a loaded training dataset could not be used for adjustment * 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 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`). Internal changes ^^^^^^^^^^^^^^^^ diff --git a/tests/test_ensembles.py b/tests/test_ensembles.py index 67aac38aa..63d62e6d4 100644 --- a/tests/test_ensembles.py +++ b/tests/test_ensembles.py @@ -735,13 +735,14 @@ def test_robustness_fractions_weighted(robust_data): def test_robustness_fractions_delta(robust_data): - delta = xr.DataArray([-2, 1, -2, -1], dims=("realization",)) + delta = xr.DataArray([-2, 1, -2, -1, 0, 0], dims=("realization",)) fracs = ensembles.robustness_fractions(delta, test="threshold", abs_thresh=1.5) - np.testing.assert_array_equal(fracs.changed, [0.5]) + np.testing.assert_array_equal(fracs.changed, [2 / 6]) np.testing.assert_array_equal(fracs.changed_positive, [0.0]) - np.testing.assert_array_equal(fracs.positive, [0.25]) - np.testing.assert_array_equal(fracs.agree, [0.75]) + np.testing.assert_array_equal(fracs.positive, [1 / 6]) + np.testing.assert_array_equal(fracs.agree, [3 / 6]) + delta = xr.DataArray([-2, 1, -2, -1], dims=("realization",)) weights = xr.DataArray([4, 3, 2, 1], dims=("realization",)) fracs = ensembles.robustness_fractions( delta, test="threshold", abs_thresh=1.5, weights=weights diff --git a/xclim/ensembles/_robustness.py b/xclim/ensembles/_robustness.py index 00e9f1b1b..9c1603336 100644 --- a/xclim/ensembles/_robustness.py +++ b/xclim/ensembles/_robustness.py @@ -91,11 +91,15 @@ def robustness_fractions( # noqa: C901 The weighted fraction of valid members showing significant change. Passing `test=None` yields change_frac = 1 everywhere. Same type as `fut`. positive : - The weighted fraction of valid members showing positive change, no matter if it is significant or not. + The weighted fraction of valid members showing strictly positive change, no matter if it is significant or not. changed_positive : - The weighted fraction of valid members showing significant and positive change (]0, 1]). + The weighted fraction of valid members showing significant and positive change. + negative : + The weighted fraction of valid members showing strictly negative change, no matter if it is significant or not. + changed_negative : + The weighted fraction of valid members showing significant and negative change. agree : - The weighted fraction of valid members agreeing on the sign of change. It is the maximum between positive and 1 - positive. + The weighted fraction of valid members agreeing on the sign of change. It is the maximum between positive, negative and the rest. valid : The weighted fraction of valid members. A member is valid is there are no NaNs along the time axes of `fut` and `ref`. pvals : @@ -106,7 +110,7 @@ def robustness_fractions( # noqa: C901 The table below shows the coefficient needed to retrieve the number of members that have the indicated characteristics, by multiplying it by the total number of members (`fut.realization.size`) and by `valid_frac`, assuming uniform weights. - For compactness, we rename the outputs cf, cpf and pf. + For compactness, we rename the outputs cf, pf, cpf, nf and cnf. +-----------------+--------------------+------------------------+------------+ | | Significant change | Non-significant change | Any change | @@ -115,9 +119,11 @@ def robustness_fractions( # noqa: C901 +-----------------+--------------------+------------------------+------------+ | Positive change | cpf | pf - cpf | pf | +-----------------+--------------------+------------------------+------------+ - | Negative change | (cf - cpf) | 1 - pf - (cf -cpf) | 1 - pf | + | Negative change | cnf | nf - cnf | nf | +-----------------+--------------------+------------------------+------------+ + And members showing absolutely no change are ``1 - nf - pf``. + Available statistical tests are : {tests_doc} @@ -213,10 +219,16 @@ def robustness_fractions( # noqa: C901 n_valid = valid.weighted(w).sum(realization) change_frac = changed.where(valid).weighted(w).sum(realization) / n_valid pos_frac = (delta > 0).where(valid).weighted(w).sum(realization) / n_valid + neg_frac = (delta < 0).where(valid).weighted(w).sum(realization) / n_valid change_pos_frac = ((delta > 0) & changed).where(valid).weighted(w).sum( realization ) / n_valid - agree_frac = xr.concat((pos_frac, 1 - pos_frac), "sign").max("sign") + change_neg_frac = ((delta < 0) & changed).where(valid).weighted(w).sum( + realization + ) / n_valid + agree_frac = xr.concat((pos_frac, neg_frac, 1 - pos_frac - neg_frac), "sign").max( + "sign" + ) # Metadata kwargs_str = gen_call_string("", **test_params)[1:-1] @@ -233,7 +245,7 @@ def robustness_fractions( # noqa: C901 test=str(test), ), "positive": pos_frac.assign_attrs( - description="Fraction of valid members showing positive change.", + description="Fraction of valid members showing strictly positive change.", units="", ), "changed_positive": change_pos_frac.assign_attrs( @@ -242,12 +254,25 @@ def robustness_fractions( # noqa: C901 units="", test=str(test), ), + "negative": neg_frac.assign_attrs( + description="Fraction of valid members showing strictly negative change.", + units="", + ), + "changed_negative": change_neg_frac.assign_attrs( + description="Fraction of valid members showing significant and negative change. " + + test_str, + units="", + test=str(test), + ), "valid": valid_frac.assign_attrs( description="Fraction of valid members (No missing values along time).", units="", ), "agree": agree_frac.assign_attrs( - description="Fraction of valid members agreeing on the sign of change. Maximum between pos_frac and 1 - pos_frac.", + description=( + "Fraction of valid members agreeing on the sign of change. " + "Maximum between the positive, negative and no change fractions." + ), units="", ), },