Repository: cupy/cupy

Issue:
Difference from SciPy about sparse min/max `sum_duplicates` parameter
I'm not sure its background, but currently, sparse min/max have `sum_duplicates` parameter and its default value is `False`. However, their SciPy counterparts don't have the parameter and always sum duplicate entries as `sum_duplicates=True`.

It would be better to change its default value to `False`.


Discussion:
This features was added in #2711
I would say toggling the default behavior from False to True to be consistent with scipy would be good. The reason I introduced the flag is as a performance enhancement to skip the sum_duplicates operation if the user knows their matrix is canonical. Changing the default flag will make it conform with the default scipy behavior.

For #3583, there's an open pull request on scipy that will implement the explicit nonzero features. I wouldn't remove the functionality from cupy for the sake of standardizing to scipy since it's a useful feature when explicit zeros are included in the sparse matrix. The default behavior for the non_zero flag is the same as standard scipy. The scipy pull request uses the keyword "explicit" rather than "nonzero" which might be a good change to make.

Resolving patch:
diff --git a/cupyx/scipy/sparse/data.py b/cupyx/scipy/sparse/data.py
--- a/cupyx/scipy/sparse/data.py
+++ b/cupyx/scipy/sparse/data.py
@@ -137,18 +137,17 @@ class _minmax_mixin(object):
 
     """
 
-    def _min_or_max_axis(self, axis, min_or_max, sum_duplicates, nonzero):
+    def _min_or_max_axis(self, axis, min_or_max, explicit):
         N = self.shape[axis]
         if N == 0:
             raise ValueError("zero-size array to reduction operation")
         M = self.shape[1 - axis]
 
         mat = self.tocsc() if axis == 0 else self.tocsr()
-        if sum_duplicates:
-            mat.sum_duplicates()
+        mat.sum_duplicates()
 
         # Do the reudction
-        value = mat._minor_reduce(min_or_max, axis, nonzero)
+        value = mat._minor_reduce(min_or_max, axis, explicit)
         major_index = cupy.arange(M)
 
         mask = value != 0
@@ -164,7 +163,7 @@ def _min_or_max_axis(self, axis, min_or_max, sum_duplicates, nonzero):
                 (value, (major_index, cupy.zeros(len(value)))),
                 dtype=self.dtype, shape=(M, 1))
 
-    def _min_or_max(self, axis, out, min_or_max, sum_duplicates, non_zero):
+    def _min_or_max(self, axis, out, min_or_max, explicit):
         if out is not None:
             raise ValueError(("Sparse matrices do not support "
                               "an 'out' parameter."))
@@ -178,10 +177,9 @@ def _min_or_max(self, axis, out, min_or_max, sum_duplicates, non_zero):
             zero = cupy.zeros((), dtype=self.dtype)
             if self.nnz == 0:
                 return zero
-            if sum_duplicates:
-                self.sum_duplicates()
+            self.sum_duplicates()
             m = min_or_max(self.data)
-            if non_zero:
+            if explicit:
                 return m
             if self.nnz != internal.prod(self.shape):
                 if min_or_max is cupy.min:
@@ -193,27 +191,27 @@ def _min_or_max(self, axis, out, min_or_max, sum_duplicates, non_zero):
             return m
 
         if axis == 0 or axis == 1:
-            return self._min_or_max_axis(axis, min_or_max, sum_duplicates,
-                                         non_zero)
+            return self._min_or_max_axis(axis, min_or_max, explicit)
         else:
             raise ValueError("axis out of range")
 
-    def _arg_min_or_max_axis(self, axis, op, sum_duplicates):
+    def _arg_min_or_max_axis(self, axis, op):
         if self.shape[axis] == 0:
             raise ValueError("Can't apply the operation along a zero-sized "
                              "dimension.")
 
         mat = self.tocsc() if axis == 0 else self.tocsr()
-
-        if sum_duplicates:
-            mat.sum_duplicates()
+        mat.sum_duplicates()
 
         # Do the reudction
         value = mat._arg_minor_reduce(op, axis)
 
-        return value
+        if axis == 0:
+            return value[None, :]
+        else:
+            return value[:, None]
 
-    def _arg_min_or_max(self, axis, out, op, compare, sum_duplicates):
+    def _arg_min_or_max(self, axis, out, op, compare):
         if out is not None:
             raise ValueError("Sparse matrices do not support "
                              "an 'out' parameter.")
@@ -231,8 +229,7 @@ def _arg_min_or_max(self, axis, out, op, compare, sum_duplicates):
                 zero = self.dtype.type(0)
                 mat = self.tocoo()
 
-                if sum_duplicates:
-                    mat.sum_duplicates()
+                mat.sum_duplicates()
 
                 am = op(mat.data)
                 m = mat.data[am]
@@ -251,9 +248,9 @@ def _arg_min_or_max(self, axis, out, op, compare, sum_duplicates):
                         else:
                             return zero_ind
 
-        return self._arg_min_or_max_axis(axis, op, sum_duplicates)
+        return self._arg_min_or_max_axis(axis, op)
 
-    def max(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
+    def max(self, axis=None, out=None, *, explicit=False):
         """Returns the maximum of the matrix or maximum along an axis.
 
         Args:
