Repository: numpy/numpy

Issue:
BUG: mixed object arrays and nan functions
The following code produces non-intuitive behavior that I believe may be a bug:
```python
In [1]: import numpy as np

In [2]: import datetime

In [3]: test = np.array([[datetime.datetime(1970, 7, 15, 0, 0), 364.0],
   ...:        [datetime.datetime(2017, 2, 5, 0, 0), np.nan]], dtype=object)

In [4]: np.nanmin(test[:,1])
/Users/pwolfram/anaconda/bin/ipython:1: RuntimeWarning: All-NaN axis encountered
  #!/Users/pwolfram/anaconda/bin/python
Out[4]: nan

In [5]: np.nanmax(test[:,1])
/Users/pwolfram/anaconda/bin/ipython:1: RuntimeWarning: All-NaN slice encountered
  #!/Users/pwolfram/anaconda/bin/python
Out[5]: nan
```

I would expected output from `[4]` and `[5]` to be `364.0`, not `nan`.  Other nan functions, e.g., `nanmean` also appear to be corrupted.  Furthermore, the `RuntimeWarning` is fairly cryptic and doesn't appear to be correct.  

Is there something I'm missing here?  This is not at all what I would expect based on what boils down to a reducing function to a dimension that is effectively a numpy array that is not all `nan`s as apparently interpreted by the `RuntimeWarning`.


Discussion:
On python 3, I get `TypeError: unorderable types: datetime.datetime() <= float()`. `nanmean` gives me a similar error.

Python 2 lets you compare anything to anything else, and get a totally random result, which is why you don't get an error
Apologies, I forgot the slicing. I can replicate this behaviour on 3.5

Geez, seems that `nanmin` is just completely broken on the simplest input:

```python
>>> arr = np.array([np.nan, 1], dtype=np.float64)
>>> np.nanmin(arr)
nan
```

There's a comment in there that says `# Fast, but not safe for subclasses of ndarray`. Seem to me that it should read `# Fast, but not safe ever`...


Edit: not on master
For the record, here is a somewhat hacky fix that is obviously unsavory for several reasons (fairly arbitrary cast, introduction of `if`, increased code):

```python
def safe_nanfunc(data, nanfunc):
    if data.dtype == 'object':
        # cast to float
        data = np.ndarray.astype(data, dtype='f')
    return nanfunc(data)
```
```
```python
In [1]: import numpy as np

In [2]: np.array([np.nan, 1], dtype=np.float64)
Out[2]: array([ nan,   1.])

In [3]: np.nanmin(np.array([np.nan, 1], dtype=np.float64))
Out[3]: 1.0

