Skip to content

Commit 1cac85e

Browse files
committed
Support single-integer slicing of DelayedArrays, SparseNdarrays.
This allows access with 'X[i]' to extract subarrays along the first dimension, which is handy for generic functions like biocutils.show_as_cell().
1 parent c36c443 commit 1cac85e

File tree

5 files changed

+39
-19
lines changed

5 files changed

+39
-19
lines changed

src/delayedarray/DelayedArray.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -793,29 +793,31 @@ def __rand__(self, other) -> "DelayedArray":
793793
return _wrap_isometric_with_args(self, other, operation="logical_and", right=False)
794794

795795
# Subsetting.
796-
def __getitem__(self, subset: Tuple[Union[slice, Sequence], ...]) -> Union["DelayedArray", ndarray]:
796+
def __getitem__(self, subset) -> Union["DelayedArray", ndarray]:
797797
"""Take a subset of this ``DelayedArray``. This follows the same logic as NumPy slicing and will generate a
798798
:py:class:`~delayedarray.Subset.Subset` object when the subset operation preserves the dimensionality of the
799799
seed, i.e., ``args`` is defined using the :py:meth:`~numpy.ix_` function.
800800
801801
Args:
802802
subset:
803-
A :py:class:`tuple` of length equal to the dimensionality of
804-
this ``DelayedArray``. We attempt to support most types of
805-
NumPy slicing; however, only subsets that preserve
806-
dimensionality will generate a delayed subset operation.
803+
A :py:class:`tuple` of length equal to the dimensionality of this ``DelayedArray``, or a single integer specifying an index on the first dimension.
804+
We attempt to support most types of NumPy slicing; however, only subsets that preserve dimensionality will generate a delayed subset operation.
807805
808806
Returns:
809-
If the dimensionality is preserved by ``subset``, a
810-
``DelayedArray`` containing a delayed subset operation is returned.
811-
Otherwise, a :py:class:`~numpy.ndarray` is returned containing the
812-
realized subset.
807+
If the dimensionality is preserved by ``subset``, a ``DelayedArray`` containing a delayed subset operation is returned.
808+
Otherwise, a :py:class:`~numpy.ndarray` is returned containing the realized subset.
813809
"""
810+
if not isinstance(subset, Tuple):
811+
replacement = [slice(None)] * len(self.shape)
812+
replacement[0] = subset
813+
subset = (*replacement,)
814+
814815
cleaned = _getitem_subset_preserves_dimensions(self.shape, subset)
815816
if cleaned is not None:
816817
sout = Subset(self._seed, cleaned)
817818
sout = _simplify_subset(sout)
818819
return DelayedArray(sout)
820+
819821
return _getitem_subset_discards_dimensions(self._seed, subset, extract_dense_array)
820822

821823

src/delayedarray/SparseNdarray.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -748,24 +748,28 @@ def __rand__(self, other) -> Union["SparseNdarray", numpy.ndarray]:
748748

749749

750750
# Subsetting.
751-
def __getitem__(self, subset: Tuple[Union[slice, Sequence], ...]) -> Union["SparseNdarray", numpy.ndarray]:
751+
def __getitem__(self, subset) -> Union["SparseNdarray", numpy.ndarray]:
752752
"""Take a subset of this ``SparseNdarray``. This follows the same logic as NumPy slicing and will generate a
753753
:py:class:`~delayedarray.Subset.Subset` object when the subset operation preserves the dimensionality of the
754754
seed, i.e., ``args`` is defined using the :py:meth:`~numpy.ix_` function.
755755
756756
Args:
757-
args:
758-
A :py:class:`tuple` of length equal to the dimensionality of this ``SparseNdarray``.
759-
Any NumPy slicing is supported but only subsets that preserve dimensionality will generate a
760-
delayed subset operation.
757+
subset:
758+
A :py:class:`tuple` of length equal to the dimensionality of this ``SparseNdarray``, or a single integer specfying an index on the first dimension.
759+
We attempt to support most types of NumPy slicing; however, only subsets that preserve dimensionality will generate a ``SparseNdarray``.
761760
762761
Raises:
763762
ValueError: If ``args`` contain more dimensions than the shape of the array.
764763
765764
Returns:
766-
If the dimensionality is preserved by ``args``, a ``SparseNdarray`` containing a delayed subset operation is
767-
returned. Otherwise, a :py:class:`~numpy.ndarray` is returned containing the realized subset.
765+
If the dimensionality is preserved by ``subset``, a ``SparseNdarray`` containing the specified subset is returned.
766+
Otherwise, a :py:class:`~numpy.ndarray` is returned containing the realized subset.
768767
"""
768+
if not isinstance(subset, Tuple):
769+
replacement = [slice(None)] * len(self.shape)
770+
replacement[0] = subset
771+
subset = (*replacement,)
772+
769773
cleaned = _getitem_subset_preserves_dimensions(self.shape, subset)
770774
if cleaned is not None:
771775
# No need to sanitize here, as the extractors can take unsorted subsets.

