Skip to content

Commit 1e1938f

Browse files
authored
Use to_numpy in time decoding (#10081)
* Use to_numpy in time decoding * WIP * Revert "WIP" This reverts commit 54be9b1. * fix * Update xarray/namedarray/pycompat.py
1 parent 4a41386 commit 1e1938f

File tree

6 files changed

+59
-17
lines changed

6 files changed

+59
-17
lines changed

xarray/coding/times.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from xarray.core.utils import attempt_import, emit_user_level_warning
2929
from xarray.core.variable import Variable
3030
from xarray.namedarray.parallelcompat import T_ChunkedArray, get_chunked_array_type
31-
from xarray.namedarray.pycompat import is_chunked_array
31+
from xarray.namedarray.pycompat import is_chunked_array, to_numpy
3232
from xarray.namedarray.utils import is_duck_dask_array
3333

3434
try:
@@ -310,7 +310,7 @@ def _decode_cf_datetime_dtype(
310310
# Dataset.__repr__ when users try to view their lazily decoded array.
311311
values = indexing.ImplicitToExplicitIndexingAdapter(indexing.as_indexable(data))
312312
example_value = np.concatenate(
313-
[first_n_items(values, 1) or [0], last_item(values) or [0]]
313+
[to_numpy(first_n_items(values, 1)), to_numpy(last_item(values))]
314314
)
315315

316316
try:
@@ -516,7 +516,7 @@ def decode_cf_datetime(
516516
--------
517517
cftime.num2date
518518
"""
519-
num_dates = np.asarray(num_dates)
519+
num_dates = to_numpy(num_dates)
520520
flat_num_dates = ravel(num_dates)
521521
if calendar is None:
522522
calendar = "standard"
@@ -643,7 +643,7 @@ def decode_cf_timedelta(
643643
"""Given an array of numeric timedeltas in netCDF format, convert it into a
644644
numpy timedelta64 ["s", "ms", "us", "ns"] array.
645645
"""
646-
num_timedeltas = np.asarray(num_timedeltas)
646+
num_timedeltas = to_numpy(num_timedeltas)
647647
unit = _netcdf_to_numpy_timeunit(units)
648648

649649
with warnings.catch_warnings():

xarray/core/formatting.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
from pandas.errors import OutOfBoundsDatetime
1919

2020
from xarray.core.datatree_render import RenderDataTree
21-
from xarray.core.duck_array_ops import array_all, array_any, array_equiv, astype
21+
from xarray.core.duck_array_ops import array_all, array_any, array_equiv, astype, ravel
2222
from xarray.core.indexing import MemoryCachedArray
2323
from xarray.core.options import OPTIONS, _get_boolean_with_default
2424
from xarray.core.treenode import group_subtrees
2525
from xarray.core.utils import is_duck_array
26-
from xarray.namedarray.pycompat import array_type, to_duck_array, to_numpy
26+
from xarray.namedarray.pycompat import array_type, to_duck_array
2727

2828
if TYPE_CHECKING:
2929
from xarray.core.coordinates import AbstractCoordinates
@@ -94,7 +94,7 @@ def first_n_items(array, n_desired):
9494
# pass Variable._data
9595
if isinstance(array, Variable):
9696
array = array._data
97-
return np.ravel(to_duck_array(array))[:n_desired]
97+
return ravel(to_duck_array(array))[:n_desired]
9898

9999

100100
def last_n_items(array, n_desired):
@@ -118,18 +118,13 @@ def last_n_items(array, n_desired):
118118
# pass Variable._data
119119
if isinstance(array, Variable):
120120
array = array._data
121-
return np.ravel(to_duck_array(array))[-n_desired:]
121+
return ravel(to_duck_array(array))[-n_desired:]
122122

123123

124124
def last_item(array):
125-
"""Returns the last item of an array in a list or an empty list."""
126-
if array.size == 0:
127-
# work around for https://github.com/numpy/numpy/issues/5195
128-
return []
129-
125+
"""Returns the last item of an array."""
130126
indexer = (slice(-1, None),) * array.ndim
131-
# to_numpy since dask doesn't support tolist
132-
return np.ravel(to_numpy(array[indexer])).tolist()
127+
return ravel(to_duck_array(array[indexer]))
133128

134129

135130
def calc_max_rows_first(max_rows: int) -> int:

xarray/namedarray/pycompat.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ def to_numpy(
102102
from xarray.core.indexing import ExplicitlyIndexed
103103
from xarray.namedarray.parallelcompat import get_chunked_array_type
104104

105+
try:
106+
# for tests only at the moment
107+
return data.to_numpy() # type: ignore[no-any-return,union-attr]
108+
except AttributeError:
109+
pass
110+
105111
if isinstance(data, ExplicitlyIndexed):
106112
data = data.get_duck_array() # type: ignore[no-untyped-call]
107113

@@ -122,15 +128,18 @@ def to_numpy(
122128

123129

124130
def to_duck_array(data: Any, **kwargs: dict[str, Any]) -> duckarray[_ShapeType, _DType]:
125-
from xarray.core.indexing import ExplicitlyIndexed
131+
from xarray.core.indexing import (
132+
ExplicitlyIndexed,
133+
ImplicitToExplicitIndexingAdapter,
134+
)
126135
from xarray.namedarray.parallelcompat import get_chunked_array_type
127136

128137
if is_chunked_array(data):
129138
chunkmanager = get_chunked_array_type(data)
130139
loaded_data, *_ = chunkmanager.compute(data, **kwargs) # type: ignore[var-annotated]
131140
return loaded_data
132141

133-
if isinstance(data, ExplicitlyIndexed):
142+
if isinstance(data, ExplicitlyIndexed | ImplicitToExplicitIndexingAdapter):
134143
return data.get_duck_array() # type: ignore[no-untyped-call, no-any-return]
135144
elif is_duck_array(data):
136145
return data

xarray/tests/arrays.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,20 @@ def __init__(self, array: np.ndarray):
5151
def __getitem__(self, key):
5252
return type(self)(self.array[key])
5353

54+
def to_numpy(self) -> np.ndarray:
55+
"""Allow explicit conversions to numpy in `to_numpy`, but disallow np.asarray etc."""
56+
return self.array
57+
5458
def __array__(
5559
self, dtype: np.typing.DTypeLike = None, /, *, copy: bool | None = None
5660
) -> np.ndarray:
5761
raise UnexpectedDataAccess("Tried accessing data")
5862

5963
def __array_namespace__(self):
6064
"""Present to satisfy is_duck_array test."""
65+
from xarray.tests import namespace
66+
67+
return namespace
6168

6269

6370
CONCATENATABLEARRAY_HANDLED_ARRAY_FUNCTIONS: dict[str, Callable] = {}

xarray/tests/namespace.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from xarray.core import duck_array_ops
2+
3+
4+
def reshape(array, shape, **kwargs):
5+
return type(array)(duck_array_ops.reshape(array.array, shape=shape, **kwargs))

xarray/tests/test_coding_times.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from xarray.core.utils import is_duck_dask_array
4242
from xarray.testing import assert_equal, assert_identical
4343
from xarray.tests import (
44+
DuckArrayWrapper,
4445
FirstElementAccessibleArray,
4546
arm_xfail,
4647
assert_array_equal,
@@ -1935,6 +1936,31 @@ def test_lazy_decode_timedelta_error() -> None:
19351936
decoded.load()
19361937

19371938

1939+
@pytest.mark.parametrize(
1940+
"calendar",
1941+
[
1942+
"standard",
1943+
pytest.param(
1944+
"360_day", marks=pytest.mark.skipif(not has_cftime, reason="no cftime")
1945+
),
1946+
],
1947+
)
1948+
def test_duck_array_decode_times(calendar) -> None:
1949+
from xarray.core.indexing import LazilyIndexedArray
1950+
1951+
days = LazilyIndexedArray(DuckArrayWrapper(np.array([1.0, 2.0, 3.0])))
1952+
var = Variable(
1953+
["time"], days, {"units": "days since 2001-01-01", "calendar": calendar}
1954+
)
1955+
decoded = conventions.decode_cf_variable(
1956+
"foo", var, decode_times=CFDatetimeCoder(use_cftime=None)
1957+
)
1958+
if calendar not in _STANDARD_CALENDARS:
1959+
assert decoded.dtype == np.dtype("O")
1960+
else:
1961+
assert decoded.dtype == np.dtype("=M8[ns]")
1962+
1963+
19381964
@pytest.mark.parametrize("decode_timedelta", [True, False])
19391965
@pytest.mark.parametrize("mask_and_scale", [True, False])
19401966
def test_decode_timedelta_mask_and_scale(

0 commit comments

Comments
 (0)