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

Tests for variables with units #3654

Merged
merged 39 commits into from
Jan 15, 2020
Merged
Changes from 3 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a342a36
add tests for variable by inheriting from VariableSubclassobjects
keewis Dec 27, 2019
c27cf4c
make sure the utility functions work with variables
keewis Dec 28, 2019
3a7f2a0
add additional tests for aggregation and numpy methods
keewis Dec 28, 2019
b0b1e2c
don't assume variables have a name attribute
keewis Dec 29, 2019
1893329
properly merge the used arguments
keewis Dec 29, 2019
4058039
properly attach to non-xarray objects
keewis Dec 29, 2019
6099585
add a test function for searchsorted and item
keewis Dec 29, 2019
a7b0f6a
add tests for missing value detection methods
keewis Dec 29, 2019
b9338ae
add tests for comparisons
keewis Dec 29, 2019
00169c5
don't try to check missing value behaviour for int arrays
keewis Dec 29, 2019
a37fada
xfail the compatible unit tests
keewis Dec 29, 2019
9081c36
use an additional check since identical is not sufficient right now
keewis Dec 29, 2019
41cb4d7
check for compatibility by comparing the dimensionality of units
keewis Dec 29, 2019
d2258af
add tests for fillna
keewis Dec 29, 2019
2f6e6df
add tests for isel
keewis Dec 29, 2019
df7ca9e
Merge branch 'master' into tests-for-variables-with-units
keewis Dec 30, 2019
69bc684
add tests for 1d math
keewis Dec 31, 2019
e81029b
add tests for broadcast_equals
keewis Jan 8, 2020
1302d12
add tests for masking
keewis Jan 8, 2020
59c9edc
add tests for squeeze
keewis Jan 8, 2020
bbb4105
add tests for coarsen, quantile, roll, round and shift
keewis Jan 8, 2020
a11885b
add tests for searchsorted
keewis Jan 8, 2020
6dba6bc
add tests for rolling_window
keewis Jan 8, 2020
d27cf80
add tests for transpose
keewis Jan 8, 2020
5dffc12
add tests for stack and unstack
keewis Jan 8, 2020
cbb8da3
add tests for set_dims
keewis Jan 8, 2020
e2faede
add tests for concat, copy and no_conflicts
keewis Jan 8, 2020
350214b
add tests for reduce
keewis Jan 8, 2020
e34a3aa
add tests for pad_with_fill_value
keewis Jan 8, 2020
48df9c5
all and any have been implemented by pint
keewis Jan 8, 2020
fa9a80b
remove a unnecessary call to np.array
keewis Jan 8, 2020
5771d4d
mark the unrelated failures as xfail
keewis Jan 9, 2020
6da9f4b
remove a debug print
keewis Jan 14, 2020
a30f787
document the need for force_ndarray
keewis Jan 14, 2020
61dff86
make is_compatible a little bit more robust
keewis Jan 15, 2020
f67c29e
clean up is_compatible
keewis Jan 15, 2020
b412d86
add docstrings explaining the use of the method and function classes
keewis Jan 15, 2020
5375c9d
put the unit comparisons into a function
keewis Jan 15, 2020
a852aef
actually squeeze the dimensions separately
keewis Jan 15, 2020
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
164 changes: 161 additions & 3 deletions xarray/tests/test_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import xarray as xr
from xarray.core import formatting
from xarray.core.npcompat import IS_NEP18_ACTIVE
from .test_variable import VariableSubclassobjects

pint = pytest.importorskip("pint")
DimensionalityError = pint.errors.DimensionalityError


unit_registry = pint.UnitRegistry()
unit_registry = pint.UnitRegistry(force_ndarray=True)
keewis marked this conversation as resolved.
Show resolved Hide resolved
Quantity = unit_registry.Quantity

pytestmark = [
Expand Down Expand Up @@ -113,6 +114,10 @@ def extract_units(obj):
}

units = {**vars_units, **coords_units}
elif isinstance(obj, xr.Variable):
vars_units = {None: array_extract_units(obj.data)}

units = {**vars_units}
elif isinstance(obj, Quantity):
vars_units = {None: array_extract_units(obj)}

Expand Down Expand Up @@ -148,6 +153,9 @@ def strip_units(obj):
new_obj = xr.DataArray(
name=strip_units(obj.name), data=data, coords=coords, dims=obj.dims
)
elif isinstance(obj, xr.Variable):
data = array_strip_units(obj.data)
new_obj = obj.copy(data=data)
elif isinstance(obj, unit_registry.Quantity):
new_obj = obj.magnitude
elif isinstance(obj, (list, tuple)):
Expand All @@ -159,7 +167,7 @@ def strip_units(obj):


def attach_units(obj, units):
if not isinstance(obj, (xr.DataArray, xr.Dataset)):
if not isinstance(obj, (xr.DataArray, xr.Dataset, xr.Variable)):
return array_attach_units(obj, units.get("data", 1))

if isinstance(obj, xr.Dataset):
Expand All @@ -172,7 +180,7 @@ def attach_units(obj, units):
}

