From 2ca5856ad7a5029aa6003c64afc33015e2d25b8e Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Wed, 20 Nov 2024 22:13:22 -0500 Subject: [PATCH 1/7] add as_array methods --- xarray/core/dataarray.py | 22 ++++++++++++++++++++++ xarray/core/dataset.py | 26 ++++++++++++++++++++++++++ xarray/namedarray/core.py | 4 ++++ xarray/tests/test_dataarray.py | 13 +++++++++++++ xarray/tests/test_dataset.py | 15 +++++++++++++++ 5 files changed, 80 insertions(+) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index eae11c0c491..cbf41bc594c 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -844,6 +844,28 @@ def as_numpy(self) -> Self: coords = {k: v.as_numpy() for k, v in self._coords.items()} return self._replace(self.variable.as_numpy(), coords, indexes=self._indexes) + def as_array(self, asarray: Callable[[ArrayLike, ...], Any], **kwargs) -> Self: + """ + Coerces wrapped data into a specific array type. + + `asarray` should output an object that supports the Array API Standard. + This method does not convert index coordinates, which can't generally be + represented as arbitrary array types. + + Parameters + ---------- + asarray : Callable + Function that converts an array-like object to the desired array type. + For example, `cupy.asarray`, `jax.numpy.asarray`, or `sparse.COO.from_numpy`. + **kwargs : dict + Additional keyword arguments passed to the `asarray` function. + + Returns + ------- + DataArray + """ + return self._replace(self.variable.as_array(asarray, **kwargs)) + @property def _in_memory(self) -> bool: return self.variable._in_memory diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index e80ce5fa64a..4be135d2b45 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1460,6 +1460,32 @@ def as_numpy(self) -> Self: numpy_variables = {k: v.as_numpy() for k, v in self.variables.items()} return self._replace(variables=numpy_variables) + def as_array(self, asarray: Callable[[ArrayLike, ...], Any], **kwargs) -> Self: + """ + Converts wrapped data into a specific array type. + + `asarray` should output an object that supports the Array API Standard. + This method does not convert index coordinates, which can't generally be + represented as arbitrary array types. + + Parameters + ---------- + asarray : Callable + Function that converts an array-like object to the desired array type. + For example, `cupy.asarray`, `jax.numpy.asarray`, or `sparse.COO.from_numpy`. + **kwargs : dict + Additional keyword arguments passed to the `asarray` function. + + Returns + ------- + Dataset + """ + array_variables = { + k: v.as_array(asarray, **kwargs) if k not in self._indexes else v + for k, v in self.variables.items() + } + return self._replace(variables=array_variables) + def _copy_listed(self, names: Iterable[Hashable]) -> Self: """Create a new Dataset with the listed variables from this dataset and the all relevant coordinates. Skips all validation. diff --git a/xarray/namedarray/core.py b/xarray/namedarray/core.py index 98d96c73e91..8ae17ebce13 100644 --- a/xarray/namedarray/core.py +++ b/xarray/namedarray/core.py @@ -860,6 +860,10 @@ def as_numpy(self) -> Self: """Coerces wrapped data into a numpy array, returning a Variable.""" return self._replace(data=self.to_numpy()) + def as_array(self, asarray: Callable[[ArrayLike, ...], Any], **kwargs) -> Self: + """Coerces wrapped data into a specific array type, returning a Variable.""" + return self._replace(data=asarray(self._data, **kwargs)) + def reduce( self, func: Callable[..., Any], diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index c8b438948de..6e1efe85185 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -39,6 +39,7 @@ from xarray.core.utils import is_scalar from xarray.testing import _assert_internal_invariants from xarray.tests import ( + DuckArrayWrapper, InaccessibleArray, ReturnItem, assert_allclose, @@ -7165,6 +7166,18 @@ def test_from_pint_wrapping_dask(self) -> None: np.testing.assert_equal(da.to_numpy(), arr) +def test_as_array() -> None: + da = xr.DataArray([1, 2, 3], dims=["x"], coords={"x": [4, 5, 6]}) + + def as_duck_array(arr): + return DuckArrayWrapper(arr) + + result = da.as_array(as_duck_array) + + assert isinstance(result.data, DuckArrayWrapper) + assert isinstance(result.x.data, np.ndarray) + + class TestStackEllipsis: # https://github.com/pydata/xarray/issues/6051 def test_result_as_expected(self) -> None: diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 67d38aac0fe..13917e28225 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -7639,6 +7639,21 @@ def test_from_pint_wrapping_dask(self) -> None: assert_identical(result, expected) +def test_as_array() -> None: + ds = xr.Dataset( + {"a": ("x", [1, 2, 3])}, coords={"lat": ("x", [4, 5, 6]), "x": [7, 8, 9]} + ) + + def as_duck_array(arr): + return DuckArrayWrapper(arr) + + result = ds.as_array(as_duck_array) + + assert isinstance(result.a.data, DuckArrayWrapper) + assert isinstance(result.lat.data, DuckArrayWrapper) + assert isinstance(result.x.data, np.ndarray) + + def test_string_keys_typing() -> None: """Tests that string keys to `variables` are permitted by mypy""" From dfda3090a94a8ecbb4bc6658bc0c5eb66f77916b Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Wed, 20 Nov 2024 23:58:38 -0500 Subject: [PATCH 2/7] fix mypy --- xarray/core/dataarray.py | 2 +- xarray/core/dataset.py | 2 +- xarray/namedarray/core.py | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index cbf41bc594c..ee470a272fa 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -844,7 +844,7 @@ def as_numpy(self) -> Self: coords = {k: v.as_numpy() for k, v in self._coords.items()} return self._replace(self.variable.as_numpy(), coords, indexes=self._indexes) - def as_array(self, asarray: Callable[[ArrayLike, ...], Any], **kwargs) -> Self: + def as_array(self, asarray: Callable, **kwargs) -> Self: """ Coerces wrapped data into a specific array type. diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 4be135d2b45..3e716c7e941 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1460,7 +1460,7 @@ def as_numpy(self) -> Self: numpy_variables = {k: v.as_numpy() for k, v in self.variables.items()} return self._replace(variables=numpy_variables) - def as_array(self, asarray: Callable[[ArrayLike, ...], Any], **kwargs) -> Self: + def as_array(self, asarray: Callable, **kwargs) -> Self: """ Converts wrapped data into a specific array type. diff --git a/xarray/namedarray/core.py b/xarray/namedarray/core.py index 8ae17ebce13..e80a15fdc3f 100644 --- a/xarray/namedarray/core.py +++ b/xarray/namedarray/core.py @@ -860,7 +860,11 @@ def as_numpy(self) -> Self: """Coerces wrapped data into a numpy array, returning a Variable.""" return self._replace(data=self.to_numpy()) - def as_array(self, asarray: Callable[[ArrayLike, ...], Any], **kwargs) -> Self: + def as_array( + self, + asarray: Callable[[duckarray[Any, _DType_co]], duckarray[Any, _DType_co]], + **kwargs: Any, + ) -> Self: """Coerces wrapped data into a specific array type, returning a Variable.""" return self._replace(data=asarray(self._data, **kwargs)) From 20aad9bcd220093dcb80c3f67dc61873e64306c9 Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Thu, 21 Nov 2024 09:27:02 -0500 Subject: [PATCH 3/7] naming, add is_array_type --- xarray/core/dataarray.py | 22 +++++++++++++++++++--- xarray/core/dataset.py | 26 +++++++++++++++++++++++--- xarray/namedarray/core.py | 31 +++++++++++++++++++++++++++++-- xarray/tests/test_dataarray.py | 7 +++++-- xarray/tests/test_dataset.py | 7 +++++-- 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index ee470a272fa..f9cd552fbcf 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -844,7 +844,7 @@ def as_numpy(self) -> Self: coords = {k: v.as_numpy() for k, v in self._coords.items()} return self._replace(self.variable.as_numpy(), coords, indexes=self._indexes) - def as_array(self, asarray: Callable, **kwargs) -> Self: + def as_array_type(self, asarray: Callable, **kwargs) -> Self: """ Coerces wrapped data into a specific array type. @@ -856,7 +856,8 @@ def as_array(self, asarray: Callable, **kwargs) -> Self: ---------- asarray : Callable Function that converts an array-like object to the desired array type. - For example, `cupy.asarray`, `jax.numpy.asarray`, or `sparse.COO.from_numpy`. + For example, `cupy.asarray`, `jax.numpy.asarray`, `sparse.COO.from_numpy`, + or any `from_dlpack` method. **kwargs : dict Additional keyword arguments passed to the `asarray` function. @@ -864,7 +865,22 @@ def as_array(self, asarray: Callable, **kwargs) -> Self: ------- DataArray """ - return self._replace(self.variable.as_array(asarray, **kwargs)) + return self._replace(self.variable.as_array_type(asarray, **kwargs)) + + def is_array_type(self, array_type: type) -> bool: + """ + Check if the wrapped data is of a specific array type. + + Parameters + ---------- + array_type : type + The array type to check for. + + Returns + ------- + bool + """ + return self.variable.is_array_type(array_type) @property def _in_memory(self) -> bool: diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 3e716c7e941..c5dd0247d14 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1460,7 +1460,7 @@ def as_numpy(self) -> Self: numpy_variables = {k: v.as_numpy() for k, v in self.variables.items()} return self._replace(variables=numpy_variables) - def as_array(self, asarray: Callable, **kwargs) -> Self: + def as_array_type(self, asarray: Callable, **kwargs) -> Self: """ Converts wrapped data into a specific array type. @@ -1472,7 +1472,8 @@ def as_array(self, asarray: Callable, **kwargs) -> Self: ---------- asarray : Callable Function that converts an array-like object to the desired array type. - For example, `cupy.asarray`, `jax.numpy.asarray`, or `sparse.COO.from_numpy`. + For example, `cupy.asarray`, `jax.numpy.asarray`, `sparse.COO.from_numpy`, + or any `from_dlpack` method. **kwargs : dict Additional keyword arguments passed to the `asarray` function. @@ -1481,11 +1482,30 @@ def as_array(self, asarray: Callable, **kwargs) -> Self: Dataset """ array_variables = { - k: v.as_array(asarray, **kwargs) if k not in self._indexes else v + k: v.as_array_type(asarray, **kwargs) if k not in self._indexes else v for k, v in self.variables.items() } return self._replace(variables=array_variables) + def is_array_type(self, array_type: type) -> bool: + """ + Check if all data variables and non-index coordinates are of a specific array type. + + Parameters + ---------- + array_type : type + The array type to check for. + + Returns + ------- + bool + """ + return all( + v.is_array_type(array_type) + for k, v in self.variables.items() + if k not in self._indexes + ) + def _copy_listed(self, names: Iterable[Hashable]) -> Self: """Create a new Dataset with the listed variables from this dataset and the all relevant coordinates. Skips all validation. diff --git a/xarray/namedarray/core.py b/xarray/namedarray/core.py index e80a15fdc3f..2558ecec9c7 100644 --- a/xarray/namedarray/core.py +++ b/xarray/namedarray/core.py @@ -860,14 +860,41 @@ def as_numpy(self) -> Self: """Coerces wrapped data into a numpy array, returning a Variable.""" return self._replace(data=self.to_numpy()) - def as_array( + def as_array_type( self, asarray: Callable[[duckarray[Any, _DType_co]], duckarray[Any, _DType_co]], **kwargs: Any, ) -> Self: - """Coerces wrapped data into a specific array type, returning a Variable.""" + """Converts wrapped data into a specific array type. + + Parameters + ---------- + asarray : callable + Function that converts the data into a specific array type. + **kwargs : dict + Additional keyword arguments passed on to `asarray`. + + Returns + ------- + array : NamedArray + Array with the same data, but converted into a specific array type + """ return self._replace(data=asarray(self._data, **kwargs)) + def is_array_type(self, array_type: type) -> bool: + """Check if the data is an instance of a specific array type. + + Parameters + ---------- + array_type : type + Array type to check against. + + Returns + ------- + is_array_type : bool + """ + return isinstance(self._data, array_type) + def reduce( self, func: Callable[..., Any], diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index 6e1efe85185..8bc63f3bf4b 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -7166,16 +7166,19 @@ def test_from_pint_wrapping_dask(self) -> None: np.testing.assert_equal(da.to_numpy(), arr) -def test_as_array() -> None: +def test_as_array_type_is_array_type() -> None: da = xr.DataArray([1, 2, 3], dims=["x"], coords={"x": [4, 5, 6]}) + assert da.is_array_type(np.ndarray) + def as_duck_array(arr): return DuckArrayWrapper(arr) - result = da.as_array(as_duck_array) + result = da.as_array_type(as_duck_array) assert isinstance(result.data, DuckArrayWrapper) assert isinstance(result.x.data, np.ndarray) + assert result.is_array_type(DuckArrayWrapper) class TestStackEllipsis: diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 13917e28225..edca2a02c93 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -7639,19 +7639,22 @@ def test_from_pint_wrapping_dask(self) -> None: assert_identical(result, expected) -def test_as_array() -> None: +def test_as_array_type_is_array_type() -> None: ds = xr.Dataset( {"a": ("x", [1, 2, 3])}, coords={"lat": ("x", [4, 5, 6]), "x": [7, 8, 9]} ) + # lat is a PandasIndex here + assert ds.drop_vars("lat").is_array_type(np.ndarray) def as_duck_array(arr): return DuckArrayWrapper(arr) - result = ds.as_array(as_duck_array) + result = ds.as_array_type(as_duck_array) assert isinstance(result.a.data, DuckArrayWrapper) assert isinstance(result.lat.data, DuckArrayWrapper) assert isinstance(result.x.data, np.ndarray) + assert result.is_array_type(DuckArrayWrapper) def test_string_keys_typing() -> None: From 438ff7c8c4219ce5ee9c088253c985cc0851a9cb Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Thu, 21 Nov 2024 09:31:45 -0500 Subject: [PATCH 4/7] add public doc and whats new --- doc/api.rst | 4 ++++ doc/whats-new.rst | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/doc/api.rst b/doc/api.rst index 85ef46ca6ba..7a596fdaa2d 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -117,6 +117,8 @@ Dataset contents Dataset.convert_calendar Dataset.interp_calendar Dataset.get_index + Dataset.as_array_type + Dataset.is_array_type Comparisons ----------- @@ -315,6 +317,8 @@ DataArray contents DataArray.get_index DataArray.astype DataArray.item + DataArray.as_array_type + DataArray.is_array_type Indexing -------- diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 0da34df2c1a..03d69b17e93 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -85,6 +85,10 @@ New Features By `Sam Levang `_. - Speed up loading of large zarr stores using dask arrays. (:issue:`8902`) By `Deepak Cherian `_. +- Make more xarray methods fully compatible with duck array types, and introduce new + ``as_array_type`` and ``is_array_type`` methods for converting wrapped data to other + duck array types. (:issue:`7848`, :pull:`9798`). + By `Sam Levang `_. Breaking Changes ~~~~~~~~~~~~~~~~ From 24034ba011c63d02a5f8821c3671d2e7fd0e7cea Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Mon, 25 Nov 2024 10:14:29 -0500 Subject: [PATCH 5/7] doc --- xarray/core/dataarray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index f9cd552fbcf..06520676445 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -846,7 +846,7 @@ def as_numpy(self) -> Self: def as_array_type(self, asarray: Callable, **kwargs) -> Self: """ - Coerces wrapped data into a specific array type. + Converts wrapped data into a specific array type. `asarray` should output an object that supports the Array API Standard. This method does not convert index coordinates, which can't generally be From 945ffca02cf53cad0b4fbdd2d723b048ea071a7f Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Thu, 21 Nov 2024 10:57:26 -0500 Subject: [PATCH 6/7] add support for chunked arrays in as_array_type --- xarray/core/dataarray.py | 2 ++ xarray/core/dataset.py | 2 ++ xarray/namedarray/core.py | 14 +++++++++++--- xarray/tests/test_dataarray.py | 19 +++++++++++++++---- xarray/tests/test_dataset.py | 24 ++++++++++++++++++++---- 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 06520676445..e45aaac5836 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -848,6 +848,8 @@ def as_array_type(self, asarray: Callable, **kwargs) -> Self: """ Converts wrapped data into a specific array type. + If the data is a chunked array, the conversion is applied to each block. + `asarray` should output an object that supports the Array API Standard. This method does not convert index coordinates, which can't generally be represented as arbitrary array types. diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index c5dd0247d14..bb794395621 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1464,6 +1464,8 @@ def as_array_type(self, asarray: Callable, **kwargs) -> Self: """ Converts wrapped data into a specific array type. + If the data is a chunked array, the conversion is applied to each block. + `asarray` should output an object that supports the Array API Standard. This method does not convert index coordinates, which can't generally be represented as arbitrary array types. diff --git a/xarray/namedarray/core.py b/xarray/namedarray/core.py index 2558ecec9c7..ab4b3bc1820 100644 --- a/xarray/namedarray/core.py +++ b/xarray/namedarray/core.py @@ -40,8 +40,8 @@ _SupportsImag, _SupportsReal, ) -from xarray.namedarray.parallelcompat import guess_chunkmanager -from xarray.namedarray.pycompat import to_numpy +from xarray.namedarray.parallelcompat import get_chunked_array_type, guess_chunkmanager +from xarray.namedarray.pycompat import is_chunked_array, to_numpy from xarray.namedarray.utils import ( either_dict_or_kwargs, infix_dims, @@ -867,6 +867,8 @@ def as_array_type( ) -> Self: """Converts wrapped data into a specific array type. + If the data is a chunked array, the conversion is applied to each block. + Parameters ---------- asarray : callable @@ -879,7 +881,13 @@ def as_array_type( array : NamedArray Array with the same data, but converted into a specific array type """ - return self._replace(data=asarray(self._data, **kwargs)) + if is_chunked_array(self._data): + chunkmanager = get_chunked_array_type(self._data) + new_data = chunkmanager.map_blocks(asarray, self._data, **kwargs) + else: + new_data = asarray(self._data, **kwargs) + + return self._replace(data=new_data) def is_array_type(self, array_type: type) -> bool: """Check if the data is an instance of a specific array type. diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index 8bc63f3bf4b..b4af9d37e35 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -7171,16 +7171,27 @@ def test_as_array_type_is_array_type() -> None: assert da.is_array_type(np.ndarray) - def as_duck_array(arr): - return DuckArrayWrapper(arr) - - result = da.as_array_type(as_duck_array) + result = da.as_array_type(lambda x: DuckArrayWrapper(x)) assert isinstance(result.data, DuckArrayWrapper) assert isinstance(result.x.data, np.ndarray) assert result.is_array_type(DuckArrayWrapper) +@requires_dask +def test_as_array_type_dask() -> None: + import dask.array + + da = xr.DataArray([1, 2, 3], dims=["x"], coords={"x": [4, 5, 6]}).chunk() + + result = da.as_array_type(lambda x: DuckArrayWrapper(x)) + + assert isinstance(result.data, dask.array.Array) + assert isinstance(result.data._meta, DuckArrayWrapper) + assert isinstance(result.x.data, np.ndarray) + assert result.is_array_type(dask.array.Array) + + class TestStackEllipsis: # https://github.com/pydata/xarray/issues/6051 def test_result_as_expected(self) -> None: diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index edca2a02c93..b8dbcabf3ce 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -7646,10 +7646,7 @@ def test_as_array_type_is_array_type() -> None: # lat is a PandasIndex here assert ds.drop_vars("lat").is_array_type(np.ndarray) - def as_duck_array(arr): - return DuckArrayWrapper(arr) - - result = ds.as_array_type(as_duck_array) + result = ds.as_array_type(lambda x: DuckArrayWrapper(x)) assert isinstance(result.a.data, DuckArrayWrapper) assert isinstance(result.lat.data, DuckArrayWrapper) @@ -7657,6 +7654,25 @@ def as_duck_array(arr): assert result.is_array_type(DuckArrayWrapper) +@requires_dask +def test_as_array_type_dask() -> None: + import dask.array + + ds = xr.Dataset( + {"a": ("x", [1, 2, 3])}, coords={"lat": ("x", [4, 5, 6]), "x": [7, 8, 9]} + ).chunk() + + assert ds.is_array_type(dask.array.Array) + + result = ds.as_array_type(lambda x: DuckArrayWrapper(x)) + + assert isinstance(result.a.data, dask.array.Array) + assert isinstance(result.a.data._meta, DuckArrayWrapper) + assert isinstance(result.lat.data, dask.array.Array) + assert isinstance(result.lat.data._meta, DuckArrayWrapper) + assert isinstance(result.x.data, np.ndarray) + + def test_string_keys_typing() -> None: """Tests that string keys to `variables` are permitted by mypy""" From ad6c1387bd774a58d5b9621e5e32760e3e751524 Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Mon, 25 Nov 2024 11:02:58 -0500 Subject: [PATCH 7/7] whats new --- doc/whats-new.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 03d69b17e93..36a43d6824a 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -21,6 +21,9 @@ v.2024.11.1 (unreleased) New Features ~~~~~~~~~~~~ +- Add convenience methods ``as_array_type`` and ``is_array_type`` for converting wrapped + data to other duck array types. (:issue:`7848`, :pull:`9823`). + By `Sam Levang `_. Breaking changes @@ -85,10 +88,6 @@ New Features By `Sam Levang `_. - Speed up loading of large zarr stores using dask arrays. (:issue:`8902`) By `Deepak Cherian `_. -- Make more xarray methods fully compatible with duck array types, and introduce new - ``as_array_type`` and ``is_array_type`` methods for converting wrapped data to other - duck array types. (:issue:`7848`, :pull:`9798`). - By `Sam Levang `_. Breaking Changes ~~~~~~~~~~~~~~~~