Skip to content

Commit 31251d6

Browse files
committed
add bounds property
1 parent 64e8695 commit 31251d6

File tree

2 files changed

+79
-6
lines changed

2 files changed

+79
-6
lines changed

cf_xarray/accessor.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,31 @@ def _get_measure(obj: Union[DataArray, Dataset], key: str) -> List[str]:
255255
return list(results)
256256

257257

258+
def _get_bounds(obj: Union[DataArray, Dataset], key: str) -> List[str]:
259+
"""
260+
Translate from key (either CF key or variable name) to appropriate bounds names.
261+
This function interprets the ``bounds`` attribute on DataArrays.
262+
263+
Parameters
264+
----------
265+
obj : DataArray, Dataset
266+
DataArray belonging to the coordinate to be checked
267+
key : str
268+
key to check for.
269+
270+
Returns
271+
-------
272+
List[str], Variable name(s) in parent xarray object that matches axis or coordinate `key`
273+
"""
274+
275+
results = []
276+
for var in apply_mapper(_get_all, obj, key, error=False, default=[key]):
277+
if "bounds" in obj[var].attrs:
278+
results += [obj[var].attrs["bounds"]]
279+
280+
return results
281+
282+
258283
def _get_with_standard_name(
259284
obj: Union[DataArray, Dataset], name: Union[str, List[str]]
260285
) -> List[str]:
@@ -1027,13 +1052,15 @@ def make_text_section(subtitle, vardict, valid_values, default_keys=None):
10271052
"Cell Measures", self.cell_measures, coords, _CELL_MEASURES
10281053
)
10291054
text += make_text_section("Standard Names", self.standard_names, coords)
1055+
text += make_text_section("Bounds", self.bounds, coords)
10301056
if isinstance(self._obj, Dataset):
10311057
data_vars = self._obj.data_vars
10321058
text += "\nData Variables:"
10331059
text += make_text_section(
10341060
"Cell Measures", self.cell_measures, data_vars, _CELL_MEASURES
10351061
)
10361062
text += make_text_section("Standard Names", self.standard_names, data_vars)
1063+
text += make_text_section("Bounds", self.bounds, data_vars)
10371064

10381065
return text
10391066

@@ -1132,6 +1159,26 @@ def cell_measures(self) -> Dict[str, List[str]]:
11321159

11331160
return {k: sorted(set(v)) for k, v in measures.items() if v}
11341161

1162+
@property
1163+
def bounds(self) -> Dict[str, List[str]]:
1164+
"""
1165+
Property that returns a dictionary mapping valid keys to variable names of their bounds.
1166+
1167+
Returns
1168+
-------
1169+
Dictionary mapping valid keys to variable names of their bounds.
1170+
"""
1171+
1172+
obj = self._obj
1173+
keys = self.keys()
1174+
keys |= set(obj.variables if isinstance(obj, Dataset) else obj.coords)
1175+
1176+
vardict = {
1177+
key: apply_mapper(_get_bounds, obj, key, error=False) for key in keys
1178+
}
1179+
1180+
return {k: sorted(v) for k, v in vardict.items() if v}
1181+
11351182
def get_standard_names(self) -> List[str]:
11361183

11371184
warnings.warn(
@@ -1493,12 +1540,8 @@ def get_bounds(self, key: str) -> DataArray:
14931540
-------
14941541
DataArray
14951542
"""
1496-
name = apply_mapper(
1497-
_single(_get_all), self._obj, key, error=False, default=[key]
1498-
)[0]
1499-
bounds = self._obj[name].attrs["bounds"]
1500-
obj = self._maybe_to_dataset()
1501-
return obj[bounds]
1543+
1544+
return apply_mapper(_variables(_single(_get_bounds)), self._obj, key)[0]
15021545

15031546
def get_bounds_dim_name(self, key: str) -> str:
15041547
"""

cf_xarray/tests/test_accessor.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,14 @@ def test_repr():
6161
* longitude: ['lon']
6262
* time: ['time']
6363
64+
- Bounds: n/a
65+
6466
Data Variables:
6567
- Cell Measures: area, volume: n/a
6668
6769
- Standard Names: air_temperature: ['air']
70+
71+
- Bounds: n/a
6872
"""
6973
assert actual == dedent(expected)
7074

@@ -89,6 +93,8 @@ def test_repr():
8993
- Standard Names: * latitude: ['lat']
9094
* longitude: ['lon']
9195
* time: ['time']
96+
97+
- Bounds: n/a
9298
"""
9399
assert actual == dedent(expected)
94100

@@ -108,11 +114,15 @@ def test_repr():
108114
109115
- Standard Names: n/a
110116
117+
- Bounds: n/a
118+
111119
Data Variables:
112120
- Cell Measures: area, volume: n/a
113121
114122
- Standard Names: sea_water_potential_temperature: ['TEMP']
115123
sea_water_x_velocity: ['UVEL']
124+
125+
- Bounds: n/a
116126
"""
117127
assert actual == dedent(expected)
118128

@@ -163,6 +173,8 @@ def test_cell_measures():
163173
164174
- Standard Names: air_temperature: ['air']
165175
foo_std_name: ['foo']
176+
177+
- Bounds: n/a
166178
"""
167179
assert actual.endswith(dedent(expected))
168180

@@ -625,6 +637,11 @@ def test_add_bounds(obj, dims):
625637

626638
def test_bounds():
627639
ds = airds.copy(deep=True).cf.add_bounds("lat")
640+
641+
actual = ds.cf.bounds
642+
expected = {"Y": ["lat_bounds"], "lat": ["lat_bounds"], "latitude": ["lat_bounds"]}
643+
assert ds.cf.bounds == ds.cf["air"].cf.bounds == expected
644+
628645
actual = ds.cf[["lat"]]
629646
expected = ds[["lat", "lat_bounds"]]
630647
assert_identical(actual, expected)
@@ -651,6 +668,19 @@ def test_bounds():
651668
with pytest.warns(UserWarning, match="{'foo'} not found in object"):
652669
ds.cf[["air"]]
653670

671+
# Dataset has bounds
672+
expected = """\
673+
- Bounds: Y: ['lat_bounds']
674+
lat: ['lat_bounds']
675+
latitude: ['lat_bounds']
676+
"""
677+
assert dedent(expected) in ds.cf.__repr__()
678+
679+
# DataArray does not have bounds
680+
expected = airds.cf["air"].cf.__repr__()
681+
actual = ds.cf["air"].cf.__repr__()
682+
assert actual == expected
683+
654684

655685
def test_bounds_to_vertices():
656686
# All available

0 commit comments

Comments
 (0)