Repository: pandas-dev/pandas

Issue:
BUG: np.min(pd.Index) and np.max(pd.Index) raise a TypeError
#### Code Sample, a copy-pastable example if possible

```python
In [1]: import numpy as np; import pandas as pd

In [2]: np.__version__, pd.__version__
Out[2]: ('1.16.2', '0.25.0.dev0+421.g8b60ebb359')

In [3]: idx = pd.Index([10, 20, 11]); idx
Out[3]: Int64Index([10, 20, 11], dtype='int64')

In [4]: np.min(idx)
---------------------------------------------------------------------------
TypeError: min() got an unexpected keyword argument 'out'

In [5]: np.max(idx)
---------------------------------------------------------------------------
TypeError: max() got an unexpected keyword argument 'out'
```
Note that other numpy functions work fine:
```python
In [6]: np.sum(idx), np.prod(idx), np.argmax(idx), np.argmin(idx)
Out[6]: (41, 2200, 1, 0)
```
And `np.min` and `np.max` work on other pandas data structures:
```python
In [7]: s = pd.Series([10, 20, 11])

In [8]: np.min(s), np.max(s)
Out[8]: (10, 20)

In [9]: df = pd.DataFrame({'A': [10, 20, 11], 'B': [3, 1, 4]})

In [10]: np.min(df)
Out[10]:
A    10
B     1
dtype: int64

In [11]: np.max(df)
Out[11]:
A    20
B     4
dtype: int64
```

#### Problem description
Both `np.min` and `np.max` raise a `TypeError` when applied to a `pd.Index`.  Using `pd.Index.min` and `pd.Index.max` is more idiomatic for pandas, but using the numpy equivalent functions should work, as they do for other pandas data structures.

#### Expected Output
I'd expect `np.min(pd.Index)` and `np.max(pd.Index)` to be equivalent to `pd.Index.min` and `pd.Index.max`, as is the case with other pandas data structures.

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: 8b60ebb3598881571b390276d2c75ec7c7a95212
python: 3.6.8.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.25.0.dev0+421.g8b60ebb359
pytest: 4.2.0
pip: 19.0.1
setuptools: 40.6.3
Cython: 0.28.2
numpy: 1.16.2
scipy: 1.2.1
pyarrow: 0.6.0
xarray: 0.9.6
IPython: 7.2.0
sphinx: 1.8.2
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.9
feather: 0.4.0
matplotlib: 2.0.2
openpyxl: 2.4.8
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 0.9.8
lxml.etree: 3.8.0
bs4: None
html5lib: 0.999
sqlalchemy: 1.1.13
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
fastparquet: 0.1.5
pandas_gbq: None
pandas_datareader: None
gcsfs: None

</details>



Discussion:
Related issue: https://github.com/numpy/numpy/issues/13285
I have looked a bit into this, but I am not satisfied with what I got. My understanding is that as of today `numpy.core.fromnumeric._wrapreduction` will first look for `<func>` in the object's attributes instead of using `__array_ufunc__` duck array. This means that we cannot fix this issue for now by implementing `__array_ufunc__` in `pandas.core.indexes.base.Index`.

A dirty fix is to add a kwargs passthrough arg to `IndexOpsMixin`'s `min` & `max`  implementations like this :
```
max(self, axis=None, skipna=True, **kwargs)
```
It does not break tests, but it is a public interface modification 👎 
this is what we do for all other method  and is acceptable
see pandas.compat.numpy
Ok then, I will make a PR.

Resolving patch:
diff --git a/pandas/core/base.py b/pandas/core/base.py
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -981,7 +981,7 @@ def _ndarray_values(self) -> np.ndarray:
     def empty(self):
         return not self.size
 
-    def max(self, axis=None, skipna=True):
+    def max(self, axis=None, skipna=True, *args, **kwargs):
         """
         Return the maximum value of the Index.
 
@@ -1019,9 +1019,10 @@ def max(self, axis=None, skipna=True):
         ('b', 2)
         """
         nv.validate_minmax_axis(axis)
+        nv.validate_max(args, kwargs)
         return nanops.nanmax(self._values, skipna=skipna)
 
-    def argmax(self, axis=None, skipna=True):
+    def argmax(self, axis=None, skipna=True, *args, **kwargs):
         """
         Return an ndarray of the maximum argument indexer.
 
@@ -1041,9 +1042,10 @@ def argmax(self, axis=None, skipna=True):
         numpy.ndarray.argmax
         """
         nv.validate_minmax_axis(axis)
