From ebcbdaf39627c35ddbe31e0fa62f8d51cf3e32b0 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 21 Aug 2019 22:04:03 +0200 Subject: [PATCH 001/108] create the empty test file --- xarray/tests/test_units.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 xarray/tests/test_units.py diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py new file mode 100644 index 00000000000..e69de29bb2d From 663d0a25cd446ee5ef5c8873b70df49c7cbf5924 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 22 Aug 2019 01:46:18 +0200 Subject: [PATCH 002/108] add tests for data array aggregation functions --- xarray/tests/test_units.py | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index e69de29bb2d..225e02890dc 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -0,0 +1,55 @@ +import numpy as np +import pint +import pytest + +import xarray as xr +from xarray.core.npcompat import IS_NEP18_ACTIVE + +pytestmark = pytest.mark.skipif( + not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" +) + + +unit_registry = pint.UnitRegistry() + + +def assert_equal_with_units(a, b): + a_ = a if not isinstance(a, (xr.Dataset, xr.DataArray, xr.Variable)) else a.data + b_ = b if not isinstance(b, (xr.Dataset, xr.DataArray, xr.Variable)) else b.data + + assert np.allclose(a_, b_) + if hasattr(a_, "units") or hasattr(b_, "units"): + assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units + + +@pytest.fixture(params=[float, int]) +def dtype(request): + return request.param + + +class TestDataArray: + @pytest.mark.parametrize( + "func", + ( + np.all, + np.any, + np.argmax, + np.argmin, + np.max, + np.mean, + np.median, + np.min, + np.prod, + np.sum, + np.std, + np.var, + ), + ) + def test_aggregation(self, func, dtype): + array = np.arange(10).astype(dtype) * unit_registry.m + data_array = xr.DataArray(data=array) + + result_array = func(array) + result_data_array = func(data_array) + + assert_equal_with_units(result_array, result_data_array) From 5638c8829a9b3118f3c7d6a0a7294944d9444cd3 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 22 Aug 2019 16:13:34 +0200 Subject: [PATCH 003/108] include pint in the ci --- ci/requirements/py36.yml | 1 + ci/requirements/py37.yml | 1 + xarray/tests/test_units.py | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ci/requirements/py36.yml b/ci/requirements/py36.yml index 53da54e3b90..f0d0bf3d6e2 100644 --- a/ci/requirements/py36.yml +++ b/ci/requirements/py36.yml @@ -35,4 +35,5 @@ dependencies: - iris>=1.10 - pydap - lxml + - pint diff --git a/ci/requirements/py37.yml b/ci/requirements/py37.yml index 538d4679a79..fa48a0464af 100644 --- a/ci/requirements/py37.yml +++ b/ci/requirements/py37.yml @@ -32,5 +32,6 @@ dependencies: - cfgrib>=0.9.2 - lxml - pydap + - pint - pip: - numbagg diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 225e02890dc..1e0c8ca43d1 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1,13 +1,23 @@ import numpy as np -import pint import pytest import xarray as xr from xarray.core.npcompat import IS_NEP18_ACTIVE -pytestmark = pytest.mark.skipif( - not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" -) +try: + import pint + + has_pint = True +except ImportError: + has_pint = False + + +pytestmark = [ + pytest.mark.skipif( + not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" + ), + pytest.mark.skipif(not has_pint, reason="pint is not installed"), +] unit_registry = pint.UnitRegistry() From 46b878f4f92148a87a057c64cc2985d1f4b93d76 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 22 Aug 2019 16:24:49 +0200 Subject: [PATCH 004/108] ignore missing type annotations for pint --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 6cb58d2b9a2..d67fd9f57c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -78,6 +78,8 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-pandas.*] ignore_missing_imports = True +[mypy-pint.*] +ignore_missing_imports = True [mypy-PseudoNetCDF.*] ignore_missing_imports = True [mypy-pydap.*] From 828777f8b16ae3b0e9391b8672ed4474295b79c0 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 22 Aug 2019 16:28:55 +0200 Subject: [PATCH 005/108] really skip the tests if pint is not available --- xarray/tests/test_units.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 1e0c8ca43d1..7b67d5478d1 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -4,20 +4,10 @@ import xarray as xr from xarray.core.npcompat import IS_NEP18_ACTIVE -try: - import pint - - has_pint = True -except ImportError: - has_pint = False - - -pytestmark = [ - pytest.mark.skipif( - not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" - ), - pytest.mark.skipif(not has_pint, reason="pint is not installed"), -] +pint = pytest.importorskip("pint", reason="pint is not available") +pytestmark = pytest.mark.skipif( + not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" +) unit_registry = pint.UnitRegistry() From 4454331ec02b7d9b38e825b87adf392cf0478608 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 22 Aug 2019 16:47:57 +0200 Subject: [PATCH 006/108] remove the reason from the importorskip call --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 7b67d5478d1..164e7dce1e1 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -4,7 +4,7 @@ import xarray as xr from xarray.core.npcompat import IS_NEP18_ACTIVE -pint = pytest.importorskip("pint", reason="pint is not available") +pint = pytest.importorskip("pint") pytestmark = pytest.mark.skipif( not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" ) From 52e4259652c497cb07f84b2d50ae4c98d686d79b Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 22 Aug 2019 18:26:51 +0200 Subject: [PATCH 007/108] test that the dataarray constructor does not strip the unit --- xarray/tests/test_units.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 164e7dce1e1..914f8a68a89 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -28,6 +28,13 @@ def dtype(request): class TestDataArray: + @pytest.mark.filterwarnings("error::pint.errors.UnitStrippedWarning") + def test_init(self): + array = np.arange(10) * unit_registry.m + data_array = xr.DataArray(data=array) + + assert_equal_with_units(array, data_array) + @pytest.mark.parametrize( "func", ( From e8ed32985072e2453c4c8c03f248b4f5feac0803 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 24 Aug 2019 01:48:00 +0200 Subject: [PATCH 008/108] convert every unit stripped warning to an error --- xarray/tests/test_units.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 914f8a68a89..1264c75a216 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -5,9 +5,12 @@ from xarray.core.npcompat import IS_NEP18_ACTIVE pint = pytest.importorskip("pint") -pytestmark = pytest.mark.skipif( - not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" -) +pytestmark = [ + pytest.mark.skipif( + not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" + ), + pytest.mark.filterwarnings("error::pint.errors.UnitStrippedWarning"), +] unit_registry = pint.UnitRegistry() From cfee29ecc6ccabb6ce0b1f05a0492dde80b9588e Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 24 Aug 2019 01:49:28 +0200 Subject: [PATCH 009/108] work around pint not implementing np.allclose yet --- xarray/tests/test_units.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 1264c75a216..268881125d0 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -17,10 +17,19 @@ def assert_equal_with_units(a, b): + from pint.quantity import BaseQuantity + a_ = a if not isinstance(a, (xr.Dataset, xr.DataArray, xr.Variable)) else a.data b_ = b if not isinstance(b, (xr.Dataset, xr.DataArray, xr.Variable)) else b.data - assert np.allclose(a_, b_) + # workaround until pint implements allclose in __array_function__ + if isinstance(a_, BaseQuantity) or isinstance(b_, BaseQuantity): + assert (hasattr(a_, "magnitude") and hasattr(b_, "magnitude")) and np.allclose( + a_.magnitude, b_.magnitude + ) + else: + assert np.allclose(a_, b_) + if hasattr(a_, "units") or hasattr(b_, "units"): assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units From 4d36292b09906ad01f74068dbe84fc27c3c4ddb1 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 24 Aug 2019 13:19:00 +0200 Subject: [PATCH 010/108] remove the now unnecessary filterwarnings decorator --- xarray/tests/test_units.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 268881125d0..0c16f89fa58 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -40,7 +40,6 @@ def dtype(request): class TestDataArray: - @pytest.mark.filterwarnings("error::pint.errors.UnitStrippedWarning") def test_init(self): array = np.arange(10) * unit_registry.m data_array = xr.DataArray(data=array) From 8ab46b796074b1ad1a834acb426d37e97e224799 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 24 Aug 2019 13:19:52 +0200 Subject: [PATCH 011/108] xfail all tests that depend on pint having a __array_function__ --- xarray/tests/test_units.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 0c16f89fa58..93be43301c0 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -40,12 +40,16 @@ def dtype(request): class TestDataArray: + @pytest.mark.xfail(reason="pint does not implement __array_function__ yet") def test_init(self): array = np.arange(10) * unit_registry.m data_array = xr.DataArray(data=array) assert_equal_with_units(array, data_array) + @pytest.mark.xfail( + reason="pint does not implement __array_function__ for aggregation functions yet" + ) @pytest.mark.parametrize( "func", ( From 84002a4c90a4ddadcde2b9d18ccbe6600808484f Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 14:14:12 +0200 Subject: [PATCH 012/108] treat nans as equal --- xarray/tests/test_units.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 93be43301c0..6f7e3b8c8e4 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -25,10 +25,10 @@ def assert_equal_with_units(a, b): # workaround until pint implements allclose in __array_function__ if isinstance(a_, BaseQuantity) or isinstance(b_, BaseQuantity): assert (hasattr(a_, "magnitude") and hasattr(b_, "magnitude")) and np.allclose( - a_.magnitude, b_.magnitude + a_.magnitude, b_.magnitude, equal_nan=True ) else: - assert np.allclose(a_, b_) + assert np.allclose(a_, b_, equal_nan=True) if hasattr(a_, "units") or hasattr(b_, "units"): assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units From bde6db244fde085849a95220cfdd6551a9832e0b Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 14:39:40 +0200 Subject: [PATCH 013/108] implement tests for simple arithmetic operations --- xarray/tests/test_units.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 6f7e3b8c8e4..dd4d74c282a 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1,3 +1,5 @@ +import operator + import numpy as np import pytest @@ -75,3 +77,41 @@ def test_aggregation(self, func, dtype): result_data_array = func(data_array) assert_equal_with_units(result_array, result_data_array) + + @pytest.mark.parametrize( + "func", + ( + operator.neg, + abs, + pytest.param( + np.round, + marks=pytest.mark.xfail(reason="pint does not implement round"), + ), + ), + ) + def test_unary_operations(self, func, dtype): + array = np.arange(10).astype(dtype) * unit_registry.m + data_array = xr.DataArray(data=array) + + assert_equal_with_units(func(array), func(data_array)) + + @pytest.mark.parametrize( + "func_name,func", + ( + ("multiply", lambda x: 2 * x), + ("add", lambda x: x + x), + ("add_scalar", lambda x: x[0] + x), + pytest.param( + "matrix_multiply", + lambda x: x.T @ x, + marks=pytest.mark.xfail( + reason="pint does not support matrix multiplication yet" + ), + ), + ), + ) + def test_binary_operations(self, func_name, func, dtype): + array = np.arange(10).astype(dtype) * unit_registry.m + data_array = xr.DataArray(data=array) + + assert_equal_with_units(func(array), func(data_array)) From 26bee766a2996dabe5f60367d475d0f14aec468c Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 15:33:41 +0200 Subject: [PATCH 014/108] use param's id argument to assign readable names --- xarray/tests/test_units.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index dd4d74c282a..81d4e9889ae 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -96,21 +96,21 @@ def test_unary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) @pytest.mark.parametrize( - "func_name,func", + "func", ( - ("multiply", lambda x: 2 * x), - ("add", lambda x: x + x), - ("add_scalar", lambda x: x[0] + x), + pytest.param(lambda x: 2 * x, id="multiply"), + pytest.param(lambda x: x + x, id="add"), + pytest.param(lambda x: x[0] + x, id="add scalar"), pytest.param( - "matrix_multiply", lambda x: x.T @ x, + id="matrix multiply", marks=pytest.mark.xfail( reason="pint does not support matrix multiplication yet" ), ), ), ) - def test_binary_operations(self, func_name, func, dtype): + def test_binary_operations(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array) From e4a44a8374ae9aeb064cb24c81d1afc49420e770 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 17:04:46 +0200 Subject: [PATCH 015/108] add tests for sel() and isel() --- xarray/tests/test_units.py | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 81d4e9889ae..e167d804e3b 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -115,3 +115,62 @@ def test_binary_operations(self, func, dtype): data_array = xr.DataArray(data=array) assert_equal_with_units(func(array), func(data_array)) + + @pytest.mark.parametrize( + "indices", + ( + pytest.param( + 4, + id="single index", + marks=pytest.mark.xfail( + reason="single index isel() tries to coerce to int" + ), + ), + pytest.param([5, 2, 9, 1], id="multiple indices"), + ), + ) + def test_isel(self, indices, dtype): + array = np.arange(10).astype(dtype) * unit_registry.s + x = np.arange(len(array)) * unit_registry.m + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + assert_equal_with_units(array[indices], data_array.isel(x=indices)) + + @pytest.mark.parametrize( + "values,error", + ( + pytest.param(12, KeyError, id="single value without unit"), + pytest.param( + 12 * unit_registry.degree, + KeyError, + id="single value with incorrect unit", + ), + pytest.param( + 12 * unit_registry.s, + None, + id="single value with correct unit", + marks=pytest.mark.xfail(reason="single value tries to coerce to int"), + ), + pytest.param((10, 5, 13), KeyError, id="multiple values without unit"), + pytest.param( + (10, 5, 13) * unit_registry.degree, + KeyError, + id="multiple values with incorrect unit", + ), + pytest.param( + (10, 5, 13) * unit_registry.s, + None, + id="multiple values with correct unit", + ), + ), + ) + def test_sel(self, values, error, dtype): + array = np.linspace(5, 10, 20).astype(dtype) * unit_registry.m + x = np.arange(len(array)) * unit_registry.s + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + if error is not None: + with pytest.raises(error): + data_array.sel(x=values) + else: + assert_equal_with_units(array[values.magnitude], data_array.sel(x=values)) From e087865e251c21ee7035602a255b019abf5f65b9 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 17:20:39 +0200 Subject: [PATCH 016/108] add more readable names for the unary arithmetics --- xarray/tests/test_units.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index e167d804e3b..c1072b88f6d 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -81,10 +81,11 @@ def test_aggregation(self, func, dtype): @pytest.mark.parametrize( "func", ( - operator.neg, - abs, + pytest.param(operator.neg, id="negate"), + pytest.param(abs, id="absolute"), pytest.param( np.round, + id="round", marks=pytest.mark.xfail(reason="pint does not implement round"), ), ), From c0fddf68e4cc85def21c69cc7ec3c7f0f02dde0a Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 17:36:45 +0200 Subject: [PATCH 017/108] xfail every test that is not yet xfailing These don't pass because the constructor raises a unit stripped warning - fixed in pint#764. --- xarray/tests/test_units.py | 75 ++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c1072b88f6d..3e33e69c538 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -81,8 +81,20 @@ def test_aggregation(self, func, dtype): @pytest.mark.parametrize( "func", ( - pytest.param(operator.neg, id="negate"), - pytest.param(abs, id="absolute"), + pytest.param( + operator.neg, + id="negate", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + abs, + id="absolute", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), pytest.param( np.round, id="round", @@ -99,9 +111,27 @@ def test_unary_operations(self, func, dtype): @pytest.mark.parametrize( "func", ( - pytest.param(lambda x: 2 * x, id="multiply"), - pytest.param(lambda x: x + x, id="add"), - pytest.param(lambda x: x[0] + x, id="add scalar"), + pytest.param( + lambda x: 2 * x, + id="multiply", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + lambda x: x + x, + id="add", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + lambda x: x[0] + x, + id="add scalar", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), pytest.param( lambda x: x.T @ x, id="matrix multiply", @@ -127,7 +157,13 @@ def test_binary_operations(self, func, dtype): reason="single index isel() tries to coerce to int" ), ), - pytest.param([5, 2, 9, 1], id="multiple indices"), + pytest.param( + [5, 2, 9, 1], + id="multiple indices", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), ), ) def test_isel(self, indices, dtype): @@ -140,11 +176,21 @@ def test_isel(self, indices, dtype): @pytest.mark.parametrize( "values,error", ( - pytest.param(12, KeyError, id="single value without unit"), + pytest.param( + 12, + KeyError, + id="single value without unit", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), pytest.param( 12 * unit_registry.degree, KeyError, id="single value with incorrect unit", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), ), pytest.param( 12 * unit_registry.s, @@ -152,16 +198,29 @@ def test_isel(self, indices, dtype): id="single value with correct unit", marks=pytest.mark.xfail(reason="single value tries to coerce to int"), ), - pytest.param((10, 5, 13), KeyError, id="multiple values without unit"), + pytest.param( + (10, 5, 13), + KeyError, + id="multiple values without unit", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), + ), pytest.param( (10, 5, 13) * unit_registry.degree, KeyError, id="multiple values with incorrect unit", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), ), pytest.param( (10, 5, 13) * unit_registry.s, None, id="multiple values with correct unit", + marks=pytest.mark.xfail( + reason="pint does not implement __array_function__ yet" + ), ), ), ) From a3539a075e4d0704413a17620ebc35655afd9a23 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 21:49:39 +0200 Subject: [PATCH 018/108] only xfail if pint is not the current development version This is test is not really reliable, but sufficient for now. --- xarray/tests/test_units.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 3e33e69c538..09c1b326d20 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1,4 +1,5 @@ import operator +from distutils.version import LooseVersion import numpy as np import pytest @@ -14,6 +15,13 @@ pytest.mark.filterwarnings("error::pint.errors.UnitStrippedWarning"), ] +# pint version supporting __array_function__ +pint_version = "0.10" + + +def use_pint_dev_or_xfail(reason): + return pytest.mark.xfail(LooseVersion(pint.__version__) < pint_version, reason=reason) + unit_registry = pint.UnitRegistry() @@ -42,7 +50,7 @@ def dtype(request): class TestDataArray: - @pytest.mark.xfail(reason="pint does not implement __array_function__ yet") + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") def test_init(self): array = np.arange(10) * unit_registry.m data_array = xr.DataArray(data=array) @@ -84,14 +92,14 @@ def test_aggregation(self, func, dtype): pytest.param( operator.neg, id="negate", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), pytest.param( abs, id="absolute", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -114,21 +122,21 @@ def test_unary_operations(self, func, dtype): pytest.param( lambda x: 2 * x, id="multiply", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), pytest.param( lambda x: x + x, id="add", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), pytest.param( lambda x: x[0] + x, id="add scalar", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -160,7 +168,7 @@ def test_binary_operations(self, func, dtype): pytest.param( [5, 2, 9, 1], id="multiple indices", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -180,7 +188,7 @@ def test_isel(self, indices, dtype): 12, KeyError, id="single value without unit", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -188,7 +196,7 @@ def test_isel(self, indices, dtype): 12 * unit_registry.degree, KeyError, id="single value with incorrect unit", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -202,7 +210,7 @@ def test_isel(self, indices, dtype): (10, 5, 13), KeyError, id="multiple values without unit", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -210,7 +218,7 @@ def test_isel(self, indices, dtype): (10, 5, 13) * unit_registry.degree, KeyError, id="multiple values with incorrect unit", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), @@ -218,7 +226,7 @@ def test_isel(self, indices, dtype): (10, 5, 13) * unit_registry.s, None, id="multiple values with correct unit", - marks=pytest.mark.xfail( + marks=use_pint_dev_or_xfail( reason="pint does not implement __array_function__ yet" ), ), From f1f21cffbb01e6652528a515d8dfb5ba10fbfa45 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 23:15:48 +0200 Subject: [PATCH 019/108] always use lists instead of tuples for indexing --- xarray/tests/test_units.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 09c1b326d20..bd5ee149628 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -207,7 +207,7 @@ def test_isel(self, indices, dtype): marks=pytest.mark.xfail(reason="single value tries to coerce to int"), ), pytest.param( - (10, 5, 13), + [10, 5, 13], KeyError, id="multiple values without unit", marks=use_pint_dev_or_xfail( @@ -215,7 +215,7 @@ def test_isel(self, indices, dtype): ), ), pytest.param( - (10, 5, 13) * unit_registry.degree, + [10, 5, 13] * unit_registry.degree, KeyError, id="multiple values with incorrect unit", marks=use_pint_dev_or_xfail( @@ -223,7 +223,7 @@ def test_isel(self, indices, dtype): ), ), pytest.param( - (10, 5, 13) * unit_registry.s, + [10, 5, 13] * unit_registry.s, None, id="multiple values with correct unit", marks=use_pint_dev_or_xfail( From 648087fce16c975e3c169b5c7df0e7807ba9677b Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 23:16:26 +0200 Subject: [PATCH 020/108] add tests for loc and squeeze --- xarray/tests/test_units.py | 85 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index bd5ee149628..f1677ac7a51 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -242,3 +242,88 @@ def test_sel(self, values, error, dtype): data_array.sel(x=values) else: assert_equal_with_units(array[values.magnitude], data_array.sel(x=values)) + + @pytest.mark.parametrize( + "values,error", + ( + pytest.param( + 12, + KeyError, + id="single value without unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + 12 * unit_registry.degree, + KeyError, + id="single value with incorrect unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + 12 * unit_registry.s, + None, + id="single value with correct unit", + marks=pytest.mark.xfail(reason="single value tries to coerce to int"), + ), + pytest.param( + [10, 5, 13], + KeyError, + id="multiple values without unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + [10, 5, 13] * unit_registry.degree, + KeyError, + id="multiple values with incorrect unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + [10, 5, 13] * unit_registry.s, + None, + id="multiple values with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + ), + ) + def test_loc(self, values, error, dtype): + array = np.linspace(5, 10, 20).astype(dtype) * unit_registry.m + x = np.arange(len(array)) * unit_registry.s + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + if error is not None: + with pytest.raises(error): + data_array.loc[values] + else: + assert_equal_with_units(array[values.magnitude], data_array.loc[values]) + + @pytest.mark.xfail(reason="indexing calls np.asarray") + @pytest.mark.parametrize("shape", ( + (10, 20), + (10, 20, 1), + (10, 1, 20), + (1, 10, 20), + (1, 10, 1, 20), + )) + def test_squeeze(self, shape, dtype): + names = "xyzt" + coords = { + name: np.arange(length).astype(dtype) * (unit_registry.m if name != "t" else unit_registry.s) + for name, length in zip(names, shape) + } + array = np.arange(10 * 20).astype(dtype).reshape(shape) * unit_registry.J + data_array = xr.DataArray(data=array, coords=coords, dims=tuple(names[:len(shape)])) + + assert_equal_with_units(np.squeeze(array), data_array.squeeze()) + # try squeezing the dimensions separately + names = tuple(dim for dim, coord in coords.items() if len(coord) == 1) + for index, name in enumerate(names): + assert_equal_with_units(np.squeeze(array, axis=index), data_array.squeeze(dim=name)) From d02cf4095a0704fe35967a4c1852eb754f8a605a Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 25 Aug 2019 23:31:49 +0200 Subject: [PATCH 021/108] black --- xarray/tests/test_units.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index f1677ac7a51..708868d7407 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -20,7 +20,9 @@ def use_pint_dev_or_xfail(reason): - return pytest.mark.xfail(LooseVersion(pint.__version__) < pint_version, reason=reason) + return pytest.mark.xfail( + LooseVersion(pint.__version__) < pint_version, reason=reason + ) unit_registry = pint.UnitRegistry() @@ -316,14 +318,19 @@ def test_loc(self, values, error, dtype): def test_squeeze(self, shape, dtype): names = "xyzt" coords = { - name: np.arange(length).astype(dtype) * (unit_registry.m if name != "t" else unit_registry.s) + name: np.arange(length).astype(dtype) + * (unit_registry.m if name != "t" else unit_registry.s) for name, length in zip(names, shape) } array = np.arange(10 * 20).astype(dtype).reshape(shape) * unit_registry.J - data_array = xr.DataArray(data=array, coords=coords, dims=tuple(names[:len(shape)])) + data_array = xr.DataArray( + data=array, coords=coords, dims=tuple(names[: len(shape)]) + ) assert_equal_with_units(np.squeeze(array), data_array.squeeze()) # try squeezing the dimensions separately names = tuple(dim for dim, coord in coords.items() if len(coord) == 1) for index, name in enumerate(names): - assert_equal_with_units(np.squeeze(array, axis=index), data_array.squeeze(dim=name)) + assert_equal_with_units( + np.squeeze(array, axis=index), data_array.squeeze(dim=name) + ) From 7603ea28ac5c00ef2209b905eb665b76114e2717 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 26 Aug 2019 01:43:55 +0200 Subject: [PATCH 022/108] add names and xfail marks to the parameters --- xarray/tests/test_units.py | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 708868d7407..2fef4be9feb 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -307,14 +307,38 @@ def test_loc(self, values, error, dtype): else: assert_equal_with_units(array[values.magnitude], data_array.loc[values]) - @pytest.mark.xfail(reason="indexing calls np.asarray") - @pytest.mark.parametrize("shape", ( - (10, 20), - (10, 20, 1), - (10, 1, 20), - (1, 10, 20), - (1, 10, 1, 20), - )) + @pytest.mark.parametrize( + "shape", + ( + pytest.param( + (10, 20), + id="nothing squeezable", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__" + ), + ), + pytest.param( + (10, 20, 1), + id="last dimension squeezable", + marks=pytest.mark.xfail(reason="indexing calls np.asarray"), + ), + pytest.param( + (10, 1, 20), + id="middle dimension squeezable", + marks=pytest.mark.xfail(reason="indexing calls np.asarray"), + ), + pytest.param( + (1, 10, 20), + id="first dimension squeezable", + marks=pytest.mark.xfail(reason="indexing calls np.asarray"), + ), + pytest.param( + (1, 10, 1, 20), + id="first and last dimension squeezable", + marks=pytest.mark.xfail(reason="indexing calls np.asarray"), + ), + ), + ) def test_squeeze(self, shape, dtype): names = "xyzt" coords = { From a43313a1dee531eb958a2226f4b29b5154c6fc57 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 26 Aug 2019 01:44:56 +0200 Subject: [PATCH 023/108] add tests for interp and interp_like --- xarray/tests/test_units.py | 133 +++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 2fef4be9feb..f9c3c38965e 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -46,6 +46,24 @@ def assert_equal_with_units(a, b): assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units +def strip_units(data_array): + def magnitude(da): + if isinstance(da, xr.Variable): + data = da.data + else: + data = da + + try: + return data.magnitude + except AttributeError: + return data + + array = magnitude(data_array) + coords = {name: magnitude(values) for name, values in data_array.coords.items()} + + return xr.DataArray(data=array, coords=coords, dims=tuple(coords.keys())) + + @pytest.fixture(params=[float, int]) def dtype(request): return request.param @@ -352,9 +370,124 @@ def test_squeeze(self, shape, dtype): ) assert_equal_with_units(np.squeeze(array), data_array.squeeze()) + # try squeezing the dimensions separately names = tuple(dim for dim, coord in coords.items() if len(coord) == 1) for index, name in enumerate(names): assert_equal_with_units( np.squeeze(array, axis=index), data_array.squeeze(dim=name) ) + + @pytest.mark.parametrize( + "unit,error", + ( + pytest.param( + 1, + None, + id="without unit", + marks=pytest.mark.xfail(reason="retrieving the values property fails"), + ), + pytest.param( + unit_registry.dimensionless, + None, + id="dimensionless", + marks=pytest.mark.xfail(reason="retrieving the values property fails"), + ), + pytest.param( + unit_registry.s, + None, + id="with incorrect unit", + marks=pytest.mark.xfail(reason="retrieving the values property fails"), + ), + pytest.param( + unit_registry.m, + None, + id="with correct unit", + marks=pytest.mark.xfail(reason="retrieving the values property fails"), + ), + ), + ) + def test_interp(self, unit, error): + array = np.linspace(1, 2, 10 * 5).reshape(10, 5) * unit_registry.degK + new_coords = (np.arange(10) + 0.5) * unit + coords = { + "x": np.arange(10) * unit_registry.m, + "y": np.arange(5) * unit_registry.m, + } + + data_array = xr.DataArray(array, coords=coords, dims=("x", "y")) + + if error is not None: + with pytest.raises(error): + data_array.interp(x=new_coords) + else: + result_array = strip_units(data_array).interp( + x=( + new_coords.magnitude + if hasattr(new_coords, "magnitude") + else new_coords + ) + * unit_registry.degK + ) + result_data_array = data_array.interp(x=new_coords) + + assert_equal_with_units(result_array, result_data_array) + + @pytest.mark.parametrize( + "unit,error", + ( + pytest.param( + 1, + None, + id="without unit", + marks=pytest.mark.xfail(reason="reindex does not work with units"), + ), + pytest.param( + unit_registry.dimensionless, + None, + id="dimensionless", + marks=pytest.mark.xfail(reason="reindex does not work with units"), + ), + pytest.param( + unit_registry.s, + None, + id="with incorrect unit", + marks=pytest.mark.xfail(reason="reindex does not work with pint"), + ), + pytest.param( + unit_registry.m, + None, + id="with correct unit", + marks=pytest.mark.xfail(reason="reindex does not work with pint"), + ), + ), + ) + def test_interp_like(self, unit, error): + array = np.linspace(1, 2, 10 * 5).reshape(10, 5) * unit_registry.degK + coords = { + "x": (np.arange(10) + 0.3) * unit_registry.m, + "y": (np.arange(5) + 0.3) * unit_registry.m, + } + + data_array = xr.DataArray(array, coords=coords, dims=("x", "y")) + new_data_array = xr.DataArray( + data=np.empty((20, 10)), + coords={"x": np.arange(20) * unit, "y": np.arange(10) * unit}, + dims=("x", "y"), + ) + + if error is not None: + with pytest.raises(error): + data_array.interp_like(x=new_data_array) + else: + result_array = ( + xr.DataArray( + data=array.magnitude, + coords={name: value.magnitude for name, value in coords.items()}, + dims=("x", "y"), + ).interp_like(strip_units(new_data_array)) + * unit_registry.degK + ) + result_data_array = data_array.interp_like(new_data_array) + + assert_equal_with_units(result_array, result_data_array) From b36041d2bacd13da0517b8ac7eae62315ddc56ef Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 26 Aug 2019 23:13:11 +0200 Subject: [PATCH 024/108] implement tests for reindex --- xarray/tests/test_units.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index f9c3c38965e..103ccc73a57 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -491,3 +491,38 @@ def test_interp_like(self, unit, error): result_data_array = data_array.interp_like(new_data_array) assert_equal_with_units(result_array, result_data_array) + + @pytest.mark.parametrize( + "unit,error", + ( + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), + ), + ) + def test_reindex(self, unit, error): + array = np.linspace(1, 2, 10 * 5).reshape(10, 5) * unit_registry.degK + new_coords = (np.arange(10) + 0.5) * unit + coords = { + "x": np.arange(10) * unit_registry.m, + "y": np.arange(5) * unit_registry.m, + } + + data_array = xr.DataArray(array, coords=coords, dims=("x", "y")) + + if error is not None: + with pytest.raises(error): + data_array.interp(x=new_coords) + else: + result_array = strip_units(data_array).reindex( + x=( + new_coords.magnitude + if hasattr(new_coords, "magnitude") + else new_coords + ) + * unit_registry.degK + ) + result_data_array = data_array.reindex(x=new_coords) + + assert_equal_with_units(result_array, result_data_array) From a7f88a20f17b15f48f4e89d20448aa4a6b0f6c6b Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 26 Aug 2019 23:13:32 +0200 Subject: [PATCH 025/108] remove the xfail marks where it is not clear yet that pint is to blame --- xarray/tests/test_units.py | 98 ++++++-------------------------------- 1 file changed, 15 insertions(+), 83 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 103ccc73a57..48e58794460 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -178,13 +178,7 @@ def test_binary_operations(self, func, dtype): @pytest.mark.parametrize( "indices", ( - pytest.param( - 4, - id="single index", - marks=pytest.mark.xfail( - reason="single index isel() tries to coerce to int" - ), - ), + pytest.param(4, id="single index"), pytest.param( [5, 2, 9, 1], id="multiple indices", @@ -221,10 +215,7 @@ def test_isel(self, indices, dtype): ), ), pytest.param( - 12 * unit_registry.s, - None, - id="single value with correct unit", - marks=pytest.mark.xfail(reason="single value tries to coerce to int"), + 12 * unit_registry.s, None, id="single value with correct unit" ), pytest.param( [10, 5, 13], @@ -283,10 +274,7 @@ def test_sel(self, values, error, dtype): ), ), pytest.param( - 12 * unit_registry.s, - None, - id="single value with correct unit", - marks=pytest.mark.xfail(reason="single value tries to coerce to int"), + 12 * unit_registry.s, None, id="single value with correct unit" ), pytest.param( [10, 5, 13], @@ -335,26 +323,10 @@ def test_loc(self, values, error, dtype): reason="pint does not implement __array_function__" ), ), - pytest.param( - (10, 20, 1), - id="last dimension squeezable", - marks=pytest.mark.xfail(reason="indexing calls np.asarray"), - ), - pytest.param( - (10, 1, 20), - id="middle dimension squeezable", - marks=pytest.mark.xfail(reason="indexing calls np.asarray"), - ), - pytest.param( - (1, 10, 20), - id="first dimension squeezable", - marks=pytest.mark.xfail(reason="indexing calls np.asarray"), - ), - pytest.param( - (1, 10, 1, 20), - id="first and last dimension squeezable", - marks=pytest.mark.xfail(reason="indexing calls np.asarray"), - ), + pytest.param((10, 20, 1), id="last dimension squeezable"), + pytest.param((10, 1, 20), id="middle dimension squeezable"), + pytest.param((1, 10, 20), id="first dimension squeezable"), + pytest.param((1, 10, 1, 20), id="first and last dimension squeezable"), ), ) def test_squeeze(self, shape, dtype): @@ -381,30 +353,10 @@ def test_squeeze(self, shape, dtype): @pytest.mark.parametrize( "unit,error", ( - pytest.param( - 1, - None, - id="without unit", - marks=pytest.mark.xfail(reason="retrieving the values property fails"), - ), - pytest.param( - unit_registry.dimensionless, - None, - id="dimensionless", - marks=pytest.mark.xfail(reason="retrieving the values property fails"), - ), - pytest.param( - unit_registry.s, - None, - id="with incorrect unit", - marks=pytest.mark.xfail(reason="retrieving the values property fails"), - ), - pytest.param( - unit_registry.m, - None, - id="with correct unit", - marks=pytest.mark.xfail(reason="retrieving the values property fails"), - ), + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), ), ) def test_interp(self, unit, error): @@ -436,30 +388,10 @@ def test_interp(self, unit, error): @pytest.mark.parametrize( "unit,error", ( - pytest.param( - 1, - None, - id="without unit", - marks=pytest.mark.xfail(reason="reindex does not work with units"), - ), - pytest.param( - unit_registry.dimensionless, - None, - id="dimensionless", - marks=pytest.mark.xfail(reason="reindex does not work with units"), - ), - pytest.param( - unit_registry.s, - None, - id="with incorrect unit", - marks=pytest.mark.xfail(reason="reindex does not work with pint"), - ), - pytest.param( - unit_registry.m, - None, - id="with correct unit", - marks=pytest.mark.xfail(reason="reindex does not work with pint"), - ), + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), ), ) def test_interp_like(self, unit, error): From 32562fee106c2e73ed746ef8b5f568cb1a5f7a16 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 28 Aug 2019 01:08:37 +0200 Subject: [PATCH 026/108] add tests for reindex_like --- xarray/tests/test_units.py | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 48e58794460..aa2ee90b90d 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -458,3 +458,70 @@ def test_reindex(self, unit, error): result_data_array = data_array.reindex(x=new_coords) assert_equal_with_units(result_array, result_data_array) + + @pytest.mark.parametrize( + "unit,error", + ( + pytest.param( + 1, + None, + id="without unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.dimensionless, + None, + id="dimensionless", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.s, + None, + id="with incorrect unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.m, + None, + id="with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + ), + ) + def test_reindex_like(self, unit, error): + array = np.linspace(1, 2, 10 * 5).reshape(10, 5) * unit_registry.degK + coords = { + "x": (np.arange(10) + 0.3) * unit_registry.m, + "y": (np.arange(5) + 0.3) * unit_registry.m, + } + + data_array = xr.DataArray(array, coords=coords, dims=("x", "y")) + new_data_array = xr.DataArray( + data=np.empty((20, 10)), + coords={"x": np.arange(20) * unit, "y": np.arange(10) * unit}, + dims=("x", "y"), + ) + + if error is not None: + with pytest.raises(error): + data_array.reindex_like(new_data_array) + else: + result_array = ( + xr.DataArray( + data=array.magnitude, + coords={name: value.magnitude for name, value in coords.items()}, + dims=("x", "y"), + ).reindex_like(strip_units(new_data_array)) + * unit_registry.degK + ) + result_data_array = data_array.reindex_like(new_data_array) + + assert_equal_with_units(result_array, result_data_array) From 9b51812d3cc28a35d82dfe104bfa49a8aa90914f Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 28 Aug 2019 01:08:58 +0200 Subject: [PATCH 027/108] don't pass the new DataArray to a kwarg --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index aa2ee90b90d..14cc5cc14ba 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -410,7 +410,7 @@ def test_interp_like(self, unit, error): if error is not None: with pytest.raises(error): - data_array.interp_like(x=new_data_array) + data_array.interp_like(new_data_array) else: result_array = ( xr.DataArray( From 2eb12c48ede6ba5cc938f190ad9b237840ef2fad Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 28 Aug 2019 01:09:50 +0200 Subject: [PATCH 028/108] xfail if not on pint dev --- xarray/tests/test_units.py | 164 ++++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 20 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 14cc5cc14ba..8c803a64b49 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -178,7 +178,13 @@ def test_binary_operations(self, func, dtype): @pytest.mark.parametrize( "indices", ( - pytest.param(4, id="single index"), + pytest.param( + 4, + id="single index", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), pytest.param( [5, 2, 9, 1], id="multiple indices", @@ -215,7 +221,12 @@ def test_isel(self, indices, dtype): ), ), pytest.param( - 12 * unit_registry.s, None, id="single value with correct unit" + 12 * unit_registry.s, + None, + id="single value with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), ), pytest.param( [10, 5, 13], @@ -274,7 +285,12 @@ def test_sel(self, values, error, dtype): ), ), pytest.param( - 12 * unit_registry.s, None, id="single value with correct unit" + 12 * unit_registry.s, + None, + id="single value with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), ), pytest.param( [10, 5, 13], @@ -320,13 +336,37 @@ def test_loc(self, values, error, dtype): (10, 20), id="nothing squeezable", marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__" + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + (10, 20, 1), + id="last dimension squeezable", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + (10, 1, 20), + id="middle dimension squeezable", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + (1, 10, 20), + id="first dimension squeezable", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + (1, 10, 1, 20), + id="first and last dimension squeezable", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" ), ), - pytest.param((10, 20, 1), id="last dimension squeezable"), - pytest.param((10, 1, 20), id="middle dimension squeezable"), - pytest.param((1, 10, 20), id="first dimension squeezable"), - pytest.param((1, 10, 1, 20), id="first and last dimension squeezable"), ), ) def test_squeeze(self, shape, dtype): @@ -353,10 +393,38 @@ def test_squeeze(self, shape, dtype): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), - pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param( + 1, + None, + id="without unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.dimensionless, + None, + id="dimensionless", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.s, + None, + id="with incorrect unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.m, + None, + id="with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), ), ) def test_interp(self, unit, error): @@ -388,10 +456,38 @@ def test_interp(self, unit, error): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), - pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param( + 1, + None, + id="without unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.dimensionless, + None, + id="dimensionless", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.s, + None, + id="with incorrect unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.m, + None, + id="with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), ), ) def test_interp_like(self, unit, error): @@ -427,10 +523,38 @@ def test_interp_like(self, unit, error): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), - pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param( + 1, + None, + id="without unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.dimensionless, + None, + id="dimensionless", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.s, + None, + id="with incorrect unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), + pytest.param( + unit_registry.m, + None, + id="with correct unit", + marks=use_pint_dev_or_xfail( + reason="pint does not implement __array_function__ yet" + ), + ), ), ) def test_reindex(self, unit, error): From 6c27a87220afb8ac762693f42f23eb22ee81eaf7 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 30 Aug 2019 15:17:24 +0200 Subject: [PATCH 029/108] refactor the tests --- xarray/tests/test_units.py | 396 ++++++++----------------------------- 1 file changed, 87 insertions(+), 309 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 8c803a64b49..0ad054c54b2 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -136,30 +136,13 @@ def test_unary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( "func", ( - pytest.param( - lambda x: 2 * x, - id="multiply", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - lambda x: x + x, - id="add", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - lambda x: x[0] + x, - id="add scalar", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(lambda x: 2 * x, id="multiply"), + pytest.param(lambda x: x + x, id="add"), + pytest.param(lambda x: x[0] + x, id="add scalar"), pytest.param( lambda x: x.T @ x, id="matrix multiply", @@ -175,23 +158,12 @@ def test_binary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( "indices", ( - pytest.param( - 4, - id="single index", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [5, 2, 9, 1], - id="multiple indices", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(4, id="single index"), + pytest.param([5, 2, 9, 1], id="multiple indices"), ), ) def test_isel(self, indices, dtype): @@ -201,172 +173,82 @@ def test_isel(self, indices, dtype): assert_equal_with_units(array[indices], data_array.isel(x=indices)) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( - "values,error", + "values", ( - pytest.param( - 12, - KeyError, - id="single value without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - 12 * unit_registry.degree, - KeyError, - id="single value with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - 12 * unit_registry.s, - None, - id="single value with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [10, 5, 13], - KeyError, - id="multiple values without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [10, 5, 13] * unit_registry.degree, - KeyError, - id="multiple values with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [10, 5, 13] * unit_registry.s, - None, - id="multiple values with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(12, id="single value"), + pytest.param([10, 5, 13], id="list of multiple values"), + pytest.param(np.array([9, 3, 7, 12]), id="array of multiple values"), + ), + ) + @pytest.mark.parametrize( + "units,error", + ( + pytest.param(1, KeyError, id="no units"), + pytest.param(unit_registry.dimensionless, KeyError, id="dimensionless"), + pytest.param(unit_registry.degree, KeyError, id="incorrect unit"), + pytest.param(unit_registry.s, None, id="correct unit"), ), ) - def test_sel(self, values, error, dtype): + def test_sel(self, values, units, error, dtype): array = np.linspace(5, 10, 20).astype(dtype) * unit_registry.m x = np.arange(len(array)) * unit_registry.s data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + values_with_units = values * units + if error is not None: with pytest.raises(error): - data_array.sel(x=values) + data_array.sel(x=values_with_units) else: - assert_equal_with_units(array[values.magnitude], data_array.sel(x=values)) + result_array = array[values] + result_data_array = data_array.sel(x=values_with_units) + assert_equal_with_units(result_array, result_data_array) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( - "values,error", + "values", ( - pytest.param( - 12, - KeyError, - id="single value without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - 12 * unit_registry.degree, - KeyError, - id="single value with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - 12 * unit_registry.s, - None, - id="single value with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [10, 5, 13], - KeyError, - id="multiple values without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [10, 5, 13] * unit_registry.degree, - KeyError, - id="multiple values with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - [10, 5, 13] * unit_registry.s, - None, - id="multiple values with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(12, id="single value"), + pytest.param([10, 5, 13], id="list of multiple values"), + pytest.param(np.array([9, 3, 7, 12]), id="array of multiple values"), ), ) - def test_loc(self, values, error, dtype): + @pytest.mark.parametrize( + "units,error", + ( + pytest.param(1, KeyError, id="no units"), + pytest.param(unit_registry.dimensionless, KeyError, id="dimensionless"), + pytest.param(unit_registry.degree, KeyError, id="incorrect unit"), + pytest.param(unit_registry.s, None, id="correct unit"), + ), + ) + def test_loc(self, values, units, error, dtype): array = np.linspace(5, 10, 20).astype(dtype) * unit_registry.m x = np.arange(len(array)) * unit_registry.s data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + values_with_units = values * units + if error is not None: with pytest.raises(error): - data_array.loc[values] + data_array.loc[values_with_units] else: - assert_equal_with_units(array[values.magnitude], data_array.loc[values]) + result_array = array[values] + result_data_array = data_array.loc[values_with_units] + assert_equal_with_units(result_array, result_data_array) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.xfail(reason="tries to coerce using asarray") @pytest.mark.parametrize( "shape", ( - pytest.param( - (10, 20), - id="nothing squeezable", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - (10, 20, 1), - id="last dimension squeezable", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - (10, 1, 20), - id="middle dimension squeezable", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - (1, 10, 20), - id="first dimension squeezable", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - (1, 10, 1, 20), - id="first and last dimension squeezable", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param((10, 20), id="nothing squeezable"), + pytest.param((10, 20, 1), id="last dimension squeezable"), + pytest.param((10, 1, 20), id="middle dimension squeezable"), + pytest.param((1, 10, 20), id="first dimension squeezable"), + pytest.param((1, 10, 1, 20), id="first and last dimension squeezable"), ), ) def test_squeeze(self, shape, dtype): @@ -381,7 +263,9 @@ def test_squeeze(self, shape, dtype): data=array, coords=coords, dims=tuple(names[: len(shape)]) ) - assert_equal_with_units(np.squeeze(array), data_array.squeeze()) + result_array = array.squeeze() + result_data_array = data_array.squeeze() + assert_equal_with_units(result_array, result_data_array) # try squeezing the dimensions separately names = tuple(dim for dim, coord in coords.items() if len(coord) == 1) @@ -390,41 +274,15 @@ def test_squeeze(self, shape, dtype): np.squeeze(array, axis=index), data_array.squeeze(dim=name) ) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.xfail(reason="interp() mistakes quantities as objects instead of numeric type arrays") @pytest.mark.parametrize( "unit,error", ( - pytest.param( - 1, - None, - id="without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.dimensionless, - None, - id="dimensionless", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.s, - None, - id="with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.m, - None, - id="with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), ), ) def test_interp(self, unit, error): @@ -441,53 +299,25 @@ def test_interp(self, unit, error): with pytest.raises(error): data_array.interp(x=new_coords) else: + new_coords_ = ( + new_coords.magnitude if hasattr(new_coords, "magnitude") else new_coords + ) result_array = strip_units(data_array).interp( - x=( - new_coords.magnitude - if hasattr(new_coords, "magnitude") - else new_coords - ) - * unit_registry.degK + x=new_coords_ * unit_registry.degK ) result_data_array = data_array.interp(x=new_coords) assert_equal_with_units(result_array, result_data_array) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.xfail(reason="tries to coerce using asarray") @pytest.mark.parametrize( "unit,error", ( - pytest.param( - 1, - None, - id="without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.dimensionless, - None, - id="dimensionless", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.s, - None, - id="with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.m, - None, - id="with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), ), ) def test_interp_like(self, unit, error): @@ -520,41 +350,15 @@ def test_interp_like(self, unit, error): assert_equal_with_units(result_array, result_data_array) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.xfail(reason="pint does not implement np.result_type in __array_function__ yet") @pytest.mark.parametrize( "unit,error", ( - pytest.param( - 1, - None, - id="without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.dimensionless, - None, - id="dimensionless", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.s, - None, - id="with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.m, - None, - id="with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), ), ) def test_reindex(self, unit, error): @@ -583,41 +387,15 @@ def test_reindex(self, unit, error): assert_equal_with_units(result_array, result_data_array) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.xfail(reason="pint does not implement np.result_type in __array_function__ yet") @pytest.mark.parametrize( "unit,error", ( - pytest.param( - 1, - None, - id="without unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.dimensionless, - None, - id="dimensionless", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.s, - None, - id="with incorrect unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - unit_registry.m, - None, - id="with correct unit", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(1, None, id="without unit"), + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.s, None, id="with incorrect unit"), + pytest.param(unit_registry.m, None, id="with correct unit"), ), ) def test_reindex_like(self, unit, error): From da2904a4ebfae0f5a21d101dae419e93401530d3 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 31 Aug 2019 00:48:17 +0200 Subject: [PATCH 030/108] add tests for univariate and bivariate ufuncs --- xarray/tests/test_units.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 0ad054c54b2..6787786b511 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -158,6 +158,32 @@ def test_binary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.parametrize("units,error", ( + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param(unit_registry.m, pint.errors.DimensionalityError, id="incorrect unit"), + pytest.param(unit_registry.degree, None, id="correct unit"), + )) + def test_univariate_ufunc(self, units, error, dtype): + array = np.arange(10).astype(dtype) * units + data_array = xr.DataArray(data=array) + + if error is not None: + with pytest.raises(error): + np.sin(data_array) + else: + assert_equal_with_units(np.sin(array), np.sin(data_array)) + + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + def test_bivariate_ufunc(self, dtype): + unit = unit_registry.m + array = np.arange(10).astype(dtype) * unit + data_array = xr.DataArray(data=array) + + result_array = np.maximum(array, 0 * unit) + assert_equal_with_units(result_array, np.maximum(data_array, 0 * unit)) + assert_equal_with_units(result_array, np.maximum(0 * unit, data_array)) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( "indices", From e4a006f045c4a0d4c8891a16ee4d6800ee24cd86 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 31 Aug 2019 00:52:40 +0200 Subject: [PATCH 031/108] black --- xarray/tests/test_units.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 6787786b511..264ea1b0c47 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -159,11 +159,16 @@ def test_binary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") - @pytest.mark.parametrize("units,error", ( - pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.m, pint.errors.DimensionalityError, id="incorrect unit"), - pytest.param(unit_registry.degree, None, id="correct unit"), - )) + @pytest.mark.parametrize( + "units,error", + ( + pytest.param(unit_registry.dimensionless, None, id="dimensionless"), + pytest.param( + unit_registry.m, pint.errors.DimensionalityError, id="incorrect unit" + ), + pytest.param(unit_registry.degree, None, id="correct unit"), + ), + ) def test_univariate_ufunc(self, units, error, dtype): array = np.arange(10).astype(dtype) * units data_array = xr.DataArray(data=array) @@ -301,7 +306,9 @@ def test_squeeze(self, shape, dtype): ) @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") - @pytest.mark.xfail(reason="interp() mistakes quantities as objects instead of numeric type arrays") + @pytest.mark.xfail( + reason="interp() mistakes quantities as objects instead of numeric type arrays" + ) @pytest.mark.parametrize( "unit,error", ( @@ -377,7 +384,9 @@ def test_interp_like(self, unit, error): assert_equal_with_units(result_array, result_data_array) @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") - @pytest.mark.xfail(reason="pint does not implement np.result_type in __array_function__ yet") + @pytest.mark.xfail( + reason="pint does not implement np.result_type in __array_function__ yet" + ) @pytest.mark.parametrize( "unit,error", ( @@ -414,7 +423,9 @@ def test_reindex(self, unit, error): assert_equal_with_units(result_array, result_data_array) @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") - @pytest.mark.xfail(reason="pint does not implement np.result_type in __array_function__ yet") + @pytest.mark.xfail( + reason="pint does not implement np.result_type in __array_function__ yet" + ) @pytest.mark.parametrize( "unit,error", ( From c20989dedeff2a25bbd0dc4bf3c48e553dfde317 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 1 Sep 2019 22:03:14 +0200 Subject: [PATCH 032/108] xfail aggregation only if pint does not implement __array_function__ yet --- xarray/tests/test_units.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 264ea1b0c47..d4e4081e52a 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -77,9 +77,7 @@ def test_init(self): assert_equal_with_units(array, data_array) - @pytest.mark.xfail( - reason="pint does not implement __array_function__ for aggregation functions yet" - ) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( "func", ( From 179baaaa9997bd7dae0dba43d4a715ac5c772c94 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 3 Sep 2019 21:31:19 +0200 Subject: [PATCH 033/108] remove the global filterwarnings mark apparently, this caused the tests to change behavior, resulting in different errors, or causing tests to pass that should actually fail. --- xarray/tests/test_units.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d4e4081e52a..aed95830f05 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -12,7 +12,7 @@ pytest.mark.skipif( not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" ), - pytest.mark.filterwarnings("error::pint.errors.UnitStrippedWarning"), + # pytest.mark.filterwarnings("ignore:::pint[.*]"), ] # pint version supporting __array_function__ @@ -71,6 +71,7 @@ def dtype(request): class TestDataArray: @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @pytest.mark.filterwarnings("error:::pint[.*]") def test_init(self): array = np.arange(10) * unit_registry.m data_array = xr.DataArray(data=array) From 927fd9e815d076685035755cee07fdc4a2d886f0 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 3 Sep 2019 21:35:58 +0200 Subject: [PATCH 034/108] add a test case for the repr --- xarray/tests/test_units.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index aed95830f05..bab8eb0733e 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -78,6 +78,16 @@ def test_init(self): assert_equal_with_units(array, data_array) + @pytest.mark.filterwarnings("error:::pint[.*]") + def test_repr(self): + array = np.linspace(1, 2, 10) * unit_registry.m + x = np.arange(len(array)) * unit_registry.s + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + # FIXME: this just checks that the repr does not raise + # warnings or errors, but does not check the result + repr(data_array) + @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") @pytest.mark.parametrize( "func", From c163aef2ab1398cd66c4d38ef031edd125fb1a61 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 3 Sep 2019 22:33:12 +0200 Subject: [PATCH 035/108] create a pytest mark that explicitly requires pint's __array_function__ --- xarray/tests/test_units.py | 55 +++++++++++++++----------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index bab8eb0733e..97778f17734 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -15,15 +15,14 @@ # pytest.mark.filterwarnings("ignore:::pint[.*]"), ] -# pint version supporting __array_function__ -pint_version = "0.10" +def use_pint_version_or_xfail(*, version, reason): + return pytest.mark.xfail(LooseVersion(pint.__version__) < version, reason=reason) -def use_pint_dev_or_xfail(reason): - return pytest.mark.xfail( - LooseVersion(pint.__version__) < pint_version, reason=reason - ) +require_pint_array_function = use_pint_version_or_xfail( + version="0.10", reason="pint does not implement __array_function__ yet" +) unit_registry = pint.UnitRegistry() @@ -70,7 +69,7 @@ def dtype(request): class TestDataArray: - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") def test_init(self): array = np.arange(10) * unit_registry.m @@ -78,6 +77,7 @@ def test_init(self): assert_equal_with_units(array, data_array) + @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") def test_repr(self): array = np.linspace(1, 2, 10) * unit_registry.m @@ -88,7 +88,7 @@ def test_repr(self): # warnings or errors, but does not check the result repr(data_array) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -115,23 +115,12 @@ def test_aggregation(self, func, dtype): assert_equal_with_units(result_array, result_data_array) + @require_pint_array_function @pytest.mark.parametrize( "func", ( - pytest.param( - operator.neg, - id="negate", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), - pytest.param( - abs, - id="absolute", - marks=use_pint_dev_or_xfail( - reason="pint does not implement __array_function__ yet" - ), - ), + pytest.param(operator.neg, id="negate"), + pytest.param(abs, id="absolute"), pytest.param( np.round, id="round", @@ -145,7 +134,7 @@ def test_unary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -167,7 +156,7 @@ def test_binary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.parametrize( "units,error", ( @@ -188,7 +177,7 @@ def test_univariate_ufunc(self, units, error, dtype): else: assert_equal_with_units(np.sin(array), np.sin(data_array)) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function def test_bivariate_ufunc(self, dtype): unit = unit_registry.m array = np.arange(10).astype(dtype) * unit @@ -198,7 +187,7 @@ def test_bivariate_ufunc(self, dtype): assert_equal_with_units(result_array, np.maximum(data_array, 0 * unit)) assert_equal_with_units(result_array, np.maximum(0 * unit, data_array)) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.parametrize( "indices", ( @@ -213,7 +202,7 @@ def test_isel(self, indices, dtype): assert_equal_with_units(array[indices], data_array.isel(x=indices)) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.parametrize( "values", ( @@ -246,7 +235,7 @@ def test_sel(self, values, units, error, dtype): result_data_array = data_array.sel(x=values_with_units) assert_equal_with_units(result_array, result_data_array) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.parametrize( "values", ( @@ -279,7 +268,7 @@ def test_loc(self, values, units, error, dtype): result_data_array = data_array.loc[values_with_units] assert_equal_with_units(result_array, result_data_array) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.xfail(reason="tries to coerce using asarray") @pytest.mark.parametrize( "shape", @@ -314,7 +303,7 @@ def test_squeeze(self, shape, dtype): np.squeeze(array, axis=index), data_array.squeeze(dim=name) ) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.xfail( reason="interp() mistakes quantities as objects instead of numeric type arrays" ) @@ -351,7 +340,7 @@ def test_interp(self, unit, error): assert_equal_with_units(result_array, result_data_array) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.xfail(reason="tries to coerce using asarray") @pytest.mark.parametrize( "unit,error", @@ -392,7 +381,7 @@ def test_interp_like(self, unit, error): assert_equal_with_units(result_array, result_data_array) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.xfail( reason="pint does not implement np.result_type in __array_function__ yet" ) @@ -431,7 +420,7 @@ def test_reindex(self, unit, error): assert_equal_with_units(result_array, result_data_array) - @use_pint_dev_or_xfail(reason="pint does not implement __array_function__ yet") + @require_pint_array_function @pytest.mark.xfail( reason="pint does not implement np.result_type in __array_function__ yet" ) From 131809361d359c35b58f3e04cf4bb016429ee878 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 3 Sep 2019 22:36:35 +0200 Subject: [PATCH 036/108] also check the string representation in addition to the repr --- xarray/tests/test_units.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 97778f17734..fff38a9fe96 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -79,14 +79,17 @@ def test_init(self): @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") - def test_repr(self): + @pytest.mark.parametrize( + "func", (pytest.param(str, id="str"), pytest.param(repr, id="repr")) + ) + def test_repr(self, func): array = np.linspace(1, 2, 10) * unit_registry.m x = np.arange(len(array)) * unit_registry.s data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) # FIXME: this just checks that the repr does not raise # warnings or errors, but does not check the result - repr(data_array) + func(data_array) @require_pint_array_function @pytest.mark.parametrize( From 10ee1ae86b6f93bc4675008f71162dbd6dcff771 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 3 Sep 2019 22:49:28 +0200 Subject: [PATCH 037/108] add helpers for creating method tests --- xarray/tests/test_units.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index fff38a9fe96..c8be6e28f4e 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -68,6 +68,31 @@ def dtype(request): return request.param +class method: + def __init__(self, name, *args, **kwargs): + self.name = name + self.args = args + self.kwargs = kwargs + + def __call__(self, obj): + from collections.abc import Callable + from functools import partial + + func = getattr(obj, self.name, None) + if func is None or not isinstance(func, Callable): + # fall back to module level numpy functions if not a xarray object + if not isinstance(obj, (xr.Variable, xr.DataArray, xr.Dataset)): + numpy_func = getattr(np, self.name) + func = partial(numpy_func, obj) + else: + raise AttributeError(f"{obj} has no method named '{self.name}'") + + return func(*self.args, **self.kwargs) + + def __repr__(self): + return self.name + + class TestDataArray: @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") From aa45f5cb98f254fd705c7248715eeaa45139c472 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 3 Sep 2019 22:50:54 +0200 Subject: [PATCH 038/108] check that simple aggregation methods work --- xarray/tests/test_units.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c8be6e28f4e..d06fe016cff 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -143,6 +143,34 @@ def test_aggregation(self, func, dtype): assert_equal_with_units(result_array, result_data_array) + @require_pint_array_function + @pytest.mark.parametrize( + "func", + ( + method("all"), + method("any"), + method("argmax"), + method("argmin"), + method("max"), + method("mean"), + method("median"), + method("min"), + method("prod"), + method("sum"), + method("std"), + method("var"), + ), + ids=repr, + ) + def test_aggregation_methods(self, func, dtype): + array = np.arange(10).astype(dtype) * unit_registry.m + data_array = xr.DataArray(data=array) + + result_array = func(array) + result_data_array = func(data_array) + + assert_equal_with_units(result_array, result_data_array) + @require_pint_array_function @pytest.mark.parametrize( "func", From ce2ab542fe69d566c5a4f5d31cf687efea1c1a00 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 4 Sep 2019 00:16:58 +0200 Subject: [PATCH 039/108] use format() instead of format strings --- xarray/tests/test_units.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d06fe016cff..d15e742c677 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -85,7 +85,9 @@ def __call__(self, obj): numpy_func = getattr(np, self.name) func = partial(numpy_func, obj) else: - raise AttributeError(f"{obj} has no method named '{self.name}'") + raise AttributeError( + "{obj} has no method named '{self.name}'".format(obj=obj, self=self) + ) return func(*self.args, **self.kwargs) From effeffbe1ed928e2302f02b710c7ff55ea871b87 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 4 Sep 2019 00:17:29 +0200 Subject: [PATCH 040/108] make sure the repr of method calls is different from functions --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d15e742c677..eaac495f12c 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -92,7 +92,7 @@ def __call__(self, obj): return func(*self.args, **self.kwargs) def __repr__(self): - return self.name + return "method {self.name}".format(self=self) class TestDataArray: From 1fc5f5f4abb5a227d4261279a774d4faa974b85e Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 4 Sep 2019 00:18:32 +0200 Subject: [PATCH 041/108] merge the two aggregation tests --- xarray/tests/test_units.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index eaac495f12c..d7a0ca8b727 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -134,21 +134,6 @@ def test_repr(self, func): np.sum, np.std, np.var, - ), - ) - def test_aggregation(self, func, dtype): - array = np.arange(10).astype(dtype) * unit_registry.m - data_array = xr.DataArray(data=array) - - result_array = func(array) - result_data_array = func(data_array) - - assert_equal_with_units(result_array, result_data_array) - - @require_pint_array_function - @pytest.mark.parametrize( - "func", - ( method("all"), method("any"), method("argmax"), @@ -162,9 +147,9 @@ def test_aggregation(self, func, dtype): method("std"), method("var"), ), - ids=repr, + ids=str, ) - def test_aggregation_methods(self, func, dtype): + def test_aggregation(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array) From c951ff16d3a6b81d6908502827f1c32bcea68300 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:35:11 +0200 Subject: [PATCH 042/108] explicitly check whether pint supports __array_function__ relying on versions is somewhat fragile. --- xarray/tests/test_units.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d7a0ca8b727..f770a1b48e3 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -20,11 +20,11 @@ def use_pint_version_or_xfail(*, version, reason): return pytest.mark.xfail(LooseVersion(pint.__version__) < version, reason=reason) -require_pint_array_function = use_pint_version_or_xfail( - version="0.10", reason="pint does not implement __array_function__ yet" -) - unit_registry = pint.UnitRegistry() +require_pint_array_function = pytest.mark.xfail( + not hasattr(unit_registry.Quantity, "__array_function__"), + reason="pint does not implement __array_function__ yet", +) def assert_equal_with_units(a, b): From e4e505601d207aea68d1b3ca35b285553a7144da Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:36:58 +0200 Subject: [PATCH 043/108] provide a fallback for the new base quantity --- xarray/tests/test_units.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index f770a1b48e3..d606dca56bd 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -28,7 +28,10 @@ def use_pint_version_or_xfail(*, version, reason): def assert_equal_with_units(a, b): - from pint.quantity import BaseQuantity + try: + from pint.quantity import BaseQuantity + except ImportError: + BaseQuantity = unit_registry.Quantity a_ = a if not isinstance(a, (xr.Dataset, xr.DataArray, xr.Variable)) else a.data b_ = b if not isinstance(b, (xr.Dataset, xr.DataArray, xr.Variable)) else b.data From 1521d15eaa4aa64368050a20ab69458f818263d6 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:40:09 +0200 Subject: [PATCH 044/108] check that no warning is raised for both with and without coords --- xarray/tests/test_units.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d606dca56bd..8a5cc82ec67 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -101,10 +101,23 @@ def __repr__(self): class TestDataArray: @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") - def test_init(self): - array = np.arange(10) * unit_registry.m - data_array = xr.DataArray(data=array) - + @pytest.mark.parametrize( + "coords", + ( + pytest.param(True, id="with coords"), + pytest.param(False, id="without coords"), + ), + ) + def test_init(self, coords): + array = np.linspace(1, 2, 10) * unit_registry.m + parameters = {"data": array} + if coords: + x = np.arange(len(array)) * unit_registry.s + y = x.to(unit_registry.ms) + parameters["coords"] = {"x": x, "y": ("x", y)} + parameters["dims"] = ["x"] + + data_array = xr.DataArray(**parameters) assert_equal_with_units(array, data_array) @require_pint_array_function From 1ecbb37aafbb66dade14cb513fe370048883712b Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:46:07 +0200 Subject: [PATCH 045/108] also check that the repr works both with and without coords --- xarray/tests/test_units.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 8a5cc82ec67..40ecda31415 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -125,10 +125,28 @@ def test_init(self, coords): @pytest.mark.parametrize( "func", (pytest.param(str, id="str"), pytest.param(repr, id="repr")) ) - def test_repr(self, func): + @pytest.mark.parametrize( + "coords", + ( + pytest.param( + True, + id="coords", + marks=pytest.mark.xfail( + reason="formatting currently does not delegate for coordinates" + ), + ), + pytest.param(False, id="no coords"), + ), + ) + def test_repr(self, func, coords): array = np.linspace(1, 2, 10) * unit_registry.m x = np.arange(len(array)) * unit_registry.s - data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + if coords: + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + print(data_array.x._variable._data) + else: + data_array = xr.DataArray(data=array) # FIXME: this just checks that the repr does not raise # warnings or errors, but does not check the result From 0fdb0118c946caebe3223e3730921145d94bf536 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:49:06 +0200 Subject: [PATCH 046/108] wrap all aggregation function calls --- xarray/tests/test_units.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 40ecda31415..30e6970d0f7 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -98,6 +98,18 @@ def __repr__(self): return "method {self.name}".format(self=self) +class function: + def __init__(self, name): + self.name = name + self.func = getattr(np, name) + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + def __repr__(self): + return "function {self.name}".format(self=self) + + class TestDataArray: @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") @@ -156,22 +168,22 @@ def test_repr(self, func, coords): @pytest.mark.parametrize( "func", ( - np.all, - np.any, - np.argmax, - np.argmin, - np.max, - np.mean, - np.median, - np.min, - np.prod, - np.sum, - np.std, - np.var, method("all"), method("any"), method("argmax"), method("argmin"), + function("all"), + function("any"), + function("argmax"), + function("argmin"), + function("max"), + function("mean"), + function("median"), + function("min"), + function("prod"), + function("sum"), + function("std"), + function("var"), method("max"), method("mean"), method("median"), From 67ca69bedfbf6a0e67449682a071d28637c9ae55 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:50:16 +0200 Subject: [PATCH 047/108] xfail every call that fails because of something outside xarray --- xarray/tests/test_units.py | 76 +++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 30e6970d0f7..4f35816e214 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -168,32 +168,82 @@ def test_repr(self, func, coords): @pytest.mark.parametrize( "func", ( - method("all"), - method("any"), - method("argmax"), - method("argmin"), - function("all"), - function("any"), - function("argmax"), - function("argmin"), + pytest.param( + function("all"), + marks=pytest.mark.xfail(reason="not implemented by pint yet"), + ), + pytest.param( + function("any"), + marks=pytest.mark.xfail(reason="not implemented by pint yet"), + ), + pytest.param( + function("argmax"), + marks=pytest.mark.xfail( + reason="comparison of quantity with ndarrays in nanops not implemented" + ), + ), + pytest.param( + function("argmin"), + marks=pytest.mark.xfail( + reason="comparison of quantity with ndarrays in nanops not implemented" + ), + ), function("max"), function("mean"), function("median"), function("min"), - function("prod"), - function("sum"), + pytest.param( + function("prod"), + marks=pytest.mark.xfail(reason="not implemented by pint yet"), + ), + pytest.param( + function("sum"), + marks=pytest.mark.xfail( + reason="comparison of quantity with ndarrays in nanops not implemented" + ), + ), function("std"), function("var"), + pytest.param( + method("all"), + marks=pytest.mark.xfail(reason="not implemented by pint yet"), + ), + pytest.param( + method("any"), + marks=pytest.mark.xfail(reason="not implemented by pint yet"), + ), + pytest.param( + method("argmax"), + marks=pytest.mark.xfail( + reason="comparison of quantities with ndarrays in nanops not implemented" + ), + ), + pytest.param( + method("argmin"), + marks=pytest.mark.xfail( + reason="comparison of quantities with ndarrays in nanops not implemented" + ), + ), method("max"), method("mean"), method("median"), method("min"), - method("prod"), - method("sum"), + pytest.param( + method("prod"), + marks=pytest.mark.xfail( + reason="comparison of quantity with ndarrays in nanops not implemented" + ), + ), + pytest.param( + method("sum"), + marks=pytest.mark.xfail( + reason="comparison of quantity with ndarrays in nanops not implemented" + ), + ), method("std"), method("var"), ), - ids=str, + ids=repr, ) def test_aggregation(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m From 0dbf7159c26ce03eed2246bf6ed932d6facb9cc5 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 13 Sep 2019 21:55:53 +0200 Subject: [PATCH 048/108] xfail tests related to dimension coordinates and indexes --- xarray/tests/test_units.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 4f35816e214..72d5dede2f5 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -341,6 +341,9 @@ def test_isel(self, indices, dtype): assert_equal_with_units(array[indices], data_array.isel(x=indices)) + @pytest.mark.xfail( + reason="xarray does not support duck arrays in dimension coordinates" + ) @require_pint_array_function @pytest.mark.parametrize( "values", @@ -374,6 +377,9 @@ def test_sel(self, values, units, error, dtype): result_data_array = data_array.sel(x=values_with_units) assert_equal_with_units(result_array, result_data_array) + @pytest.mark.xfail( + reason="xarray does not support duck arrays in dimension coordinates" + ) @require_pint_array_function @pytest.mark.parametrize( "values", From 5908eb6280b9b69bd622f8d38cf3763633e2ffad Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 14 Sep 2019 14:45:42 +0200 Subject: [PATCH 049/108] use the dimensions from the original array --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 72d5dede2f5..6a5a55c4f94 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -63,7 +63,7 @@ def magnitude(da): array = magnitude(data_array) coords = {name: magnitude(values) for name, values in data_array.coords.items()} - return xr.DataArray(data=array, coords=coords, dims=tuple(coords.keys())) + return xr.DataArray(data=array, coords=coords, dims=data_array.dims) @pytest.fixture(params=[float, int]) From 11400064a7ad1bd766e8b36871ed7ecd4fcd26e2 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 14 Sep 2019 14:46:19 +0200 Subject: [PATCH 050/108] allow passing arguments to the method on call --- xarray/tests/test_units.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 6a5a55c4f94..7be4d4ea7ff 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -77,7 +77,7 @@ def __init__(self, name, *args, **kwargs): self.args = args self.kwargs = kwargs - def __call__(self, obj): + def __call__(self, obj, *args, **kwargs): from collections.abc import Callable from functools import partial @@ -92,7 +92,9 @@ def __call__(self, obj): "{obj} has no method named '{self.name}'".format(obj=obj, self=self) ) - return func(*self.args, **self.kwargs) + all_args = list(self.args) + list(args) + all_kwargs = {**self.kwargs, **kwargs} + return func(*all_args, **all_kwargs) def __repr__(self): return "method {self.name}".format(self=self) From b4f35163f4752bfe85989f4f2db0a23afa1c71af Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 14 Sep 2019 14:47:47 +0200 Subject: [PATCH 051/108] add tests for comparisons --- xarray/tests/test_units.py | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 7be4d4ea7ff..1ce9cd5289e 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -20,6 +20,7 @@ def use_pint_version_or_xfail(*, version, reason): return pytest.mark.xfail(LooseVersion(pint.__version__) < version, reason=reason) +DimensionalityError = pint.errors.DimensionalityError unit_registry = pint.UnitRegistry() require_pint_array_function = pytest.mark.xfail( not hasattr(unit_registry.Quantity, "__array_function__"), @@ -297,6 +298,46 @@ def test_binary_operations(self, func, dtype): assert_equal_with_units(func(array), func(data_array)) + @require_pint_array_function + @pytest.mark.parametrize( + "comparison", + ( + pytest.param(operator.lt, id="less_than"), + pytest.param(operator.ge, id="greater_equal"), + pytest.param(operator.eq, id="equal"), + ), + ) + @pytest.mark.parametrize( + "value,error", + ( + pytest.param(8, ValueError, id="without_unit"), + pytest.param( + 8 * unit_registry.dimensionless, DimensionalityError, id="dimensionless" + ), + pytest.param(8 * unit_registry.s, DimensionalityError, id="incorrect_unit"), + pytest.param(8 * unit_registry.m, None, id="correct_unit"), + ), + ) + def test_comparisons(self, comparison, value, error, dtype): + array = ( + np.array([10.1, 5.2, 6.5, 8.0, 21.3, 7.1, 1.3]).astype(dtype) + * unit_registry.m + ) + data_array = xr.DataArray(data=array) + + # incompatible units are all not equal + if error is not None and comparison is not operator.eq: + with pytest.raises(error): + comparison(array, value) + + with pytest.raises(error): + comparison(data_array, value) + else: + result_data_array = comparison(data_array, value) + result_array = comparison(array, value) + + assert_equal_with_units(result_array, result_data_array) + @require_pint_array_function @pytest.mark.parametrize( "units,error", From 3d746f9b5a65c926091abc9aa1230bdc1569ccca Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 14 Sep 2019 14:48:33 +0200 Subject: [PATCH 052/108] add tests for detecting, filling and dropping missing values --- xarray/tests/test_units.py | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 1ce9cd5289e..6999b2b53b0 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -369,6 +369,95 @@ def test_bivariate_ufunc(self, dtype): assert_equal_with_units(result_array, np.maximum(data_array, 0 * unit)) assert_equal_with_units(result_array, np.maximum(0 * unit, data_array)) + @pytest.mark.parametrize( + "func", (method("isnull"), method("notnull"), method("count")), ids=repr + ) + def test_missing_value_detection(self, func, dtype): + array = ( + np.array( + [ + [1.4, 2.3, np.nan, 7.2], + [np.nan, 9.7, np.nan, np.nan], + [2.1, np.nan, np.nan, 4.6], + [9.9, np.nan, 7.2, 9.1], + ] + ) + * unit_registry.degK + ) + x = np.arange(array.shape[0]) * unit_registry.m + y = np.arange(array.shape[1]) * unit_registry.m + + data_array = xr.DataArray(data=array, coords={"x": x, "y": y}, dims=("x", "y")) + + results_with_units = func(data_array) + results_without_units = func(strip_units(data_array)) + + assert_equal_with_units(results_with_units, results_without_units) + + @pytest.mark.xfail(reason="ffill and bfill lose units in data") + @pytest.mark.parametrize("func", (method("ffill"), method("bfill")), ids=repr) + def test_missing_value_filling(self, func, dtype): + array = ( + np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) + * unit_registry.degK + ) + x = np.arange(len(array)) + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + result_with_units = func(data_array, dim="x") + result_without_units = func(strip_units(data_array), dim="x") + result = xr.DataArray( + data=result_without_units.data * unit_registry.degK, + coords={"x": x}, + dims=["x"], + ) + + assert_equal_with_units(result, result_with_units) + + @pytest.mark.xfail(reason="fillna drops the unit") + @pytest.mark.parametrize( + "fill_value", + ( + pytest.param( + -1, + id="python scalar", + marks=pytest.mark.xfail( + reason="python scalar cannot be converted using astype()" + ), + ), + pytest.param(np.array(-1), id="numpy scalar"), + pytest.param(np.array([-1]), id="numpy array"), + ), + ) + def test_fillna(self, fill_value, dtype): + unit = unit_registry.m + array = np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) * unit + data_array = xr.DataArray(data=array) + + result_with_units = data_array.fillna(value=fill_value * unit) + result_without_units = strip_units(data_array).fillna(value=fill_value) + result = xr.DataArray(data=result_without_units.values * unit) + + assert_equal_with_units(result, result_with_units) + + def test_dropna(self, dtype): + array = ( + np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) + * unit_registry.m + ) + x = np.arange(len(array)) + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) + + result_with_units = data_array.dropna(dim="x") + result_without_units = strip_units(data_array).dropna(dim="x") + result = xr.DataArray( + data=result_without_units.values * unit_registry.m, + coords=result_without_units.coords, + dims=result_without_units.dims, + ) + + assert_equal_with_units(result, result_with_units) + @require_pint_array_function @pytest.mark.parametrize( "indices", From 5d5480d399c3b3a305b592a5256b5be8b7ca373c Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 15 Sep 2019 22:42:29 +0200 Subject: [PATCH 053/108] mark the missing value tests as requiring pint to support duck arrays --- xarray/tests/test_units.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 6999b2b53b0..d2d216bcb16 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -369,6 +369,7 @@ def test_bivariate_ufunc(self, dtype): assert_equal_with_units(result_array, np.maximum(data_array, 0 * unit)) assert_equal_with_units(result_array, np.maximum(0 * unit, data_array)) + @require_pint_array_function @pytest.mark.parametrize( "func", (method("isnull"), method("notnull"), method("count")), ids=repr ) @@ -394,6 +395,7 @@ def test_missing_value_detection(self, func, dtype): assert_equal_with_units(results_with_units, results_without_units) + @require_pint_array_function @pytest.mark.xfail(reason="ffill and bfill lose units in data") @pytest.mark.parametrize("func", (method("ffill"), method("bfill")), ids=repr) def test_missing_value_filling(self, func, dtype): @@ -414,6 +416,7 @@ def test_missing_value_filling(self, func, dtype): assert_equal_with_units(result, result_with_units) + @require_pint_array_function @pytest.mark.xfail(reason="fillna drops the unit") @pytest.mark.parametrize( "fill_value", @@ -440,6 +443,7 @@ def test_fillna(self, fill_value, dtype): assert_equal_with_units(result, result_with_units) + @require_pint_array_function def test_dropna(self, dtype): array = ( np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) From 3e6de2d7878fc878aff6b6b92d877600d20e607a Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 15 Sep 2019 22:46:31 +0200 Subject: [PATCH 054/108] add tests for isin, where and interpolate_na --- xarray/tests/test_units.py | 129 +++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d2d216bcb16..718c8d82325 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -49,6 +49,13 @@ def assert_equal_with_units(a, b): assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units +def array_strip_units(array): + try: + return array.magnitude + except AttributeError: + return array + + def strip_units(data_array): def magnitude(da): if isinstance(da, xr.Variable): @@ -56,10 +63,7 @@ def magnitude(da): else: data = da - try: - return data.magnitude - except AttributeError: - return data + return array_strip_units(data) array = magnitude(data_array) coords = {name: magnitude(values) for name, values in data_array.coords.items()} @@ -462,6 +466,123 @@ def test_dropna(self, dtype): assert_equal_with_units(result, result_with_units) + @require_pint_array_function + @pytest.mark.xfail(reason="pint does not implement `numpy.isin`") + @pytest.mark.parametrize( + "unit", + ( + pytest.param(1, id="no_unit"), + pytest.param(unit_registry.dimensionless, id="dimensionless"), + pytest.param(unit_registry.s, id="incompatible_unit"), + pytest.param(unit_registry.cm, id="compatible_unit"), + pytest.param(unit_registry.m, id="same_unit"), + ), + ) + def test_isin(self, unit, dtype): + array = ( + np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) + * unit_registry.m + ) + data_array = xr.DataArray(data=array, dims="x") + + raw_values = np.array([1.4, np.nan, 2.3]).astype(dtype) + values = raw_values * unit + result_without_units = strip_units(data_array).isin(raw_values) + if unit != unit_registry.m: + result_without_units[:] = False + result_with_units = data_array.isin(values) + + assert_equal_with_units(result_without_units, result_with_units) + + @require_pint_array_function + @pytest.mark.parametrize( + "variant", + ( + pytest.param( + "masking", + marks=pytest.mark.xfail(reason="nan not compatible with quantity"), + ), + pytest.param( + "replacing_scalar", + marks=pytest.mark.xfail(reason="scalar not convertible using astype"), + ), + pytest.param( + "replacing_array", + marks=pytest.mark.xfail( + reason="replacing using an array drops the units" + ), + ), + pytest.param( + "dropping", + marks=pytest.mark.xfail(reason="nan not compatible with quantity"), + ), + ), + ) + @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="same_unit"), + ), + ) + def test_where(self, variant, unit, error, dtype): + def attach_unit(array, unit): + return xr.DataArray(data=array.data * unit) + + def _strip_units(mapping): + return {key: array_strip_units(value) for key, value in mapping.items()} + + original_unit = unit_registry.m + array = np.linspace(0, 1, 10).astype(dtype) * original_unit + data_array = xr.DataArray(data=array) + array_without_units = strip_units(data_array) + + condition = data_array < 0.5 * original_unit + other = np.linspace(-2, -1, 10).astype(dtype) * unit + variant_kwargs = { + "masking": {"cond": condition}, + "replacing_scalar": {"cond": condition, "other": -1 * unit}, + "replacing_array": {"cond": condition, "other": other}, + "dropping": {"cond": condition, "drop": True}, + } + kwargs = variant_kwargs.get(variant) + + if variant not in ("masking", "dropping") and error is not None: + with pytest.raises(error): + data_array.where(**kwargs) + else: + result_with_units = data_array.where(**kwargs) + result = attach_unit( + array_without_units.where(**_strip_units(kwargs)), original_unit + ) + + assert_equal_with_units(result, result_with_units) + + @pytest.mark.xfail(reason="interpolate strips units") + @require_pint_array_function + def test_interpolate_na(self, dtype): + array = ( + np.array([-1.03, 0.1, 1.4, np.nan, 2.3, np.nan, np.nan, 9.1]) + * unit_registry.m + ) + x = np.arange(len(array)) + data_array = xr.DataArray(data=array, coords={"x": x}, dims="x").astype(dtype) + + result_with_units = data_array.interpolate_na(dim="x") + result_without_units = strip_units(data_array).interpolate_na(dim="x") + result = xr.DataArray( + data=result_without_units.values * unit_registry.m, + coords=result_without_units.coords, + dims=result_without_units.dims, + ) + + assert_equal_with_units(result, result_with_units) + @require_pint_array_function @pytest.mark.parametrize( "indices", From 190a2f10bfe675741c061ef932d5d33c9b9753bc Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 15 Sep 2019 22:47:38 +0200 Subject: [PATCH 055/108] reformat unit ids and add a test parameter for compatible units --- xarray/tests/test_units.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 718c8d82325..10756daa919 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -712,10 +712,11 @@ def test_squeeze(self, shape, dtype): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), + pytest.param(1, None, id="no_unit"), pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param(unit_registry.s, None, id="incompatible_unit"), + pytest.param(unit_registry.cm, None, id="compatible_unit"), + pytest.param(unit_registry.m, None, id="identical_unit"), ), ) def test_interp(self, unit, error): @@ -747,10 +748,11 @@ def test_interp(self, unit, error): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), + pytest.param(1, None, id="no_unit"), pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param(unit_registry.s, None, id="incompatible_unit"), + pytest.param(unit_registry.cm, None, id="compatible_unit"), + pytest.param(unit_registry.m, None, id="identical_unit"), ), ) def test_interp_like(self, unit, error): @@ -790,10 +792,11 @@ def test_interp_like(self, unit, error): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), + pytest.param(1, None, id="no_unit"), pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param(unit_registry.s, None, id="incompatible_unit"), + pytest.param(unit_registry.cm, None, id="compatible_unit"), + pytest.param(unit_registry.m, None, id="identical_unit"), ), ) def test_reindex(self, unit, error): @@ -829,10 +832,11 @@ def test_reindex(self, unit, error): @pytest.mark.parametrize( "unit,error", ( - pytest.param(1, None, id="without unit"), + pytest.param(1, None, id="no_unit"), pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param(unit_registry.s, None, id="with incorrect unit"), - pytest.param(unit_registry.m, None, id="with correct unit"), + pytest.param(unit_registry.s, None, id="incompatible_unit"), + pytest.param(unit_registry.cm, None, id="compatible_unit"), + pytest.param(unit_registry.m, None, id="identical_unit"), ), ) def test_reindex_like(self, unit, error): From 55bdcda04064e2ed97e3b3d38c936c64d54ab5d2 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 15 Sep 2019 22:48:57 +0200 Subject: [PATCH 056/108] remove an unnecessary xfail --- xarray/tests/test_units.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 10756daa919..b041c54ebd8 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -706,9 +706,6 @@ def test_squeeze(self, shape, dtype): ) @require_pint_array_function - @pytest.mark.xfail( - reason="interp() mistakes quantities as objects instead of numeric type arrays" - ) @pytest.mark.parametrize( "unit,error", ( From c596572835e18d4fdc69912bc90911dcec298b73 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 15 Sep 2019 22:49:30 +0200 Subject: [PATCH 057/108] add tests for the top-level replication functions (*_like) --- xarray/tests/test_units.py | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index b041c54ebd8..787cf5c43a9 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -865,3 +865,43 @@ def test_reindex_like(self, unit, error): result_data_array = data_array.reindex_like(new_data_array) assert_equal_with_units(result_array, result_data_array) + + # TODO: what should these *_like functions even do for unit arrays? + @require_pint_array_function + @pytest.mark.parametrize("func", (xr.zeros_like, xr.ones_like)) + def test_replication(self, func, dtype): + array = np.linspace(0, 10, 20).astype(dtype) * unit_registry.s + data_array = xr.DataArray(data=array, dims="x") + + replicated = func(data_array) + numpy_func = getattr(np, func.__name__) + expected = xr.DataArray(data=numpy_func(array) * unit_registry.s, dims="x") + + assert_equal_with_units(expected, replicated) + + @require_pint_array_function + @pytest.mark.parametrize( + "unit,error", + ( + pytest.param(1, DimensionalityError, id="no_unit"), + pytest.param( + unit_registry.dimensionless, DimensionalityError, id="dimensionless" + ), + pytest.param(unit_registry.m, DimensionalityError, id="incompatible_unit"), + pytest.param(unit_registry.ms, None, id="compatible_unit"), + pytest.param(unit_registry.s, None, id="identical_unit"), + ), + ) + def test_replication_full_like(self, unit, error, dtype): + array = np.linspace(0, 5, 10) * unit_registry.s + data_array = xr.DataArray(data=array, dims="x") + + fill_value = -1 * unit + if error is not None: + with pytest.raises(error): + xr.full_like(data_array, fill_value=fill_value) + else: + replicated = xr.full_like(data_array, fill_value=fill_value) + expected = xr.DataArray(data=np.ones_like(array) * fill_value, dims="x") + + assert_equal_with_units(expected, replicated) From f0892ce226385363e40dbf0fcd90387ba104d270 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 16 Sep 2019 01:20:05 +0200 Subject: [PATCH 058/108] check for whatever pint does with *_like functions --- xarray/tests/test_units.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 787cf5c43a9..4524d85c4bc 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -866,7 +866,6 @@ def test_reindex_like(self, unit, error): assert_equal_with_units(result_array, result_data_array) - # TODO: what should these *_like functions even do for unit arrays? @require_pint_array_function @pytest.mark.parametrize("func", (xr.zeros_like, xr.ones_like)) def test_replication(self, func, dtype): @@ -875,10 +874,13 @@ def test_replication(self, func, dtype): replicated = func(data_array) numpy_func = getattr(np, func.__name__) - expected = xr.DataArray(data=numpy_func(array) * unit_registry.s, dims="x") + expected = numpy_func(array) assert_equal_with_units(expected, replicated) + @pytest.mark.xfail( + reason="np.full_like on Variable strips the unit and pint does not allow mixed args" + ) @require_pint_array_function @pytest.mark.parametrize( "unit,error", @@ -902,6 +904,6 @@ def test_replication_full_like(self, unit, error, dtype): xr.full_like(data_array, fill_value=fill_value) else: replicated = xr.full_like(data_array, fill_value=fill_value) - expected = xr.DataArray(data=np.ones_like(array) * fill_value, dims="x") + expected = np.full_like(array, fill_value=fill_value) assert_equal_with_units(expected, replicated) From c2dd40a74a1a1ad2979d9c35dcab7489732edb2a Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 16 Sep 2019 01:21:35 +0200 Subject: [PATCH 059/108] add tests for combine_first --- xarray/tests/test_units.py | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 4524d85c4bc..407fd59efcd 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -583,6 +583,47 @@ def test_interpolate_na(self, dtype): assert_equal_with_units(result, result_with_units) + @pytest.mark.xfail(reason="uses DataArray.where, which currently fails") + @require_pint_array_function + @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_combine_first(self, unit, error, dtype): + array = np.zeros(shape=(2, 2), dtype=dtype) * unit_registry.m + other_array = np.ones_like(array) * unit + + data_array = xr.DataArray( + data=array, coords={"x": ["a", "b"], "y": [-1, 0]}, dims=["x", "y"] + ) + other = xr.DataArray( + data=other_array, coords={"x": ["b", "c"], "y": [0, 1]}, dims=["x", "y"] + ) + + if error is not None: + with pytest.raises(error): + data_array.combine_first(other) + else: + result = data_array.combine_first(other) + expected_wo_units = strip_units(data_array).combine_first( + strip_units(other) + ) + expected = xr.DataArray( + data=expected_wo_units.data, + coords=expected_wo_units.coords, + dims=["x", "y"], + ) + + assert_equal_with_units(expected, result) + @require_pint_array_function @pytest.mark.parametrize( "indices", From a7b0450c9fa150bc1e53baba78e92767b85b8180 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 16 Sep 2019 01:25:22 +0200 Subject: [PATCH 060/108] xfail the bivariate ufunc tests --- xarray/tests/test_units.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 407fd59efcd..f91e746dd82 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -363,6 +363,7 @@ def test_univariate_ufunc(self, units, error, dtype): else: assert_equal_with_units(np.sin(array), np.sin(data_array)) + @pytest.mark.xfail(reason="pint's implementation of `np.maximum` strips units") @require_pint_array_function def test_bivariate_ufunc(self, dtype): unit = unit_registry.m From 22419c02b17f111b93b65a0b6a7e47ff70954c47 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 16 Sep 2019 01:28:06 +0200 Subject: [PATCH 061/108] xfail the call to np.median --- xarray/tests/test_units.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index f91e746dd82..b16eedf3156 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -197,7 +197,12 @@ def test_repr(self, func, coords): ), function("max"), function("mean"), - function("median"), + pytest.param( + function("median"), + marks=pytest.mark.xfail( + reason="np.median on DataArray strips the units" + ), + ), function("min"), pytest.param( function("prod"), From 0d9b6d851d33469b352582bfa814fb1bc008024f Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 17 Sep 2019 14:34:54 +0200 Subject: [PATCH 062/108] move the top-level function tests out of the DataArray namespace class --- xarray/tests/test_units.py | 86 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index b16eedf3156..32ebe7fc71b 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -117,6 +117,50 @@ def __repr__(self): return "function {self.name}".format(self=self) +@require_pint_array_function +@pytest.mark.parametrize("func", (xr.zeros_like, xr.ones_like)) +def test_replication(func, dtype): + array = np.linspace(0, 10, 20).astype(dtype) * unit_registry.s + data_array = xr.DataArray(data=array, dims="x") + + replicated = func(data_array) + numpy_func = getattr(np, func.__name__) + expected = numpy_func(array) + + assert_equal_with_units(expected, replicated) + + +@pytest.mark.xfail( + reason="np.full_like on Variable strips the unit and pint does not allow mixed args" +) +@require_pint_array_function +@pytest.mark.parametrize( + "unit,error", + ( + pytest.param(1, DimensionalityError, id="no_unit"), + pytest.param( + unit_registry.dimensionless, DimensionalityError, id="dimensionless" + ), + pytest.param(unit_registry.m, DimensionalityError, id="incompatible_unit"), + pytest.param(unit_registry.ms, None, id="compatible_unit"), + pytest.param(unit_registry.s, None, id="identical_unit"), + ), +) +def test_replication_full_like(unit, error, dtype): + array = np.linspace(0, 5, 10) * unit_registry.s + data_array = xr.DataArray(data=array, dims="x") + + fill_value = -1 * unit + if error is not None: + with pytest.raises(error): + xr.full_like(data_array, fill_value=fill_value) + else: + replicated = xr.full_like(data_array, fill_value=fill_value) + expected = np.full_like(array, fill_value=fill_value) + + assert_equal_with_units(expected, replicated) + + class TestDataArray: @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") @@ -912,45 +956,3 @@ def test_reindex_like(self, unit, error): result_data_array = data_array.reindex_like(new_data_array) assert_equal_with_units(result_array, result_data_array) - - @require_pint_array_function - @pytest.mark.parametrize("func", (xr.zeros_like, xr.ones_like)) - def test_replication(self, func, dtype): - array = np.linspace(0, 10, 20).astype(dtype) * unit_registry.s - data_array = xr.DataArray(data=array, dims="x") - - replicated = func(data_array) - numpy_func = getattr(np, func.__name__) - expected = numpy_func(array) - - assert_equal_with_units(expected, replicated) - - @pytest.mark.xfail( - reason="np.full_like on Variable strips the unit and pint does not allow mixed args" - ) - @require_pint_array_function - @pytest.mark.parametrize( - "unit,error", - ( - pytest.param(1, DimensionalityError, id="no_unit"), - pytest.param( - unit_registry.dimensionless, DimensionalityError, id="dimensionless" - ), - pytest.param(unit_registry.m, DimensionalityError, id="incompatible_unit"), - pytest.param(unit_registry.ms, None, id="compatible_unit"), - pytest.param(unit_registry.s, None, id="identical_unit"), - ), - ) - def test_replication_full_like(self, unit, error, dtype): - array = np.linspace(0, 5, 10) * unit_registry.s - data_array = xr.DataArray(data=array, dims="x") - - fill_value = -1 * unit - if error is not None: - with pytest.raises(error): - xr.full_like(data_array, fill_value=fill_value) - else: - replicated = xr.full_like(data_array, fill_value=fill_value) - expected = np.full_like(array, fill_value=fill_value) - - assert_equal_with_units(expected, replicated) From 2d8da574900f16b0742b488b4ad07998b446a0da Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 18 Sep 2019 01:14:26 +0200 Subject: [PATCH 063/108] add cumsum and cumprod to the list of aggregation functions --- xarray/tests/test_units.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 32ebe7fc71b..927f46ebaab 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -260,6 +260,11 @@ def test_repr(self, func, coords): ), function("std"), function("var"), + function("cumsum"), + pytest.param( + function("cumprod"), + marks=pytest.mark.xfail(reason="not implemented by pint yet"), + ), pytest.param( method("all"), marks=pytest.mark.xfail(reason="not implemented by pint yet"), @@ -298,6 +303,11 @@ def test_repr(self, func, coords): ), method("std"), method("var"), + method("cumsum"), + pytest.param( + method("cumprod"), + marks=pytest.mark.xfail(reason="pint does not implement cumprod yet"), + ), ), ids=repr, ) From 20159094d685a9df55f5ec13588d7ddd776a786d Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 18 Sep 2019 01:19:52 +0200 Subject: [PATCH 064/108] add tests for the numpy methods --- xarray/tests/test_units.py | 82 +++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 927f46ebaab..33cd3eb385a 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -86,19 +86,27 @@ def __call__(self, obj, *args, **kwargs): from collections.abc import Callable from functools import partial + all_args = list(self.args) + list(args) + all_kwargs = {**self.kwargs, **kwargs} + func = getattr(obj, self.name, None) if func is None or not isinstance(func, Callable): # fall back to module level numpy functions if not a xarray object if not isinstance(obj, (xr.Variable, xr.DataArray, xr.Dataset)): numpy_func = getattr(np, self.name) func = partial(numpy_func, obj) + # remove typical xr args like "dim" + exclude_kwargs = ("dim", "dims") + all_kwargs = { + key: value + for key, value in all_kwargs.items() + if key not in exclude_kwargs + } else: raise AttributeError( "{obj} has no method named '{self.name}'".format(obj=obj, self=self) ) - all_args = list(self.args) + list(args) - all_kwargs = {**self.kwargs, **kwargs} return func(*all_args, **all_kwargs) def __repr__(self): @@ -433,6 +441,76 @@ def test_bivariate_ufunc(self, dtype): assert_equal_with_units(result_array, np.maximum(data_array, 0 * unit)) assert_equal_with_units(result_array, np.maximum(0 * unit, data_array)) + @pytest.mark.parametrize("property", ("T", "imag", "real")) + def test_numpy_properties(self, property, dtype): + array = ( + np.arange(5 * 10).astype(dtype) + + 1j * np.linspace(-1, 0, 5 * 10).astype(dtype) + ).reshape(5, 10) * unit_registry.s + data_array = xr.DataArray(data=array, dims=("x", "y")) + + result_data_array = getattr(data_array, property) + result_array = getattr(array, property) + + assert_equal_with_units(result_array, result_data_array) + + @pytest.mark.parametrize( + "func", + ( + method("conj"), + method("argsort"), + method("conjugate"), + method("round"), + pytest.param( + method("rank", dim="x"), + marks=pytest.mark.xfail(reason="pint does not implement rank yet"), + ), + ), + ids=repr, + ) + def test_numpy_methods(self, func, dtype): + array = np.arange(10).astype(dtype) * unit_registry.m + data_array = xr.DataArray(data=array, dims="x") + + expected = func(array) + result_xarray = func(data_array) + + assert_equal_with_units(expected, result_xarray) + + @pytest.mark.parametrize( + "func", (method("clip", min=3, max=8), method("searchsorted", v=5)), 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_with_args(self, func, unit, error, dtype): + array = np.arange(10).astype(dtype) * unit_registry.m + data_array = xr.DataArray(data=array, dims="x") + + scalar_types = (int, float) + kwargs = { + key: (value * unit if isinstance(value, scalar_types) else value) + for key, value in func.kwargs.items() + } + + if error is not None: + with pytest.raises(error): + func(data_array, **kwargs) + else: + expected = func(array, **kwargs) + result_xarray = func(data_array, **kwargs) + + assert_equal_with_units(expected, result_xarray) + @require_pint_array_function @pytest.mark.parametrize( "func", (method("isnull"), method("notnull"), method("count")), ids=repr From 0abac1a2a9e730f1f1daf7ba5465f35b525f574b Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 18 Sep 2019 20:36:45 +0200 Subject: [PATCH 065/108] check for equal units directly after checking the magnitude --- xarray/tests/test_units.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 33cd3eb385a..c481560243b 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -42,12 +42,10 @@ def assert_equal_with_units(a, b): assert (hasattr(a_, "magnitude") and hasattr(b_, "magnitude")) and np.allclose( a_.magnitude, b_.magnitude, equal_nan=True ) + assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units else: assert np.allclose(a_, b_, equal_nan=True) - if hasattr(a_, "units") or hasattr(b_, "units"): - assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units - def array_strip_units(array): try: From 9f918b8ae33c94b4339659d27d1304481fb59e58 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 18 Sep 2019 22:45:53 +0200 Subject: [PATCH 066/108] add tests for content manipulation methods --- xarray/tests/test_units.py | 120 +++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c481560243b..789fad2a28c 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -69,6 +69,23 @@ def magnitude(da): return xr.DataArray(data=array, coords=coords, dims=data_array.dims) +def attach_units(data_array, units): + data_units = units.get("data", 1) + + if not isinstance(data_array, (xr.DataArray,)): + return data_array * data_units + + data = data_array.data * data_units + + coords = { + name: value * units.get(name, 1) for name, value in data_array.coords.items() + } + dims = data_array.dims + attrs = data_array.attrs + + return xr.DataArray(data=data, coords=coords, attrs=attrs, dims=dims) + + @pytest.fixture(params=[float, int]) def dtype(request): return request.param @@ -760,6 +777,109 @@ def test_combine_first(self, unit, error, dtype): assert_equal_with_units(expected, result) + @require_pint_array_function + @pytest.mark.parametrize( + "func", + ( + method("pipe", lambda da: da * 10), + method("assign_coords", y2=("y", np.arange(10) * unit_registry.ms)), + method("assign_attrs", attr1="value"), + method("rename", x2="x_mm"), + method("swap_dims", {"x": "x2"}), + method( + "expand_dims", + dim={"z": np.linspace(10, 20, 12) * unit_registry.s}, + axis=1, + ), + method("drop", labels="x"), + method("reset_coords", names="x2"), + method("copy"), + pytest.param( + method("astype", np.float32), + marks=pytest.mark.xfail(reason="units get stripped"), + ), + pytest.param( + method("item", 1), marks=pytest.mark.xfail(reason="units get stripped") + ), + ), + ids=repr, + ) + def test_content_manipulation(self, func, dtype): + quantity = ( + np.linspace(0, 10, 5 * 10).reshape(5, 10).astype(dtype) + * unit_registry.pascal + ) + x = np.arange(quantity.shape[0]) * unit_registry.m + y = np.arange(quantity.shape[1]) * unit_registry.m + x2 = x.to(unit_registry.mm) + + data_array = xr.DataArray( + name="data", + data=quantity, + coords={"x": x, "x2": ("x", x2), "y": y}, + dims=("x", "y"), + ) + + stripped_kwargs = { + key: array_strip_units(value) for key, value in func.kwargs.items() + } + expected = attach_units( + func(strip_units(data_array), **stripped_kwargs), + {"data": quantity.units, "x": x.units, "x2": x2.units, "y": y.units}, + ) + result = func(data_array) + + assert_equal_with_units(expected, result) + + @require_pint_array_function + @pytest.mark.parametrize( + "func", + ( + pytest.param( + method("drop", labels=np.array([1, 5]), dim="x"), + marks=pytest.mark.xfail( + reason="selecting using incompatible units does not raise" + ), + ), + pytest.param(method("copy", data=np.arange(20))), + ), + 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, KeyError, id="compatible_unit"), + pytest.param(unit_registry.m, None, id="identical_unit"), + ), + ) + def test_content_manipulation_with_units(self, func, unit, error, dtype): + quantity = np.linspace(0, 10, 20, dtype=dtype) * unit_registry.pascal + x = np.arange(len(quantity)) * unit_registry.m + + data_array = xr.DataArray(name="data", data=quantity, coords={"x": x}, dims="x") + + kwargs = { + key: (value * unit if isinstance(value, np.ndarray) else value) + for key, value in func.kwargs.items() + } + stripped_kwargs = func.kwargs + + expected = attach_units( + func(strip_units(data_array), **stripped_kwargs), + {"data": quantity.units if func.name == "drop" else unit, "x": x.units}, + ) + if error is not None and func.name == "drop": + with pytest.raises(error): + func(data_array, **kwargs) + else: + result = func(data_array, **kwargs) + assert_equal_with_units(expected, result) + @require_pint_array_function @pytest.mark.parametrize( "indices", From 38c791b2063e341cdb64b01b6bb7c5451591ae24 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 19 Sep 2019 18:13:23 +0200 Subject: [PATCH 067/108] add tests for comparing DataArrays (equals, indentical) --- xarray/tests/test_units.py | 88 +++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 789fad2a28c..200cf8c1290 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -404,7 +404,7 @@ def test_binary_operations(self, func, dtype): pytest.param(8 * unit_registry.m, None, id="correct_unit"), ), ) - def test_comparisons(self, comparison, value, error, dtype): + def test_comparison_operations(self, comparison, value, error, dtype): array = ( np.array([10.1, 5.2, 6.5, 8.0, 21.3, 7.1, 1.3]).astype(dtype) * unit_registry.m @@ -777,6 +777,92 @@ def test_combine_first(self, unit, error, dtype): assert_equal_with_units(expected, result) + @require_pint_array_function + @pytest.mark.parametrize( + "unit", + ( + pytest.param(1, id="no_unit"), + pytest.param(unit_registry.dimensionless, id="dimensionless"), + pytest.param(unit_registry.s, id="incompatible_unit"), + pytest.param(unit_registry.cm, id="compatible_unit"), + pytest.param(unit_registry.m, id="identical_unit"), + ), + ) + @pytest.mark.parametrize( + "variation", + ( + "data", + pytest.param( + "dims", marks=pytest.mark.xfail(reason="units in indexes not supported") + ), + "coords", + ), + ) + @pytest.mark.parametrize( + "func", + ( + method("equals"), + pytest.param( + method("identical"), + marks=pytest.mark.xfail(reason="identical does not check units yet"), + ), + ), + ids=repr, + ) + def test_comparisons(self, func, variation, unit, dtype): + def attach_unit(data, unit): + # to make sure we also encounter the case of "equal if converted" + if ( + isinstance(unit, unit_registry.Unit) + and "[length]" in unit.dimensionality + ): + quantity = (data * unit_registry.m).to(unit) + else: + quantity = data * unit + return quantity + + data = np.linspace(0, 5, 10).astype(dtype) + coord = np.arange(len(data)).astype(dtype) + + quantity = data * unit_registry.m + x = coord * unit_registry.m + y = coord * unit_registry.m + + units = { + "data": (unit, unit_registry.m, unit_registry.m), + "dims": (unit_registry.m, unit, unit_registry.m), + "coords": (unit_registry.m, unit_registry.m, unit), + } + data_unit, dim_unit, coord_unit = units.get(variation) + + data_array = xr.DataArray( + data=quantity, coords={"x": x, "y": ("x", y)}, dims="x" + ) + other = xr.DataArray( + data=attach_unit(data, data_unit), + coords={ + "x": attach_unit(coord, dim_unit), + "y": ("x", attach_unit(coord, coord_unit)), + }, + dims="x", + ) + + # TODO: test dim coord once indexes leave units intact + equal_arrays = ( + np.all(quantity == other.data) + and (np.all(x == other.x.data) or True) # dims can't be checked yet + and np.all(y == other.y.data) + ) + equal_units = ( + data_unit == unit_registry.m + and coord_unit == unit_registry.m + and dim_unit == unit_registry.m + ) + expected = equal_arrays and (func.name != "identical" or equal_units) + result = func(data_array, other) + + assert expected == result + @require_pint_array_function @pytest.mark.parametrize( "func", From 4fe23d9c6a1d5658eab2770750c10805c42a25a3 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 19 Sep 2019 18:37:52 +0200 Subject: [PATCH 068/108] add a test for broadcast_equals --- xarray/tests/test_units.py | 61 ++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 200cf8c1290..78961a8a937 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -54,6 +54,21 @@ def array_strip_units(array): return array +def array_attach_units(data, unit, convert=False): + data_ = data if not hasattr(data, "magnitude") else data.magnitude + # to make sure we also encounter the case of "equal if converted" + if ( + convert + and isinstance(unit, unit_registry.Unit) + and "[length]" in unit.dimensionality + ): + quantity = (data_ * unit_registry.m).to(unit) + else: + quantity = data_ * unit + + return quantity + + def strip_units(data_array): def magnitude(da): if isinstance(da, xr.Variable): @@ -73,9 +88,9 @@ def attach_units(data_array, units): data_units = units.get("data", 1) if not isinstance(data_array, (xr.DataArray,)): - return data_array * data_units + return array_attach_units(data_array, data_units) - data = data_array.data * data_units + data = array_attach_units(data_array.data, data_units) coords = { name: value * units.get(name, 1) for name, value in data_array.coords.items() @@ -810,17 +825,6 @@ def test_combine_first(self, unit, error, dtype): ids=repr, ) def test_comparisons(self, func, variation, unit, dtype): - def attach_unit(data, unit): - # to make sure we also encounter the case of "equal if converted" - if ( - isinstance(unit, unit_registry.Unit) - and "[length]" in unit.dimensionality - ): - quantity = (data * unit_registry.m).to(unit) - else: - quantity = data * unit - return quantity - data = np.linspace(0, 5, 10).astype(dtype) coord = np.arange(len(data)).astype(dtype) @@ -839,10 +843,10 @@ def attach_unit(data, unit): data=quantity, coords={"x": x, "y": ("x", y)}, dims="x" ) other = xr.DataArray( - data=attach_unit(data, data_unit), + data=array_attach_units(data, data_unit, convert=True), coords={ - "x": attach_unit(coord, dim_unit), - "y": ("x", attach_unit(coord, coord_unit)), + "x": array_attach_units(coord, dim_unit, convert=True), + "y": ("x", array_attach_units(coord, coord_unit, convert=True)), }, dims="x", ) @@ -863,6 +867,31 @@ def attach_unit(data, unit): assert expected == result + @require_pint_array_function + @pytest.mark.parametrize( + "unit", + ( + pytest.param(1, id="no_unit"), + pytest.param(unit_registry.dimensionless, id="dimensionless"), + pytest.param(unit_registry.s, id="incompatible_unit"), + pytest.param(unit_registry.cm, id="compatible_unit"), + pytest.param(unit_registry.m, id="identical_unit"), + ), + ) + def test_broadcast_equals(self, unit, dtype): + left_array = np.ones(shape=(2, 2), dtype=dtype) * unit_registry.m + right_array = array_attach_units( + np.ones(shape=(2,), dtype=dtype), unit, convert=True + ) + + left = xr.DataArray(data=left_array, dims=("x", "y")) + right = xr.DataArray(data=right_array, dims="x") + + expected = np.all(left_array == right_array[:, None]) + result = left.broadcast_equals(right) + + assert expected == result + @require_pint_array_function @pytest.mark.parametrize( "func", From c5ae6a705e08eab5fb7e08d40e686de0c32926f9 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 21 Sep 2019 13:51:11 +0200 Subject: [PATCH 069/108] refactor the comparison operation tests --- xarray/tests/test_units.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 78961a8a937..16c11ccaa44 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -409,33 +409,39 @@ def test_binary_operations(self, func, dtype): ), ) @pytest.mark.parametrize( - "value,error", + "unit,error", ( - pytest.param(8, ValueError, id="without_unit"), + pytest.param(1, ValueError, id="without_unit"), pytest.param( - 8 * unit_registry.dimensionless, DimensionalityError, id="dimensionless" + unit_registry.dimensionless, DimensionalityError, id="dimensionless" ), - pytest.param(8 * unit_registry.s, DimensionalityError, id="incorrect_unit"), - pytest.param(8 * unit_registry.m, None, id="correct_unit"), + pytest.param(unit_registry.s, DimensionalityError, id="incorrect_unit"), + pytest.param(unit_registry.m, None, id="correct_unit"), ), ) - def test_comparison_operations(self, comparison, value, error, dtype): + def test_comparison_operations(self, comparison, unit, error, dtype): array = ( np.array([10.1, 5.2, 6.5, 8.0, 21.3, 7.1, 1.3]).astype(dtype) * unit_registry.m ) data_array = xr.DataArray(data=array) + value = 8 + to_compare_with = value * unit + # incompatible units are all not equal if error is not None and comparison is not operator.eq: with pytest.raises(error): - comparison(array, value) + comparison(array, to_compare_with) with pytest.raises(error): - comparison(data_array, value) + comparison(data_array, to_compare_with) else: - result_data_array = comparison(data_array, value) - result_array = comparison(array, value) + result_data_array = comparison(data_array, to_compare_with) + # pint compares incompatible arrays to False, so we need to extend + result_array = comparison(array, to_compare_with) * np.ones_like( + array, dtype=bool + ) assert_equal_with_units(result_array, result_data_array) From a04128e6b5eb8db140d60659f57d032bbf0e6217 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 21 Sep 2019 13:59:09 +0200 Subject: [PATCH 070/108] rewrite the strip, attach and assert_equal functions and add extract --- xarray/tests/test_units.py | 145 +++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 37 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 16c11ccaa44..3da6ceb1249 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -27,24 +27,18 @@ def use_pint_version_or_xfail(*, version, reason): reason="pint does not implement __array_function__ yet", ) +try: + from pint.quantity import BaseQuantity +except ImportError: + BaseQuantity = unit_registry.Quantity -def assert_equal_with_units(a, b): - try: - from pint.quantity import BaseQuantity - except ImportError: - BaseQuantity = unit_registry.Quantity - - a_ = a if not isinstance(a, (xr.Dataset, xr.DataArray, xr.Variable)) else a.data - b_ = b if not isinstance(b, (xr.Dataset, xr.DataArray, xr.Variable)) else b.data - # workaround until pint implements allclose in __array_function__ - if isinstance(a_, BaseQuantity) or isinstance(b_, BaseQuantity): - assert (hasattr(a_, "magnitude") and hasattr(b_, "magnitude")) and np.allclose( - a_.magnitude, b_.magnitude, equal_nan=True - ) - assert (hasattr(a_, "units") and hasattr(b_, "units")) and a_.units == b_.units - else: - assert np.allclose(a_, b_, equal_nan=True) +def array_extract_units(obj): + raw = obj.data if hasattr(obj, "data") else obj + try: + return raw.units + except AttributeError: + return None def array_strip_units(array): @@ -69,36 +63,113 @@ def array_attach_units(data, unit, convert=False): return quantity -def strip_units(data_array): - def magnitude(da): - if isinstance(da, xr.Variable): - data = da.data - else: - data = da +def extract_units(obj): + if isinstance(obj, xr.Dataset): + vars_units = { + name: array_extract_units(value) for name, value in obj.data_vars.items() + } + coords_units = { + name: array_extract_units(value) for name, value in obj.coords.items() + } - return array_strip_units(data) + units = {**vars_units, **coords_units} + elif isinstance(obj, xr.DataArray): + vars_units = {obj.name: array_extract_units(obj)} + coords_units = { + name: array_extract_units(value) for name, value in obj.coords.items() + } - array = magnitude(data_array) - coords = {name: magnitude(values) for name, values in data_array.coords.items()} + units = {**vars_units, **coords_units} + elif hasattr(obj, "units"): + vars_units = {"": array_extract_units(obj)} - return xr.DataArray(data=array, coords=coords, dims=data_array.dims) + units = {**vars_units} + else: + units = {} + return units -def attach_units(data_array, units): - data_units = units.get("data", 1) - if not isinstance(data_array, (xr.DataArray,)): - return array_attach_units(data_array, data_units) +def strip_units(obj): + if isinstance(obj, xr.Dataset): + data_vars = {name: strip_units(value) for name, value in obj.data_vars.items()} + coords = {name: strip_units(value) for name, value in obj.coords.items()} - data = array_attach_units(data_array.data, data_units) + new_obj = xr.Dataset(data_vars=data_vars, coords=coords) + elif isinstance(obj, xr.DataArray): + data = array_strip_units(obj.data) + coords = { + name: (value.dims, array_strip_units(value.data)) + for name, value in obj.coords.items() + } - coords = { - name: value * units.get(name, 1) for name, value in data_array.coords.items() - } - dims = data_array.dims - attrs = data_array.attrs + new_obj = xr.DataArray(name=obj.name, data=data, coords=coords, dims=obj.dims) + elif hasattr(obj, "magnitude"): + new_obj = obj.magnitude + else: + new_obj = obj - return xr.DataArray(data=data, coords=coords, attrs=attrs, dims=dims) + return new_obj + + +def attach_units(obj, units): + if not isinstance(obj, (xr.DataArray, xr.Dataset)): + return array_attach_units(obj, units.get("data", 1)) + + if isinstance(obj, xr.Dataset): + data_vars = { + name: attach_units(value, units) for name, value in obj.data_vars.items() + } + + coords = { + name: attach_units(value, units) for name, value in obj.coords.items() + } + + new_obj = xr.Dataset(data_vars=data_vars, coords=coords, attrs=obj.attrs) + else: + data_units = units.get(obj.name, 1) + + data = array_attach_units(obj.data, data_units) + + coords = { + name: (value.dims, array_attach_units(value, units.get(name, 1))) + for name, value in obj.coords.items() + } + dims = obj.dims + attrs = obj.attrs + + new_obj = xr.DataArray(data=data, coords=coords, attrs=attrs, dims=dims) + + return new_obj + + +def assert_equal_with_units(a, b): + # works like xr.testing.assert_equal, but also explicitly checks units + # so, it is more like assert_identical + __tracebackhide__ = True + + if isinstance(a, xr.Dataset) or isinstance(b, xr.Dataset): + a_units = extract_units(a) + b_units = extract_units(b) + + a_without_units = strip_units(a) + b_without_units = strip_units(b) + assert a_without_units.equals(b_without_units) + assert a_units == b_units + else: + a = a if not isinstance(a, (xr.DataArray, xr.Variable)) else a.data + b = b if not isinstance(b, (xr.DataArray, xr.Variable)) else b.data + + assert type(a) == type(b) + + # workaround until pint implements allclose in __array_function__ + if isinstance(a, BaseQuantity) or isinstance(b, BaseQuantity): + assert ( + hasattr(a, "magnitude") and hasattr(b, "magnitude") + ) and np.allclose(a.magnitude, b.magnitude, equal_nan=True) + assert (hasattr(a, "units") and hasattr(b, "units")) and a.units == b.units + else: + assert np.allclose(a, b, equal_nan=True) @pytest.fixture(params=[float, int]) From 534e32950ce6a84949c5371ffd85c2c65cc90f70 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 23 Sep 2019 00:16:45 +0200 Subject: [PATCH 071/108] preserve multiindex in strip and attach --- xarray/tests/test_units.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 3da6ceb1249..12411dce988 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -99,7 +99,11 @@ def strip_units(obj): elif isinstance(obj, xr.DataArray): data = array_strip_units(obj.data) coords = { - name: (value.dims, array_strip_units(value.data)) + name: ( + (value.dims, array_strip_units(value.data)) + if isinstance(value.data, BaseQuantity) + else value # to preserve multiindices + ) for name, value in obj.coords.items() } @@ -132,7 +136,12 @@ def attach_units(obj, units): data = array_attach_units(obj.data, data_units) coords = { - name: (value.dims, array_attach_units(value, units.get(name, 1))) + name: ( + (value.dims, array_attach_units(value, units.get(name))) + if name in units + # to preserve multiindices + else value + ) for name, value in obj.coords.items() } dims = obj.dims From c5564166e17a910e3ac6fb9e595fcebecdad9f29 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 23 Sep 2019 00:17:35 +0200 Subject: [PATCH 072/108] attach the unit from element "data" as fallback --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 12411dce988..f56ee55fe57 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -131,7 +131,7 @@ def attach_units(obj, units): new_obj = xr.Dataset(data_vars=data_vars, coords=coords, attrs=obj.attrs) else: - data_units = units.get(obj.name, 1) + data_units = units.get(obj.name or "data", 1) data = array_attach_units(obj.data, data_units) From f60326f96ead62136d553297f77337ea1f8da18b Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 23 Sep 2019 00:19:21 +0200 Subject: [PATCH 073/108] fix some small typos --- xarray/tests/test_units.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index f56ee55fe57..0dfe70c237f 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -983,7 +983,7 @@ def test_broadcast_equals(self, unit, dtype): "func", ( method("pipe", lambda da: da * 10), - method("assign_coords", y2=("y", np.arange(10) * unit_registry.ms)), + method("assign_coords", y2=("y", np.arange(10) * unit_registry.mm)), method("assign_attrs", attr1="value"), method("rename", x2="x_mm"), method("swap_dims", {"x": "x2"}), @@ -1092,6 +1092,7 @@ def test_content_manipulation_with_units(self, func, unit, error, dtype): def test_isel(self, indices, dtype): array = np.arange(10).astype(dtype) * unit_registry.s x = np.arange(len(array)) * unit_registry.m + data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) assert_equal_with_units(array[indices], data_array.isel(x=indices)) From 51d24c72a558a5bf60f12afdf358c8a81e6b6daf Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 23 Sep 2019 00:20:04 +0200 Subject: [PATCH 074/108] compare QuantityScalar and QuantitySequence based on their values --- xarray/tests/test_units.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 0dfe70c237f..88668082c20 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -169,7 +169,9 @@ def assert_equal_with_units(a, b): a = a if not isinstance(a, (xr.DataArray, xr.Variable)) else a.data b = b if not isinstance(b, (xr.DataArray, xr.Variable)) else b.data - assert type(a) == type(b) + assert type(a) == type(b) or ( + isinstance(a, BaseQuantity) and isinstance(b, BaseQuantity) + ) # workaround until pint implements allclose in __array_function__ if isinstance(a, BaseQuantity) or isinstance(b, BaseQuantity): From f45ac4d2b42414b88cfe6d911e7c14d3f3650deb Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 23 Sep 2019 00:22:55 +0200 Subject: [PATCH 075/108] make the isel test more robust --- xarray/tests/test_units.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 88668082c20..a16321bee5f 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1097,7 +1097,13 @@ def test_isel(self, indices, dtype): data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) - assert_equal_with_units(array[indices], data_array.isel(x=indices)) + expected = attach_units( + strip_units(data_array).isel(x=indices), + {"data": unit_registry.s, "x": unit_registry.m}, + ) + result = data_array.isel(x=indices) + + assert_equal_with_units(expected, result) @pytest.mark.xfail( reason="xarray does not support duck arrays in dimension coordinates" From 56fd07d934fce3877d57f4f858c760b9f93074e9 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 23 Sep 2019 00:26:22 +0200 Subject: [PATCH 076/108] add tests for reshaping and reordering --- xarray/tests/test_units.py | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index a16321bee5f..2156de3fe9a 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1372,3 +1372,67 @@ def test_reindex_like(self, unit, error): result_data_array = data_array.reindex_like(new_data_array) assert_equal_with_units(result_array, result_data_array) + + @require_pint_array_function + @pytest.mark.parametrize( + "func", + (method("unstack"), method("reset_index", "z"), method("reorder_levels")), + ids=repr, + ) + def test_stacking_stacked(self, func, dtype): + array = ( + np.linspace(0, 10, 5 * 10).reshape(5, 10).astype(dtype) * unit_registry.m + ) + x = np.arange(array.shape[0]) + y = np.arange(array.shape[1]) + + data_array = xr.DataArray( + name="data", data=array, coords={"x": x, "y": y}, dims=("x", "y") + ) + stacked = data_array.stack(z=("x", "y")) + + expected = attach_units(func(strip_units(stacked)), {"data": unit_registry.m}) + result = func(stacked) + + assert_equal_with_units(expected, result) + + @require_pint_array_function + @pytest.mark.parametrize( + "func", + ( + method("transpose", "y", "x", "z"), + method("stack", a=("x", "y")), + method("set_index", x="x2"), + pytest.param( + method("shift", x=2), marks=pytest.mark.xfail(reason="strips units") + ), + pytest.param( + method("roll", x=2), marks=pytest.mark.xfail(reason="strips units") + ), + method("sortby", "x2"), + ), + ids=repr, + ) + def test_stacking_reordering(self, func, dtype): + array = ( + np.linspace(0, 10, 2 * 5 * 10).reshape(2, 5, 10).astype(dtype) + * unit_registry.m + ) + x = np.arange(array.shape[0]) + y = np.arange(array.shape[1]) + z = np.arange(array.shape[2]) + x2 = np.linspace(0, 1, array.shape[0])[::-1] + + data_array = xr.DataArray( + name="data", + data=array, + coords={"x": x, "y": y, "z": z, "x2": ("x", x2)}, + dims=("x", "y", "z"), + ) + + expected = attach_units( + func(strip_units(data_array)), {"data": unit_registry.m} + ) + result = func(data_array) + + assert_equal_with_units(expected, result) From c97f6da4d62357595f1dd38563e393c2e1127b12 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 26 Sep 2019 17:07:11 +0200 Subject: [PATCH 077/108] unify the structure of the tests --- xarray/tests/test_units.py | 159 ++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 74 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 2156de3fe9a..8cea76e211d 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -102,7 +102,7 @@ def strip_units(obj): name: ( (value.dims, array_strip_units(value.data)) if isinstance(value.data, BaseQuantity) - else value # to preserve multiindices + else value # to preserve multiindexes ) for name, value in obj.coords.items() } @@ -139,7 +139,7 @@ def attach_units(obj, units): name: ( (value.dims, array_attach_units(value, units.get(name))) if name in units - # to preserve multiindices + # to preserve multiindexes else value ) for name, value in obj.coords.items() @@ -243,11 +243,11 @@ def test_replication(func, dtype): array = np.linspace(0, 10, 20).astype(dtype) * unit_registry.s data_array = xr.DataArray(data=array, dims="x") - replicated = func(data_array) numpy_func = getattr(np, func.__name__) expected = numpy_func(array) + result = func(data_array) - assert_equal_with_units(expected, replicated) + assert_equal_with_units(expected, result) @pytest.mark.xfail( @@ -275,10 +275,10 @@ def test_replication_full_like(unit, error, dtype): with pytest.raises(error): xr.full_like(data_array, fill_value=fill_value) else: - replicated = xr.full_like(data_array, fill_value=fill_value) + result = xr.full_like(data_array, fill_value=fill_value) expected = np.full_like(array, fill_value=fill_value) - assert_equal_with_units(expected, replicated) + assert_equal_with_units(expected, result) class TestDataArray: @@ -435,10 +435,10 @@ def test_aggregation(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array) - result_array = func(array) - result_data_array = func(data_array) + expected = func(array) + result = func(data_array) - assert_equal_with_units(result_array, result_data_array) + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( @@ -457,7 +457,10 @@ def test_unary_operations(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array) - assert_equal_with_units(func(array), func(data_array)) + expected = xr.DataArray(data=func(array)) + result = func(data_array) + + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( @@ -479,7 +482,10 @@ def test_binary_operations(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array) - assert_equal_with_units(func(array), func(data_array)) + expected = xr.DataArray(data=func(array)) + result = func(data_array) + + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( @@ -519,13 +525,14 @@ def test_comparison_operations(self, comparison, unit, error, dtype): with pytest.raises(error): comparison(data_array, to_compare_with) else: - result_data_array = comparison(data_array, to_compare_with) + result = comparison(data_array, to_compare_with) # pint compares incompatible arrays to False, so we need to extend - result_array = comparison(array, to_compare_with) * np.ones_like( + # the multiplication works for both scalar and array results + expected = comparison(array, to_compare_with) * np.ones_like( array, dtype=bool ) - assert_equal_with_units(result_array, result_data_array) + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( @@ -546,7 +553,10 @@ def test_univariate_ufunc(self, units, error, dtype): with pytest.raises(error): np.sin(data_array) else: - assert_equal_with_units(np.sin(array), np.sin(data_array)) + expected = xr.DataArray(data=np.sin(array)) + result = np.sin(data_array) + + assert_equal_with_units(expected, result) @pytest.mark.xfail(reason="pint's implementation of `np.maximum` strips units") @require_pint_array_function @@ -555,9 +565,10 @@ def test_bivariate_ufunc(self, dtype): array = np.arange(10).astype(dtype) * unit data_array = xr.DataArray(data=array) - result_array = np.maximum(array, 0 * unit) - assert_equal_with_units(result_array, np.maximum(data_array, 0 * unit)) - assert_equal_with_units(result_array, np.maximum(0 * unit, data_array)) + expected = xr.DataArray(np.maximum(array, 0 * unit)) + + assert_equal_with_units(expected, np.maximum(data_array, 0 * unit)) + assert_equal_with_units(expected, np.maximum(0 * unit, data_array)) @pytest.mark.parametrize("property", ("T", "imag", "real")) def test_numpy_properties(self, property, dtype): @@ -567,10 +578,13 @@ def test_numpy_properties(self, property, dtype): ).reshape(5, 10) * unit_registry.s data_array = xr.DataArray(data=array, dims=("x", "y")) - result_data_array = getattr(data_array, property) - result_array = getattr(array, property) + expected = xr.DataArray( + data=getattr(array, property), + dims=("x", "y")[:: 1 if property != "T" else -1], + ) + result = getattr(data_array, property) - assert_equal_with_units(result_array, result_data_array) + assert_equal_with_units(expected, result) @pytest.mark.parametrize( "func", @@ -590,10 +604,10 @@ def test_numpy_methods(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array, dims="x") - expected = func(array) - result_xarray = func(data_array) + expected = xr.DataArray(func(array), dims="x") + result = func(data_array) - assert_equal_with_units(expected, result_xarray) + assert_equal_with_units(expected, result) @pytest.mark.parametrize( "func", (method("clip", min=3, max=8), method("searchsorted", v=5)), ids=repr @@ -625,9 +639,9 @@ def test_numpy_methods_with_args(self, func, unit, error, dtype): func(data_array, **kwargs) else: expected = func(array, **kwargs) - result_xarray = func(data_array, **kwargs) + result = func(data_array, **kwargs) - assert_equal_with_units(expected, result_xarray) + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( @@ -650,10 +664,10 @@ def test_missing_value_detection(self, func, dtype): data_array = xr.DataArray(data=array, coords={"x": x, "y": y}, dims=("x", "y")) - results_with_units = func(data_array) - results_without_units = func(strip_units(data_array)) + expected = func(strip_units(data_array)) + result = func(data_array) - assert_equal_with_units(results_with_units, results_without_units) + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.xfail(reason="ffill and bfill lose units in data") @@ -666,7 +680,6 @@ def test_missing_value_filling(self, func, dtype): x = np.arange(len(array)) data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) - result_with_units = func(data_array, dim="x") result_without_units = func(strip_units(data_array), dim="x") result = xr.DataArray( data=result_without_units.data * unit_registry.degK, @@ -674,7 +687,12 @@ def test_missing_value_filling(self, func, dtype): dims=["x"], ) - assert_equal_with_units(result, result_with_units) + expected = attach_units( + func(strip_units(data_array), dim="x"), {"data": unit_registry.degK} + ) + result = func(data_array, dim="x") + + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.xfail(reason="fillna drops the unit") @@ -697,11 +715,12 @@ def test_fillna(self, fill_value, dtype): array = np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) * unit data_array = xr.DataArray(data=array) - result_with_units = data_array.fillna(value=fill_value * unit) - result_without_units = strip_units(data_array).fillna(value=fill_value) - result = xr.DataArray(data=result_without_units.values * unit) + expected = attach_units( + strip_units(data_array).fillna(value=fill_value), {"data": unit} + ) + result = data_array.fillna(value=fill_value * unit) - assert_equal_with_units(result, result_with_units) + assert_equal_with_units(expected, result) @require_pint_array_function def test_dropna(self, dtype): @@ -712,15 +731,12 @@ def test_dropna(self, dtype): x = np.arange(len(array)) data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) - result_with_units = data_array.dropna(dim="x") - result_without_units = strip_units(data_array).dropna(dim="x") - result = xr.DataArray( - data=result_without_units.values * unit_registry.m, - coords=result_without_units.coords, - dims=result_without_units.dims, + expected = attach_units( + strip_units(data_array).dropna(dim="x"), {"data": unit_registry.m} ) + result = data_array.dropna(dim="x") - assert_equal_with_units(result, result_with_units) + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.xfail(reason="pint does not implement `numpy.isin`") @@ -743,6 +759,7 @@ def test_isin(self, unit, dtype): raw_values = np.array([1.4, np.nan, 2.3]).astype(dtype) values = raw_values * unit + result_without_units = strip_units(data_array).isin(raw_values) if unit != unit_registry.m: result_without_units[:] = False @@ -787,16 +804,13 @@ def test_isin(self, unit, dtype): ), ) def test_where(self, variant, unit, error, dtype): - def attach_unit(array, unit): - return xr.DataArray(data=array.data * unit) - def _strip_units(mapping): return {key: array_strip_units(value) for key, value in mapping.items()} original_unit = unit_registry.m array = np.linspace(0, 1, 10).astype(dtype) * original_unit + data_array = xr.DataArray(data=array) - array_without_units = strip_units(data_array) condition = data_array < 0.5 * original_unit other = np.linspace(-2, -1, 10).astype(dtype) * unit @@ -807,17 +821,19 @@ def _strip_units(mapping): "dropping": {"cond": condition, "drop": True}, } kwargs = variant_kwargs.get(variant) + kwargs_without_units = _strip_units(kwargs) if variant not in ("masking", "dropping") and error is not None: with pytest.raises(error): data_array.where(**kwargs) else: - result_with_units = data_array.where(**kwargs) - result = attach_unit( - array_without_units.where(**_strip_units(kwargs)), original_unit + expected = attach_units( + strip_units(array).where(**kwargs_without_units), + {"data": original_unit}, ) + result = data_array.where(**kwargs) - assert_equal_with_units(result, result_with_units) + assert_equal_with_units(expected, result) @pytest.mark.xfail(reason="interpolate strips units") @require_pint_array_function @@ -829,15 +845,12 @@ def test_interpolate_na(self, dtype): x = np.arange(len(array)) data_array = xr.DataArray(data=array, coords={"x": x}, dims="x").astype(dtype) - result_with_units = data_array.interpolate_na(dim="x") - result_without_units = strip_units(data_array).interpolate_na(dim="x") - result = xr.DataArray( - data=result_without_units.values * unit_registry.m, - coords=result_without_units.coords, - dims=result_without_units.dims, + expected = attach_units( + strip_units(data_array).interpolate_na(dim="x"), {"data": unit_registry.m} ) + result = data_array.interpolate_na(dim="x") - assert_equal_with_units(result, result_with_units) + assert_equal_with_units(expected, result) @pytest.mark.xfail(reason="uses DataArray.where, which currently fails") @require_pint_array_function @@ -868,15 +881,11 @@ def test_combine_first(self, unit, error, dtype): with pytest.raises(error): data_array.combine_first(other) else: - result = data_array.combine_first(other) - expected_wo_units = strip_units(data_array).combine_first( - strip_units(other) - ) - expected = xr.DataArray( - data=expected_wo_units.data, - coords=expected_wo_units.coords, - dims=["x", "y"], + expected = attach_units( + strip_units(data_array).combine_first(strip_units(other)), + {"data": unit_registry.m}, ) + result = data_array.combine_first(other) assert_equal_with_units(expected, result) @@ -940,6 +949,8 @@ def test_comparisons(self, func, variation, unit, dtype): ) # TODO: test dim coord once indexes leave units intact + # also, express this in terms of calls on the raw data array + # and then check the units equal_arrays = ( np.all(quantity == other.data) and (np.all(x == other.x.data) or True) # dims can't be checked yet @@ -1361,17 +1372,17 @@ def test_reindex_like(self, unit, error): with pytest.raises(error): data_array.reindex_like(new_data_array) else: - result_array = ( - xr.DataArray( - data=array.magnitude, - coords={name: value.magnitude for name, value in coords.items()}, - dims=("x", "y"), - ).reindex_like(strip_units(new_data_array)) - * unit_registry.degK + expected = attach_units( + strip_units(data_array).reindex_like(strip_units(new_data_array)), + { + "data": unit_registry.degK, + "x": unit_registry.m, + "y": unit_registry.m, + }, ) - result_data_array = data_array.reindex_like(new_data_array) + result = data_array.reindex_like(new_data_array) - assert_equal_with_units(result_array, result_data_array) + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( From 71cb40300cf3e793ce4308df31795a907c04b579 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 26 Sep 2019 17:15:34 +0200 Subject: [PATCH 078/108] mark the remaining tests as requiring a recent pint version, too --- xarray/tests/test_units.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 8cea76e211d..43e3a576eea 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -570,6 +570,7 @@ def test_bivariate_ufunc(self, dtype): assert_equal_with_units(expected, np.maximum(data_array, 0 * unit)) assert_equal_with_units(expected, np.maximum(0 * unit, data_array)) + @require_pint_array_function @pytest.mark.parametrize("property", ("T", "imag", "real")) def test_numpy_properties(self, property, dtype): array = ( @@ -586,6 +587,7 @@ def test_numpy_properties(self, property, dtype): assert_equal_with_units(expected, result) + @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -609,6 +611,7 @@ def test_numpy_methods(self, func, dtype): assert_equal_with_units(expected, result) + @require_pint_array_function @pytest.mark.parametrize( "func", (method("clip", min=3, max=8), method("searchsorted", v=5)), ids=repr ) From e1f095e3a9abe55665e53d61307ec1dff0ca8259 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 30 Sep 2019 13:08:23 +0200 Subject: [PATCH 079/108] explicitly handle quantities as parameters --- xarray/tests/test_units.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 43e3a576eea..b5361d84465 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -49,16 +49,25 @@ def array_strip_units(array): def array_attach_units(data, unit, convert=False): - data_ = data if not hasattr(data, "magnitude") else data.magnitude + if isinstance(data, BaseQuantity): + if convert: + return data.to(unit if unit != 1 else unit_registry.dimensionless) + else: + raise RuntimeError( + "cannot attach unit {unit} to quantity with {data.units}".format( + unit=unit, data=data + ) + ) + # to make sure we also encounter the case of "equal if converted" if ( convert and isinstance(unit, unit_registry.Unit) and "[length]" in unit.dimensionality ): - quantity = (data_ * unit_registry.m).to(unit) + quantity = (data * unit_registry.m).to(unit) else: - quantity = data_ * unit + quantity = data * unit return quantity From f93ed6d616c6b8100b6627e1f899fecb5ac8a14e Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 1 Oct 2019 01:00:11 +0200 Subject: [PATCH 080/108] change the repr of the function / method wrappers --- xarray/tests/test_units.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index b5361d84465..231e5da5d1e 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -231,7 +231,7 @@ def __call__(self, obj, *args, **kwargs): return func(*all_args, **all_kwargs) def __repr__(self): - return "method {self.name}".format(self=self) + return "method_{self.name}".format(self=self) class function: @@ -243,7 +243,7 @@ def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) def __repr__(self): - return "function {self.name}".format(self=self) + return "function_{self.name}".format(self=self) @require_pint_array_function From 65f1e8034b01c4f131603400b64628010a7fb612 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 1 Oct 2019 03:22:02 +0200 Subject: [PATCH 081/108] check whether __init__ and repr / str handle units in data and coords --- xarray/tests/test_units.py | 73 +++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 231e5da5d1e..92a2bb0e823 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -294,23 +294,38 @@ class TestDataArray: @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") @pytest.mark.parametrize( - "coords", + "variant", ( - pytest.param(True, id="with coords"), - pytest.param(False, id="without coords"), + pytest.param( + "with_dims", + marks=pytest.mark.xfail(reason="units in indexes are not supported"), + ), + pytest.param("with_coords"), + pytest.param("without_coords"), ), ) - def test_init(self, coords): - array = np.linspace(1, 2, 10) * unit_registry.m - parameters = {"data": array} - if coords: - x = np.arange(len(array)) * unit_registry.s - y = x.to(unit_registry.ms) - parameters["coords"] = {"x": x, "y": ("x", y)} - parameters["dims"] = ["x"] - - data_array = xr.DataArray(**parameters) - assert_equal_with_units(array, data_array) + def test_init(self, variant, dtype): + array = np.linspace(1, 2, 10, dtype=dtype) * unit_registry.m + + x = np.arange(len(array)) * unit_registry.s + y = x.to(unit_registry.ms) + + variants = { + "with_dims": {"x": x}, + "with_coords": {"y": ("x", y)}, + "without_coords": {}, + } + + kwargs = {"data": array, "dims": "x", "coords": variants.get(variant)} + data_array = xr.DataArray(**kwargs) + + assert isinstance(data_array.data, BaseQuantity) + assert all( + { + name: isinstance(coord.data, BaseQuantity) + for name, coord in data_array.coords.items() + }.values() + ) @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") @@ -318,27 +333,29 @@ def test_init(self, coords): "func", (pytest.param(str, id="str"), pytest.param(repr, id="repr")) ) @pytest.mark.parametrize( - "coords", + "variant", ( pytest.param( - True, - id="coords", - marks=pytest.mark.xfail( - reason="formatting currently does not delegate for coordinates" - ), + "with_dims", + marks=pytest.mark.xfail(reason="units in indexes are not supported"), ), - pytest.param(False, id="no coords"), + pytest.param("with_coords"), + pytest.param("without_coords"), ), ) - def test_repr(self, func, coords): - array = np.linspace(1, 2, 10) * unit_registry.m + def test_repr(self, func, variant, dtype): + array = np.linspace(1, 2, 10, dtype=dtype) * unit_registry.m x = np.arange(len(array)) * unit_registry.s + y = x.to(unit_registry.ms) - if coords: - data_array = xr.DataArray(data=array, coords={"x": x}, dims=["x"]) - print(data_array.x._variable._data) - else: - data_array = xr.DataArray(data=array) + variants = { + "with_dims": {"x": x}, + "with_coords": {"y": ("x", y)}, + "without_coords": {}, + } + + kwargs = {"data": array, "dims": "x", "coords": variants.get(variant)} + data_array = xr.DataArray(**kwargs) # FIXME: this just checks that the repr does not raise # warnings or errors, but does not check the result From 244f6a69964098df6b10b4df79ba42d75025ad12 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 4 Oct 2019 00:21:54 +0200 Subject: [PATCH 082/108] generalize array_attach_units --- xarray/tests/test_units.py | 49 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 92a2bb0e823..b70b6ff0966 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -48,24 +48,38 @@ def array_strip_units(array): return array -def array_attach_units(data, unit, convert=False): +def array_attach_units(data, unit, convert_from=None): if isinstance(data, BaseQuantity): - if convert: - return data.to(unit if unit != 1 else unit_registry.dimensionless) - else: - raise RuntimeError( - "cannot attach unit {unit} to quantity with {data.units}".format( + if not convert_from: + raise ValueError( + "cannot attach unit {unit} to quantity ({data.units})".format( unit=unit, data=data ) ) + elif isinstance(convert_from, unit_registry.Unit): + data = data.magnitude + elif convert_from is True: # intentionally accept exactly true + if data.check(unit): + convert_from = data.units + data = data.magnitude + else: + raise ValueError( + "cannot convert quantity ({data.units}) to {unit}".format( + unit=unit, data=data + ) + ) + else: + raise ValueError( + "cannot convert from invalid unit {convert_from}".format( + convert_from=convert_from + ) + ) # to make sure we also encounter the case of "equal if converted" - if ( - convert - and isinstance(unit, unit_registry.Unit) - and "[length]" in unit.dimensionality - ): - quantity = (data * unit_registry.m).to(unit) + if convert_from is not None: + quantity = (data * convert_from).to( + unit if isinstance(unit, unit_registry.Unit) else unit.dimensionless + ) else: quantity = data * unit @@ -969,10 +983,13 @@ def test_comparisons(self, func, variation, unit, dtype): data=quantity, coords={"x": x, "y": ("x", y)}, dims="x" ) other = xr.DataArray( - data=array_attach_units(data, data_unit, convert=True), + data=array_attach_units(data, data_unit, convert_from=unit_registry.m), coords={ - "x": array_attach_units(coord, dim_unit, convert=True), - "y": ("x", array_attach_units(coord, coord_unit, convert=True)), + "x": array_attach_units(coord, dim_unit, convert_from=unit_registry.m), + "y": ( + "x", + array_attach_units(coord, coord_unit, convert_from=unit_registry.m), + ), }, dims="x", ) @@ -1009,7 +1026,7 @@ def test_comparisons(self, func, variation, unit, dtype): def test_broadcast_equals(self, unit, dtype): left_array = np.ones(shape=(2, 2), dtype=dtype) * unit_registry.m right_array = array_attach_units( - np.ones(shape=(2,), dtype=dtype), unit, convert=True + np.ones(shape=(2,), dtype=dtype), unit, convert_from=unit_registry.m ) left = xr.DataArray(data=left_array, dims=("x", "y")) From e5e31f8c334ef3b46d13a7f73da12c92f923a449 Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 4 Oct 2019 00:25:21 +0200 Subject: [PATCH 083/108] move the redefinition of DimensionalityError --- xarray/tests/test_units.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index b70b6ff0966..ff3fa6ea928 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -8,6 +8,9 @@ from xarray.core.npcompat import IS_NEP18_ACTIVE pint = pytest.importorskip("pint") +DimensionalityError = pint.errors.DimensionalityError + + pytestmark = [ pytest.mark.skipif( not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" @@ -20,7 +23,6 @@ def use_pint_version_or_xfail(*, version, reason): return pytest.mark.xfail(LooseVersion(pint.__version__) < version, reason=reason) -DimensionalityError = pint.errors.DimensionalityError unit_registry = pint.UnitRegistry() require_pint_array_function = pytest.mark.xfail( not hasattr(unit_registry.Quantity, "__array_function__"), @@ -579,9 +581,7 @@ def test_comparison_operations(self, comparison, unit, error, dtype): "units,error", ( pytest.param(unit_registry.dimensionless, None, id="dimensionless"), - pytest.param( - unit_registry.m, pint.errors.DimensionalityError, id="incorrect unit" - ), + pytest.param(unit_registry.m, DimensionalityError, id="incorrect unit"), pytest.param(unit_registry.degree, None, id="correct unit"), ), ) From c92972f9a8634f3a823488a979bb66094b6df97f Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 4 Oct 2019 00:43:41 +0200 Subject: [PATCH 084/108] identify quantities using isinstance --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index ff3fa6ea928..e012bec5339 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -105,7 +105,7 @@ def extract_units(obj): } units = {**vars_units, **coords_units} - elif hasattr(obj, "units"): + elif isinstance(obj, BaseQuantity): vars_units = {"": array_extract_units(obj)} units = {**vars_units} From 90593fdb22dd08f1e2f5f21205501b739b477d1e Mon Sep 17 00:00:00 2001 From: Keewis Date: Fri, 4 Oct 2019 00:44:40 +0200 Subject: [PATCH 085/108] typo --- xarray/tests/test_units.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index e012bec5339..0a57783a4ae 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -80,7 +80,9 @@ def array_attach_units(data, unit, convert_from=None): # to make sure we also encounter the case of "equal if converted" if convert_from is not None: quantity = (data * convert_from).to( - unit if isinstance(unit, unit_registry.Unit) else unit.dimensionless + unit + if isinstance(unit, unit_registry.Unit) + else unit_registry.dimensionless ) else: quantity = data * unit From 20ca14938dde3ffe2d6e3984718fd7b25f1296a9 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 5 Oct 2019 14:14:04 +0200 Subject: [PATCH 086/108] skip tests with a pint version without __array_function__ --- xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 0a57783a4ae..e7b2d59cfbc 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -24,7 +24,7 @@ def use_pint_version_or_xfail(*, version, reason): unit_registry = pint.UnitRegistry() -require_pint_array_function = pytest.mark.xfail( +require_pint_array_function = pytest.mark.skipif( not hasattr(unit_registry.Quantity, "__array_function__"), reason="pint does not implement __array_function__ yet", ) From fbb00da26c00c504b240681db17b04f304a51cff Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 5 Oct 2019 15:40:54 +0200 Subject: [PATCH 087/108] compare DataArrays where possible --- xarray/tests/test_units.py | 54 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index e7b2d59cfbc..a4c32bba2a5 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -51,6 +51,11 @@ def array_strip_units(array): def array_attach_units(data, unit, convert_from=None): + try: + unit, convert_from = unit + except TypeError: + pass + if isinstance(data, BaseQuantity): if not convert_from: raise ValueError( @@ -271,7 +276,7 @@ def test_replication(func, dtype): data_array = xr.DataArray(data=array, dims="x") numpy_func = getattr(np, func.__name__) - expected = numpy_func(array) + expected = xr.DataArray(data=numpy_func(array), dims="x") result = func(data_array) assert_equal_with_units(expected, result) @@ -479,7 +484,7 @@ def test_aggregation(self, func, dtype): array = np.arange(10).astype(dtype) * unit_registry.m data_array = xr.DataArray(data=array) - expected = func(array) + expected = xr.DataArray(data=func(array)) result = func(data_array) assert_equal_with_units(expected, result) @@ -572,8 +577,9 @@ def test_comparison_operations(self, comparison, unit, error, dtype): result = comparison(data_array, to_compare_with) # pint compares incompatible arrays to False, so we need to extend # the multiplication works for both scalar and array results - expected = comparison(array, to_compare_with) * np.ones_like( - array, dtype=bool + expected = xr.DataArray( + data=comparison(array, to_compare_with) + * np.ones_like(array, dtype=bool) ) assert_equal_with_units(expected, result) @@ -671,7 +677,7 @@ def test_numpy_methods(self, func, dtype): ) def test_numpy_methods_with_args(self, func, unit, error, dtype): array = np.arange(10).astype(dtype) * unit_registry.m - data_array = xr.DataArray(data=array, dims="x") + data_array = xr.DataArray(data=array) scalar_types = (int, float) kwargs = { @@ -684,9 +690,14 @@ def test_numpy_methods_with_args(self, func, unit, error, dtype): func(data_array, **kwargs) else: expected = func(array, **kwargs) + if func.name not in ["searchsorted"]: + expected = xr.DataArray(data=expected) result = func(data_array, **kwargs) - assert_equal_with_units(expected, result) + if func.name in ["searchsorted"]: + assert np.allclose(expected, result) + else: + assert_equal_with_units(expected, result) @require_pint_array_function @pytest.mark.parametrize( @@ -970,28 +981,31 @@ def test_comparisons(self, func, variation, unit, dtype): data = np.linspace(0, 5, 10).astype(dtype) coord = np.arange(len(data)).astype(dtype) - quantity = data * unit_registry.m - x = coord * unit_registry.m - y = coord * unit_registry.m + base_unit = unit_registry.m + quantity = data * base_unit + x = coord * base_unit + y = coord * base_unit units = { - "data": (unit, unit_registry.m, unit_registry.m), - "dims": (unit_registry.m, unit, unit_registry.m), - "coords": (unit_registry.m, unit_registry.m, unit), + "data": (unit, base_unit, base_unit), + "dims": (base_unit, unit, base_unit), + "coords": (base_unit, base_unit, unit), } data_unit, dim_unit, coord_unit = units.get(variation) data_array = xr.DataArray( data=quantity, coords={"x": x, "y": ("x", y)}, dims="x" ) + + data_kwargs = {"convert_from": base_unit if quantity.check(data_unit) else None} + dim_kwargs = {"convert_from": base_unit if x.check(dim_unit) else None} + coord_kwargs = {"convert_from": base_unit if y.check(coord_unit) else None} + other = xr.DataArray( - data=array_attach_units(data, data_unit, convert_from=unit_registry.m), + data=array_attach_units(data, data_unit, **data_kwargs), coords={ - "x": array_attach_units(coord, dim_unit, convert_from=unit_registry.m), - "y": ( - "x", - array_attach_units(coord, coord_unit, convert_from=unit_registry.m), - ), + "x": array_attach_units(coord, dim_unit, **dim_kwargs), + "y": ("x", array_attach_units(coord, coord_unit, **coord_kwargs)), }, dims="x", ) @@ -1028,7 +1042,9 @@ def test_comparisons(self, func, variation, unit, dtype): def test_broadcast_equals(self, unit, dtype): left_array = np.ones(shape=(2, 2), dtype=dtype) * unit_registry.m right_array = array_attach_units( - np.ones(shape=(2,), dtype=dtype), unit, convert_from=unit_registry.m + np.ones(shape=(2,), dtype=dtype), + unit, + convert_from=unit_registry.m if left_array.check(unit) else None, ) left = xr.DataArray(data=left_array, dims=("x", "y")) From 4846de83935a60bb082ae5eec6ad723844992cfe Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 5 Oct 2019 15:41:54 +0200 Subject: [PATCH 088/108] mark only the compatible unit as xfailing --- xarray/tests/test_units.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index a4c32bba2a5..c948fbe0944 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -952,7 +952,11 @@ def test_combine_first(self, unit, error, dtype): pytest.param(1, id="no_unit"), pytest.param(unit_registry.dimensionless, id="dimensionless"), pytest.param(unit_registry.s, id="incompatible_unit"), - pytest.param(unit_registry.cm, id="compatible_unit"), + pytest.param( + unit_registry.cm, + id="compatible_unit", + marks=pytest.mark.xfail(reason="identical does not check units yet"), + ), pytest.param(unit_registry.m, id="identical_unit"), ), ) @@ -966,17 +970,7 @@ def test_combine_first(self, unit, error, dtype): "coords", ), ) - @pytest.mark.parametrize( - "func", - ( - method("equals"), - pytest.param( - method("identical"), - marks=pytest.mark.xfail(reason="identical does not check units yet"), - ), - ), - ids=repr, - ) + @pytest.mark.parametrize("func", (method("equals"), method("identical")), ids=repr) def test_comparisons(self, func, variation, unit, dtype): data = np.linspace(0, 5, 10).astype(dtype) coord = np.arange(len(data)).astype(dtype) From 603c554c85f26600134be323c4ff29a37d7d6177 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 9 Oct 2019 00:43:17 +0200 Subject: [PATCH 089/108] preserve the name of data arrays --- xarray/tests/test_units.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c948fbe0944..97ef0001c9e 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -179,7 +179,9 @@ def attach_units(obj, units): dims = obj.dims attrs = obj.attrs - new_obj = xr.DataArray(data=data, coords=coords, attrs=attrs, dims=dims) + new_obj = xr.DataArray( + name=obj.name, data=data, coords=coords, attrs=attrs, dims=dims + ) return new_obj From 6e18c67a8f0dd9947e72f805bde3a00c6776b505 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 9 Oct 2019 00:43:59 +0200 Subject: [PATCH 090/108] also attach units to x_mm --- xarray/tests/test_units.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 97ef0001c9e..4e0e02fd64a 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1099,7 +1099,13 @@ def test_content_manipulation(self, func, dtype): } expected = attach_units( func(strip_units(data_array), **stripped_kwargs), - {"data": quantity.units, "x": x.units, "x2": x2.units, "y": y.units}, + { + "data": quantity.units, + "x": x.units, + "x_mm": x2.units, + "x2": x2.units, + "y": y.units, + }, ) result = func(data_array) From ba81871900c235c0895e04dfd2cff24b31d891d9 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Wed, 9 Oct 2019 01:18:53 +0100 Subject: [PATCH 091/108] Test in more CI environments; documentation --- ci/requirements/py36-min-all-deps.yml | 1 + ci/requirements/py36-min-nep18.yml | 3 ++- ci/requirements/py37-windows.yml | 1 + doc/installing.rst | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ci/requirements/py36-min-all-deps.yml b/ci/requirements/py36-min-all-deps.yml index 4e4f8550e16..3f10a158f91 100644 --- a/ci/requirements/py36-min-all-deps.yml +++ b/ci/requirements/py36-min-all-deps.yml @@ -31,6 +31,7 @@ dependencies: - numba=0.44 - numpy=1.14 - pandas=0.24 + # - pint # See py36-min-nep18.yml - pip - pseudonetcdf=3.0 - pydap=3.2 diff --git a/ci/requirements/py36-min-nep18.yml b/ci/requirements/py36-min-nep18.yml index 5b291cf554c..4ecafb4888c 100644 --- a/ci/requirements/py36-min-nep18.yml +++ b/ci/requirements/py36-min-nep18.yml @@ -2,7 +2,7 @@ name: xarray-tests channels: - conda-forge dependencies: - # Optional dependencies that require NEP18, such as sparse, + # Optional dependencies that require NEP18, such as sparse and pint, # require drastically newer packages than everything else - python=3.6 - coveralls @@ -10,6 +10,7 @@ dependencies: - distributed=2.4 - numpy=1.17 - pandas=0.24 + - pint=0.9 - pytest - pytest-cov - pytest-env diff --git a/ci/requirements/py37-windows.yml b/ci/requirements/py37-windows.yml index bf485b59a49..1d150d9f2af 100644 --- a/ci/requirements/py37-windows.yml +++ b/ci/requirements/py37-windows.yml @@ -27,6 +27,7 @@ dependencies: - numba - numpy - pandas + - pint - pip - pseudonetcdf - pydap diff --git a/doc/installing.rst b/doc/installing.rst index b1bf072dbe1..e9f6be8f5fc 100644 --- a/doc/installing.rst +++ b/doc/installing.rst @@ -66,6 +66,7 @@ For plotting Alternative data containers ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - `sparse `_: for sparse arrays +- `pint `_: for units of measure - Any numpy-like objects that support `NEP-18 `_. Note that while such libraries theoretically should work, they are untested. @@ -85,7 +86,7 @@ dependencies: (`NEP-29 `_) - **pandas:** 12 months - **scipy:** 12 months -- **sparse** and other libraries that rely on +- **sparse, pint** and other libraries that rely on `NEP-18 `_ for integration: very latest available versions only, until the technology will have matured. This extends to dask when used in conjunction with any of these libraries. From 52154ccdd1397ada9b753268258fe3b3c4657a91 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Wed, 9 Oct 2019 01:21:53 +0100 Subject: [PATCH 092/108] What's New --- doc/whats-new.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 81206cc5cc1..693a8325a94 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -43,8 +43,12 @@ Breaking changes (:issue:`3222`, :issue:`3293`, :issue:`3340`, :issue:`3346`, :issue:`3358`). By `Guido Imperiale `_. -New functions/methods -~~~~~~~~~~~~~~~~~~~~~ +New features +~~~~~~~~~~~~ +- Added integration tests against `pint `_. + Note that, at the moment of writing, there are issues when pint interacts + with non-numpy array libraries, e.g. dask or sparse. + (:pull:`3238`) by `Justus Magin `_. Enhancements ~~~~~~~~~~~~ From 10e3249b2f505ac23fbbe6102fc0a9bef3b14446 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 10 Oct 2019 13:08:13 +0200 Subject: [PATCH 093/108] remove a stale function --- xarray/tests/test_units.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 4e0e02fd64a..d45dff2eba2 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1,5 +1,4 @@ import operator -from distutils.version import LooseVersion import numpy as np import pytest @@ -19,10 +18,6 @@ ] -def use_pint_version_or_xfail(*, version, reason): - return pytest.mark.xfail(LooseVersion(pint.__version__) < version, reason=reason) - - unit_registry = pint.UnitRegistry() require_pint_array_function = pytest.mark.skipif( not hasattr(unit_registry.Quantity, "__array_function__"), From f307ca5f0b1e3b99ff6357e7a7a8a6e6174b1351 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 10 Oct 2019 13:11:54 +0200 Subject: [PATCH 094/108] use Quantity directly for instance tests --- xarray/tests/test_units.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index d45dff2eba2..95b36876157 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -24,10 +24,7 @@ reason="pint does not implement __array_function__ yet", ) -try: - from pint.quantity import BaseQuantity -except ImportError: - BaseQuantity = unit_registry.Quantity +Quantity = unit_registry.Quantity def array_extract_units(obj): @@ -51,7 +48,7 @@ def array_attach_units(data, unit, convert_from=None): except TypeError: pass - if isinstance(data, BaseQuantity): + if isinstance(data, Quantity): if not convert_from: raise ValueError( "cannot attach unit {unit} to quantity ({data.units})".format( @@ -107,7 +104,7 @@ def extract_units(obj): } units = {**vars_units, **coords_units} - elif isinstance(obj, BaseQuantity): + elif isinstance(obj, Quantity): vars_units = {"": array_extract_units(obj)} units = {**vars_units} @@ -128,7 +125,7 @@ def strip_units(obj): coords = { name: ( (value.dims, array_strip_units(value.data)) - if isinstance(value.data, BaseQuantity) + if isinstance(value.data, Quantity) else value # to preserve multiindexes ) for name, value in obj.coords.items() @@ -199,11 +196,11 @@ def assert_equal_with_units(a, b): b = b if not isinstance(b, (xr.DataArray, xr.Variable)) else b.data assert type(a) == type(b) or ( - isinstance(a, BaseQuantity) and isinstance(b, BaseQuantity) + isinstance(a, Quantity) and isinstance(b, Quantity) ) # workaround until pint implements allclose in __array_function__ - if isinstance(a, BaseQuantity) or isinstance(b, BaseQuantity): + if isinstance(a, Quantity) or isinstance(b, Quantity): assert ( hasattr(a, "magnitude") and hasattr(b, "magnitude") ) and np.allclose(a.magnitude, b.magnitude, equal_nan=True) @@ -339,10 +336,10 @@ def test_init(self, variant, dtype): kwargs = {"data": array, "dims": "x", "coords": variants.get(variant)} data_array = xr.DataArray(**kwargs) - assert isinstance(data_array.data, BaseQuantity) + assert isinstance(data_array.data, Quantity) assert all( { - name: isinstance(coord.data, BaseQuantity) + name: isinstance(coord.data, Quantity) for name, coord in data_array.coords.items() }.values() ) From ea45c7c2f0c887e01e2cdd378dc242e71a64fb32 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sun, 13 Oct 2019 21:18:49 +0200 Subject: [PATCH 095/108] explicitly set roll_coords to silence a deprecation warning --- xarray/tests/test_units.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 95b36876157..a42fdf4aeba 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1476,7 +1476,8 @@ def test_stacking_stacked(self, func, dtype): method("shift", x=2), marks=pytest.mark.xfail(reason="strips units") ), pytest.param( - method("roll", x=2), marks=pytest.mark.xfail(reason="strips units") + method("roll", x=2, roll_coords=False), + marks=pytest.mark.xfail(reason="strips units"), ), method("sortby", "x2"), ), From 7252adac4210796dc01c655892607b6ae0c0fc3d Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 14 Oct 2019 13:12:34 +0200 Subject: [PATCH 096/108] skip the whole module if pint does not implement __array_function__ the advantage is that now forgetting to decorate a test case is not possible. --- xarray/tests/test_units.py | 51 ++++++-------------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index a42fdf4aeba..c504677254b 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -10,23 +10,21 @@ DimensionalityError = pint.errors.DimensionalityError +unit_registry = pint.UnitRegistry() +Quantity = unit_registry.Quantity + pytestmark = [ pytest.mark.skipif( not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" ), + pytest.mark.skipif( + not hasattr(unit_registry.Quantity, "__array_function__"), + reason="pint does not implement __array_function__ yet", + ), # pytest.mark.filterwarnings("ignore:::pint[.*]"), ] -unit_registry = pint.UnitRegistry() -require_pint_array_function = pytest.mark.skipif( - not hasattr(unit_registry.Quantity, "__array_function__"), - reason="pint does not implement __array_function__ yet", -) - -Quantity = unit_registry.Quantity - - def array_extract_units(obj): raw = obj.data if hasattr(obj, "data") else obj try: @@ -263,7 +261,6 @@ def __repr__(self): return "function_{self.name}".format(self=self) -@require_pint_array_function @pytest.mark.parametrize("func", (xr.zeros_like, xr.ones_like)) def test_replication(func, dtype): array = np.linspace(0, 10, 20).astype(dtype) * unit_registry.s @@ -279,7 +276,6 @@ def test_replication(func, dtype): @pytest.mark.xfail( reason="np.full_like on Variable strips the unit and pint does not allow mixed args" ) -@require_pint_array_function @pytest.mark.parametrize( "unit,error", ( @@ -308,7 +304,6 @@ def test_replication_full_like(unit, error, dtype): class TestDataArray: - @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") @pytest.mark.parametrize( "variant", @@ -344,7 +339,6 @@ def test_init(self, variant, dtype): }.values() ) - @require_pint_array_function @pytest.mark.filterwarnings("error:::pint[.*]") @pytest.mark.parametrize( "func", (pytest.param(str, id="str"), pytest.param(repr, id="repr")) @@ -378,7 +372,6 @@ def test_repr(self, func, variant, dtype): # warnings or errors, but does not check the result func(data_array) - @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -483,7 +476,6 @@ def test_aggregation(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -505,7 +497,6 @@ def test_unary_operations(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -530,7 +521,6 @@ def test_binary_operations(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "comparison", ( @@ -578,7 +568,6 @@ def test_comparison_operations(self, comparison, unit, error, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "units,error", ( @@ -601,7 +590,6 @@ def test_univariate_ufunc(self, units, error, dtype): assert_equal_with_units(expected, result) @pytest.mark.xfail(reason="pint's implementation of `np.maximum` strips units") - @require_pint_array_function def test_bivariate_ufunc(self, dtype): unit = unit_registry.m array = np.arange(10).astype(dtype) * unit @@ -612,7 +600,6 @@ def test_bivariate_ufunc(self, dtype): assert_equal_with_units(expected, np.maximum(data_array, 0 * unit)) assert_equal_with_units(expected, np.maximum(0 * unit, data_array)) - @require_pint_array_function @pytest.mark.parametrize("property", ("T", "imag", "real")) def test_numpy_properties(self, property, dtype): array = ( @@ -629,7 +616,6 @@ def test_numpy_properties(self, property, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -653,7 +639,6 @@ def test_numpy_methods(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", (method("clip", min=3, max=8), method("searchsorted", v=5)), ids=repr ) @@ -693,7 +678,6 @@ def test_numpy_methods_with_args(self, func, unit, error, dtype): else: assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", (method("isnull"), method("notnull"), method("count")), ids=repr ) @@ -719,7 +703,6 @@ def test_missing_value_detection(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.xfail(reason="ffill and bfill lose units in data") @pytest.mark.parametrize("func", (method("ffill"), method("bfill")), ids=repr) def test_missing_value_filling(self, func, dtype): @@ -744,7 +727,6 @@ def test_missing_value_filling(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.xfail(reason="fillna drops the unit") @pytest.mark.parametrize( "fill_value", @@ -772,7 +754,6 @@ def test_fillna(self, fill_value, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function def test_dropna(self, dtype): array = ( np.array([1.4, np.nan, 2.3, np.nan, np.nan, 9.1]).astype(dtype) @@ -788,7 +769,6 @@ def test_dropna(self, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.xfail(reason="pint does not implement `numpy.isin`") @pytest.mark.parametrize( "unit", @@ -817,7 +797,6 @@ def test_isin(self, unit, dtype): assert_equal_with_units(result_without_units, result_with_units) - @require_pint_array_function @pytest.mark.parametrize( "variant", ( @@ -886,7 +865,6 @@ def _strip_units(mapping): assert_equal_with_units(expected, result) @pytest.mark.xfail(reason="interpolate strips units") - @require_pint_array_function def test_interpolate_na(self, dtype): array = ( np.array([-1.03, 0.1, 1.4, np.nan, 2.3, np.nan, np.nan, 9.1]) @@ -903,7 +881,6 @@ def test_interpolate_na(self, dtype): assert_equal_with_units(expected, result) @pytest.mark.xfail(reason="uses DataArray.where, which currently fails") - @require_pint_array_function @pytest.mark.parametrize( "unit,error", ( @@ -939,7 +916,6 @@ def test_combine_first(self, unit, error, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "unit", ( @@ -1016,7 +992,6 @@ def test_comparisons(self, func, variation, unit, dtype): assert expected == result - @require_pint_array_function @pytest.mark.parametrize( "unit", ( @@ -1043,7 +1018,6 @@ def test_broadcast_equals(self, unit, dtype): assert expected == result - @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -1103,7 +1077,6 @@ def test_content_manipulation(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", ( @@ -1152,7 +1125,6 @@ def test_content_manipulation_with_units(self, func, unit, error, dtype): result = func(data_array, **kwargs) assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "indices", ( @@ -1177,7 +1149,6 @@ def test_isel(self, indices, dtype): @pytest.mark.xfail( reason="xarray does not support duck arrays in dimension coordinates" ) - @require_pint_array_function @pytest.mark.parametrize( "values", ( @@ -1213,7 +1184,6 @@ def test_sel(self, values, units, error, dtype): @pytest.mark.xfail( reason="xarray does not support duck arrays in dimension coordinates" ) - @require_pint_array_function @pytest.mark.parametrize( "values", ( @@ -1246,7 +1216,6 @@ def test_loc(self, values, units, error, dtype): result_data_array = data_array.loc[values_with_units] assert_equal_with_units(result_array, result_data_array) - @require_pint_array_function @pytest.mark.xfail(reason="tries to coerce using asarray") @pytest.mark.parametrize( "shape", @@ -1281,7 +1250,6 @@ def test_squeeze(self, shape, dtype): np.squeeze(array, axis=index), data_array.squeeze(dim=name) ) - @require_pint_array_function @pytest.mark.parametrize( "unit,error", ( @@ -1316,7 +1284,6 @@ def test_interp(self, unit, error): assert_equal_with_units(result_array, result_data_array) - @require_pint_array_function @pytest.mark.xfail(reason="tries to coerce using asarray") @pytest.mark.parametrize( "unit,error", @@ -1358,7 +1325,6 @@ def test_interp_like(self, unit, error): assert_equal_with_units(result_array, result_data_array) - @require_pint_array_function @pytest.mark.xfail( reason="pint does not implement np.result_type in __array_function__ yet" ) @@ -1398,7 +1364,6 @@ def test_reindex(self, unit, error): assert_equal_with_units(result_array, result_data_array) - @require_pint_array_function @pytest.mark.xfail( reason="pint does not implement np.result_type in __array_function__ yet" ) @@ -1442,7 +1407,6 @@ def test_reindex_like(self, unit, error): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", (method("unstack"), method("reset_index", "z"), method("reorder_levels")), @@ -1465,7 +1429,6 @@ def test_stacking_stacked(self, func, dtype): assert_equal_with_units(expected, result) - @require_pint_array_function @pytest.mark.parametrize( "func", ( From cb8d586bdf8643c78549532c968faeada97a254c Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 14 Oct 2019 22:50:13 +0200 Subject: [PATCH 097/108] allow to attach units using the mapping from extract_units --- xarray/tests/test_units.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c504677254b..c9b1cb23467 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -153,13 +153,19 @@ def attach_units(obj, units): new_obj = xr.Dataset(data_vars=data_vars, coords=coords, attrs=obj.attrs) else: - data_units = units.get(obj.name or "data", 1) + # try the array name, "data" and None, then fall back to dimensionless + 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) coords = { name: ( - (value.dims, array_attach_units(value, units.get(name))) + (value.dims, array_attach_units(value, units.get(name) or 1)) if name in units # to preserve multiindexes else value From 9b3473c51b5ad2e64e7a9a97672d67bafbd60382 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 02:11:02 +0200 Subject: [PATCH 098/108] add tests for computation methods resampling fails until I figure out how to use it with non-datetime coords. --- xarray/tests/test_units.py | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c9b1cb23467..5b96866d40d 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1475,3 +1475,72 @@ def test_stacking_reordering(self, func, dtype): result = func(data_array) assert_equal_with_units(expected, result) + + @pytest.mark.parametrize( + "func", + ( + method("diff", dim="x"), + method("differentiate", coord="x"), + method("integrate", dim="x"), + pytest.param( + method("quantile", q=[0.25, 0.75]), + marks=pytest.mark.xfail( + reason="pint does not implement nanpercentile yet" + ), + ), + pytest.param( + method("reduce", func=np.sum, dim="x"), + marks=pytest.mark.xfail(reason="strips units"), + ), + ), + ids=repr, + ) + def test_computation(self, func, dtype): + array = ( + np.linspace(0, 10, 5 * 10).reshape(5, 10).astype(dtype) * unit_registry.m + ) + + x = np.arange(array.shape[0]) * unit_registry.m + y = np.arange(array.shape[1]) * unit_registry.s + + data_array = xr.DataArray(data=array, coords={"x": x, "y": y}, dims=("x", "y")) + units = extract_units(data_array) + + expected = attach_units(func(strip_units(data_array)), units) + result = func(data_array) + + assert_equal_with_units(expected, result) + + @pytest.mark.parametrize( + "func", + ( + pytest.param( + method("groupby", "y"), marks=pytest.mark.xfail(reason="strips units") + ), + pytest.param( + method("groupby_bins", "y", bins=4), + marks=pytest.mark.xfail(reason="strips units"), + ), + method("coarsen", y=2), + method("resample", x=5 * unit_registry.m), + pytest.param( + method("rolling", y=3), marks=pytest.mark.xfail(reason="strips units") + ), + ), + ids=repr, + ) + def test_computation_objects(self, func, dtype): + array = ( + np.linspace(0, 10, 5 * 10).reshape(5, 10).astype(dtype) * unit_registry.m + ) + + x = np.arange(array.shape[0]) * unit_registry.m + y = np.arange(array.shape[1]) * 3 * unit_registry.s + + data_array = xr.DataArray(data=array, coords={"x": x, "y": y}, dims=("x", "y")) + units = extract_units(data_array) + + expected = attach_units(func(strip_units(data_array)).mean(), units) + result = func(data_array).mean() + + assert_equal_with_units(expected, result) From 788d6ff7d955d4a7cbf336c814720bd9235b0d99 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 02:13:20 +0200 Subject: [PATCH 099/108] add tests for grouped operations --- xarray/tests/test_units.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 5b96866d40d..c09a86b4e9b 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1544,3 +1544,35 @@ def test_computation_objects(self, func, dtype): result = func(data_array).mean() assert_equal_with_units(expected, result) + + @pytest.mark.parametrize( + "func", + ( + pytest.param( + method("assign_coords", {"z": (["x"], np.arange(5) * unit_registry.s)}), + marks=pytest.mark.xfail(reason="strips units"), + ), + pytest.param(method("first")), + pytest.param(method("last")), + pytest.param( + method("quantile", q=[0.25, 0.5, 0.75], dim="x"), + marks=pytest.mark.xfail(reason="strips units"), + ), + ), + ids=repr, + ) + def test_grouped_operations(self, func, dtype): + array = ( + np.linspace(0, 10, 5 * 10).reshape(5, 10).astype(dtype) * unit_registry.m + ) + + x = np.arange(array.shape[0]) * unit_registry.m + y = np.arange(array.shape[1]) * 3 * unit_registry.s + + data_array = xr.DataArray(data=array, coords={"x": x, "y": y}, dims=("x", "y")) + units = extract_units(data_array) + + expected = attach_units(func(strip_units(data_array).groupby("y")), units) + result = func(data_array.groupby("y")) + + assert_equal_with_units(expected, result) From cb8b642e3a1333dc5d006999944f27a94c52000a Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 13:29:52 +0200 Subject: [PATCH 100/108] add a test for rolling_exp --- xarray/tests/test_units.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index c09a86b4e9b..347a8476297 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1526,6 +1526,10 @@ def test_computation(self, func, dtype): pytest.param( method("rolling", y=3), marks=pytest.mark.xfail(reason="strips units") ), + pytest.param( + method("rolling_exp", y=3), + marks=pytest.mark.xfail(reason="strips units"), + ), ), ids=repr, ) From 22ba57c9bd62caf3e923ea12c2b4758f187f842a Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 14:15:08 +0200 Subject: [PATCH 101/108] add a todo note for the module level skip on __array_function__ --- xarray/tests/test_units.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 347a8476297..8f4aad17ebb 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -17,6 +17,7 @@ pytest.mark.skipif( not IS_NEP18_ACTIVE, reason="NUMPY_EXPERIMENTAL_ARRAY_FUNCTION is not enabled" ), + # TODO: remove this once pint has a released version with __array_function__ pytest.mark.skipif( not hasattr(unit_registry.Quantity, "__array_function__"), reason="pint does not implement __array_function__ yet", From b321d4592d1f03a50394ab2395032952b6490ec4 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 14:23:03 +0200 Subject: [PATCH 102/108] add a test for dot --- xarray/tests/test_units.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 8f4aad17ebb..20c6844cb58 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1493,6 +1493,11 @@ def test_stacking_reordering(self, func, dtype): method("reduce", func=np.sum, dim="x"), marks=pytest.mark.xfail(reason="strips units"), ), + pytest.param( + lambda x: x.dot(x), + id="method_dot", + marks=pytest.mark.xfail(reason="pint does not implement einsum"), + ), ), ids=repr, ) From c499e5c8eb3586dd0023cfed71a284311ada5974 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 14:46:12 +0200 Subject: [PATCH 103/108] use attach_units instead of manually attaching --- xarray/tests/test_units.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 20c6844cb58..a39623798b8 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -166,7 +166,7 @@ def attach_units(obj, units): coords = { name: ( - (value.dims, array_attach_units(value, units.get(name) or 1)) + (value.dims, array_attach_units(value.data, units.get(name) or 1)) if name in units # to preserve multiindexes else value @@ -968,17 +968,13 @@ def test_comparisons(self, func, variation, unit, dtype): data=quantity, coords={"x": x, "y": ("x", y)}, dims="x" ) - data_kwargs = {"convert_from": base_unit if quantity.check(data_unit) else None} - dim_kwargs = {"convert_from": base_unit if x.check(dim_unit) else None} - coord_kwargs = {"convert_from": base_unit if y.check(coord_unit) else None} - - other = xr.DataArray( - data=array_attach_units(data, data_unit, **data_kwargs), - coords={ - "x": array_attach_units(coord, dim_unit, **dim_kwargs), - "y": ("x", array_attach_units(coord, coord_unit, **coord_kwargs)), + other = attach_units( + strip_units(data_array), + { + None: (data_unit, base_unit if quantity.check(data_unit) else None), + "x": (dim_unit, base_unit if x.check(dim_unit) else None), + "y": (coord_unit, base_unit if y.check(coord_unit) else None), }, - dims="x", ) # TODO: test dim coord once indexes leave units intact From 1897159a6a138dc3d6d06889ea2d93806ba7acd6 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 18:30:14 +0200 Subject: [PATCH 104/108] modify the resample test to actually work --- xarray/tests/test_units.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index a39623798b8..233b4f0d1de 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -1,6 +1,7 @@ import operator import numpy as np +import pandas as pd import pytest import xarray as xr @@ -81,7 +82,13 @@ def array_attach_units(data, unit, convert_from=None): else unit_registry.dimensionless ) else: - quantity = data * unit + try: + quantity = data * unit + except np.core._exceptions.UFuncTypeError: + if unit != 1: + raise + + quantity = data return quantity @@ -1524,7 +1531,6 @@ def test_computation(self, func, dtype): marks=pytest.mark.xfail(reason="strips units"), ), method("coarsen", y=2), - method("resample", x=5 * unit_registry.m), pytest.param( method("rolling", y=3), marks=pytest.mark.xfail(reason="strips units") ), @@ -1551,6 +1557,21 @@ def test_computation_objects(self, func, dtype): assert_equal_with_units(expected, result) + @pytest.mark.xfail(reason="strips units") + def test_resample(self, dtype): + array = np.linspace(0, 5, 10).astype(dtype) * unit_registry.m + + time = pd.date_range("10-09-2010", periods=len(array), freq="1y") + data_array = xr.DataArray(data=array, coords={"time": time}, dims="time") + units = extract_units(data_array) + + func = method("resample", time="6m") + + expected = attach_units(func(strip_units(data_array)).mean(), units) + result = func(data_array).mean() + + assert_equal_with_units(expected, result) + @pytest.mark.parametrize( "func", ( From d3f6773dca27e3d84ec3a26b7ff452b4f75c7951 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 15 Oct 2019 19:25:39 +0200 Subject: [PATCH 105/108] add a test for to_unstacked_dataset --- xarray/tests/test_units.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_units.py b/xarray/tests/test_units.py index 233b4f0d1de..15bb40ce4b2 100644 --- a/xarray/tests/test_units.py +++ b/xarray/tests/test_units.py @@ -5,6 +5,7 @@ import pytest import xarray as xr +from xarray.core import formatting from xarray.core.npcompat import IS_NEP18_ACTIVE pint = pytest.importorskip("pint") @@ -201,7 +202,10 @@ def assert_equal_with_units(a, b): a_without_units = strip_units(a) b_without_units = strip_units(b) - assert a_without_units.equals(b_without_units) + + assert a_without_units.equals(b_without_units), formatting.diff_dataset_repr( + a, b, "equals" + ) assert a_units == b_units else: a = a if not isinstance(a, (xr.DataArray, xr.Variable)) else a.data @@ -1439,6 +1443,33 @@ def test_stacking_stacked(self, func, dtype): assert_equal_with_units(expected, result) + @pytest.mark.xfail(reason="indexes strip the label units") + def test_to_unstacked_dataset(self, dtype): + array = ( + np.linspace(0, 10, 5 * 10).reshape(5, 10).astype(dtype) + * unit_registry.pascal + ) + x = np.arange(array.shape[0]) * unit_registry.m + y = np.arange(array.shape[1]) * unit_registry.s + + data_array = xr.DataArray( + data=array, coords={"x": x, "y": y}, dims=("x", "y") + ).stack(z=("x", "y")) + + func = method("to_unstacked_dataset", dim="z") + + expected = attach_units( + func(strip_units(data_array)), + {"y": y.units, **dict(zip(x.magnitude, [array.units] * len(y)))}, + ).rename({elem.magnitude: elem for elem in x}) + result = func(data_array) + + print(data_array, expected, result, sep="\n") + + assert_equal_with_units(expected, result) + + assert False + @pytest.mark.parametrize( "func", ( From 0ab6a82acba00e5281d192de671015d41f644602 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 17 Oct 2019 01:04:43 +0200 Subject: [PATCH 106/108] update whats-new.rst and installing.rst --- doc/installing.rst | 8 ++++++++ doc/whats-new.rst | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/doc/installing.rst b/doc/installing.rst index e9f6be8f5fc..0c5e8916ca3 100644 --- a/doc/installing.rst +++ b/doc/installing.rst @@ -67,6 +67,14 @@ Alternative data containers ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - `sparse `_: for sparse arrays - `pint `_: for units of measure + + .. note:: + + At the moment of writing, xarray requires a `highly experimental version of pint + `_ (install with + ``pip install git+https://github.com/andrewgsavage/pint.git@refs/pull/6/head)``. + Even with it, interaction with non-numpy array libraries, e.g. dask or sparse, is broken. + - Any numpy-like objects that support `NEP-18 `_. Note that while such libraries theoretically should work, they are untested. diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 86cc8f44895..d9b671762e5 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -18,6 +18,19 @@ What's New v0.14.1 (unreleased) -------------------- +New Features +~~~~~~~~~~~~ +- Added integration tests against `pint `_. + + .. note:: + + At the moment of writing, these tests *as well as the ability to use pint in general* + require `a highly experimental version of pint + `_ (install with + ``pip install git+https://github.com/andrewgsavage/pint.git@refs/pull/6/head)``. + Even with it, interaction with non-numpy array libraries, e.g. dask or sparse, is broken. + + (:pull:`3238`) by `Justus Magin `_. .. _whats-new.0.14.0: @@ -51,10 +64,6 @@ Breaking changes New features ~~~~~~~~~~~~ -- Added integration tests against `pint `_. - Note that, at the moment of writing, there are issues when pint interacts - with non-numpy array libraries, e.g. dask or sparse. - (:pull:`3238`) by `Justus Magin `_. - Dropped the `drop=False` optional parameter from :meth:`Variable.isel`. It was unused and doesn't make sense for a Variable. (:pull:`3375`). By `Guido Imperiale `_. From a9916ef631b0061703ac55095cc6d74e5f47b929 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 17 Oct 2019 01:15:02 +0200 Subject: [PATCH 107/108] reformat the whats-new.rst entry --- doc/whats-new.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 312cd94bbef..12b0f6b3c2e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -21,6 +21,7 @@ v0.14.1 (unreleased) New Features ~~~~~~~~~~~~ - Added integration tests against `pint `_. + (:pull:`3238`) by `Justus Magin `_. .. note:: @@ -30,8 +31,6 @@ New Features ``pip install git+https://github.com/andrewgsavage/pint.git@refs/pull/6/head)``. Even with it, interaction with non-numpy array libraries, e.g. dask or sparse, is broken. - (:pull:`3238`) by `Justus Magin `_. - Documentation ~~~~~~~~~~~~~ From 21853d70ff900c7f097dbbc5711cc3b4e0ce88b7 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 17 Oct 2019 00:26:59 +0100 Subject: [PATCH 108/108] What's New --- ci/requirements/py36-min-nep18.yml | 2 +- doc/whats-new.rst | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ci/requirements/py36-min-nep18.yml b/ci/requirements/py36-min-nep18.yml index 4ecafb4888c..fc9523ce249 100644 --- a/ci/requirements/py36-min-nep18.yml +++ b/ci/requirements/py36-min-nep18.yml @@ -10,7 +10,7 @@ dependencies: - distributed=2.4 - numpy=1.17 - pandas=0.24 - - pint=0.9 + - pint=0.9 # Actually not enough as it doesn't implement __array_function__yet! - pytest - pytest-cov - pytest-env diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 12b0f6b3c2e..6c09b44940b 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -69,8 +69,6 @@ Breaking changes (:issue:`3222`, :issue:`3293`, :issue:`3340`, :issue:`3346`, :issue:`3358`). By `Guido Imperiale `_. -New features -~~~~~~~~~~~~ - Dropped the `drop=False` optional parameter from :meth:`Variable.isel`. It was unused and doesn't make sense for a Variable. (:pull:`3375`). By `Guido Imperiale `_.