Skip to content

Commit

Permalink
No change is different from negative change (#1711)
Browse files Browse the repository at this point in the history
<!--Please ensure the PR fulfills the following requirements! -->
<!-- If this is your first PR, make sure to add your details to the
AUTHORS.rst! -->
### Pull Request Checklist:
- [x] This PR addresses an already opened issue (for bug fixes /
features)
    - This PR fixes #1690 
- [x] Tests for the changes have been added (for bug fixes / features)
- [x] (If applicable) Documentation has been added / updated (for bug
fixes / features)
- [x] CHANGES.rst has been updated (with summary of main changes)
- [x] Link to issue (:issue:`number`) and pull request (:pull:`number`)
has been added

### What kind of change does this PR introduce?
In `robustness_fractions`

* Computes the fraction of negative change explicitly and returns it 
* The agreement fraction is the maximum of the positive, negative or no
change fractions.

### Does this PR introduce a breaking change?
Yes, `agree_frac` has changed. However, it now better reflects its
definition and usual expectations. And the case where "no change" is the
largest group should not be very frequent, it usually happens with
zero-bounded indicators.


### Other information:
  • Loading branch information
aulemahal committed Apr 17, 2024
2 parents 2d8726b + 0bb653e commit 50705ba
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 13 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^
Expand Down
9 changes: 5 additions & 4 deletions tests/test_ensembles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 33 additions & 8 deletions xclim/ensembles/_robustness.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand All @@ -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 |
Expand All @@ -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}
Expand Down Expand Up @@ -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]
Expand All @@ -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(
Expand All @@ -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="",
),
},
Expand Down

0 comments on commit 50705ba

Please sign in to comment.