+        nv.validate_argmax_with_skipna(skipna, args, kwargs)
         return nanops.nanargmax(self._values, skipna=skipna)
 
-    def min(self, axis=None, skipna=True):
+    def min(self, axis=None, skipna=True, *args, **kwargs):
         """
         Return the minimum value of the Index.
 
@@ -1081,9 +1083,10 @@ def min(self, axis=None, skipna=True):
         ('a', 1)
         """
         nv.validate_minmax_axis(axis)
+        nv.validate_min(args, kwargs)
         return nanops.nanmin(self._values, skipna=skipna)
 
-    def argmin(self, axis=None, skipna=True):
+    def argmin(self, axis=None, skipna=True, *args, **kwargs):
         """
         Return a ndarray of the minimum argument indexer.
 
@@ -1102,6 +1105,7 @@ def argmin(self, axis=None, skipna=True):
         numpy.ndarray.argmin
         """
         nv.validate_minmax_axis(axis)
+        nv.validate_argmax_with_skipna(skipna, args, kwargs)
         return nanops.nanargmin(self._values, skipna=skipna)
 
     def tolist(self):
diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py
--- a/pandas/core/indexes/range.py
+++ b/pandas/core/indexes/range.py
@@ -327,14 +327,16 @@ def _minmax(self, meth):
 
         return self._start + self._step * no_steps
 
-    def min(self, axis=None, skipna=True):
+    def min(self, axis=None, skipna=True, *args, **kwargs):
         """The minimum value of the RangeIndex"""
         nv.validate_minmax_axis(axis)
+        nv.validate_min(args, kwargs)
         return self._minmax('min')
 
-    def max(self, axis=None, skipna=True):
+    def max(self, axis=None, skipna=True, *args, **kwargs):
         """The maximum value of the RangeIndex"""
         nv.validate_minmax_axis(axis)
+        nv.validate_max(args, kwargs)
         return self._minmax('max')
 
     def argsort(self, *args, **kwargs):


Test patch:
diff --git a/pandas/tests/reductions/test_reductions.py b/pandas/tests/reductions/test_reductions.py
--- a/pandas/tests/reductions/test_reductions.py
+++ b/pandas/tests/reductions/test_reductions.py
@@ -318,6 +318,59 @@ def test_minmax_nat_datetime64(self, op):
         obj = DatetimeIndex([pd.NaT, pd.NaT, pd.NaT])
         assert pd.isna(getattr(obj, op)())
 
+    def test_numpy_minmax_integer(self):
+        # GH#26125
+        idx = Index([1, 2, 3])
+
+        expected = idx.values.max()
+        result = np.max(idx)
+        assert result == expected
+
+        expected = idx.values.min()
+        result = np.min(idx)
+        assert result == expected
+
+        errmsg = "the 'out' parameter is not supported"
+        with pytest.raises(ValueError, match=errmsg):
+            np.min(idx, out=0)
+        with pytest.raises(ValueError, match=errmsg):
+            np.max(idx, out=0)
+
+        expected = idx.values.argmax()
+        result = np.argmax(idx)
+        assert result == expected
+
+        expected = idx.values.argmin()
+        result = np.argmin(idx)
+        assert result == expected
+
+        errmsg = "the 'out' parameter is not supported"
+        with pytest.raises(ValueError, match=errmsg):
+            np.argmin(idx, out=0)
+        with pytest.raises(ValueError, match=errmsg):
+            np.argmax(idx, out=0)
+
+    def test_numpy_minmax_range(self):
+        # GH#26125
+        idx = RangeIndex(0, 10, 3)
+
+        expected = idx._int64index.max()
+        result = np.max(idx)
+        assert result == expected
+
+        expected = idx._int64index.min()
+        result = np.min(idx)
+        assert result == expected
+
+        errmsg = "the 'out' parameter is not supported"
+        with pytest.raises(ValueError, match=errmsg):
+            np.min(idx, out=0)
+        with pytest.raises(ValueError, match=errmsg):
+            np.max(idx, out=0)
+
+        # No need to test again argmax/argmin compat since the implementation
+        # is the same as basic integer index
+
     def test_numpy_minmax_datetime64(self):
         dr = pd.date_range(start='2016-01-15', end='2016-01-20')