Repository: pandas-dev/pandas
Pull request: Min max sparse
- [x] closes #40921
- [x] tests added / passed
- [x] Ensure all linting tests pass, see [here](https://pandas.pydata.org/pandas-docs/dev/development/contributing.html#code-standards) for how to run them
- [x] whatsnew entry


Problem statement:
BUG: max()/min() don't work on DataFrames  constructed from scipy sparse matrix
Example:
```python
import pandas as pd
from scipy import sparse as sc
import numpy as np

np.random.seed(42)
a = np.random.randint(0, 10, size=(200, 5))
a_sp = sc.coo_matrix(a)
a_sp_pd = pd.DataFrame.sparse.from_spmatrix(a_sp)
a_sp_pd.max()
```
Expected output:
```
0    9
1    9
2    9
3    9
4    9
dtype: int64

```

Actual output:
```
Series([], dtype: float64)
```


System info: 
```
  - numpy [required: >=1.17, installed: 1.20.2]
  - scipy [required: >=0.19.1, installed: 1.6.2]
  - pandas [required: >=1.0.0,<=1.1.4, installed: 1.2.4]
```

Hints from issue discussion:
This raises 
```
Traceback (most recent call last):
  File "/home/developer/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-0/211.6693.115/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/home/developer/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-0/211.6693.115/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/home/developer/.config/JetBrains/PyCharm2021.1/scratches/scratch_4.py", line 527, in <module>
    a_sp = sc.sparse.coo_matrix(a)
AttributeError: module 'scipy' has no attribute 'sparse'

Process finished with exit code 1
```

for me. Could you check your example?
My bad, updated
This is not implemented. The error is caught in the call stack and an empty result is returned.
take
I will be adding a max & min method pandas\core\arrays\sparse\array.py in the sparsearray class
Take

Commit message: Add test coverage percentage

Patch to review:
diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst
index 7189c6e68d53d..3a1dd04b6c6e2 100644
--- a/doc/source/whatsnew/v1.3.0.rst
+++ b/doc/source/whatsnew/v1.3.0.rst
@@ -877,7 +877,7 @@ Sparse
 
 - Bug in :meth:`DataFrame.sparse.to_coo` raising ``KeyError`` with columns that are a numeric :class:`Index` without a 0 (:issue:`18414`)
 - Bug in :meth:`SparseArray.astype` with ``copy=False`` producing incorrect results when going from integer dtype to floating dtype (:issue:`34456`)
--
+- Bug in :class:`SparseArray` type as :meth:`max` and :meth:`min` do not exist (:issue:`40921`)
 
 ExtensionArray
 ^^^^^^^^^^^^^^
diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py
index 37898ce682e4f..06000d133f526 100644
--- a/pandas/core/arrays/sparse/array.py
+++ b/pandas/core/arrays/sparse/array.py
@@ -1392,6 +1392,30 @@ def mean(self, axis=0, *args, **kwargs):
             nsparse = self.sp_index.ngaps
             return (sp_sum + self.fill_value * nsparse) / (ct + nsparse)
 
+    def max(self, axis=0, *args, **kwargs):
+        nv.validate_max(args, kwargs)
+
+        if self.sp_index.ngaps > 0 and np.all(self._valid_sp_values < 0):
+
+            if self.size > 0 and self._valid_sp_values.size == 0:
+                return np.nan
+
+            return 0
+        else:
+            return np.amax(self._valid_sp_values, axis)
+
+    def min(self, axis=0, *args, **kwargs):
+        nv.validate_min(args, kwargs)
+
+        if self.sp_index.ngaps > 0 and np.all(self._valid_sp_values > 0):
+
+            if self.size > 0 and self._valid_sp_values.size == 0:
+                return np.nan
+
+            return 0
+        else:
+            return np.amin(self._valid_sp_values, axis)
+
     # ------------------------------------------------------------------------
     # Ufuncs
     # ------------------------------------------------------------------------
diff --git a/pandas/tests/arrays/sparse/test_array.py b/pandas/tests/arrays/sparse/test_array.py
index e073871f96bb4..a96e5b07b7f7e 100644
--- a/pandas/tests/arrays/sparse/test_array.py
+++ b/pandas/tests/arrays/sparse/test_array.py
@@ -1311,3 +1311,25 @@ def test_dropna(fill_value):
     df = pd.DataFrame({"a": [0, 1], "b": arr})
     expected_df = pd.DataFrame({"a": [1], "b": exp}, index=pd.Int64Index([1]))
     tm.assert_equal(df.dropna(), expected_df)
+
+
+class TestMinMax:
+    plain_data = np.arange(5).astype(float)
+    data_neg = plain_data * (-1)
+    data_NaN = SparseArray(np.array([0, 1, 2, np.nan, 4]))
+    data_all_NaN = SparseArray(np.array([np.nan, np.nan, np.nan, np.nan, np.nan]))
+
+    @pytest.mark.parametrize(
+        "raw_data,max_expected,min_expected",
+        [
+            (plain_data, [4], [0]),
+            (data_neg, [0], [-4]),
+            (data_NaN, [4], [0]),
+            (data_all_NaN, [np.nan], [np.nan]),
+        ],
+    )
+    def test_maxmin(self, raw_data, max_expected, min_expected):
+        max_result = SparseArray(raw_data).max()
+        min_result = SparseArray(raw_data).min()
+        assert max_result in max_expected
+        assert min_result in min_expected


Code review comments:
diff_hunk: @@ -1392,6 +1392,30 @@ def mean(self, axis=0, *args, **kwargs):
             nsparse = self.sp_index.ngaps
             return (sp_sum + self.fill_value * nsparse) / (ct + nsparse)
 
+    def max(self, axis=0, *args, **kwargs):
+        nv.validate_max(args, kwargs)
+
+        if self.sp_index.ngaps > 0 and np.all(self._valid_sp_values < 0):
+
+            if self.size > 0 and self._valid_sp_values.size == 0:
+                return np.nan
path: pandas/core/arrays/sparse/array.py
text: @user1:
wouldnt self.fill_value  be relevant here?

diff_hunk: @@ -1392,6 +1392,30 @@ def mean(self, axis=0, *args, **kwargs):
             nsparse = self.sp_index.ngaps
             return (sp_sum + self.fill_value * nsparse) / (ct + nsparse)
 
+    def max(self, axis=0, *args, **kwargs):
+        nv.validate_max(args, kwargs)
+
+        if self.sp_index.ngaps > 0 and np.all(self._valid_sp_values < 0):
+
+            if self.size > 0 and self._valid_sp_values.size == 0:
+                return np.nan
+
+            return 0
+        else:
+            return np.amax(self._valid_sp_values, axis)
path: pandas/core/arrays/sparse/array.py
text: @user1:
any particular reason for np.amax instead of np.max?