Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove center_times kwarg from temporal averaging methods #254

Merged
merged 5 commits into from
Jun 9, 2022
Merged
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 docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Below is a list of top-level API functions available in ``xcdat``.
.. autosummary::
:toctree: generated/

axis.center_times
axis.swap_lon_axis
dataset.open_dataset
dataset.open_mfdataset
Expand Down Expand Up @@ -107,6 +108,5 @@ Methods
Dataset.temporal.group_average
Dataset.temporal.climatology
Dataset.temporal.departures
Dataset.temporal.center_times

.. _dsmeth_1:
271 changes: 151 additions & 120 deletions tests/test_axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,99 @@
import xarray as xr

from tests.fixtures import generate_dataset
from xcdat.axis import (
_align_lon_bounds_to_360,
_get_prime_meridian_index,
swap_lon_axis,
)
from xcdat.axis import center_times, swap_lon_axis


class TestCenterTimes:
@pytest.fixture(autouse=True)
def setup(self):
self.ds = generate_dataset(cf_compliant=True, has_bounds=True)

def test_raises_error_if_time_coord_var_does_not_exist_in_dataset(self):
ds = self.ds.copy()
ds = ds.drop_dims("time")

with pytest.raises(KeyError):
center_times(ds)

def test_raises_error_if_time_bounds_does_not_exist_in_the_dataset(self):
ds = self.ds.copy()
ds = ds.drop_vars("time_bnds")

with pytest.raises(KeyError):
center_times(ds)

def test_gets_time_as_the_midpoint_between_time_bounds(self):
ds = self.ds.copy()

# Make the time coordinates uncentered.
uncentered_time = np.array(
[
"2000-01-31T12:00:00.000000000",
"2000-02-29T12:00:00.000000000",
"2000-03-31T12:00:00.000000000",
"2000-04-30T00:00:00.000000000",
"2000-05-31T12:00:00.000000000",
"2000-06-30T00:00:00.000000000",
"2000-07-31T12:00:00.000000000",
"2000-08-31T12:00:00.000000000",
"2000-09-30T00:00:00.000000000",
"2000-10-16T12:00:00.000000000",
"2000-11-30T00:00:00.000000000",
"2000-12-31T12:00:00.000000000",
"2001-01-31T12:00:00.000000000",
"2001-02-28T00:00:00.000000000",
"2001-12-31T12:00:00.000000000",
],
dtype="datetime64[ns]",
)
ds.time.data[:] = uncentered_time

# Compare result of the method against the expected.
expected = ds.copy()
expected_time_data = np.array(
[
"2000-01-16T12:00:00.000000000",
"2000-02-15T12:00:00.000000000",
"2000-03-16T12:00:00.000000000",
"2000-04-16T00:00:00.000000000",
"2000-05-16T12:00:00.000000000",
"2000-06-16T00:00:00.000000000",
"2000-07-16T12:00:00.000000000",
"2000-08-16T12:00:00.000000000",
"2000-09-16T00:00:00.000000000",
"2000-10-16T12:00:00.000000000",
"2000-11-16T00:00:00.000000000",
"2000-12-16T12:00:00.000000000",
"2001-01-16T12:00:00.000000000",
"2001-02-15T00:00:00.000000000",
"2001-12-16T12:00:00.000000000",
],
dtype="datetime64[ns]",
)
expected = expected.assign_coords(
{
"time": xr.DataArray(
name="time",
data=expected_time_data,
coords={"time": expected_time_data},
dims="time",
attrs={
"long_name": "time",
"standard_name": "time",
"axis": "T",
"bounds": "time_bnds",
},
)
}
)
# Update time bounds with centered time coordinates.
time_bounds = ds.time_bnds.copy()
time_bounds["time"] = expected.time
expected["time_bnds"] = time_bounds

result = center_times(ds)
assert result.identical(expected)


class TestSwapLonAxis:
Expand All @@ -16,7 +104,9 @@ def test_raises_error_with_incorrect_lon_orientation_for_swapping(self):
with pytest.raises(ValueError):
swap_lon_axis(ds, to=9000) # type: ignore

def test_swap_from_180_to_360_and_sorts_with_prime_meridian_cell(self):
def test_raises_error_if_lon_bounds_contains_more_than_one_prime_meridian_cell(
self,
):
ds_180 = xr.Dataset(
coords={
"lon": xr.DataArray(
Expand All @@ -35,7 +125,7 @@ def test_swap_from_180_to_360_and_sorts_with_prime_meridian_cell(self):
[-1.5, -0.5],
[-0.5, 0.5],
[0.5, 1.5],
[1.5, 179.5],
[-180.5, 1.5],
]
),
dims=["lon", "bnds"],
Expand All @@ -50,42 +140,8 @@ def test_swap_from_180_to_360_and_sorts_with_prime_meridian_cell(self):
},
)

result = swap_lon_axis(ds_180, to=(0, 360))
expected = xr.Dataset(
coords={
"lon": xr.DataArray(
name="lon",
data=np.array([0, 1, 179, 180, 359, 360]),
dims=["lon"],
attrs={"units": "degrees_east", "axis": "X", "bounds": "lon_bnds"},
)
},
data_vars={
"lon_bnds": xr.DataArray(
name="lon_bnds",
data=np.array(
[
[0, 0.5],
[0.5, 1.5],
[1.5, 179.5],
[179.5, 358.5],
[358.5, 359.5],
[359.5, 360],
]
),
dims=["lon", "bnds"],
attrs={"is_generated": "True"},
),
"ts": xr.DataArray(
name="ts",
data=np.array([2, 3, 4, 0, 1, 2]),
dims=["lon"],
attrs={"test_attr": "test"},
),
},
)