src/delayedarray/_subset.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def _sanitize_subset(subset: Sequence):
6262
return san, remap
6363

6464

65-
def _getitem_subset_preserves_dimensions(shape: Tuple[int, ...], args: Tuple[Union[slice, Sequence], ...]):
65+
def _getitem_subset_preserves_dimensions(shape: Tuple[int, ...], args: Tuple):
6666
ndim = len(shape)
6767
if not isinstance(args, tuple):
6868
args = [args] + [slice(None)] * (ndim - 1)
@@ -71,6 +71,11 @@ def _getitem_subset_preserves_dimensions(shape: Tuple[int, ...], args: Tuple[Uni
7171
elif len(args) > ndim:
7272
raise ValueError("more indices in 'args' than there are dimensions in 'seed'")
7373

74+
# Checking if there are any integers here.
75+
for d, idx in enumerate(args):
76+
if isinstance(idx, int) or isinstance(idx, integer):
77+
return None
78+
7479
# Checking if we're preserving the shape via a cross index.
7580
cross_index = True
7681
for d, idx in enumerate(args):
@@ -117,7 +122,7 @@ def _getitem_subset_preserves_dimensions(shape: Tuple[int, ...], args: Tuple[Uni
117122
return None
118123

119124

120-
def _getitem_subset_discards_dimensions(x, args: Tuple[Union[slice, Sequence], ...], injected_extract_dense_array: Callable):
125+
def _getitem_subset_discards_dimensions(x, args: Tuple, injected_extract_dense_array: Callable):
121126
failed = False
122127
sanitized = []
123128
remapping = []
@@ -129,7 +134,6 @@ def _getitem_subset_discards_dimensions(x, args: Tuple[Union[slice, Sequence], .
129134
if isinstance(idx, ndarray):
130135
if len(idx.shape) != 1:
131136
raise NotImplementedError("high-dimensional index arrays are not supported yet")
132-
133137
elif isinstance(idx, slice):
134138
idx = range(*idx.indices(shape[d]))
135139
elif not isinstance(idx, Sequence):

tests/test_SparseNdarray.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ def test_SparseNdarray_subset_collapse(mask_rate):
435435
assert isinstance(first, numpy.ndarray)
436436
assert_identical_ndarrays(first, ref[:,1])
437437

438+
stuff = y[10]
439+
assert_identical_ndarrays(stuff, ref[10])
440+
stuff = y[numpy.int32(19)]
441+
assert_identical_ndarrays(stuff, ref[numpy.int32(19)])
442+
438443

439444
#######################################################
440445
#######################################################

tests/test_Subset.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ def test_Subset_collapse(mask_rate):
147147
stuff = x[0, :, 2]
148148
assert_identical_ndarrays(stuff, y[0, :, 2])
149149

150+
stuff = x[10]
151+
assert_identical_ndarrays(stuff, y[10])
152+
stuff = x[numpy.int32(20)]
153+
assert_identical_ndarrays(stuff, y[numpy.int32(20)])
154+
150155
# # Trying vectorized index.
151156
# stuff = x[[1,2,3],[4,5,6],[7,8,9]]
152157
# assert stuff.shape == (3,)

0 commit comments

Comments
 (0)