Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute_weights #252

Merged
merged 33 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ab6e956
Generate weights: added stationary and non-stationary weights in attr…
sarahclaude Aug 30, 2023
436c541
Merge branch 'main' into experiement_weights
sarahclaude Aug 30, 2023
48b13e9
Added pull request info to authors.rst, history.rst
sarahclaude Aug 31, 2023
7f379d1
Zenodo.json
sarahclaude Aug 31, 2023
be5b0c3
Merge branch 'main' into experiement_weights
Zeitsperre Sep 8, 2023
61762d5
Merge remote-tracking branch 'origin/main' into experiement_weights
RondeauG Sep 15, 2023
da10069
typo
RondeauG Sep 15, 2023
b08a0e4
Merge branch 'main' into experiement_weights
Zeitsperre Sep 19, 2023
e9d3ddc
Added PR#
sarahclaude Sep 19, 2023
b09cec2
added error and warnings
sarahclaude Sep 20, 2023
5ba6e8c
bug fix
sarahclaude Sep 20, 2023
6be2569
tests warnings and errors
sarahclaude Sep 21, 2023
d6ac4de
Merge remote-tracking branch 'origin/experiement_weights' into experi…
sarahclaude Sep 21, 2023
153053e
replace experiment_weights to balance_experiments + depreciation warning
sarahclaude Sep 25, 2023
f9a0c87
Merge branch 'main' into experiement_weights
sarahclaude Sep 26, 2023
1d266c9
Update tests/test_ensembles.py
sarahclaude Sep 27, 2023
e3d68ce
Update xscen/ensembles.py
sarahclaude Sep 27, 2023
7a3b07e
Update xscen/ensembles.py
sarahclaude Sep 27, 2023
476ee69
Update xscen/ensembles.py
sarahclaude Sep 27, 2023
e0b7e54
Update xscen/ensembles.py
sarahclaude Sep 27, 2023
489ca99
Update xscen/ensembles.py
sarahclaude Sep 27, 2023
066150b
Update xscen/ensembles.py
sarahclaude Sep 27, 2023
9ddec48
Update xscen/ensembles.py
sarahclaude Oct 2, 2023
d33bde6
Update xscen/ensembles.py
sarahclaude Oct 2, 2023
4da37e8
Update xscen/ensembles.py
sarahclaude Oct 2, 2023
eb4bef9
add description, remove standardize on horizon/time
sarahclaude Oct 10, 2023
21d7e0c
Merge branch 'main' into experiement_weights
sarahclaude Oct 10, 2023
dd7a3ee
add breaking changes
sarahclaude Oct 10, 2023
4e9892f
Update HISTORY.rst
sarahclaude Oct 11, 2023
cdd90e6
Update xscen/ensembles.py
sarahclaude Oct 11, 2023
228c4e0
Update xscen/ensembles.py
RondeauG Oct 11, 2023
89785ba
fixed typo
sarahclaude Oct 11, 2023
3567adf
Merge remote-tracking branch 'origin/experiement_weights' into experi…
sarahclaude Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .zenodo.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
"name": "Braun, Marco",
"affiliation": "Ouranos",
"orcid": "0000-0001-5061-3217"
},
{
"name": "Bourdeau-Goulet, Sarah-Claude",
"affiliation": "Ouranos",
"orcid": "0000-0002-6125-2557"
}
],
"keywords": [
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ Contributors
* Sarah Gammon <gammon.sarah@ouranos.ca> `@SarahG-579462 <https://github.com/SarahG-579462>`_
* Yannick Rousseau
* Marco Braun <Braun.Marco@ouranos.ca> `@vindelico <https://github.com/vindelico>`_
* Sarah-Claude Bourdeau-Goulet <bourdeau-goulet.sarah-claude@ouranos.ca> `@sarahclaude <https://github.com/sarahclaude>`_
5 changes: 3 additions & 2 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ History

v0.8.0 (unreleased)
-------------------
Contributors to this version: Gabriel Rondeau-Genesse (:user:`RondeauG`), Pascal Bourgault (:user:`aulemahal`), Juliette Lavoie (:user:`juliettelavoie`).
Contributors to this version: Gabriel Rondeau-Genesse (:user:`RondeauG`), Pascal Bourgault (:user:`aulemahal`), Juliette Lavoie (:user:`juliettelavoie`), Sarah-Claude Bourdeau-Goulet (:user:`sarahclaude`).

Announcements
^^^^^^^^^^^^^
Expand All @@ -18,10 +18,11 @@ New features and enhancements
* Added documentation for `require_all_on` in `search_data_catalogs`. (:pull:`263`).
* ``xs.save_to_table`` and ``xs.io.to_table`` to transform datasets and arrays to DataFrames, but with support for multi-columns, multi-sheets and localized table of content generation.
* Better ``xs.extract.resample`` : support for weighted resampling operations when starting with frequencies coarser than daily and missing timesteps/values handling. (:issue:`80`, :issue:`93`, :pull:`265`).
* New argument ``attribute_weights`` to ``generate_weights`` to allow for custom weights. (:pull:`252`).

Breaking changes
sarahclaude marked this conversation as resolved.
Show resolved Hide resolved
^^^^^^^^^^^^^^^^
* N/A
* ``experiment_weights`` argument in ``generate_weights`` was renamed to ``balance_experiments``. (:pull:`252`).

Bug fixes
^^^^^^^^^
Expand Down
305 changes: 299 additions & 6 deletions tests/test_ensembles.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,16 @@ def make_ensemble():
] = ds2
return {o: out[o] for o in sorted(out)}