assert result.identical(expected)
with pytest.raises(ValueError):
swap_lon_axis(ds_180, to=(0, 360))

def test_swap_from_360_to_180_and_sorts(self):
ds_360 = xr.Dataset(
Expand Down Expand Up @@ -128,98 +184,73 @@ def test_swap_from_360_to_180_and_sorts(self):

assert result.identical(expected)


class TestAlignLonBoundsto360:
@pytest.fixture(autouse=True)
def setup(self):
self.ds = generate_dataset(cf_compliant=True, has_bounds=True)

def test_raises_error_if_bounds_below_0(self):
domain_bounds = xr.DataArray(
name="lon_bnds",
data=np.array([[-1, 1], [1, 90], [90, 180], [180, 359]]),
dims=["lon", "bnds"],
)
with pytest.raises(ValueError):
_align_lon_bounds_to_360(domain_bounds, np.array([0]))

def test_raises_error_if_bounds_above_360(self):
domain_bounds = xr.DataArray(
name="lon_bnds",
data=np.array([[359, 361], [1, 90], [90, 180], [180, 359]]),
dims=["lon", "bnds"],
)
with pytest.raises(ValueError):
_align_lon_bounds_to_360(domain_bounds, np.array([0]))

def test_extends_bounds_array_for_cell_spanning_prime_meridian(self):
domain_bounds = xr.DataArray(
name="lon_bnds",
def test_swap_from_180_to_360_and_sorts_with_prime_meridian_cell_in_lon_bnds(self):
ds_180 = xr.Dataset(
coords={
"lon": xr.DataArray(
name="lon",
data=np.array([0, 90, 180, 359]),
data=np.array([-180, -1, 0, 1, 179]),
dims=["lon"],
attrs={"axis": "X"},
attrs={"units": "degrees_east", "axis": "X", "bounds": "lon_bnds"},
)
},
data=np.array([[359, 1], [1, 90], [90, 180], [180, 359]]),
dims=["lon", "bnds"],
)

result_bounds = _align_lon_bounds_to_360(domain_bounds, np.array([0]))
expected_bounds = xr.DataArray(
name="lon_bnds",
coords={
"lon": xr.DataArray(
name="lon",
data=np.array([0, 90, 180, 359, 0]),
data_vars={
"lon_bnds": xr.DataArray(
name="lon_bnds",
data=np.array(
[
[-180.5, -1.5],
[-1.5, -0.5],
[-0.5, 0.5],
[0.5, 1.5],
[1.5, 179.5],
]
),
dims=["lon", "bnds"],
attrs={"is_generated": "True"},
),
"ts": xr.DataArray(
name="ts",
data=np.array([0, 1, 2, 3, 4]),
dims=["lon"],
attrs={"axis": "X"},
)
attrs={"test_attr": "test"},
),
},
data=np.array([[0, 1], [1, 90], [90, 180], [180, 359], [359, 360]]),
dims=["lon", "bnds"],
)
assert result_bounds.identical(expected_bounds)

def test_retains_total_weight(self):
# construct array spanning 0 to 360
domain_bounds = xr.DataArray(
name="lon_bnds",
result = swap_lon_axis(ds_180, to=(0, 360))
expected = xr.Dataset(
coords={
"lon": xr.DataArray(
name="lon",
data=np.array([0, 90, 180, 359]),
data=np.array([0, 1, 179, 180, 359, 360]),
dims=["lon"],
attrs={"axis": "X"},
attrs={"units": "degrees_east", "axis": "X", "bounds": "lon_bnds"},
)
},
data=np.array([[359, 1], [1, 90], [90, 180], [180, 359]]),
dims=["lon", "bnds"],
)

result_bounds = _align_lon_bounds_to_360(domain_bounds, np.array(0))
dbdiff = np.sum(np.array(result_bounds[:, 1] - result_bounds[:, 0]))
assert dbdiff == 360.0


class TestGetPrimeMeridianIndex:
def test_raises_error_if_multiple_bounds_span_prime_meridian(self):
domain_bounds = xr.DataArray(
name="lon_bnds",
data=np.array([[359, 1], [1, 90], [90, 180], [180, 2]]),
dims=["lon", "bnds"],
)
with pytest.raises(ValueError):
_get_prime_meridian_index(domain_bounds)

def test_returns_none_if_there_is_no_prime_meridian(self):
domain_bounds = xr.DataArray(
name="lon_bnds",
data=np.array([[0, 1], [1, 90], [90, 180], [180, 360]]),
dims=["lon", "bnds"],
data_vars={
"lon_bnds": xr.DataArray(
name="lon_bnds",
data=np.array(
[
[0, 0.5],
[0.5, 1.5],
[1.5, 179.5],
[179.5, 358.5],
[358.5, 359.5],
[359.5, 360],
]
),
dims=["lon", "bnds"],
attrs={"is_generated": "True"},
),
"ts": xr.DataArray(
name="ts",
data=np.array([2, 3, 4, 0, 1, 2]),
dims=["lon"],
attrs={"test_attr": "test"},
),
},
)
result = _get_prime_meridian_index(domain_bounds)

assert result is None
assert result.identical(expected)
Loading