In [4]: np.nanmax(np.array([np.nan, 1], dtype=np.float64))
Out[4]: 1.0
```

wasn't a problem for me.  I'm on `Python 2.7.13` via conda macOS and 
```
In [5]: np.__version__
Out[5]: '1.12.1'
```
It seems like unit tests should have caught this error, but regardless we have one above now!  @eric-wieser if you agree this is a bug how long would it take to get this pushed into a minor version number if a fix can be found quickly?


I've filed a more specific issue at #8975

I doubt that there'll be a new release of `1.12` in the immediate future, since `1.13` is supposedly right round the corner - and the https://github.com/numpy/numpy/milestone/48 milestone is pretty empty right now.
@eric-wieser, I should note the speed to fix isn't a real problem but more of a curiosity for me.  This is the type of thing I could likely fix but the unfortunate things is I'm under some severe time pressure right now and have a work around via `safe_nanfunc` for my current workflow.
Thanks @eric-wieser for the hints on how to fix the issue.  It sounds like this might be fast to fix but my numpy dev environment is quite stale now.  What is typically the timeline here, e.g., the process to typically get this type of thing fixed?  I can't work on this now but may be able to make some time over the next 60 days or so.
I've split off the `fmin` thing into a new issue (#8975)

There's a quick fix to the nanfunctions which would be to change `nanmin` and friends to take the slow path for object arrays with a simply `dtype == object` check - I'd imagine you could submit that PR directly through the github web interface in around half an hour
A related bug: #6209

Resolving patch:
diff --git a/numpy/lib/nanfunctions.py b/numpy/lib/nanfunctions.py
--- a/numpy/lib/nanfunctions.py
+++ b/numpy/lib/nanfunctions.py
@@ -61,17 +61,19 @@ def _replace_nan(a, val):
         NaNs, otherwise return None.
 
     """
-    is_new = not isinstance(a, np.ndarray)
-    if is_new:
-        a = np.array(a)
-    if not issubclass(a.dtype.type, np.inexact):
-        return a, None
-    if not is_new:
-        # need copy
-        a = np.array(a, subok=True)
-
-    mask = np.isnan(a)
-    np.copyto(a, val, where=mask)
+    a = np.array(a, subok=True, copy=True)
+
+    if a.dtype == np.object_:
+        # object arrays do not support `isnan` (gh-9009), so make a guess
+        mask = a != a
+    elif issubclass(a.dtype.type, np.inexact):
+        mask = np.isnan(a)
+    else:
+        mask = None
+
+    if mask is not None:
+        np.copyto(a, val, where=mask)
+
     return a, mask
 
 
@@ -232,8 +234,9 @@ def nanmin(a, axis=None, out=None, keepdims=np._NoValue):
     kwargs = {}
     if keepdims is not np._NoValue:
         kwargs['keepdims'] = keepdims
-    if not isinstance(a, np.ndarray) or type(a) is np.ndarray:
-        # Fast, but not safe for subclasses of ndarray
+    if type(a) is np.ndarray and a.dtype != np.object_:
+        # Fast, but not safe for subclasses of ndarray, or object arrays,
+        # which do not implement isnan (gh-9009), or fmin correctly (gh-8975)
         res = np.fmin.reduce(a, axis=axis, out=out, **kwargs)
         if np.isnan(res).any():
             warnings.warn("All-NaN axis encountered", RuntimeWarning, stacklevel=2)
@@ -339,8 +342,9 @@ def nanmax(a, axis=None, out=None, keepdims=np._NoValue):
     kwargs = {}
     if keepdims is not np._NoValue:
         kwargs['keepdims'] = keepdims
-    if not isinstance(a, np.ndarray) or type(a) is np.ndarray:
-        # Fast, but not safe for subclasses of ndarray
+    if type(a) is np.ndarray and a.dtype != np.object_:
+        # Fast, but not safe for subclasses of ndarray, or object arrays,
+        # which do not implement isnan (gh-9009), or fmax correctly (gh-8975)
         res = np.fmax.reduce(a, axis=axis, out=out, **kwargs)
         if np.isnan(res).any():
             warnings.warn("All-NaN slice encountered", RuntimeWarning, stacklevel=2)


Test patch:
diff --git a/numpy/lib/tests/test_nanfunctions.py b/numpy/lib/tests/test_nanfunctions.py
--- a/numpy/lib/tests/test_nanfunctions.py
+++ b/numpy/lib/tests/test_nanfunctions.py
@@ -152,6 +152,18 @@ def test_matrices(self):
                 assert_(res != np.nan)
                 assert_(len(w) == 0)
 
+    def test_object_array(self):
+        arr = np.array([[1.0, 2.0], [np.nan, 4.0], [np.nan, np.nan]], dtype=object)
+        assert_equal(np.nanmin(arr), 1.0)
+        assert_equal(np.nanmin(arr, axis=0), [1.0, 2.0])
+
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            # assert_equal does not work on object arrays of nan
+            assert_equal(list(np.nanmin(arr, axis=1)), [1.0, 4.0, np.nan])
+            assert_(len(w) == 1, 'no warning raised')
+            assert_(issubclass(w[0].category, RuntimeWarning))
+
 
 class TestNanFunctions_ArgminArgmax(TestCase):