@@ -265,11 +262,11 @@ def max(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except
                 for the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
-            nonzero (bool): Return the maximum nonzero value and ignore all
-                zero entries. If the dimension has no nonzero values, a zero is
-                then returned to indicate that it is the only available value.
+            explicit (bool): Return the maximum value explicitly specified and
+                ignore all implicit zero entries. If the dimension has no
+                explicit values, a zero is then returned to indicate that it is
+                the only implicit value. This parameter is experimental and may
+                change in the future.
 
         Returns:
             (cupy.ndarray or float): Maximum of ``a``. If ``axis`` is
@@ -283,10 +280,13 @@ def max(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
           matrices
 
         """
+        if explicit:
+            api_name = 'explicit of cupyx.scipy.sparse.{}.max'.format(
+                self.__class__.__name__)
+            cupy.util.experimental(api_name)
+        return self._min_or_max(axis, out, cupy.max, explicit)
 
-        return self._min_or_max(axis, out, cupy.max, sum_duplicates, nonzero)
-
-    def min(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
+    def min(self, axis=None, out=None, *, explicit=False):
         """Returns the minimum of the matrix or maximum along an axis.
 
         Args:
@@ -298,11 +298,11 @@ def min(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except for
                 the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
-            nonzero (bool): Return the minimum nonzero value and ignore all
-                zero entries. If the dimension has no nonzero values, a zero is
-                then returned to indicate that it is the only available value.
+            explicit (bool): Return the minimum value explicitly specified and
+                ignore all implicit zero entries. If the dimension has no
+                explicit values, a zero is then returned to indicate that it is
+                the only implicit value. This parameter is experimental and may
+                change in the future.
 
         Returns:
             (cupy.ndarray or float): Minimum of ``a``. If ``axis`` is
@@ -316,10 +316,13 @@ def min(self, axis=None, out=None, sum_duplicates=False, nonzero=False):
           matrices
 
         """
+        if explicit:
+            api_name = 'explicit of cupyx.scipy.sparse.{}.min'.format(
+                self.__class__.__name__)
+            cupy.util.experimental(api_name)
+        return self._min_or_max(axis, out, cupy.min, explicit)
 
-        return self._min_or_max(axis, out, cupy.min, sum_duplicates, nonzero)
-
-    def argmax(self, axis=None, out=None, sum_duplicates=False):
+    def argmax(self, axis=None, out=None):
         """Returns indices of maximum elements along an axis.
 
         Implicit zero elements are taken into account. If there are several
@@ -335,19 +338,15 @@ def argmax(self, axis=None, out=None, sum_duplicates=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except for
                 the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
 
         Returns:
             (cupy.narray or int): Indices of maximum elements. If array,
                 its size along ``axis`` is 1.
 
         """
+        return self._arg_min_or_max(axis, out, cupy.argmax, cupy.greater)
 
-        return self._arg_min_or_max(axis, out, cupy.argmax, cupy.greater,
-                                    sum_duplicates)
-
-    def argmin(self, axis=None, out=None, sum_duplicates=False):
+    def argmin(self, axis=None, out=None):
         """
         Returns indices of minimum elements along an axis.
 
@@ -364,17 +363,13 @@ def argmin(self, axis=None, out=None, sum_duplicates=False):
                 This argument is in the signature *solely* for NumPy
                 compatibility reasons. Do not pass in anything except for
                 the default value, as this argument is not used.
-            sum_duplicates (bool): Flag to indicate that duplicate elements
-                should be combined prior to the operation
 
         Returns:
             (cupy.narray or int): Indices of minimum elements. If matrix,
                 its size along ``axis`` is 1.
 
         """
-
-        return self._arg_min_or_max(axis, out, cupy.argmin, cupy.less,
-                                    sum_duplicates)
+        return self._arg_min_or_max(axis, out, cupy.argmin, cupy.less)
 
 
 def _install_ufunc(func_name):


Test patch:
diff --git a/tests/cupyx_tests/scipy_tests/sparse_tests/test_csc.py b/tests/cupyx_tests/scipy_tests/sparse_tests/test_csc.py
--- a/tests/cupyx_tests/scipy_tests/sparse_tests/test_csc.py
+++ b/tests/cupyx_tests/scipy_tests/sparse_tests/test_csc.py
@@ -1020,6 +1020,12 @@ def test_getnnz(self, xp, sp):
         return _make(xp, sp, self.dtype).getnnz()
 
 
+@testing.parameterize(*testing.product({
+    # TODO(takagi) Test dtypes
+    # TODO(takagi) Test negative axis
+    'axis': [None, 0, 1],
+    'dense': [False, True],  # means a sparse matrix but all elements filled
+}))
 @testing.with_requires('scipy>=0.19.0')
 class TestCscMatrixScipyCompressedMinMax(unittest.TestCase):
 
@@ -1032,14 +1038,15 @@ def _make_data_min(self, xp, sp, dense=False):
     def _make_data_max(self, xp, sp, dense=False):
         return -self._make_data_min(xp, sp, dense=dense)
 
-    def _make_data_min_nonzero(self, xp, sp, axis):
+    def _make_data_min_explicit(self, xp, sp, axis):
         dm_data = testing.shaped_random((10, 20), xp=xp, scale=1.0)
         if xp is cupy:
             dm_data[dm_data < 0.95] = 0
         else:
-            # As SciPy sparse matrix does not have `nonzero` parameter, we make
-            # SciPy inputs such that SciPy's spmatrix.min(axis=axis) returns
-            # the same value as CuPy's spmatrix.min(axis=axis, nonzero=True).
+            # As SciPy sparse matrix does not have `explicit` parameter, we
+            # make SciPy inputs such that SciPy's spmatrix.min(axis=axis)
+            # returns the same value as CuPy's spmatrix.min(axis=axis,
+            # explicit=True).
 
             # Put infinity instead of zeros so spmatrix.min(axis=axis) returns
             # the smallest numbers except for zero.
@@ -1067,214 +1074,50 @@ def _make_data_min_nonzero(self, xp, sp, axis):
 
         return sp.csc_matrix(xp.array(dm_data))
 
-    def _make_data_max_nonzero(self, xp, sp, axis):
-        return -self._make_data_min_nonzero(xp, sp, axis=axis)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_none(self, xp, sp):
-        data = self._make_data_min(xp, sp)
-        return data.min(axis=None)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_none_nonzero(self, xp, sp):
-        data = self._make_data_min_nonzero(xp, sp, axis=None)
-        if xp is cupy:
-            return data.min(axis=None, nonzero=True)
-        else:
-            return data.min(axis=None)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_0(self, xp, sp):
-        data = self._make_data_min(xp, sp)
-        return data.min(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_dense_axis_0(self, xp, sp):
-        data = self._make_data_min(xp, sp, dense=True)
-        return data.min(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_axis_0_nonzero(self, xp, sp):
-        data = self._make_data_min_nonzero(xp, sp, axis=0)
-        if xp is cupy:
-            return data.min(axis=0, nonzero=True)
-        else:
-            return data.min(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_1(self, xp, sp):
-        data = self._make_data_min(xp, sp)
-        return data.min(axis=1)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_dense_axis_1(self, xp, sp):
-        data = self._make_data_min(xp, sp, dense=True)
-        return data.min(axis=1)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_axis_1_nonzero(self, xp, sp):
-        data = self._make_data_min_nonzero(xp, sp, axis=1)
-        if xp is cupy:
-            return data.min(axis=1, nonzero=True)
-        else:
-            return data.min(axis=1)
+    def _make_data_max_explicit(self, xp, sp, axis):
+        return -self._make_data_min_explicit(xp, sp, axis=axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_none(self, xp, sp):
-        data = self._make_data_max(xp, sp)
-        return data.max(axis=None)
+    def test_min(self, xp, sp):
+        data = self._make_data_min(xp, sp, dense=self.dense)
+        return data.min(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_none_nonzero(self, xp, sp):
-        data = self._make_data_max_nonzero(xp, sp, axis=None)
+    def test_min_explicit(self, xp, sp):
+        data = self._make_data_min_explicit(xp, sp, axis=self.axis)
         if xp is cupy:
-            return data.max(axis=None, nonzero=True)
+            return data.min(axis=self.axis, explicit=True)
         else:
-            return data.max(axis=None)
+            return data.min(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_0(self, xp, sp):
-        data = self._make_data_max(xp, sp)
-        return data.max(axis=0)
+    def test_max(self, xp, sp):
+        data = self._make_data_max(xp, sp, dense=self.dense)
+        return data.max(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_dense_axis_0(self, xp, sp):
-        data = self._make_data_max(xp, sp, dense=True)
-        return data.max(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_axis_0_nonzero(self, xp, sp):
-        data = self._make_data_max_nonzero(xp, sp, axis=0)
+    def test_max_explicit(self, xp, sp):
+        data = self._make_data_max_explicit(xp, sp, axis=self.axis)
         if xp is cupy:
-            return data.max(axis=0, nonzero=True)
+            return data.max(axis=self.axis, explicit=True)
         else:
-            return data.max(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_1(self, xp, sp):
-        data = self._make_data_max(xp, sp)
-        return data.max(axis=1)
+            return data.max(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_dense_axis_1(self, xp, sp):
-        data = self._make_data_max(xp, sp, dense=True)
-        return data.max(axis=1)
+    def test_argmin(self, xp, sp):
+        # TODO(takagi) Fix axis=None
+        if self.axis is None:
+            pytest.skip()
+        data = self._make_data_min(xp, sp, dense=self.dense)
+        return data.argmin(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_axis_1_nonzero(self, xp, sp):
-        data = self._make_data_max_nonzero(xp, sp, axis=1)
-        if xp is cupy:
-            return data.max(axis=1, nonzero=True)
-        else:
-            return data.max(axis=1)
-
-    def test_argmin_sparse_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmin_dense_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmin_sparse_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmin_dense_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_sparse_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_dense_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_sparse_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_dense_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csc_matrix(dm_data)
-        cp_matrix = sparse.csc_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
+    def test_argmax(self, xp, sp):
+        # TODO(takagi) Fix axis=None
+        if self.axis is None:
+            pytest.skip()
+        data = self._make_data_max(xp, sp, dense=self.dense)
+        return data.argmax(axis=self.axis)
 
 
 @testing.parameterize(*testing.product({
diff --git a/tests/cupyx_tests/scipy_tests/sparse_tests/test_csr.py b/tests/cupyx_tests/scipy_tests/sparse_tests/test_csr.py
--- a/tests/cupyx_tests/scipy_tests/sparse_tests/test_csr.py
+++ b/tests/cupyx_tests/scipy_tests/sparse_tests/test_csr.py
@@ -1140,9 +1140,14 @@ def test_getnnz(self, xp, sp):
         return _make(xp, sp, self.dtype).getnnz()
 
 
+@testing.parameterize(*testing.product({
+    # TODO(takagi) Test dtypes
+    # TODO(takagi) Test negative axis
+    'axis': [None, 0, 1],
+    'dense': [False, True],  # means a sparse matrix but all elements filled
+}))
 @testing.with_requires('scipy>=0.19.0')
 class TestCsrMatrixScipyCompressedMinMax(unittest.TestCase):
-
     def _make_data_min(self, xp, sp, dense=False):
         dm_data = testing.shaped_random((10, 20), xp=xp, scale=1.0)
         if not dense:
@@ -1152,14 +1157,15 @@ def _make_data_min(self, xp, sp, dense=False):
     def _make_data_max(self, xp, sp, dense=False):
         return -self._make_data_min(xp, sp, dense=dense)
 
-    def _make_data_min_nonzero(self, xp, sp, axis):
+    def _make_data_min_explicit(self, xp, sp, axis):
         dm_data = testing.shaped_random((10, 20), xp=xp, scale=1.0)
         if xp is cupy:
             dm_data[dm_data < 0.95] = 0
         else:
-            # As SciPy sparse matrix does not have `nonzero` parameter, we make
-            # SciPy inputs such that SciPy's spmatrix.min(axis=axis) returns
-            # the same value as CuPy's spmatrix.min(axis=axis, nonzero=True).
+            # As SciPy sparse matrix does not have `explicit` parameter, we
+            # make SciPy inputs such that SciPy's spmatrix.min(axis=axis)
+            # returns the same value as CuPy's spmatrix.min(axis=axis,
+            # explicit=True).
 
             # Put infinity instead of zeros so spmatrix.min(axis=axis) returns
             # the smallest numbers except for zero.
@@ -1187,214 +1193,50 @@ def _make_data_min_nonzero(self, xp, sp, axis):
 
         return sp.csr_matrix(xp.array(dm_data))
 
-    def _make_data_max_nonzero(self, xp, sp, axis):
-        return -self._make_data_min_nonzero(xp, sp, axis=axis)
+    def _make_data_max_explicit(self, xp, sp, axis):
+        return -self._make_data_min_explicit(xp, sp, axis=axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_none(self, xp, sp):
-        data = self._make_data_min(xp, sp)
-        return data.min(axis=None)
+    def test_min(self, xp, sp):
+        data = self._make_data_min(xp, sp, dense=self.dense)
+        return data.min(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_none_nonzero(self, xp, sp):
-        data = self._make_data_min_nonzero(xp, sp, axis=None)
+    def test_min_explicit(self, xp, sp):
+        data = self._make_data_min_explicit(xp, sp, axis=self.axis)
         if xp is cupy:
-            return data.min(axis=None, nonzero=True)
+            return data.min(axis=self.axis, explicit=True)
         else:
-            return data.min(axis=None)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_0(self, xp, sp):
-        data = self._make_data_min(xp, sp)
-        return data.min(axis=0)
+            return data.min(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_dense_axis_0(self, xp, sp):
-        data = self._make_data_min(xp, sp, dense=True)
-        return data.min(axis=0)
+    def test_max(self, xp, sp):
+        data = self._make_data_max(xp, sp, dense=self.dense)
+        return data.max(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_axis_0_nonzero(self, xp, sp):
-        data = self._make_data_min_nonzero(xp, sp, axis=0)
+    def test_max_explicit(self, xp, sp):
+        data = self._make_data_max_explicit(xp, sp, axis=self.axis)
         if xp is cupy:
-            return data.min(axis=0, nonzero=True)
+            return data.max(axis=self.axis, explicit=True)
         else:
-            return data.min(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_sparse_axis_1(self, xp, sp):
-        data = self._make_data_min(xp, sp)
-        return data.min(axis=1)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_dense_axis_1(self, xp, sp):
-        data = self._make_data_min(xp, sp, dense=True)
-        return data.min(axis=1)
+            return data.max(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_min_axis_1_nonzero(self, xp, sp):
-        data = self._make_data_min_nonzero(xp, sp, axis=1)
-        if xp is cupy:
-            return data.min(axis=1, nonzero=True)
-        else:
-            return data.min(axis=1)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_none(self, xp, sp):
-        data = self._make_data_max(xp, sp)
-        return data.max(axis=None)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_none_nonzero(self, xp, sp):
-        data = self._make_data_max_nonzero(xp, sp, axis=None)
-        if xp is cupy:
-            return data.max(axis=None, nonzero=True)
-        else:
-            return data.max(axis=None)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_0(self, xp, sp):
-        data = self._make_data_max(xp, sp)
-        return data.max(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_dense_axis_0(self, xp, sp):
-        data = self._make_data_max(xp, sp, dense=True)
-        return data.max(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_axis_0_nonzero(self, xp, sp):
-        data = self._make_data_max_nonzero(xp, sp, axis=0)
-        if xp is cupy:
-            return data.max(axis=0, nonzero=True)
-        else:
-            return data.max(axis=0)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_sparse_axis_1(self, xp, sp):
-        data = self._make_data_max(xp, sp)
-        return data.max(axis=1)
-
-    @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_dense_axis_1(self, xp, sp):
-        data = self._make_data_max(xp, sp, dense=True)
-        return data.max(axis=1)
+    def test_argmin(self, xp, sp):
+        # TODO(takagi) Fix axis=None
+        if self.axis is None:
+            pytest.skip()
+        data = self._make_data_min(xp, sp, dense=self.dense)
+        return data.argmin(axis=self.axis)
 
     @testing.numpy_cupy_array_equal(sp_name='sp')
-    def test_max_axis_1_nonzero(self, xp, sp):
-        data = self._make_data_max_nonzero(xp, sp, axis=1)
-        if xp is cupy:
-            return data.max(axis=1, nonzero=True)
-        else:
-            return data.max(axis=1)
-
-    def test_argmin_sparse_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmin_dense_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmin_sparse_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmin_dense_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmin(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmin(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_sparse_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_dense_axis_0(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=0))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=0))[0, :]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_sparse_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-        dm_data[dm_data < 0.95] = 0
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
-
-    def test_argmax_dense_axis_1(self):
-        dm_data = numpy.random.random((10, 20))
-
-        dm_data = scipy.sparse.csr_matrix(dm_data)
-        cp_matrix = sparse.csr_matrix((cupy.array(dm_data.data),
-                                       cupy.array(dm_data.indices),
-                                       cupy.array(dm_data.indptr)),
-                                      shape=(10, 20))
-
-        da_cupy_values = cupy.asnumpy(cp_matrix.argmax(axis=1))
-        da_scipy_values = numpy.array(dm_data.argmax(axis=1))[:, 0]
-        assert numpy.array_equal(da_cupy_values, da_scipy_values)
+    def test_argmax(self, xp, sp):
+        # TODO(takagi) Fix axis=None
+        if self.axis is None:
+            pytest.skip()
+        data = self._make_data_max(xp, sp, dense=self.dense)
+        return data.argmax(axis=self.axis)
 
 
 @testing.parameterize(*testing.product({