diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index 3b0335aa5a6..bc251d05631 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -67,6 +67,9 @@ def __init__(self, variable_name, datastore): def get_array(self): return self.datastore.zarr_group[self.variable_name] + def _oindex(self, key): + return self.get_array().oindex[key] + def __getitem__(self, key): array = self.get_array() if isinstance(key, indexing.BasicIndexer): @@ -77,7 +80,10 @@ def __getitem__(self, key): ] else: assert isinstance(key, indexing.OuterIndexer) - return array.oindex[key.tuple] + return indexing.explicit_indexing_adapter( + key, array.shape, indexing.IndexingSupport.VECTORIZED, self._oindex + ) + # if self.ndim == 0: # could possibly have a work-around for 0d data here diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py index becf1554453..46d128b9e9d 100644 --- a/xarray/core/indexing.py +++ b/xarray/core/indexing.py @@ -23,6 +23,7 @@ NDArrayMixin, either_dict_or_kwargs, get_valid_numpy_dtype, + is_scalar, to_0d_array, ) @@ -829,9 +830,21 @@ def decompose_indexer( raise TypeError(f"unexpected key type: {indexer}") -def _decompose_slice(key, size): +def _decompose_slice(key: slice, size: int) -> tuple[slice, slice]: """convert a slice to successive two slices. The first slice always has a positive step. + + >>> _decompose_slice(slice(2, 98, 2), 99) + (slice(2, 98, 2), slice(None, None, None)) + + >>> _decompose_slice(slice(98, 2, -2), 99) + (slice(4, 99, 2), slice(None, None, -1)) + + >>> _decompose_slice(slice(98, 2, -2), 98) + (slice(3, 98, 2), slice(None, None, -1)) + + >>> _decompose_slice(slice(360, None, -10), 361) + (slice(0, 361, 10), slice(None, None, -1)) """ start, stop, step = key.indices(size) if step > 0: @@ -839,10 +852,10 @@ def _decompose_slice(key, size): return key, slice(None) else: # determine stop precisely for step > 1 case + # Use the range object to do the calculation # e.g. [98:2:-2] -> [98:3:-2] - stop = start + int((stop - start - 1) / step) * step + 1 - start, stop = stop + 1, start + 1 - return slice(start, stop, -step), slice(None, None, -1) + exact_stop = range(start, stop, step)[-1] + return slice(exact_stop, start + 1, -step), slice(None, None, -1) def _decompose_vectorized_indexer( @@ -968,12 +981,25 @@ def _decompose_outer_indexer( [14, 15, 14], [ 8, 9, 8]]) """ - if indexing_support == IndexingSupport.VECTORIZED: - return indexer, BasicIndexer(()) + backend_indexer: list[Any] = [] + np_indexer: list[Any] = [] + assert isinstance(indexer, (OuterIndexer, BasicIndexer)) - backend_indexer: list[Any] = [] - np_indexer = [] + if indexing_support == IndexingSupport.VECTORIZED: + for k, s in zip(indexer.tuple, shape): + if isinstance(k, slice): + # If it is a slice, then we will slice it as-is + # (but make its step positive) in the backend, + bk_slice, np_slice = _decompose_slice(k, s) + backend_indexer.append(bk_slice) + np_indexer.append(np_slice) + else: + backend_indexer.append(k) + if not is_scalar(k): + np_indexer.append(slice(None)) + return type(indexer)(tuple(backend_indexer)), BasicIndexer(tuple(np_indexer)) + # make indexer positive pos_indexer: list[np.ndarray | int | np.number] = [] for k, s in zip(indexer.tuple, shape): diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 91daabd12d5..4d1d1f39fe5 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -753,6 +753,16 @@ def multiple_indexing(indexers): ] multiple_indexing(indexers) + def test_outer_indexing_reversed(self) -> None: + # regression test for GH6560 + ds = xr.Dataset( + {"z": (("t", "p", "y", "x"), np.ones((1, 1, 31, 40)))}, + ) + + with self.roundtrip(ds) as on_disk: + subset = on_disk.isel(t=[0], p=0).z[:, ::10, ::10][:, ::-1, :] + assert subset.sizes == subset.load().sizes + def test_isel_dataarray(self) -> None: # Make sure isel works lazily. GH:issue:1688 in_memory = create_test_data()