Skip to content

Commit

Permalink
Modify logic to not throw error for singleton coordinates (with no bo…
Browse files Browse the repository at this point in the history
…unds) (#313)

* do not throw valuerror if coord length <= 1 in order to accommodate single timestep datasets; simply do not add bounds and warn user

* update docstring

* add test and ensure warning is suppressed

* Refactor `add_missing_bounds()`
- Update try and except statements to skip multidimensional or single dimension bounds
- Reduce nesting for cleaner code
- Update docstrings with explicit criteria on when bounds can be added for coordinates

* Update tests/test_bounds.py

* Add continue statement if bounds are found

* Update xcdat/bounds.py

Co-authored-by: Tom Vo <tomvothecoder@gmail.com>
  • Loading branch information
pochedls and tomvothecoder authored Aug 16, 2022
1 parent 1469adf commit 641b9f2
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 20 deletions.
22 changes: 19 additions & 3 deletions tests/test_bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ def test_adds_bounds_to_the_dataset_skips_nondimensional_axes(self):
# and added height coordinate
assert result.identical(ds)

def test_skips_adding_bounds_for_coords_that_are_multidimensional_or_len_of_1(self):
# Multidimensional
lat = xr.DataArray(
data=np.array([[0, 1, 2], [3, 4, 5]]),
dims=["placeholder_1", "placeholder_2"],
attrs={"units": "degrees_north", "axis": "Y"},
)
# Length <=1
lon = xr.DataArray(
data=np.array([0]),
dims=["lon"],
attrs={"units": "degrees_east", "axis": "X"},
)
ds = xr.Dataset(coords={"lat": lat, "lon": lon})

result = ds.bounds.add_missing_bounds("Y")

assert result.identical(ds)


class TestGetBounds:
@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -166,9 +185,6 @@ def test_raises_errors_for_data_dim_and_length(self):
# If coords dimensions does not equal 1.
with pytest.raises(ValueError):
ds.bounds.add_bounds("Y")
# If coords are length of <=1.
with pytest.raises(ValueError):
ds.bounds.add_bounds("X")

def test_raises_error_if_lat_coord_var_units_is_not_in_degrees(self):
lat = xr.DataArray(
Expand Down
63 changes: 46 additions & 17 deletions xcdat/bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,16 @@ def keys(self) -> List[str]:
def add_missing_bounds(self, width: float = 0.5) -> xr.Dataset:
"""Adds missing coordinate bounds for supported axes in the Dataset.
This function loops through the Dataset's axes and adds coordinate
bounds for an axis that doesn't have any.
This function loops through the Dataset's axes and attempts to adds
bounds to its coordinates if they don't exist. The coordinates must meet
the following criteria in order to add bounds:
1. The axis for the coordinates are "X", "Y", "T", or "Z"
2. Coordinates are a single dimension, not multidimensional
3. Coordinates are a length > 1 (not singleton)
4. Bounds must not already exist.
* Determined by attempting to map the coordinate variable's
"bounds" attr (if set) to the bounds data variable of the same key.
Parameters
----------
Expand All @@ -133,24 +141,31 @@ def add_missing_bounds(self, width: float = 0.5) -> xr.Dataset:
axes = CF_NAME_MAP.keys()

for axis in axes:
coord_var = None
# Check if the axis coordinates can be mapped to.
try:
get_axis_coord(self._dataset, axis)
except KeyError:
continue

# Determine if the axis is also a dimension by determining if there
# is overlap between the CF axis names and the dimension names. If
# not, skip over axis for validation.
if len(set(CF_NAME_MAP[axis]) & set(self._dataset.dims.keys())) == 0:
continue

# Check if bounds also exist using the "bounds" attribute.
# Otherwise, try to add bounds if it meets the function's criteria.
try:
coord_var = get_axis_coord(self._dataset, axis)
self.get_bounds(axis)
continue
except KeyError:
pass

# determine if the axis is also a dimension by determining
# if there is overlap between the CF axis names and the dimension
# names. If not, skip over axis for validation.
if len(set(CF_NAME_MAP[axis]) & set(self._dataset.dims.keys())) == 0:
try:
self._dataset = self.add_bounds(axis, width)
except ValueError:
continue

if coord_var is not None:
try:
self.get_bounds(axis)
except KeyError:
self._dataset = self.add_bounds(axis, width)
return self._dataset

def get_bounds(self, axis: CFAxisName) -> xr.DataArray:
Expand Down Expand Up @@ -201,6 +216,16 @@ def get_bounds(self, axis: CFAxisName) -> xr.DataArray:
def add_bounds(self, axis: CFAxisName, width: float = 0.5) -> xr.Dataset:
"""Add bounds for an axis using its coordinate points.
The coordinates must meet the following criteria in order to add
bounds:
1. The axis for the coordinates are "X", "Y", "T", or "Z"
2. Coordinates are a single dimension, not multidimensional
3. Coordinates are a length > 1 (not singleton)
4. Bounds must not already exist.
* Determined by attempting to map the coordinate variable's
"bounds" attr (if set) to the bounds data variable of the same key.
Parameters
----------
axis : CFAxisName
Expand Down Expand Up @@ -252,8 +277,6 @@ def _add_bounds(self, axis: CFAxisName, width: float = 0.5) -> xr.Dataset:
------
ValueError
If coords dimensions does not equal 1.
ValueError
If coords are length of <=1.
Notes
-----
Expand All @@ -272,9 +295,15 @@ def _add_bounds(self, axis: CFAxisName, width: float = 0.5) -> xr.Dataset:

# Validate coordinate shape and dimensions
if coord_var.ndim != 1:
raise ValueError("Cannot generate bounds for multidimensional coordinates.")
raise ValueError(
f"Cannot generate bounds for coordinate variable '{coord_var.name}'"
" because it is multidimensional coordinates."
)
if coord_var.shape[0] <= 1:
raise ValueError("Cannot generate bounds for a coordinate of length <= 1.")
raise ValueError(
f"Cannot generate bounds for coordinate variable '{coord_var.name}'"
" which has a length <= 1."
)

# Retrieve coordinate dimension to calculate the diffs between points.
dim = coord_var.dims[0]
Expand Down

0 comments on commit 641b9f2

Please sign in to comment.