Skip to content

Commit f95bd2d

Browse files
authored
(fix): ndim accessible as np.ndim on PandasExtensionArray (#10414)
1 parent 97fb90b commit f95bd2d

File tree

6 files changed

+35
-8
lines changed

6 files changed

+35
-8
lines changed

xarray/coding/variables.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,9 +520,9 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable:
520520

521521
scale_factor = pop_to(attrs, encoding, "scale_factor", name=name)
522522
add_offset = pop_to(attrs, encoding, "add_offset", name=name)
523-
if np.ndim(scale_factor) > 0:
523+
if duck_array_ops.ndim(scale_factor) > 0:
524524
scale_factor = np.asarray(scale_factor).item()
525-
if np.ndim(add_offset) > 0:
525+
if duck_array_ops.ndim(add_offset) > 0:
526526
add_offset = np.asarray(add_offset).item()
527527
# if we have a _FillValue/masked_value in encoding we already have the wanted
528528
# floating point dtype here (via CFMaskCoder), so no check is necessary

xarray/core/duck_array_ops.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,12 @@ def _nd_cum_func(cum_func, array, axis, **kwargs):
796796
return out
797797

798798

799+
def ndim(array) -> int:
800+
# Required part of the duck array and the array-api, but we fall back in case
801+
# https://docs.xarray.dev/en/latest/internals/duck-arrays-integration.html#duck-array-requirements
802+
return array.ndim if hasattr(array, "ndim") else np.ndim(array)
803+
804+
799805
def cumprod(array, axis=None, **kwargs):
800806
"""N-dimensional version of cumprod."""
801807
return _nd_cum_func(cumprod_1d, array, axis, **kwargs)

xarray/core/extension_array.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ def __extension_duck_array__where(
6666
return cast(T_ExtensionArray, pd.Series(x).where(condition, pd.Series(y)).array)
6767

6868

69+
@implements(np.ndim)
70+
def __extension_duck_array__ndim(x: PandasExtensionArray) -> int:
71+
return x.ndim
72+
73+
6974
@implements(np.reshape)
7075
def __extension_duck_array__reshape(
7176
arr: T_ExtensionArray, shape: tuple

xarray/core/indexing.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -769,14 +769,15 @@ def __repr__(self) -> str:
769769

770770
def _wrap_numpy_scalars(array):
771771
"""Wrap NumPy scalars in 0d arrays."""
772-
if np.ndim(array) == 0 and (
772+
ndim = duck_array_ops.ndim(array)
773+
if ndim == 0 and (
773774
isinstance(array, np.generic)
774775
or not (is_duck_array(array) or isinstance(array, NDArrayMixin))
775776
):
776777
return np.array(array)
777778
elif hasattr(array, "dtype"):
778779
return array
779-
elif np.ndim(array) == 0:
780+
elif ndim == 0:
780781
return np.array(array)
781782
else:
782783
return array

xarray/indexes/range_index.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66
import pandas as pd
77

8+
from xarray.core import duck_array_ops
89
from xarray.core.coordinate_transform import CoordinateTransform
910
from xarray.core.dataarray import DataArray
1011
from xarray.core.indexes import CoordinateTransformIndex, Index, PandasIndex
@@ -320,7 +321,9 @@ def isel(
320321

321322
if isinstance(idxer, slice):
322323
return RangeIndex(self.transform.slice(idxer))
323-
elif (isinstance(idxer, Variable) and idxer.ndim > 1) or np.ndim(idxer) == 0:
324+
elif (isinstance(idxer, Variable) and idxer.ndim > 1) or duck_array_ops.ndim(
325+
idxer
326+
) == 0:
324327
return None
325328
else:
326329
values = self.transform.forward({self.dim: np.asarray(idxer)})[

xarray/tests/test_variable.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from xarray import DataArray, Dataset, IndexVariable, Variable, set_options
1616
from xarray.core import dtypes, duck_array_ops, indexing
1717
from xarray.core.common import full_like, ones_like, zeros_like
18+
from xarray.core.extension_array import PandasExtensionArray
1819
from xarray.core.indexing import (
1920
BasicIndexer,
2021
CopyOnWriteArray,
@@ -2894,6 +2895,7 @@ class TestBackendIndexing:
28942895
@pytest.fixture(autouse=True)
28952896
def setUp(self):
28962897
self.d = np.random.random((10, 3)).astype(np.float64)
2898+
self.cat = PandasExtensionArray(pd.Categorical(["a", "b"] * 5))
28972899

28982900
def check_orthogonal_indexing(self, v):
28992901
assert np.allclose(v.isel(x=[8, 3], y=[2, 1]), self.d[[8, 3]][:, [2, 1]])
@@ -2913,6 +2915,14 @@ def test_NumpyIndexingAdapter(self):
29132915
dims=("x", "y"), data=NumpyIndexingAdapter(NumpyIndexingAdapter(self.d))
29142916
)
29152917

2918+
def test_extension_array_duck_array(self):
2919+
lazy = LazilyIndexedArray(self.cat)
2920+
assert (lazy.get_duck_array().array == self.cat).all()
2921+
2922+
def test_extension_array_duck_indexed(self):
2923+
lazy = Variable(dims=("x"), data=LazilyIndexedArray(self.cat))
2924+
assert (lazy[[0, 1, 5]] == ["a", "b", "b"]).all()
2925+
29162926
def test_LazilyIndexedArray(self):
29172927
v = Variable(dims=("x", "y"), data=LazilyIndexedArray(self.d))
29182928
self.check_orthogonal_indexing(v)
@@ -2951,12 +2961,14 @@ def test_MemoryCachedArray(self):
29512961
def test_DaskIndexingAdapter(self):
29522962
import dask.array as da
29532963

2954-
da = da.asarray(self.d)
2955-
v = Variable(dims=("x", "y"), data=DaskIndexingAdapter(da))
2964+
dask_array = da.asarray(self.d)
2965+
v = Variable(dims=("x", "y"), data=DaskIndexingAdapter(dask_array))
29562966
self.check_orthogonal_indexing(v)
29572967
self.check_vectorized_indexing(v)
29582968
# doubly wrapping
2959-
v = Variable(dims=("x", "y"), data=CopyOnWriteArray(DaskIndexingAdapter(da)))
2969+
v = Variable(
2970+
dims=("x", "y"), data=CopyOnWriteArray(DaskIndexingAdapter(dask_array))
2971+
)
29602972
self.check_orthogonal_indexing(v)
29612973
self.check_vectorized_indexing(v)
29622974

0 commit comments

Comments
 (0)