@staticmethod
def make_ensemble_rcm(ens):
ens_rcm = ens.copy()
for k in ens.keys():
if "cat:driving_model" not in ens_rcm[k].attrs.keys():
ens_rcm.pop(k)
return ens_rcm

ens = make_ensemble.__func__()
ens_rcm = make_ensemble_rcm.__func__(ens)

@staticmethod
def make_answer(independence_level, exp_weights, skipna):
Expand Down Expand Up @@ -469,7 +478,7 @@ def test_generate_weights(self, independence_level, exp_weights, skipna):
out = xs.generate_weights(
self.ens,
independence_level=independence_level,
experiment_weights=exp_weights,
balance_experiments=exp_weights,
skipna=skipna,
)

Expand Down Expand Up @@ -504,12 +513,32 @@ def test_changing_horizon(self):
)

@pytest.mark.parametrize(
"standardize, skipna", [(True, True), (True, False), (False, True)]
"standardize, skipna, attribute_weights",
[
(True, True, None),
(True, False, None),
(False, True, None),
(
True,
True,
{"institution": {"CCCma": 2, "ECMWF": 0, "GFDL": 5}},
),
],
)
def test_standardize(self, standardize, skipna):
out = xs.generate_weights(self.ens, standardize=standardize, skipna=skipna)
def test_standardize(self, standardize, skipna, attribute_weights):
if attribute_weights:
datasets = self.ens_rcm
else:
datasets = self.ens
out = xs.generate_weights(
datasets,
standardize=standardize,
skipna=skipna,
attribute_weights=attribute_weights,
)
if standardize:
np.testing.assert_allclose(out.sum(), 1 if skipna else 4)

else:
np.testing.assert_allclose(out.sum(), 10)

