Skip to content

Commit

Permalink
Merge pull request scipy#19743 from dschult/minmax-1d
Browse files Browse the repository at this point in the history
ENH: sparse: Add min-max 1d support and tests
  • Loading branch information
tylerjereddy authored Dec 28, 2023
2 parents 30a0d13 + 1866fed commit 6452a48
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 12 deletions.
29 changes: 17 additions & 12 deletions scipy/sparse/_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,13 @@ def _min_or_max_axis(self, axis, min_or_max):

def _min_or_max(self, axis, out, min_or_max):
if out is not None:
raise ValueError("Sparse matrices do not support "
"an 'out' parameter.")
raise ValueError("Sparse arrays do not support an 'out' parameter.")

validateaxis(axis)
if self.ndim == 1:
if axis not in (None, 0, -1):
raise ValueError("axis out of range")
axis = None # avoid calling special axis case. no impact on 1d

if axis is None:
if 0 in self.shape:
Expand All @@ -234,8 +237,7 @@ def _min_or_max(self, axis, out, min_or_max):

def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare):
if self.shape[axis] == 0:
raise ValueError("Can't apply the operation along a zero-sized "
"dimension.")
raise ValueError("Cannot apply the operation along a zero-sized dimension.")

if axis < 0:
axis += 2
Expand Down Expand Up @@ -275,11 +277,16 @@ def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare):

validateaxis(axis)

if self.ndim == 1:
if axis not in (None, 0, -1):
raise ValueError("axis out of range")
axis = None # avoid calling special axis case. no impact on 1d

if axis is not None:
return self._arg_min_or_max_axis(axis, argmin_or_argmax, compare)

if 0 in self.shape:
raise ValueError("Can't apply the operation to an empty matrix.")
raise ValueError("Cannot apply the operation to an empty matrix.")

if self.nnz == 0:
return 0
Expand All @@ -290,20 +297,18 @@ def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare):
mat.sum_duplicates()
extreme_index = argmin_or_argmax(mat.data)
extreme_value = mat.data[extreme_index]
num_row, num_col = mat.shape
num_col = mat.shape[-1]

# If the min value is less than zero, or max is greater than zero,
# then we don't need to worry about implicit zeros.
# then we do not need to worry about implicit zeros.
if compare(extreme_value, zero):
# cast to Python int to avoid overflow and RuntimeError
return (int(mat.row[extreme_index]) * num_col +
int(mat.col[extreme_index]))
return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index])

# Cheap test for the rare case where we have no implicit zeros.
size = num_row * num_col
size = np.prod(self.shape)
if size == mat.nnz:
return (int(mat.row[extreme_index]) * num_col +
int(mat.col[extreme_index]))
return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index])

# At this stage, any implicit zero could be the min or max value.
# After sum_duplicates(), the `row` and `col` arrays are guaranteed to
Expand Down
1 change: 1 addition & 0 deletions scipy/sparse/tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ python_sources = [
'test_csr.py',
'test_extract.py',
'test_matrix_io.py',
'test_minmax1d.py',
'test_sparsetools.py',
'test_spfuncs.py',
'test_sputils.py'
Expand Down
82 changes: 82 additions & 0 deletions scipy/sparse/tests/test_minmax1d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Test of min-max 1D features of sparse array classes"""

import pytest

import numpy as np

from numpy.testing import assert_equal, assert_array_equal

from scipy.sparse import coo_array
from scipy.sparse._sputils import isscalarlike


def toarray(a):
if isinstance(a, np.ndarray) or isscalarlike(a):
return a
return a.toarray()


formats_for_minmax = [coo_array]


@pytest.mark.parametrize("spcreator", formats_for_minmax)
class Test_MinMaxMixin1D:
def test_minmax(self, spcreator):
D = np.arange(5)
X = spcreator(D)

assert_equal(X.min(), 0)
assert_equal(X.max(), 4)
assert_equal((-X).min(), -4)
assert_equal((-X).max(), 0)


def test_minmax_axis(self, spcreator):
D = np.arange(50)
X = spcreator(D)

for axis in [0, -1]:
assert_array_equal(
toarray(X.max(axis=axis)), D.max(axis=axis, keepdims=True)
)
assert_array_equal(
toarray(X.min(axis=axis)), D.min(axis=axis, keepdims=True)
)
for axis in [-2, 1]:
with pytest.raises(ValueError, match="axis out of range"):
X.min(axis=axis)
with pytest.raises(ValueError, match="axis out of range"):
X.max(axis=axis)


def test_numpy_minmax(self, spcreator):
dat = np.array([0, 1, 2])
datsp = spcreator(dat)
assert_array_equal(np.min(datsp), np.min(dat))
assert_array_equal(np.max(datsp), np.max(dat))


def test_argmax(self, spcreator):
D1 = np.array([-1, 5, 2, 3])
D2 = np.array([0, 0, -1, -2])
D3 = np.array([-1, -2, -3, -4])
D4 = np.array([1, 2, 3, 4])
D5 = np.array([1, 2, 0, 0])

for D in [D1, D2, D3, D4, D5]:
mat = spcreator(D)

assert_equal(mat.argmax(), np.argmax(D))
assert_equal(mat.argmin(), np.argmin(D))

assert_equal(mat.argmax(axis=0), np.argmax(D, axis=0))
assert_equal(mat.argmin(axis=0), np.argmin(D, axis=0))

D6 = np.empty((0,))

for axis in [None, 0]:
mat = spcreator(D6)
with pytest.raises(ValueError, match="to an empty matrix"):
mat.argmin(axis=axis)
with pytest.raises(ValueError, match="to an empty matrix"):
mat.argmax(axis=axis)

0 comments on commit 6452a48

Please sign in to comment.