Skip to content

Commit e216720

Browse files
authored
More pint compatibility: silence UnitStrippedWarnings (#4163)
* globally promote UnitStrippedWarning to errors * separately test apply_ufunc with units in dims, coords and data * split the DataArray align test into data, dims and coords tests * use dtypes instead of python types and use a dtype specific fill value * rewrite the dataset align tests * compare with dtypes.NA instead of using np.isnan * mention the issue in the xfail reason * make sure the combine_* variants are properly separated from each other * improve the test case names * note that broadcast uses align * properly separate the test cases for concat * always use the same reason when xfailing units in indexes tests * also check that the replication functions work with dims and units * apply full_like to the data instead of the variable * check full_like with units in dims, data and coords separately * clearly separate the test variants of the merge tests * don't use indexes for the dataset where tests * replace numpy.testing.assert_allclose with assert numpy.allclose * remove a conditional xfail that depends on a very old pint version * use assert_identical from the local namespace * properly separate between the broadcast_like test variants * don't accept "data" as an alias of the DataArray's data * properly separate between the variants of the content manipulation tests * use assert np.allclose(...) instead of np.testing.assert_allclose(...) * don't test units in indexes in the isel tests * don't use units in indexes for the head / tail / thin tests * properly separate the variants of more tests * rewrite the squeeze tests * use assert_allclose from the module's namespace * rewrite the copy tests * xfail the equal comparison for a pint version lower than 0.14 * try to implement a duckarray friendly assert_array_equal * add tests for not raising an assertion error * skip only the dask test if it isn't installed * also check using pint if available * add a duckarray version of np.testing.assert_allclose * add both to __all__ * make both available in xarray.tests * don't inherit from VariableSubtests since that was not written to test duck arrays. * test the constant pad mode along with all other modes * remove most pint version checks, now that pint 0.13 has been released * use conda to install pint * xfail the DataArray comparison test until pint's dev version fixed it * add tests for the pad method of DataArray and Dataset * add tests for weighted * update whats-new.rst * replace assert np.allclose(...) with assert_duckarray_allclose(...) * fix the dask fallback * xfail the pint tests for now since there's a bug in pint * use utils.is_array_like and utils.is_scalar
1 parent 06c213e commit e216720

File tree

13 files changed

+919
-626
lines changed

13 files changed

+919
-626
lines changed

ci/requirements/py36-min-nep18.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ dependencies:
1111
- msgpack-python=0.6 # remove once distributed is bumped. distributed GH3491
1212
- numpy=1.17
1313
- pandas=0.25
14+
- pint=0.13
1415
- pip
1516
- pytest
1617
- pytest-cov
1718
- pytest-env
1819
- scipy=1.2
1920
- setuptools=41.2
2021
- sparse=0.8
21-
- pip:
22-
- pint==0.13

ci/requirements/py36.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies:
2828
- numba
2929
- numpy
3030
- pandas
31+
- pint
3132
- pip
3233
- pseudonetcdf
3334
- pydap
@@ -44,4 +45,3 @@ dependencies:
4445
- zarr
4546
- pip:
4647
- numbagg
47-
- pint

ci/requirements/py37-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies:
2828
- numba
2929
- numpy
3030
- pandas
31+
- pint
3132
- pip
3233
- pseudonetcdf
3334
- pydap
@@ -44,4 +45,3 @@ dependencies:
4445
- zarr
4546
- pip:
4647
- numbagg
47-
- pint

ci/requirements/py37.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies:
2828
- numba
2929
- numpy
3030
- pandas
31+
- pint
3132
- pip
3233
- pseudonetcdf
3334
- pydap
@@ -44,4 +45,3 @@ dependencies:
4445
- zarr
4546
- pip:
4647
- numbagg
47-
- pint

ci/requirements/py38-all-but-dask.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies:
2525
- numba
2626
- numpy
2727
- pandas
28+
- pint
2829
- pip
2930
- pseudonetcdf
3031
- pydap
@@ -41,4 +42,3 @@ dependencies:
4142
- zarr
4243
- pip:
4344
- numbagg
44-
- pint

ci/requirements/py38.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies:
2828
- numba
2929
- numpy
3030
- pandas
31+
- pint
3132
- pip
3233
- pseudonetcdf
3334
- pydap
@@ -44,4 +45,3 @@ dependencies:
4445
- zarr
4546
- pip:
4647
- numbagg
47-
- pint

doc/whats-new.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ New Features
9191
- Support dask handling for :py:meth:`DataArray.idxmax`, :py:meth:`DataArray.idxmin`,
9292
:py:meth:`Dataset.idxmax`, :py:meth:`Dataset.idxmin`. (:pull:`3922`, :pull:`4135`)
9393
By `Kai Mühlbauer <https://github.com/kmuehlbauer>`_ and `Pascal Bourgault <https://github.com/aulemahal>`_.
94-
- More support for unit aware arrays with pint (:pull:`3643`, :pull:`3975`)
94+
- More support for unit aware arrays with pint (:pull:`3643`, :pull:`3975`, :pull:`4163`)
9595
By `Justus Magin <https://github.com/keewis>`_.
9696
- Support overriding existing variables in ``to_zarr()`` with ``mode='a'`` even
9797
without ``append_dim``, as long as dimension sizes do not change.

xarray/core/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1434,7 +1434,7 @@ def _full_like_variable(other, fill_value, dtype: DTypeLike = None):
14341434
other.shape, fill_value, dtype=dtype, chunks=other.data.chunks
14351435
)
14361436
else:
1437-
data = np.full_like(other, fill_value, dtype=dtype)
1437+
data = np.full_like(other.data, fill_value, dtype=dtype)
14381438

