Skip to content

Commit 020b4c0

Browse files
authored
Fix lazy negative slice rewriting. (#7586)
* Fix lazy slice rewriting. There was a bug in estimating the last index of the slice. Index a range object instead. Closes #6560 * Support reversed slices with Zarr * Update xarray/core/indexing.py * Fix test * bring back xfail * Smaller test * Better? * fix typing
1 parent de05403 commit 020b4c0

File tree

3 files changed

+51
-9
lines changed

3 files changed

+51
-9
lines changed

xarray/backends/zarr.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ def __init__(self, variable_name, datastore):
6767
def get_array(self):
6868
return self.datastore.zarr_group[self.variable_name]
6969

70+
def _oindex(self, key):
71+
return self.get_array().oindex[key]
72+
7073
def __getitem__(self, key):
7174
array = self.get_array()
7275
if isinstance(key, indexing.BasicIndexer):
@@ -77,7 +80,10 @@ def __getitem__(self, key):
7780
]
7881
else:
7982
assert isinstance(key, indexing.OuterIndexer)
80-
return array.oindex[key.tuple]
83+
return indexing.explicit_indexing_adapter(
84+
key, array.shape, indexing.IndexingSupport.VECTORIZED, self._oindex
85+
)
86+
8187
# if self.ndim == 0:
8288
# could possibly have a work-around for 0d data here
8389

xarray/core/indexing.py

+34-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
NDArrayMixin,
2929
either_dict_or_kwargs,
3030
get_valid_numpy_dtype,
31+
is_scalar,
3132
to_0d_array,
3233
)
3334

@@ -840,20 +841,32 @@ def decompose_indexer(
840841
raise TypeError(f"unexpected key type: {indexer}")
841842

842843

843-
def _decompose_slice(key, size):
844+
def _decompose_slice(key: slice, size: int) -> tuple[slice, slice]:
844845
"""convert a slice to successive two slices. The first slice always has
845846
a positive step.
847+
848+
>>> _decompose_slice(slice(2, 98, 2), 99)
849+
(slice(2, 98, 2), slice(None, None, None))
850+
851+
>>> _decompose_slice(slice(98, 2, -2), 99)
852+
(slice(4, 99, 2), slice(None, None, -1))
853+
854+
>>> _decompose_slice(slice(98, 2, -2), 98)
855+
(slice(3, 98, 2), slice(None, None, -1))
856+
857+
>>> _decompose_slice(slice(360, None, -10), 361)
858+
(slice(0, 361, 10), slice(None, None, -1))
846859
"""
847860
start, stop, step = key.indices(size)
848861
if step > 0:
849862
# If key already has a positive step, use it as is in the backend
850863
return key, slice(None)
851864
else:
852865
# determine stop precisely for step > 1 case
866+
# Use the range object to do the calculation
853867
# e.g. [98:2:-2] -> [98:3:-2]
854-
stop = start + int((stop - start - 1) / step) * step + 1
855-
start, stop = stop + 1, start + 1
856-
return slice(start, stop, -step), slice(None, None, -1)
868+
exact_stop = range(start, stop, step)[-1]
869+
return slice(exact_stop, start + 1, -step), slice(None, None, -1)
857870

858871

859872
def _decompose_vectorized_indexer(
@@ -979,12 +992,25 @@ def _decompose_outer_indexer(
979992
[14, 15, 14],
980993
[ 8, 9, 8]])
981994
"""
982-
if indexing_support == IndexingSupport.VECTORIZED:
983-
return indexer, BasicIndexer(())
995+
backend_indexer: list[Any] = []
996+
np_indexer: list[Any] = []
997+
984998
assert isinstance(indexer, (OuterIndexer, BasicIndexer))
985999

986-
backend_indexer: list[Any] = []
987-
np_indexer = []
1000+
if indexing_support == IndexingSupport.VECTORIZED:
1001+
for k, s in zip(indexer.tuple, shape):
1002+
if isinstance(k, slice):
1003+
# If it is a slice, then we will slice it as-is
1004+
# (but make its step positive) in the backend,
1005+
bk_slice, np_slice = _decompose_slice(k, s)
1006+
backend_indexer.append(bk_slice)
1007+
np_indexer.append(np_slice)
1008+
else:
1009+
backend_indexer.append(k)
1010+
if not is_scalar(k):
1011+
np_indexer.append(slice(None))
1012+
return type(indexer)(tuple(backend_indexer)), BasicIndexer(tuple(np_indexer))
1013+
9881014
# make indexer positive
9891015
pos_indexer: list[np.ndarray | int | np.number] = []
9901016
for k, s in zip(indexer.tuple, shape):

xarray/tests/test_backends.py

+10
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,16 @@ def multiple_indexing(indexers):
753753
]
754754
multiple_indexing(indexers)
755755

756+
def test_outer_indexing_reversed(self) -> None:
757+
# regression test for GH6560
758+
ds = xr.Dataset(
759+
{"z": (("t", "p", "y", "x"), np.ones((1, 1, 31, 40)))},
760+
)
761+
762+
with self.roundtrip(ds) as on_disk:
763+
subset = on_disk.isel(t=[0], p=0).z[:, ::10, ::10][:, ::-1, :]
764+
assert subset.sizes == subset.load().sizes
765+
756766
def test_isel_dataarray(self) -> None:
757767
# Make sure isel works lazily. GH:issue:1688
758768
in_memory = create_test_data()

0 commit comments

Comments
 (0)