Skip to content

Commit 1833910

Browse files
authored
Add support for more numpy functions to dense and sparse DelayedArrays (#62)
Update tests and documentation
1 parent 6b0d9cd commit 1833910

File tree

5 files changed

+476
-25
lines changed

5 files changed

+476
-25
lines changed

src/delayedarray/DelayedArray.py

+150-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Sequence, Tuple, Union, Optional, List, Callable
22
import numpy
3-
from numpy import array, dtype, integer, issubdtype, ndarray, prod, array2string
3+
from numpy import dtype, ndarray, array2string
44
from collections import namedtuple
55

66
from .SparseNdarray import SparseNdarray
@@ -24,7 +24,7 @@
2424

2525
from ._subset import _getitem_subset_preserves_dimensions, _getitem_subset_discards_dimensions, _repr_subset
2626
from ._isometric import translate_ufunc_to_op_simple, translate_ufunc_to_op_with_args
27-
from ._statistics import array_mean, array_var, array_sum, _create_offset_multipliers
27+
from ._statistics import array_mean, array_var, array_sum, _create_offset_multipliers, array_any, array_all
2828

2929
__author__ = "ltla"
3030
__copyright__ = "ltla"
@@ -255,6 +255,21 @@ def __array_function__(self, func, types, args, kwargs) -> "DelayedArray":
255255
decimals = 0
256256
return DelayedArray(Round(seed, decimals=decimals))
257257

258+
if func == numpy.mean:
259+
return self.mean(**kwargs)
260+
261+
if func == numpy.sum:
262+
return self.sum(**kwargs)
263+
264+
if func == numpy.var:
265+
return self.var(**kwargs)
266+
267+
if func == numpy.any:
268+
return self.any(**kwargs)
269+
270+
if func == numpy.all:
271+
return self.all(**kwargs)
272+
258273
if func == numpy.shape:
259274
return self.shape
260275

@@ -691,6 +706,66 @@ def __abs__(self) -> "DelayedArray":
691706
"""
692707
return DelayedArray(UnaryIsometricOpSimple(self._seed, operation="abs"))
693708

709+
def __or__(self, other) -> "DelayedArray":
710+
"""Element-wise OR with something.
711+
712+
Args:
713+
other:
714+
A numeric scalar;
715+
or a NumPy array with dimensions as described in
716+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
717+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
718+
719+
Returns:
720+
A ``DelayedArray`` containing the delayed OR operation.
721+
"""
722+
return _wrap_isometric_with_args(self, other, operation="logical_or", right=True)
723+
724+
def __ror__(self, other) -> "DelayedArray":
725+
"""Element-wise OR with the right-hand-side of a ``DelayedArray``.
726+
727+
Args:
728+
other:
729+
A numeric scalar;
730+
or a NumPy array with dimensions as described in
731+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
732+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
733+
734+
Returns:
735+
A ``DelayedArray`` containing the delayed OR operation.
736+
"""
737+
return _wrap_isometric_with_args(self, other, operation="logical_or", right=False)
738+
739+
def __and__(self, other) -> "DelayedArray":
740+
"""Element-wise AND with something.
741+
742+
Args:
743+
other:
744+
A numeric scalar;
745+
or a NumPy array with dimensions as described in
746+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
747+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
748+
749+
Returns:
750+
A ``DelayedArray`` containing the delayed AND operation.
751+
"""
752+
return _wrap_isometric_with_args(self, other, operation="logical_and", right=True)
753+
754+
def __rand__(self, other) -> "DelayedArray":
755+
"""Element-wise AND with the right-hand-side of a ``DelayedArray``.
756+
757+
Args:
758+
other:
759+
A numeric scalar;
760+
or a NumPy array with dimensions as described in
761+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
762+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
763+
764+
Returns:
765+
A ``DelayedArray`` containing the delayed AND operation.
766+
"""
767+
return _wrap_isometric_with_args(self, other, operation="logical_and", right=False)
768+
694769
# Subsetting.
695770
def __getitem__(self, subset: Tuple[Union[slice, Sequence], ...]) -> Union["DelayedArray", ndarray]:
696771
"""Take a subset of this ``DelayedArray``. This follows the same logic as NumPy slicing and will generate a
@@ -832,6 +907,79 @@ def var(self, axis: Optional[Union[int, Tuple[int, ...]]] = None, dtype: Optiona
832907
masked=is_masked(self),
833908
)
834909

910+
def any(self, axis: Optional[Union[int, Tuple[int, ...]]] = None, dtype: Optional[numpy.dtype] = None, buffer_size: int = 1e8) -> numpy.ndarray:
911+
"""Test whether any array element along a given axis evaluates to True.
912+
913+
Compute this test across the ``DelayedArray``, possibly over a
914+
given axis or set of axes. If the seed has a ``any()`` method, that
915+
method is called directly with the supplied arguments.
916+
917+
Args:
918+
axis:
919+
A single integer specifying the axis over which to test
920+
for any. Alternatively, a tuple (multiple axes) or None (no
921+
axes), see :py:func:`~numpy.any` for details.
922+
923+
dtype:
924+
NumPy type for the output array. If None, this is automatically
925+
chosen based on the type of the ``DelayedArray``, see
926+
:py:func:`~numpy.any` for details.
927+
928+
buffer_size:
929+
Buffer size in bytes to use for block processing. Larger values
930+
generally improve speed at the cost of memory.
931+
932+
Returns:
933+
A NumPy array containing the boolean values. If ``axis = None``, this will
934+
be a NumPy scalar instead.
935+
"""
936+
if hasattr(self._seed, "any"):
937+
return self._seed.any(axis=axis).astype(dtype)
938+
else:
939+
return array_any(
940+
self,
941+
axis=axis,
942+
dtype=dtype,
943+
reduce_over_x=lambda x, axes, op : _reduce(x, axes, op, buffer_size),
944+
masked=is_masked(self),
945+
)
946+
947+
def all(self, axis: Optional[Union[int, Tuple[int, ...]]] = None, dtype: Optional[numpy.dtype] = None, buffer_size: int = 1e8) -> numpy.ndarray:
948+
"""Test whether all array elements along a given axis evaluate to True.
949+
950+
Compute this test across the ``DelayedArray``, possibly over a
951+
given axis or set of axes. If the seed has a ``all()`` method, that
952+
method is called directly with the supplied arguments.
953+
954+
Args:
955+
axis:
956+
A single integer specifying the axis over which to test
957+
for all. Alternatively, a tuple (multiple axes) or None (no
958+
axes), see :py:func:`~numpy.all` for details.
959+
960+
dtype:
961+
NumPy type for the output array. If None, this is automatically
962+
chosen based on the type of the ``DelayedArray``, see
963+
:py:func:`~numpy.all` for details.
964+
965+
buffer_size:
966+
Buffer size in bytes to use for block processing. Larger values
967+
generally improve speed at the cost of memory.
968+
969+
Returns:
970+
A NumPy array containing the boolean values. If ``axis = None``, this will
971+
be a NumPy scalar instead.
972+
"""
973+
if hasattr(self._seed, "all"):
974+
return self._seed.all(axis=axis).astype(dtype)
975+
else:
976+
return array_all(
977+
self,
978+
axis=axis,
979+
dtype=dtype,
980+
reduce_over_x=lambda x, axes, op : _reduce(x, axes, op, buffer_size),
981+
masked=is_masked(self),
982+
)
835983

836984
@extract_dense_array.register
837985
def extract_dense_array_DelayedArray(x: DelayedArray, subset: Tuple[Sequence[int], ...]) -> numpy.ndarray:

src/delayedarray/SparseNdarray.py

+139-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
_concatenate_unmasked_ndarrays,
1515
_concatenate_maybe_masked_ndarrays
1616
)
17-
from ._statistics import array_mean, array_var, array_sum, _create_offset_multipliers
17+
from ._statistics import array_mean, array_var, array_sum, _create_offset_multipliers, array_all, array_any
1818

