Repository: pandas-dev/pandas

Issue:
.min() on a series of NaTs returns nan, while .max() returns NaT
#### Code Sample

```python
s = pd.Series([pd.NaT, pd.NaT])

# Returns nan
s.min()

# Returns NaT
s.max()

```
#### Problem description

Calling `.min()` on an series of `NaT` returns `nan`. I would expect this to return `NaT` to as to keep the type the same. Also, whether it returns `nan` or `NaT`, I would expect it to be consistent with `.max()`. I'm on version `0.22`, not `0.23`, so maybe it was fixed in the latest version, but I didn't see an issue addressing it.

#### Expected Output

I would expect that the `s.min()` call would also return `NaT`.

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.4.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 45 Stepping 7, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.22.0
pytest: None
pip: 10.0.1
setuptools: 40.2.0
Cython: None
numpy: 1.15.1
scipy: 1.1.0
pyarrow: None
xarray: None
IPython: 6.5.0
sphinx: None
patsy: None
dateutil: 2.7.3
pytz: 2018.5
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: 1.0.1
sqlalchemy: 1.2.11
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>



Discussion:
Thanks for the report. I am seeing this behavior on master too. Also looks similar to https://github.com/pandas-dev/pandas/issues/10390.

Investigations and PRs welcome!

Resolving patch:
diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py
--- a/pandas/core/nanops.py
+++ b/pandas/core/nanops.py
@@ -244,7 +244,7 @@ def _get_values(values, skipna, fill_value=None, fill_value_typ=None,
     elif is_float_dtype(dtype):
         dtype_max = np.float64
 
-    return values, mask, dtype, dtype_max
+    return values, mask, dtype, dtype_max, fill_value
 
 
 def _isfinite(values):
@@ -266,16 +266,21 @@ def _view_if_needed(values):
     return values
 
 
-def _wrap_results(result, dtype):
+def _wrap_results(result, dtype, fill_value=None):
     """ wrap our results if needed """
 
     if is_datetime64_dtype(dtype):
         if not isinstance(result, np.ndarray):
+            assert not isna(fill_value), "Expected non-null fill_value"
+            if result == fill_value:
+                result = np.nan
             result = tslibs.Timestamp(result)
         else:
             result = result.view(dtype)
     elif is_timedelta64_dtype(dtype):
         if not isinstance(result, np.ndarray):
+            if result == fill_value:
+                result = np.nan
 
             # raise if we have a timedelta64[ns] which is too large
             if np.fabs(result) > _int64_max:
@@ -346,8 +351,8 @@ def nanany(values, axis=None, skipna=True, mask=None):
     >>> nanops.nanany(s)
     False
     """
-    values, mask, dtype, _ = _get_values(values, skipna, False, copy=skipna,
-                                         mask=mask)
+    values, mask, dtype, _, _ = _get_values(values, skipna, False, copy=skipna,
+                                            mask=mask)
     return values.any(axis)
 
 
@@ -379,8 +384,8 @@ def nanall(values, axis=None, skipna=True, mask=None):
     >>> nanops.nanall(s)
     False
     """
-    values, mask, dtype, _ = _get_values(values, skipna, True, copy=skipna,
-                                         mask=mask)
+    values, mask, dtype, _, _ = _get_values(values, skipna, True, copy=skipna,
+                                            mask=mask)
     return values.all(axis)
 
 
@@ -409,7 +414,8 @@ def nansum(values, axis=None, skipna=True, min_count=0, mask=None):
     >>> nanops.nansum(s)
     3.0
     """
-    values, mask, dtype, dtype_max = _get_values(values, skipna, 0, mask=mask)
+    values, mask, dtype, dtype_max, _ = _get_values(values,
+                                                    skipna, 0, mask=mask)
     dtype_sum = dtype_max
     if is_float_dtype(dtype):
         dtype_sum = dtype
@@ -448,7 +454,8 @@ def nanmean(values, axis=None, skipna=True, mask=None):
     >>> nanops.nanmean(s)
     1.5
     """
-    values, mask, dtype, dtype_max = _get_values(values, skipna, 0, mask=mask)
+    values, mask, dtype, dtype_max, _ = _get_values(
+        values, skipna, 0, mask=mask)
     dtype_sum = dtype_max
     dtype_count = np.float64
     if is_integer_dtype(dtype) or is_timedelta64_dtype(dtype):
@@ -501,7 +508,7 @@ def get_median(x):
             return np.nan
         return np.nanmedian(x[mask])
 
-    values, mask, dtype, dtype_max = _get_values(values, skipna, mask=mask)
+    values, mask, dtype, dtype_max, _ = _get_values(values, skipna, mask=mask)
     if not is_float_dtype(values):
         values = values.astype('f8')
         values[mask] = np.nan
@@ -705,7 +712,8 @@ def nansem(values, axis=None, skipna=True, ddof=1, mask=None):
 def _nanminmax(meth, fill_value_typ):
     @bottleneck_switch()
     def reduction(values, axis=None, skipna=True, mask=None):
-        values, mask, dtype, dtype_max = _get_values(
+
+        values, mask, dtype, dtype_max, fill_value = _get_values(
             values, skipna, fill_value_typ=fill_value_typ, mask=mask)
 
         if ((axis is not None and values.shape[axis] == 0) or
@@ -719,7 +727,7 @@ def reduction(values, axis=None, skipna=True, mask=None):
         else:
             result = getattr(values, meth)(axis)
 
-        result = _wrap_results(result, dtype)
+        result = _wrap_results(result, dtype, fill_value)
         return _maybe_null_out(result, axis, mask)
 
     reduction.__name__ = 'nan' + meth
@@ -753,8 +761,8 @@ def nanargmax(values, axis=None, skipna=True, mask=None):
     >>> nanops.nanargmax(s)
     4
     """
-    values, mask, dtype, _ = _get_values(values, skipna, fill_value_typ='-inf',
-                                         mask=mask)
+    values, mask, dtype, _, _ = _get_values(
+        values, skipna, fill_value_typ='-inf', mask=mask)
     result = values.argmax(axis)
     result = _maybe_arg_null_out(result, axis, mask, skipna)
     return result
@@ -783,8 +791,8 @@ def nanargmin(values, axis=None, skipna=True, mask=None):
     >>> nanops.nanargmin(s)
     0
     """
-    values, mask, dtype, _ = _get_values(values, skipna, fill_value_typ='+inf',
-                                         mask=mask)
+    values, mask, dtype, _, _ = _get_values(
+        values, skipna, fill_value_typ='+inf', mask=mask)
     result = values.argmin(axis)
     result = _maybe_arg_null_out(result, axis, mask, skipna)
     return result


Test patch:
diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py
--- a/pandas/tests/series/test_datetime_values.py
+++ b/pandas/tests/series/test_datetime_values.py
@@ -510,3 +510,21 @@ def test_dt_timetz_accessor(self, tz_naive_fixture):
                            time(22, 14, tzinfo=tz)])
         result = s.dt.timetz
         tm.assert_series_equal(result, expected)
+
+    @pytest.mark.parametrize('nat', [
+        pd.Series([pd.NaT, pd.NaT]),
+        pd.Series([pd.NaT, pd.Timedelta('nat')]),
+        pd.Series([pd.Timedelta('nat'), pd.Timedelta('nat')])])
+    def test_minmax_nat_series(self, nat):
+        # GH 23282
+        assert nat.min() is pd.NaT
+        assert nat.max() is pd.NaT
+
+    @pytest.mark.parametrize('nat', [
+        # GH 23282
+        pd.DataFrame([pd.NaT, pd.NaT]),
+        pd.DataFrame([pd.NaT, pd.Timedelta('nat')]),
+        pd.DataFrame([pd.Timedelta('nat'), pd.Timedelta('nat')])])
+    def test_minmax_nat_dataframe(self, nat):
+        assert nat.min()[0] is pd.NaT
+        assert nat.max()[0] is pd.NaT