Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/valenspy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from valenspy.diagnostic import Diagnostic, Model2Self, Model2Ref, Ensemble2Ref, Ensemble2Self
from valenspy.diagnostic.visualizations import *
#Utility
from valenspy._utilities import is_cf_compliant, cf_status
from valenspy._utilities import is_cf_compliant, cf_status, datatree_to_dataset, datatree_to_dataframe, restructure_by_level, split_by_level

# =============================================================================
# Version
Expand Down
3 changes: 2 additions & 1 deletion src/valenspy/_utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
load_yml,
generate_parameters_doc
)
from._formatting import create_named_regex, parse_string_to_time_period
from ._formatting import create_named_regex, parse_string_to_time_period
from .cf_checks import is_cf_compliant, cf_status
from .unit_converter import CORDEX_VARIABLES, _convert_all_units_to_CF
from ._datatree import datatree_to_dataset, datatree_to_dataframe, restructure_by_level, split_by_level
98 changes: 98 additions & 0 deletions src/valenspy/_utilities/_datatree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import pandas as pd
import xarray as xr
from copy import deepcopy

def split_by_level(dt: xr.DataTree, level: int):
"""
Split a DataTree into multiple DataTrees based on the unique values at a given level in the node paths.

Parameters
----------
dt : xr.DataTree
The DataTree to split.
level : int
The level in the node paths to split the DataTree by. Level 0 is the root level.

Returns
-------
dict
A dictionary where the keys are the unique values at the specified level in the node paths, and the values are the corresponding DataTrees containing only the nodes with that value at the specified level.
"""
#Make a deep copy of the datatree to avoid modifying the original one. This is needed as we are going to orphan the datasets in the new datatrees, which would also orphan them in the original datatree if we don't make a copy.
dt = deepcopy(dt)
if level >= 2:
dt = restructure_by_level(dt, level)
result = {value: dt[value] for value in set(dt.children)}
for value in result:
result[value].orphan()
return result

def restructure_by_level(dt: xr.DataTree, level: int):
"""
Restructure a DataTree such that level n in the node paths becomes the new root level.

Parameters
----------
dt : xr.DataTree
The DataTree to restructure.
level : int
The level in the node paths to restructure the DataTree by. Level 0 is the root level.

Returns
-------
xr.DataTree
A restructured DataTree where level n in the node paths becomes the new root level.
"""
if level < 2:
raise ValueError("Level must be greater than or equal to 2 as reording the first level leaves the tree unchanged")
level = level - 1 #As the root level is not included in the path split, we need to subtract 1 from the level to get the correct index.
reorganized_nodes = {
"/".join([path.split("/")[level]] + path.split("/")[:level] + path.split("/")[level+1:]): node.dataset
for path, node in dt.subtree_with_keys
if len(path.split("/")) > level #Strict
}
return xr.DataTree.from_dict(reorganized_nodes)

def datatree_to_dataset(dt: xr.DataTree, **kwargs):
"""
Convert a DataTree to a xarray Dataset.

Parameters
----------
dt : xr.DataTree
The DataTree to convert to a xarray Dataset.
**kwargs : dict
Keyword arguments to pass to the xarray concat function.
"""
datasets = []
for key, ds in dt.to_dict().items():
if ds:
ds_copy = ds.copy()
ds_copy = ds_copy.expand_dims({"id": [str(key)]})
datasets.append(ds_copy)

return xr.concat(datasets, dim="id", **kwargs)

def datatree_to_dataframe(dt: xr.DataTree, add_attributes=False):
"""
Convert a DataTree to a pandas DataFrame.
"""
data_frames = []
for key, ds in dt.to_dict().items():
if ds:
if not ds.dims: # Non-dimensional datasets
df = pd.DataFrame({var: float(ds[var].values) for var in ds.data_vars}, index=[key])
else: # Dimensional datasets
df = ds.to_dataframe()
df["id"] = str(key)

if add_attributes:
for attr in add_attributes:
#if attr is a substring of any attribute in ds.attrs:
for ds_attr in ds.attrs:
if attr in ds_attr:
df[attr] = ds.attrs[ds_attr]
break
data_frames.append(df)

return pd.concat(data_frames, axis=0).reset_index()
21 changes: 20 additions & 1 deletion src/valenspy/diagnostic/_ensemble2ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,26 @@
from valenspy.diagnostic.functions import *
from valenspy.diagnostic.visualizations import *

__all__ = ["MetricsRankings"]
__all__ = [
"ClimateChangeSignal",
"ClimateChangeSignalOfSpatialMean",
"MetricsRankings",
]

ClimateChangeSignalOfSpatialMean = Ensemble2Ref(
climate_change_signal_of_spatial_mean,
plot_map,
"Climate Change Signal of the spatial means",
"The spatial climate change signal as the difference between the temporal average of two periods",
plot_type="facetted"
)

ClimateChangeSignal = Ensemble2Ref(
mean_climate_change_signal,
lambda ds : ds,
"Climate Change Signal",
"The climate change signal as the difference between the spatial and temporal average of two periods."
)

MetricsRankings = Ensemble2Ref(
calc_metrics_dt,
Expand Down
14 changes: 14 additions & 0 deletions src/valenspy/diagnostic/_ensemble2self.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from valenspy.diagnostic.diagnostic import Ensemble2Self
from valenspy.diagnostic.functions import *
from valenspy.diagnostic.visualizations import *

__all__ = [
"Ensemble_Quantile_Spatial_Mean"
]

Ensemble_Quantile_Spatial_Mean = Ensemble2Self(
ensemble_quantile_of_spatial_mean,
plot_map_per_dimension,
"Ensemble quantiles of spatial mean",
"The quantiles accross the ensembles spatial mean."
)
Loading
Loading