Source code for xclim.ensembles._filters
"""
Ensemble filters for data processing
====================================
"""
from __future__ import annotations
import numpy as np
import xarray as xr
[docs]
def _concat_hist(da: xr.DataArray, **hist) -> xr.DataArray:
r"""
Concatenate historical scenario with future scenarios along the time dimension.
Parameters
----------
da : xr.DataArray
Input data where the historical scenario is stored alongside other, future, scenarios.
**hist : dict
Mapping of the scenario dimension name to the historical scenario coordinate, e.g. `scenario="historical"`.
Returns
-------
xr.DataArray
Data with the historical scenario is stacked in time before each one of the other scenarios.
Notes
-----
Data goes from:
+------------+----------------------------------+
| scenario | time |
+============+==================================+
| historical | ``hhhhhhhhhhhhhhhh------------`` |
+------------+----------------------------------+
| ssp245 | ``----------------111111111111`` |
+------------+----------------------------------+
| ssp370 | ``----------------222222222222`` |
+------------+----------------------------------+
to:
+----------+----------------------------------+
| scenario | time |
+==========+==================================+
| ssp245 | ``hhhhhhhhhhhhhhhh111111111111`` |
+----------+----------------------------------+
| ssp370 | ``hhhhhhhhhhhhhhhh222222222222`` |
+----------+----------------------------------+
"""
if len(hist) > 1:
raise ValueError("Too many values in hist scenario.")
# Scenario dimension, and name of the historical scenario
((dim, _),) = hist.items() # pylint: disable=unbalanced-dict-unpacking
# Select historical scenario and drop it from the data
h = da.sel(drop=True, **hist).dropna("time", how="all")
ens = da.drop_sel(**hist)
index = ens[dim]
bare = ens.drop_vars(dim).dropna("time", how="all")
return xr.concat([h, bare], dim="time").assign_coords({dim: index})
[docs]
def _model_in_all_scens(da: xr.DataArray, dimensions: dict | None = None) -> xr.DataArray:
"""
Return data with only simulations that have at least one member in each scenario.
Parameters
----------
da : xr.DataArray
Input data with dimensions for time, member, model and scenario.
dimensions : dict, optional
Mapping from original dimension names to standard dimension names: scenario, model, member.
Returns
-------
xr.DataArray
Data for models that have values for all scenarios.
Notes
-----
In the following example, model `C` would be filtered out from the data because it has no member for `ssp370`.
+-------+--------+--------+
| model | members |
+-------+-----------------+
| | ssp245 | ssp370 |
+=======+========+========+
| A | 1,2,3 | 1,2,3 |
+-------+--------+--------+
| B | 1 | 2,3 |
+-------+--------+--------+
| C | 1,2,3 | |
+-------+--------+--------+
"""
if dimensions is None:
dimensions = {}
da = da.rename(reverse_dict(dimensions))
ok = da.notnull().any("time").any("member").all("scenario")
return da.sel(model=ok).rename(dimensions)
[docs]
def _single_member(da: xr.DataArray, dimensions: dict | None = None) -> xr.DataArray:
"""
Return data for a single member per model.
Parameters
----------
da : xr.DataArray
Input data with dimensions for time, member, model and scenario.
dimensions : dict
Mapping from original dimension names to standard dimension names: scenario, model, member.
Returns
-------
xr.DataArray
Data with only one member per model.
Notes
-----
In the following example, the original members would be filtered to return only the first member found for each
scenario.
+-------+--------+--------+----+--------+--------+
| model | member | | Selected |
+-------+-----------------+----+-----------------+
| | ssp245 | ssp370 | | ssp245 | ssp370 |
+=======+========+========+====+========+========+
| A | 1,2,3 | 1,2,3 | | 1 | 1 |
+-------+--------+--------+----+--------+--------+
| B | 1,2 | 2,3 | | 1 | 2 |
+-------+--------+--------+----+--------+--------+
"""
if dimensions is None:
dimensions = {}
da = da.rename(reverse_dict(dimensions))
# Stack by simulation specifications - drop simulations with missing values
full = da.stack(i=("scenario", "model", "member")).dropna("i", how="any")
# Pick first run with data
s = full.i.to_series()
s[:] = np.arange(len(s))
i = s.unstack().T.min().to_list()
out = full.isel(i=i).unstack().squeeze()
return out.rename(dimensions)
[docs]
def reverse_dict(d: dict) -> dict:
"""
Reverse dictionary.
Parameters
----------
d : dict
Dictionary to reverse.
Returns
-------
dict
Reversed dictionary.
"""
return {v: k for (k, v) in d.items()}