Expand Down Expand Up @@ -581,7 +610,7 @@ def test_errors(self):
with pytest.raises(
ValueError, match="The 'cat:experiment' attribute is missing"
):
xs.generate_weights(ens2, experiment_weights=True)
xs.generate_weights(ens2, balance_experiments=True)
ens2 = deepcopy(self.ens)
ens2["CCCma-CanESM2-rcp45-r1i1p1-CanESM2"].attrs["cat:institution"] = None
with pytest.raises(
Expand All @@ -597,11 +626,275 @@ def test_errors(self):
UserWarning,
match="The 'cat:experiment' attribute is missing from all datasets",
):
xs.generate_weights(ens2, experiment_weights=False)
xs.generate_weights(ens2, balance_experiments=False)
ens2 = deepcopy(self.ens)
ens2["CCCma-CanESM2-rcp45-r1i1p1-CanESM2"].attrs["cat:member"] = None
with pytest.warns(
UserWarning,
match="The 'cat:member' attribute is inconsistent across datasets.",
):
xs.generate_weights(ens2)

@staticmethod
def answer_attribute_weight(can45, can85, clim, csi45, csi85, ec85, gfd45):
ans = np.concatenate(
(
np.array(
np.split(
np.array(
[
0.08333,
0.25,
0.25,
0.25,
0.25,
]
),
5,
)
)
* can45, # CanESM2 family, RCP4.5
np.array(
np.split(
np.array(
[
0.08333,
0.25,
0.25,
0.25,
0.25,
]
),
5,
)
)
* can85, # CanESM2 family, RCP8.5
np.array(np.split(np.repeat(np.array(0.08333), 10, 0), 10))
* clim, # ClimEx
np.array(np.split(np.repeat(0.2, 5, 0), 5))
* csi45, # CSIRO-Mk6, RCP4.5
np.array([1], ndmin=2) * csi85, # CSIRO2, RCP8.5
np.array(np.split(np.array([1, 1]), 2)) * ec85, # EC-EARTH RCMs, RCP8.5
np.array(np.split(np.array([0.5, 0.5]), 2))
* gfd45, # GFDL-ESM2G family, RCP4.5
np.array(np.split(np.array([1, 1]), 2))
* gfd45, # GFDL-ESM2M family, RCP4.5
)
)
return np.squeeze(ans)

@pytest.mark.parametrize(
"coefs, weights",
[
(
{
"can45": 2,
"can85": 2,
"clim": 2,
"csi45": 3,
"csi85": 3,
"ec85": 0,
"gfd45": 5,
},
{"institution": {"CCCma": 2, "CSIRO-QCCCE": 3, "ECMWF": 0, "GFDL": 5}},
),
(
{
"can45": 2,
"can85": 2,
"clim": 2,
"csi45": 1,
"csi85": 1,
"ec85": 1,
"gfd45": 1,
},
{"institution": {"CCCma": 2, "others": 1}},
),
(
{
"can45": 2,
"can85": 1,
"clim": 1,
"csi45": 2,
"csi85": 1,
"ec85": 1,
"gfd45": 2,
},
{"experiment": {"rcp45": 2, "rcp85": 1}},
),
(
{
"can45": [0, 0, 1, 1],
"can85": [0, 1, 0, 1],
"clim": [0, 1, 0, 1],
"csi45": [0, 0, 1, 1],
"csi85": [0, 1, 0, 1],
"ec85": [0, 1, 0, 1],
"gfd45": [0, 0, 1, 1],
},
{
"experiment": xr.DataArray(
data=np.array([[0, 0], [0, 1], [1, 0], [1, 1]]),
dims=["horizon", "experiment"],
coords=dict(
horizon=["1981-2010", "2041-2070", "+2C", "+4C"],
experiment=["rcp45", "rcp85"],
),
attrs=dict(
description="Experiment weight through horizons",
units="",
),
)
},
),
(
{
"can45": [0, 0, 1, 1],
"can85": [0, 1, 0, 1],
"clim": [0, 1, 0, 1],
"csi45": [0, 0, 1, 1],
"csi85": [0, 1, 0, 1],
"ec85": [0, 1, 0, 1],
"gfd45": [0, 0, 1, 1],
},
{
"experiment": xr.DataArray(
data=np.array([[0, 0], [0, 1], [1, 0], [1, 1]]),
dims=["horizon", "experiment"],
coords=dict(
horizon=["1981-2010", "2041-2070", "+2C", "+4C"],
experiment=["rcp45", "others"],
),
attrs=dict(
description="Experiment weight through horizons",
units="",
),
)
},
),
],
)
def test_attribute_weight(self, coefs, weights):
# only test for RCMs