14391439
return Variable(dims=other.dims, data=data, attrs=other.attrs)
14401440

xarray/core/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ def is_list_like(value: Any) -> bool:
247247
return isinstance(value, list) or isinstance(value, tuple)
248248

249249

250+
def is_array_like(value: Any) -> bool:
251+
return (
252+
hasattr(value, "ndim") and hasattr(value, "shape") and hasattr(value, "dtype")
253+
)
254+
255+
250256
def either_dict_or_kwargs(
251257
pos_kwargs: Optional[Mapping[Hashable, T]],
252258
kw_kwargs: Mapping[str, T],

xarray/testing.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
from xarray.core.indexes import default_indexes
1212
from xarray.core.variable import IndexVariable, Variable
1313

14-
__all__ = ("assert_allclose", "assert_chunks_equal", "assert_equal", "assert_identical")
14+
__all__ = (
15+
"assert_allclose",
16+
"assert_chunks_equal",
17+
"assert_duckarray_equal",
18+
"assert_duckarray_allclose",
19+
"assert_equal",
20+
"assert_identical",
21+
)
1522

1623

1724
def _decode_string_data(data):
@@ -148,6 +155,62 @@ def compat_variable(a, b):
148155
raise TypeError("{} not supported by assertion comparison".format(type(a)))
149156

150157

158+
def _format_message(x, y, err_msg, verbose):
159+
diff = x - y
160+
abs_diff = max(abs(diff))
161+
rel_diff = "not implemented"
162+
163+
n_diff = int(np.count_nonzero(diff))
164+
n_total = diff.size
165+
166+
fraction = f"{n_diff} / {n_total}"
167+
percentage = float(n_diff / n_total * 100)
168+
169+
parts = [
170+
"Arrays are not equal",
171+
err_msg,
172+
f"Mismatched elements: {fraction} ({percentage:.0f}%)",
173+
f"Max absolute difference: {abs_diff}",
174+
f"Max relative difference: {rel_diff}",
175+
]
176+
if verbose:
177+
parts += [
178+
f" x: {x!r}",
179+
f" y: {y!r}",
180+
]
181+
182+
return "\n".join(parts)
183+
184+
185+
def assert_duckarray_allclose(
186+
actual, desired, rtol=1e-07, atol=0, err_msg="", verbose=True
187+
):
188+
""" Like `np.testing.assert_allclose`, but for duckarrays. """
189+
__tracebackhide__ = True
190+
191+
allclose = duck_array_ops.allclose_or_equiv(actual, desired, rtol=rtol, atol=atol)
192+
assert allclose, _format_message(actual, desired, err_msg=err_msg, verbose=verbose)
193+
194+
195+
def assert_duckarray_equal(x, y, err_msg="", verbose=True):
196+
""" Like `np.testing.assert_array_equal`, but for duckarrays """
197+
__tracebackhide__ = True
198+
199+
if not utils.is_array_like(x) and not utils.is_scalar(x):
200+
x = np.asarray(x)
201+
202+
if not utils.is_array_like(y) and not utils.is_scalar(y):
203+
y = np.asarray(y)
204+
205+
if (utils.is_array_like(x) and utils.is_scalar(y)) or (
206+
utils.is_scalar(x) and utils.is_array_like(y)
207+
):
208+
equiv = (x == y).all()
209+
else:
210+
equiv = duck_array_ops.array_equiv(x, y)
211+
assert equiv, _format_message(x, y, err_msg=err_msg, verbose=verbose)
212+
213+
151214
def assert_chunks_equal(a, b):
152215
"""
153216
Assert that chunksizes along chunked dimensions are equal.

xarray/tests/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
from xarray.core.duck_array_ops import allclose_or_equiv # noqa: F401
1717
from xarray.core.indexing import ExplicitlyIndexed
1818
from xarray.core.options import set_options
19+
from xarray.testing import ( # noqa: F401
20+
assert_duckarray_allclose,
21+
assert_duckarray_equal,
22+
)
1923

2024
# import mpl and change the backend before other mpl imports
2125
try:

xarray/tests/test_testing.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
1+
import numpy as np
12
import pytest
23

34
import xarray as xr
45

6+
from . import has_dask
7+
8+
try:
9+
from dask.array import from_array as dask_from_array
10+
except ImportError:
11+
dask_from_array = lambda x: x
12+
13+
try:
14+
import pint
15+
16+
unit_registry = pint.UnitRegistry(force_ndarray_like=True)
17+
18+
def quantity(x):
19+
return unit_registry.Quantity(x, "m")
20+
21+
has_pint = True
22+
except ImportError:
23+
24+
def quantity(x):
25+
return x
26+
27+
has_pint = False
28+
529

630
def test_allclose_regression():
731
x = xr.DataArray(1.01)
@@ -30,3 +54,78 @@ def test_allclose_regression():
3054
def test_assert_allclose(obj1, obj2):
3155
with pytest.raises(AssertionError):
3256
xr.testing.assert_allclose(obj1, obj2)
57+
58+
59+
@pytest.mark.filterwarnings("error")
60+
@pytest.mark.parametrize(
61+
"duckarray",
62+
(
63+
pytest.param(np.array, id="numpy"),
64+
pytest.param(
65+
dask_from_array,
66+
id="dask",
67+
marks=pytest.mark.skipif(not has_dask, reason="requires dask"),
68+
),
69+
pytest.param(
70+
quantity,
71+
id="pint",
72+
marks=[
73+
pytest.mark.skipif(not has_pint, reason="requires pint"),
74+
pytest.mark.xfail(
75+
reason="inconsistencies in the return value of pint's implementation of eq"
76+
),
77+
],
78+
),
79+
),
80+
)
81+
@pytest.mark.parametrize(
82+
["obj1", "obj2"],
83+
(
84+
pytest.param([1e-10, 2], [0.0, 2.0], id="both arrays"),
85+
pytest.param([1e-17, 2], 0.0, id="second scalar"),
86+
pytest.param(0.0, [1e-17, 2], id="first scalar"),
87+
),
88+
)
89+
def test_assert_duckarray_equal_failing(duckarray, obj1, obj2):
90+
# TODO: actually check the repr
91+
a = duckarray(obj1)
92+
b = duckarray(obj2)
93+
with pytest.raises(AssertionError):
94+
xr.testing.assert_duckarray_equal(a, b)
95+
96+
97+
@pytest.mark.filterwarnings("error")
98+
@pytest.mark.parametrize(
99+
"duckarray",
100+
(
101+
pytest.param(np.array, id="numpy"),
102+
pytest.param(
103+
dask_from_array,
104+
id="dask",
105+
marks=pytest.mark.skipif(not has_dask, reason="requires dask"),
106+
),
107+
pytest.param(
108+
quantity,
109+
id="pint",
110+
marks=[
111+
pytest.mark.skipif(not has_pint, reason="requires pint"),
112+
pytest.mark.xfail(
113+
reason="inconsistencies in the return value of pint's implementation of eq"
114+
),
115+
],
116+
),
117+
),
118+
)
119+
@pytest.mark.parametrize(
120+
["obj1", "obj2"],
121+
(
122+
pytest.param([0, 2], [0.0, 2.0], id="both arrays"),
123+
pytest.param([0, 0], 0.0, id="second scalar"),
124+
pytest.param(0.0, [0, 0], id="first scalar"),
125+
),
126+
)
127+
def test_assert_duckarray_equal(duckarray, obj1, obj2):
128+
a = duckarray(obj1)
129+
b = duckarray(obj2)
130+
131+
xr.testing.assert_duckarray_equal(a, b)

0 commit comments

Comments
 (0)