Repository: pandas-dev/pandas

Issue:
API: Should Index.min and max use nanmin and nanmax?
Index and Series `min` and `max` handles `nan` and `NaT` differently. Even though `min` and `max` are defined in `IndexOpsMixin`, `Series` doesn't use them and use `NDFrame` definitions.

```
pd.Index([np.nan, 1.0]).min()
# nan

pd.Index([np.nan, 1.0]).max()
# nan

pd.DatetimeIndex([pd.NaT, '2011-01-01']).min()
# NaT

pd.DatetimeIndex([pd.NaT, '2011-01-01']).max()
#2011-01-01 00:00:00

# Series excludes nan and NaT
pd.Series([np.nan, 1.0]).min()
#1.0

pd.Series([np.nan, 1.0]).max()
#1.0

pd.Series([pd.NaT, pd.Timestamp('2011-01-01')]).min()
#2011-01-01 00:00:00

pd.Series([pd.NaT, pd.Timestamp('2011-01-01')]).max()
#2011-01-01 00:00:00
```



Discussion:
yah that is right; these are actually overriden in `core/generic.py` for `Series`; so these can/should   directly use `nanmin/max`


Resolving patch:
diff --git a/pandas/core/base.py b/pandas/core/base.py
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -236,11 +236,13 @@ def _wrap_access_object(self, obj):
 
     def max(self):
         """ The maximum value of the object """
-        return self.values.max()
+        import pandas.core.nanops
+        return pandas.core.nanops.nanmax(self.values)
 
     def min(self):
         """ The minimum value of the object """
-        return self.values.min()
+        import pandas.core.nanops
+        return pandas.core.nanops.nanmin(self.values)
 
     def value_counts(self, normalize=False, sort=True, ascending=False,
                      bins=None):
diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py
--- a/pandas/tseries/index.py
+++ b/pandas/tseries/index.py
@@ -1758,20 +1758,28 @@ def min(self, axis=None):
         """
         Overridden ndarray.min to return a Timestamp
         """
-        if self.is_monotonic:
-            return self[0]
+        mask = self.asi8 == tslib.iNaT
+        masked = self[~mask]
+        if len(masked) == 0:
+            return tslib.NaT
+        elif self.is_monotonic:
+            return masked[0]
         else:
-            min_stamp = self.asi8.min()
+            min_stamp = masked.asi8.min()
             return Timestamp(min_stamp, tz=self.tz)
 
     def max(self, axis=None):
         """
         Overridden ndarray.max to return a Timestamp
         """
-        if self.is_monotonic:
-            return self[-1]
+        mask = self.asi8 == tslib.iNaT
+        masked = self[~mask]
+        if len(masked) == 0:
+            return tslib.NaT
+        elif self.is_monotonic:
+            return masked[-1]
         else:
-            max_stamp = self.asi8.max()
+            max_stamp = masked.asi8.max()
             return Timestamp(max_stamp, tz=self.tz)
 
     def to_julian_date(self):


Test patch:
diff --git a/pandas/tests/test_base.py b/pandas/tests/test_base.py
--- a/pandas/tests/test_base.py
+++ b/pandas/tests/test_base.py
@@ -1,5 +1,5 @@
 import re
-from datetime import timedelta
+from datetime import datetime, timedelta
 import numpy as np
 import pandas.compat as compat
 import pandas as pd
@@ -210,6 +210,39 @@ def test_ops(self):
                     expected = expected.astype('M8[ns]').astype('int64')
                     self.assertEqual(result.value, expected)
 
+    def test_nanops(self):
+        # GH 7261
+        for op in ['max','min']:
+            for klass in [Index, Series]:
+
+                obj = klass([np.nan, 2.0])
+                self.assertEqual(getattr(obj, op)(), 2.0)
+
+                obj = klass([np.nan])
+                self.assertTrue(pd.isnull(getattr(obj, op)()))
+
+                obj = klass([])
+                self.assertTrue(pd.isnull(getattr(obj, op)()))
+
+                obj = klass([pd.NaT, datetime(2011, 11, 1)])
+                # check DatetimeIndex monotonic path
+                self.assertEqual(getattr(obj, op)(), datetime(2011, 11, 1))
+
+                obj = klass([pd.NaT, datetime(2011, 11, 1), pd.NaT])
+                # check DatetimeIndex non-monotonic path
+                self.assertEqual(getattr(obj, op)(), datetime(2011, 11, 1))
+
+            # explicitly create DatetimeIndex
+            obj = DatetimeIndex([])
+            self.assertTrue(pd.isnull(getattr(obj, op)()))
+
+            obj = DatetimeIndex([pd.NaT])
+            self.assertTrue(pd.isnull(getattr(obj, op)()))
+
+            obj = DatetimeIndex([pd.NaT, pd.NaT, pd.NaT])
+            self.assertTrue(pd.isnull(getattr(obj, op)()))
+
+
     def test_value_counts_unique_nunique(self):
         for o in self.objs:
             klass = type(o)