Skip to content

Commit

Permalink
Add _bounds attr for DataArrayBoundsAccessor (#98)
Browse files Browse the repository at this point in the history
- Add `.copy()` method to DataArrayBoundsAccessor
- Add `copy_variable` function
  • Loading branch information
tomvothecoder authored Aug 25, 2021
1 parent c9d0529 commit 533d150
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 219 deletions.
31 changes: 0 additions & 31 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,37 +144,6 @@
dims=["time", "lat", "lon"],
)

# Special cases, after copying bounds from parent Dataset to data variable
# using xCDAT.
ts_with_bnds_from_parent_cf = xr.DataArray(
name="ts",
data=np.ones((2, 12, 4, 4)),
coords={
"bnds": np.array([0, 1]),
"time": time_cf.assign_attrs(bounds="time_bnds"),
"lat": lat.assign_attrs(bounds="lat_bnds"),
"lon": lon.assign_attrs(bounds="lon_bnds"),
"lat_bnds": lat_bnds.copy(),
"lon_bnds": lon_bnds.copy(),
"time_bnds": time_bnds.copy(),
},
dims=["bnds", "time", "lat", "lon"],
)
ts_with_bnds_from_parent_non_cf = xr.DataArray(
name="ts",
data=np.ones((2, 12, 4, 4)),
coords={
"bnds": np.array([0, 1]),
"time": time_cf.copy(),
"lat": lat.copy(),
"lon": lon.copy(),
"lat_bnds": lat_bnds,
"lon_bnds": lon_bnds,
"time_bnds": time_bnds_non_cf,
},
dims=["bnds", "time", "lat", "lon"],
)