new_obj = xr.Dataset(data_vars=data_vars, coords=coords, attrs=obj.attrs)
else:
elif isinstance(obj, xr.DataArray):
# try the array name, "data" and None, then fall back to dimensionless
data_units = (
units.get(obj.name, None)
Expand All @@ -198,6 +206,16 @@ def attach_units(obj, units):
new_obj = xr.DataArray(
name=obj.name, data=data, coords=coords, attrs=attrs, dims=dims
)
else:
data_units = (
units.get(obj.name, None)
or units.get("data", None)
or units.get(None, None)
or 1
)

data = array_attach_units(obj.data, data_units)
new_obj = obj.copy(data=data)

return new_obj

Expand Down Expand Up @@ -1245,6 +1263,146 @@ def test_dot_dataarray(dtype):
assert_equal_with_units(expected, actual)


def delete_attrs(*to_delete):
def wrapper(cls):
for item in to_delete:
setattr(cls, item, None)

return cls

return wrapper


@delete_attrs(
"test_getitem_with_mask",
"test_getitem_with_mask_nd_indexer",
Comment on lines +1318 to +1330
Copy link
Collaborator Author

@keewis keewis Dec 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there better ways to exclude inherited tests? I'd like to avoid marking them as skipped since they will probably never pass.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea. You could split the parent class into two I guess?

Maybe @max-sixty knows some tricks.

"test_index_0d_string",
"test_index_0d_datetime",
"test_index_0d_timedelta64",
"test_0d_time_data",
"test_datetime64_conversion",
"test_timedelta64_conversion",
"test_pandas_period_index",
"test_1d_math",
"test_1d_reduce",
"test_array_interface",
"test___array__",
"test_copy_index",
"test_concat",
"test_concat_number_strings",
"test_concat_fixed_len_str",
"test_concat_mixed_dtypes",
"test_pandas_datetime64_with_tz",
"test_multiindex",
)
class TestVariable(VariableSubclassobjects):
@staticmethod
def cls(dims, data, *args, **kwargs):
return xr.Variable(
dims, unit_registry.Quantity(data, unit_registry.m), *args, **kwargs
)

@pytest.mark.parametrize(
"func",
(
pytest.param(
method("all"), marks=pytest.mark.xfail(reason="not implemented by pint")
),
pytest.param(
method("any"), marks=pytest.mark.xfail(reason="not implemented by pint")
),
method("argmax"),
method("argmin"),
method("argsort"),
method("cumprod"),
method("cumsum"),
method("max"),
method("mean"),
method("median"),
method("min"),
pytest.param(
method("prod"),
marks=pytest.mark.xfail(reason="not implemented by pint"),
),
method("std"),
method("sum"),
method("var"),
),
ids=repr,
)
def test_aggregation(self, func, dtype):
array = np.linspace(0, 1, 10).astype(dtype) * (
unit_registry.m if func.name != "cumprod" else unit_registry.dimensionless
)
variable = xr.Variable("x", array)

units = extract_units(func(array))
expected = attach_units(func(strip_units(variable)), units)
actual = func(variable)

xr.testing.assert_identical(expected, actual)

@pytest.mark.parametrize(
"func",
(
method("astype", np.float32),
method("conj"),
method("conjugate"),
method("searchsorted", 5),
method("clip", min=2, max=7),
),
ids=repr,
)
@pytest.mark.parametrize(
"unit,error",
(
pytest.param(1, DimensionalityError, id="no_unit"),
pytest.param(
unit_registry.dimensionless, DimensionalityError, id="dimensionless"
),
pytest.param(unit_registry.s, DimensionalityError, id="incompatible_unit"),
pytest.param(unit_registry.cm, None, id="compatible_unit"),
pytest.param(unit_registry.m, None, id="identical_unit"),
),
)
def test_numpy_methods(self, func, unit, error, dtype):
array = np.linspace(0, 1, 10).astype(dtype) * unit_registry.m
variable = xr.Variable("x", array)

args = [
item * unit if isinstance(item, (int, float, list)) else item
for item in func.args
]
kwargs = {
key: value * unit if isinstance(value, (int, float, list)) else value
for key, value in func.kwargs.items()
}

if error is not None and func.name in ("searchsorted", "clip"):
with pytest.raises(error):
func(variable, *args, **kwargs)

return

converted_args = [
strip_units(convert_units(item, {None: unit_registry.m})) for item in args
]
converted_kwargs = {
key: strip_units(convert_units(value, {None: unit_registry.m}))
for key, value in kwargs.items()
}

print("running on:", func, args, kwargs)
units = extract_units(func(array, *args, **kwargs))
expected = attach_units(
func(strip_units(variable), *converted_args, **converted_kwargs), units
)
actual = func(variable, *args, **kwargs)

assert extract_units(expected) == extract_units(actual)
xr.testing.assert_allclose(expected, actual)


class TestDataArray:
@pytest.mark.filterwarnings("error:::pint[.*]")
@pytest.mark.parametrize(
Expand Down