1919
__author__ = "ltla"
2020
__copyright__ = "ltla"
@@ -665,6 +665,71 @@ def __abs__(self):
665665
A ``SparseNdarray`` containing the delayed absolute value operation.
666666
"""
667667
return _transform_sparse_array_from_SparseNdarray(self, lambda l, i, v : (i, abs(v)), self._dtype)
668+
669+
def __or__(self, other) -> Union["SparseNdarray", numpy.ndarray]:
670+
"""Element-wise OR with something.
671+
672+
Args:
673+
other:
674+
A numeric scalar;
675+
or a NumPy array with dimensions as described in
676+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
677+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
678+
679+
Returns:
680+
Array containing the result of the check.
681+
This may or may not be sparse depending on ``other``.
682+
"""
683+
return _operate_with_args_on_SparseNdarray(self, other, operation="logical_or", right=True)
684+
685+
def __ror__(self, other) -> Union["SparseNdarray", numpy.ndarray]:
686+
"""Element-wise OR with the right-hand-side of a ``DelayedArray``.
687+
688+
Args:
689+
other:
690+
A numeric scalar;
691+
or a NumPy array with dimensions as described in
692+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
693+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
694+
695+
Returns:
696+
Array containing the result of the check.
697+
This may or may not be sparse depending on ``other``.
698+
"""
699+
return _operate_with_args_on_SparseNdarray(self, other, operation="logical_or", right=False)
700+
701+
def __and__(self, other) -> Union["SparseNdarray", numpy.ndarray]:
702+
"""Element-wise AND with something.
703+
704+
Args:
705+
other:
706+
A numeric scalar;
707+
or a NumPy array with dimensions as described in
708+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
709+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
710+
711+
Returns:
712+
Array containing the result of the check.
713+
This may or may not be sparse depending on ``other``.
714+
"""
715+
return _operate_with_args_on_SparseNdarray(self, other, operation="logical_and", right=True)
716+
717+
def __rand__(self, other) -> Union["SparseNdarray", numpy.ndarray]:
718+
"""Element-wise AND with the right-hand-side of a ``DelayedArray``.
719+
720+
Args:
721+
other:
722+
A numeric scalar;
723+
or a NumPy array with dimensions as described in
724+
:py:class:`~delayedarray.UnaryIsometricOpWithArgs.UnaryIsometricOpWithArgs`;
725+
or a ``DelayedArray`` of the same dimensions as :py:attr:`~shape`.
726+
727+
Returns:
728+
Array containing the result of the check.
729+
This may or may not be sparse depending on ``other``.
730+
"""
731+
return _operate_with_args_on_SparseNdarray(self, other, operation="logical_and", right=False)
732+
668733

669734
# Subsetting.
670735
def __getitem__(self, subset: Tuple[Union[slice, Sequence], ...]) -> Union["SparseNdarray", numpy.ndarray]:
@@ -760,6 +825,21 @@ def __array_function__(self, func, types, args, kwargs) -> "SparseNdarray":
760825
if func == numpy.round:
761826
return _transform_sparse_array_from_SparseNdarray(self, lambda l, i, v : (i, func(v, **kwargs)), self._dtype)
762827

828+
if func == numpy.mean:
829+
return self.mean(**kwargs)
830+
831+
if func == numpy.sum:
832+
return self.sum(**kwargs)
833+
834+
if func == numpy.var:
835+
return self.var(**kwargs)
836+
837+
if func == numpy.any:
838+
return self.any(**kwargs)
839+
840+
if func == numpy.all:
841+
return self.all(**kwargs)
842+
763843
raise NotImplementedError(f"'{func.__name__}' is not implemented!")
764844

765845

@@ -872,6 +952,64 @@ def var(self, axis: Optional[Union[int, Tuple[int, ...]]] = None, dtype: Optiona
872952
masked=self._is_masked,
873953
)
874954

955+
def any(self, axis: Optional[Union[int, Tuple[int, ...]]] = None, dtype: Optional[numpy.dtype] = None) -> numpy.ndarray:
956+
"""Test whether any array element along a given axis evaluates to True.
957+
958+
Compute this test across the ``SparseNdarray``, possibly over a
959+
given axis or set of axes. If the seed has a ``any()`` method, that
960+
method is called directly with the supplied arguments.
961+
962+
Args:
963+
axis:
964+
A single integer specifying the axis over which to test
965+
for any. Alternatively, a tuple (multiple axes) or None
966+
(no axes), see :py:func:`~numpy.any` for details.
967+
968+
dtype:
969+
NumPy type for the output array. If None, this is automatically
970+
chosen based on the type of the ``SparseNdarray``, see
971+
:py:func:`~numpy.any` for details.
972+
973+
Returns:
974+
A NumPy array containing the variances. If ``axis = None``,
975+
this will be a NumPy scalar instead.
976+
"""
977+
return array_any(
978+
self,
979+
axis=axis,
980+
dtype=dtype,
981+
reduce_over_x=_reduce_SparseNdarray,
982+
masked=self._is_masked,
983+
)
984+
985+
def all(self, axis: Optional[Union[int, Tuple[int, ...]]] = None, dtype: Optional[numpy.dtype] = None) -> numpy.ndarray:
986+
"""Test whether all array elements along a given axis evaluate to True.
987+
988+
Compute this test across the ``SparseNdarray``, possibly over a
989+
given axis or set of axes. If the seed has a ``all()`` method, that
990+
method is called directly with the supplied arguments.
991+
Args:
992+
axis:
993+
A single integer specifying the axis over which to test
994+
for any. Alternatively, a tuple (multiple axes) or None
995+
(no axes), see :py:func:`~numpy.any` for details.
996+
997+
dtype:
998+
NumPy type for the output array. If None, this is automatically
999+
chosen based on the type of the ``SparseNdarray``, see
1000+
:py:func:`~numpy.any` for details.
1001+
1002+
Returns:
1003+
A NumPy array containing the variances. If ``axis = None``,
1004+
this will be a NumPy scalar instead.
1005+
"""
1006+
return array_all(
1007+
self,
1008+
axis=axis,
1009+
dtype=dtype,
1010+
reduce_over_x=_reduce_SparseNdarray,
1011+
masked=self._is_masked,
1012+
)
8751013

8761014
# Other stuff
8771015
def __copy__(self) -> "SparseNdarray":

src/delayedarray/_isometric.py

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def _execute(left, right, operation):
106106
"floor",
107107
"trunc",
108108
"sign",
109+
"isnan"
109110
]
110111
)
111112

0 commit comments

Comments
 (0)