Skip to content

Commit e4fa084

Browse files
authored
Add .cf.formula_terms (#213)
1 parent 3252c74 commit e4fa084

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
lines changed

cf_xarray/accessor.py

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import functools
22
import inspect
33
import itertools
4+
import re
45
import warnings
56
from collections import ChainMap
67
from typing import (
@@ -1445,16 +1446,19 @@ def differentiate(
14451446
self, coord, *xr_args, positive_upward: bool = False, **xr_kwargs
14461447
):
14471448
"""
1449+
Differentiate an xarray object.
1450+
14481451
Parameters
14491452
----------
1450-
xr_args, xr_kwargs are passed directly to the underlying xarray function.
1451-
The following are added by cf_xarray:
1452-
14531453
positive_upward: optional, bool
14541454
Change sign of the derivative based on the ``"positive"`` attribute of ``coord``
14551455
so that positive values indicate increasing upward.
14561456
If ``positive=="down"``, then multiplied by -1.
14571457
1458+
Notes
1459+
-----
1460+
``xr_args``, ``xr_kwargs`` are passed directly to the underlying xarray function.
1461+
14581462
See Also
14591463
--------
14601464
DataArray.cf.differentiate
@@ -1515,6 +1519,16 @@ def __getitem__(self, key: Union[str, List[str]]) -> Union[DataArray, Dataset]:
15151519
"""
15161520
return _getitem(self, key)
15171521

1522+
@property
1523+
def formula_terms(self) -> Dict[str, Dict[str, str]]:
1524+
"""
1525+
Property that returns a dictionary
1526+
{parametric_coord_name: {standard_term_name: variable_name}}
1527+
"""
1528+
return {
1529+
dim: self._obj[dim].cf.formula_terms for dim in _get_dims(self._obj, "Z")
1530+
}
1531+
15181532
@property
15191533
def bounds(self) -> Dict[str, List[str]]:
15201534
"""
@@ -1719,36 +1733,29 @@ def decode_vertical_coords(self, prefix="z"):
17191733
.. warning::
17201734
Very lightly tested. Please double check the results.
17211735
"""
1722-
import re
1723-
17241736
ds = self._obj
1725-
dims = _get_dims(ds, "Z")
17261737

17271738
requirements = {
17281739
"ocean_s_coordinate_g1": {"depth_c", "depth", "s", "C", "eta"},
17291740
"ocean_s_coordinate_g2": {"depth_c", "depth", "s", "C", "eta"},
17301741
}
17311742

1732-
for dim in dims:
1743+
allterms = self.formula_terms
1744+
for dim in allterms:
17331745
suffix = dim.split("_")
17341746
zname = f"{prefix}_" + "_".join(suffix[1:])
17351747

1736-
if (
1737-
"formula_terms" not in ds[dim].attrs
1738-
or "standard_name" not in ds[dim].attrs
1739-
):
1748+
if "standard_name" not in ds[dim].attrs:
17401749
continue
1741-
1742-
formula_terms = ds[dim].attrs["formula_terms"]
17431750
stdname = ds[dim].attrs["standard_name"]
17441751

17451752
# map "standard" formula term names to actual variable names
17461753
terms = {}
1747-
for mapping in re.sub(": ", ":", formula_terms).split(" "):
1748-
key, value = mapping.split(":")
1754+
for key, value in allterms[dim].items():
17491755
if value not in ds:
17501756
raise KeyError(
1751-
f"Variable {value!r} is required to decode coordinate for {dim} but it is absent in the Dataset."
1757+
f"Variable {value!r} is required to decode coordinate for {dim!r}"
1758+
" but it is absent in the Dataset."
17521759
)
17531760
terms[key] = ds[value]
17541761

@@ -1776,12 +1783,30 @@ def decode_vertical_coords(self, prefix="z"):
17761783

17771784
else:
17781785
raise NotImplementedError(
1779-
f"Coordinate function for {stdname} not implemented yet. Contributions welcome!"
1786+
f"Coordinate function for {stdname!r} not implemented yet. Contributions welcome!"
17801787
)
17811788

17821789

17831790
@xr.register_dataarray_accessor("cf")
17841791
class CFDataArrayAccessor(CFAccessor):
1792+
@property
1793+
def formula_terms(self) -> Dict[str, str]:
1794+
"""
1795+
Property that returns a dictionary
1796+
{parametric_coord_name: {standard_term_name: variable_name}}
1797+
"""
1798+
da = self._obj
1799+
if "formula_terms" not in da.attrs:
1800+
var = da[_single(_get_dims)(da, "Z")[0]]
1801+
else:
1802+
var = da
1803+
terms = {}
1804+
formula_terms = var.attrs.get("formula_terms", "")
1805+
for mapping in re.sub(r"\s*:\s*", ":", formula_terms).split():
1806+
key, value = mapping.split(":")
1807+
terms[key] = value
1808+
return terms
1809+
17851810
def __getitem__(self, key: Union[str, List[str]]) -> DataArray:
17861811
"""
17871812
Index into a DataArray making use of CF attributes.

cf_xarray/tests/test_accessor.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,28 @@ def test_param_vcoord_ocean_s_coord():
945945
copy.cf.decode_vertical_coords()
946946

947947

948+
def test_formula_terms():
949+
srhoterms = {
950+
"s": "s_rho",
951+
"C": "Cs_r",
952+
"eta": "zeta",
953+
"depth": "h",
954+
"depth_c": "hc",
955+
}
956+
assert romsds.cf.formula_terms == {"s_rho": srhoterms}
957+
assert romsds["temp"].cf.formula_terms == srhoterms
958+
assert romsds["s_rho"].cf.formula_terms == srhoterms
959+
960+
s_rho = romsds["s_rho"].copy(deep=True)
961+
del s_rho.attrs["standard_name"]
962+
del s_rho.s_rho.attrs["standard_name"] # TODO: xarray bug
963+
assert s_rho.cf.formula_terms == srhoterms
964+
965+
with pytest.raises(KeyError):
966+
# x,y,t variable
967+
romsds["zeta"].cf.formula_terms
968+
969+
948970
def test_standard_name_mapper():
949971
da = xr.DataArray(
950972
np.arange(6),

doc/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Attributes
3030
DataArray.cf.axes
3131
DataArray.cf.cell_measures
3232
DataArray.cf.coordinates
33+
DataArray.cf.formula_terms
3334
DataArray.cf.standard_names
3435
DataArray.cf.plot
3536

@@ -66,6 +67,7 @@ Attributes
6667
Dataset.cf.bounds
6768
Dataset.cf.cell_measures
6869
Dataset.cf.coordinates
70+
Dataset.cf.formula_terms
6971
Dataset.cf.standard_names
7072

7173
.. _dsmeth:

doc/whats-new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ What's New
66
v0.5.2 (unreleased)
77
===================
88

9+
- Added :py:attr:`DataArray.cf.formula_terms` and :py:attr:`Dataset.cf.formula_terms`.
10+
By `Deepak Cherian`_.
911
- Added :py:attr:`Dataset.cf.bounds` to return a dictionary mapping valid keys to the variable names of their bounds. By `Mattia Almansi`_.
1012
- :py:meth:`DataArray.cf.differentiate` and :py:meth:`Dataset.cf.differentiate` can optionally correct
1113
sign of the derivative by interpreting the ``"positive"`` attribute. By `Deepak Cherian`_.

0 commit comments

Comments
 (0)