# generate ans
ans = self.answer_attribute_weight(**coefs)
ind = [
index
for index, value in enumerate(list(self.ens.keys()))
if value in list(self.ens_rcm.keys())
]
ans_rcms = ans[ind]
if len(ans_rcms.shape) > 1:
ans_rcms = ans_rcms.transpose()
out = xs.generate_weights(self.ens_rcm, attribute_weights=weights)
np.testing.assert_array_almost_equal(out, ans_rcms, decimal=4)

def test_attribute_weight_error(self):
# Required attributes
with pytest.raises(
ValueError,
match="Attribute_weights should be dict or xr.DataArray.",
):
xs.generate_weights(
self.ens_rcm, attribute_weights={"experiment": [1, 2, 3]}
)
with pytest.raises(
ValueError,
match="The test attribute is missing from some simulations.",
):
xs.generate_weights(
self.ens_rcm, attribute_weights={"test": {"CCCma": 2, "others": 1}}
)
with pytest.raises(
ValueError,
match="The institution ECMWF or others are not in the attribute_weights dict.",
):
xs.generate_weights(
self.ens_rcm, attribute_weights={"institution": {"CCCma": 2, "GFDL": 1}}
)
with pytest.raises(
ValueError, match="experiment is not in the xr.DataArray coords."
):
xs.generate_weights(
self.ens_rcm,
attribute_weights={
"experiment": xr.DataArray(
data=np.array([[0, 0], [0, 1], [1, 0], [1, 1]]),
dims=["horizon", "test"],
coords=dict(
horizon=["1981-2010", "2041-2070", "+2C", "+4C"],
test=["rcp45", "rcp85"],
),
)
},
)
with pytest.raises(
ValueError,
match="The experiment DataArray has more than one coord dimension to apply weights",
):
xs.generate_weights(
self.ens_rcm,
attribute_weights={
"experiment": xr.DataArray(
data=np.array(
[
[[0, 0], [0, 1]],
[[0, 1], [2, 1]],
[[1, 0], [1, 2]],
[[1, 1], [2, 2]],
]
),
dims=["horizon", "experiment", "test"],
coords=dict(
horizon=["1981-2010", "2041-2070", "+2C", "+4C"],
experiment=["rcp45", "rcp85"],
test=[0, 1],
),
)
},
)
with pytest.raises(
ValueError,
match="The experiment rcp85 or others are not in the attribute_weights datarray coords.",
):
xs.generate_weights(
self.ens_rcm,
attribute_weights={
"experiment": xr.DataArray(
data=np.array([[0], [0], [1], [1]]),
dims=["horizon", "experiment"],
coords=dict(
horizon=["1981-2010", "2041-2070", "+2C", "+4C"],
experiment=["rcp45"],
),
)
},
)
# mismatch RCMs & GCMs
with pytest.raises(
NotImplementedError,
match="Management of RCM and GCM in same datasets dictionary not "
"yet implemented with attribute_weights.",
):
xs.generate_weights(
self.ens,
attribute_weights={"institution": {"CCCma": 2, "others": 1}},
)
# warning mismatch attribute_weights & independance_level
with pytest.warns(
UserWarning,
match="The institution weights do not match the model independance_level",
):
xs.generate_weights(
self.ens_rcm,
attribute_weights={"institution": {"CCCma": 2, "others": 1}},
)
with pytest.warns(
UserWarning,
match="Key experiment given in attribute_weights without argument balance_experiments=True",
):
xs.generate_weights(
self.ens_rcm,
attribute_weights={"experiment": {"rcp45": 2, "rcp85": 1}},
)
Loading
Loading