def generate_dataset(cf_compliant: bool, has_bounds: bool) -> xr.Dataset:
"""Generates a dataset using coordinate and data variable fixtures.
Expand Down
114 changes: 60 additions & 54 deletions tests/test_bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@
import pytest
import xarray as xr

from tests.fixtures import (
generate_dataset,
lat_bnds,
lon_bnds,
time_bnds,
ts_cf,
ts_with_bnds_from_parent_cf,
)
from tests.fixtures import generate_dataset, lat_bnds, lon_bnds, time_bnds, ts_cf
from xcdat.bounds import DataArrayBoundsAccessor, DatasetBoundsAccessor


Expand All @@ -29,19 +22,20 @@ def test_decorator_call(self):
def test_bounds_property_returns_map_of_coordinate_key_to_bounds_dataarray(self):
ds = self.ds_with_bnds.copy()
expected = {
"T": ds.time,
"X": ds.lon,
"Y": ds.lat,
"lat": ds.lat,
"latitude": ds.lat,
"lon": ds.lon,
"longitude": ds.lon,
"time": ds.time,
"T": ds.time_bnds,
"X": ds.lon_bnds,
"Y": ds.lat_bnds,
"lat": ds.lat_bnds,
"latitude": ds.lat_bnds,
"lon": ds.lon_bnds,
"longitude": ds.lon_bnds,
"time": ds.time_bnds,
}

result = ds.bounds.bounds

assert result == expected
for key in expected.keys():
assert result[key].identical(expected[key])

def test_fill_missing_returns_dataset_with_filled_bounds(self):
ds = self.ds_with_bnds.copy()
Expand Down Expand Up @@ -141,6 +135,7 @@ class TestDataArrayBoundsAccessor:
@pytest.fixture(autouse=True)
def setUp(self):
self.ds = generate_dataset(cf_compliant=True, has_bounds=True)
self.ts = ts_cf.copy()

def test__init__(self):
obj = DataArrayBoundsAccessor(self.ds.ts)
Expand All @@ -151,65 +146,76 @@ def test_decorator_call(self):
result = self.ds["ts"].bounds._dataarray
assert result.identical(expected)

def test__copy_from_parent_copies_bounds(self):
ts_expected = ts_with_bnds_from_parent_cf.copy()
ts_result = ts_cf.copy().bounds.copy_from_parent(self.ds)

assert ts_result.identical(ts_expected)
def test_copy_returns_exact_copy_with_attrs(self):
ds_bounds = self.ds.drop_vars("ts")
ts = self.ts.copy()

def test__set_bounds_dim_adds_bnds(self):
ts_expected = ts_cf.copy().expand_dims(bnds=np.array([0, 1]))
ts_result = ts_cf.copy().bounds._set_bounds_dim(self.ds)
ts_result = ts.bounds.copy_from_parent(self.ds)
assert ts_result.bounds._bounds.identical(ds_bounds)

assert ts_result.identical(ts_expected)
ts_result_copy = ts_result.bounds.copy()
assert ts_result_copy.bounds._dataarray.identical(ts_result.bounds._dataarray)
assert ts_result_copy.bounds._bounds.identical(ts_result.bounds._bounds)

def test__set_bounds_dim_adds_bounds(self):
ds = self.ds.swap_dims({"bnds": "bounds"}).copy()
def test__copy_from_parent_copies_bounds(self):
ds_bounds = self.ds.drop_vars("ts")
ts = self.ts.copy()

ts_expected = ts_cf.copy().expand_dims(bounds=np.array([0, 1]))
ts_result = ts_cf.copy().bounds._set_bounds_dim(ds)
assert ts_result.identical(ts_expected)
ts_result = ts.bounds.copy_from_parent(self.ds)
assert ts_result.bounds._bounds.identical(ds_bounds)

def test_bounds_property_returns_mapping_of_coordinate_keys_to_bounds_dataarrays(
def test_bounds_property_returns_mapping_of_keys_to_bounds_dataarrays(
self,
):
ts = ts_with_bnds_from_parent_cf.copy()
ds = self.ds
ts = self.ts.copy()
ts = ts.bounds.copy_from_parent(ds)

result = ts.bounds.bounds
expected = {"time": ts.time_bnds, "lat": ts.lat_bnds, "lon": ts.lon_bnds}
expected = {"time": ds.time_bnds, "lat": ds.lat_bnds, "lon": ds.lon_bnds}

for key in expected.keys():
assert result[key].identical(expected[key])

def test_bounds_names_property_returns_mapping_of_coordinate_keys_to_names_of_bounds(
self,
):
ts = ts_with_bnds_from_parent_cf.copy()
ts = ts_cf.copy()
ts = ts.bounds.copy_from_parent(self.ds)

result = ts.bounds.bounds_names
expected = {"time": "time_bnds", "lat": "lat_bnds", "lon": "lon_bnds"}
expected = {
"lat": ["lat_bnds"],
"Y": ["lat_bnds"],
"latitude": ["lat_bnds"],
"longitude": ["lon_bnds"],
"lon": ["lon_bnds"],
"time": ["time_bnds"],
"T": ["time_bnds"],
"X": ["lon_bnds"],
}
assert result == expected

def test__check_bounds_are_set_raises_error_if_not_set(
self,
):
ts = self.ts.copy()

with pytest.raises(ValueError):
ts.bounds.bounds

def test_get_bounds_returns_bounds_dataarray_for_coordinate_key(self):
ts = ts_with_bnds_from_parent_cf.copy()
ts = self.ts.copy()
ts = ts.bounds.copy_from_parent(self.ds)

result = ts.bounds.get_bounds("lat")
expected = ts.lat_bnds
expected = self.ds.lat_bnds

assert result.identical(expected)

def test_get_bounds_returns_keyerror_with_incorrect_key(self):
ts = ts_with_bnds_from_parent_cf.copy()
def test_get_bounds_raises_error_with_incorrect_key(self):
ts = self.ts.copy()
ts = ts.bounds.copy_from_parent(self.ds)

with pytest.raises(KeyError):
with pytest.raises(ValueError):
ts.bounds.get_bounds("something")

def test_get_bounds_dim_name_returns_dim_of_coordinate_bounds(self):
ts = ts_with_bnds_from_parent_cf.copy()
result = ts.bounds.get_bounds_dim_name("lat")
expected = "bnds"

assert result == expected

def test_get_bounds_dim_name_raises_keyerror_with_incorrect_key(self):
ts = ts_with_bnds_from_parent_cf.copy()

with pytest.raises(KeyError):
ts.bounds.get_bounds_dim_name("something")
57 changes: 43 additions & 14 deletions tests/test_variable.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from tests.fixtures import generate_dataset, ts_with_bnds_from_parent_cf
from xcdat.variable import open_variable
from tests.fixtures import generate_dataset
from xcdat.variable import copy_variable, open_variable


class TestOpenVariable:
Expand All @@ -11,12 +11,6 @@ def test_raises_error_if_variable_does_not_exist(self):
with pytest.raises(KeyError):
open_variable(ds, "invalid_var")

def test_raises_error_if_bounds_dim_is_missing(self):
ds = generate_dataset(cf_compliant=True, has_bounds=False)

with pytest.raises(KeyError):
open_variable(ds, "ts")

def test_raises_error_if_bounds_are_missing_for_coordinates(self):
ds = generate_dataset(cf_compliant=True, has_bounds=True)

Expand All @@ -31,11 +25,46 @@ def test_raises_error_if_bounds_are_missing_for_coordinates(self):

def test_returns_variable_with_bounds(self):
ds = generate_dataset(cf_compliant=True, has_bounds=True)
ds.lat.attrs["bounds"] = "lat_bnds"
ds.lon.attrs["bounds"] = "lon_bnds"
ds.time.attrs["bounds"] = "time_bnds"
expected = {
"T": ds.time_bnds,
"X": ds.lon_bnds,
"Y": ds.lat_bnds,
"lat": ds.lat_bnds,
"latitude": ds.lat_bnds,
"lon": ds.lon_bnds,
"longitude": ds.lon_bnds,
"time": ds.time_bnds,
}

ts = open_variable(ds, "ts")
result = ts.bounds.bounds

for key in expected.keys():
assert result[key].identical(expected[key])


class TestCopyVariable:
def test_returns_copy_of_variable_with_bounds(self):
ds = generate_dataset(cf_compliant=True, has_bounds=True)
expected = {
"T": ds.time_bnds,
"X": ds.lon_bnds,
"Y": ds.lat_bnds,
"lat": ds.lat_bnds,
"latitude": ds.lat_bnds,
"lon": ds.lon_bnds,
"longitude": ds.lon_bnds,
"time": ds.time_bnds,
}

ts = open_variable(ds, "ts")
ts_bounds = ts.bounds.bounds

for key in expected.keys():
assert ts_bounds[key].identical(expected[key])

ts_expected = ts_with_bnds_from_parent_cf.copy()
ts_copy = copy_variable(ts)
ts_copy_bounds = ts_copy.bounds.bounds

ts_result = open_variable(ds, "ts")
assert ts_result.identical(ts_expected)
for key in expected.keys():
assert ts_copy_bounds[key].identical(ts_bounds[key])
Loading

0 comments on commit 533d150

Please sign in to comment.