Repository: pydata/xarray

Issue:
zarr RTD docs broken
This is what is getting rendered on RTD 
http://xarray.pydata.org/en/latest/io.html#zarr

```
In [26]: ds = xr.Dataset({'foo': (('x', 'y'), np.random.rand(4, 5))},
   ....:                 coords={'x': [10, 20, 30, 40],
   ....:                         'y': pd.date_range('2000-01-01', periods=5),
   ....:                         'z': ('x', list('abcd'))})
   ....: 

In [27]: ds.to_zarr('path/to/directory.zarr')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-27-8c5f1b00edbc> in <module>()
----> 1 ds.to_zarr('path/to/directory.zarr')

/home/docs/checkouts/readthedocs.org/user_builds/xray/conda/latest/lib/python3.5/site-packages/xarray-0.10.0+dev55.g1d32399-py3.5.egg/xarray/core/dataset.py in to_zarr(self, store, mode, synchronizer, group, encoding)
   1165         from ..backends.api import to_zarr
   1166         return to_zarr(self, store=store, mode=mode, synchronizer=synchronizer,
-> 1167                        group=group, encoding=encoding)
   1168 
   1169     def __unicode__(self):

/home/docs/checkouts/readthedocs.org/user_builds/xray/conda/latest/lib/python3.5/site-packages/xarray-0.10.0+dev55.g1d32399-py3.5.egg/xarray/backends/api.py in to_zarr(dataset, store, mode, synchronizer, group, encoding)
    752     # I think zarr stores should always be sync'd immediately
    753     # TODO: figure out how to properly handle unlimited_dims
--> 754     dataset.dump_to_store(store, sync=True, encoding=encoding)
    755     return store

/home/docs/checkouts/readthedocs.org/user_builds/xray/conda/latest/lib/python3.5/site-packages/xarray-0.10.0+dev55.g1d32399-py3.5.egg/xarray/core/dataset.py in dump_to_store(self, store, encoder, sync, encoding, unlimited_dims)
   1068 
   1069         store.store(variables, attrs, check_encoding,
-> 1070                     unlimited_dims=unlimited_dims)
   1071         if sync:
   1072             store.sync()

/home/docs/checkouts/readthedocs.org/user_builds/xray/conda/latest/lib/python3.5/site-packages/xarray-0.10.0+dev55.g1d32399-py3.5.egg/xarray/backends/zarr.py in store(self, variables, attributes, *args, **kwargs)
    378     def store(self, variables, attributes, *args, **kwargs):
    379         AbstractWritableDataStore.store(self, variables, attributes,
--> 380                                         *args, **kwargs)
    381 
    382 

/home/docs/checkouts/readthedocs.org/user_builds/xray/conda/latest/lib/python3.5/site-packages/xarray-0.10.0+dev55.g1d32399-py3.5.egg/xarray/backends/common.py in store(self, variables, attributes, check_encoding_set, unlimited_dims)
    275         variables, attributes = self.encode(variables, attributes)
    276 
--> 277         self.set_attributes(attributes)
    278         self.set_dimensions(variables, unlimited_dims=unlimited_dims)
    279         self.set_variables(variables, check_encoding_set,

/home/docs/checkouts/readthedocs.org/user_builds/xray/conda/latest/lib/python3.5/site-packages/xarray-0.10.0+dev55.g1d32399-py3.5.egg/xarray/backends/zarr.py in set_attributes(self, attributes)
    341 
    342     def set_attributes(self, attributes):
--> 343         self.ds.attrs.put(attributes)
    344 
    345     def encode_variable(self, variable):

AttributeError: 'Attributes' object has no attribute 'put'
```

Discussion:
jhamman: I think we've talked about doing this a number of times in the past. It seems that we've been running into a number of uncaught build failures (e.g. #1898, #1881). This is one way of testing that the documentation build will pass on read the docs. If someone has a better of idea of how to do this, I'm all ears.
shoyer: This is really good idea, thanks!
shoyer: The build is failing with an error message about upgrading ruby?!?
jhamman: Well isn't that nice. CI is a two edged sword! I'll take a look.
shoyer: It looks like the doc build fails when it hits a warning:
```
Warning, treated as error:
/home/travis/build/pydata/xarray/doc/api.rst:1: WARNING: Inline emphasis start-string without end-string.
```

We should either fix these warnings or stop them from failing the build :)
fmaussion: > We should either fix these warnings or stop them from failing the build :)

It's really hard to get rid of all of them - I'd rather try option B ;)
fmaussion: Currently we have 106 warnings at doc build on my system
jhamman: I had the fail-on-warning option turned on. I agree we should drop that for now. After we merge this, I'll open an issue to track cleaning those up at some point. I don't have time to fix them right now. 

Patch:
diff --git a/.travis.yml b/.travis.yml
index 068ea3cc..ee8ffcc4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -45,6 +45,8 @@ matrix:
     env: CONDA_ENV=py36-rasterio1.0alpha
   - python: 3.6
     env: CONDA_ENV=py36-zarr-dev
+  - python: 3.5
+    env: CONDA_ENV=docs
   allow_failures:
   - python: 3.6
     env:
@@ -86,16 +88,25 @@ before_install:
   - conda info -a
 
 install:
-  - conda env create --file ci/requirements-$CONDA_ENV.yml
+  - if [[ "$CONDA_ENV" == "docs" ]]; then
+      conda env create -n test_env --file doc/environment.yml;
+    else
+      conda env create -n test_env --file ci/requirements-$CONDA_ENV.yml;
+    fi
   - source activate test_env
   - conda list
-  - python setup.py install
+  - pip install --no-deps -e .
   - python xarray/util/print_versions.py
 
 script:
   - flake8 -j auto xarray
   - python -OO -c "import xarray"
-  - py.test xarray --cov=xarray --cov-config ci/.coveragerc --cov-report term-missing --verbose $EXTRA_FLAGS
+  - if [[ "$CONDA_ENV" == "docs" ]]; then
+      conda install -c conda-forge sphinx_rtd_theme;
+      sphinx-build -n -b html -d _build/doctrees doc _build/html;
+    else
+      py.test xarray --cov=xarray --cov-config ci/.coveragerc --cov-report term-missing --verbose $EXTRA_FLAGS;
+    fi
 
 after_success:
   - coveralls
diff --git a/doc/environment.yml b/doc/environment.yml
index b14fba35..2758612c 100644
--- a/doc/environment.yml
+++ b/doc/environment.yml
@@ -18,3 +18,4 @@ dependencies:
   - sphinx-gallery
   - zarr
   - iris
+  - flake8
diff --git a/doc/installing.rst b/doc/installing.rst
index b6ec2bd8..b9a1fff5 100644
--- a/doc/installing.rst
+++ b/doc/installing.rst
@@ -78,6 +78,7 @@ Testing
 -------
 
 To run the test suite after installing xarray, first install (via pypi or conda)
+
 - `py.test <https://pytest.org>`__: Simple unit testing library
 - `mock <https://pypi.python.org/pypi/mock>`__: additional testing library required for python version 2
 
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 0a3f949b..bc5a5bb5 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -43,7 +43,16 @@ Documentation
 
 Enhancements
 ~~~~~~~~~~~~
-- reduce methods such as :py:func:`DataArray.sum()` now accepts ``dtype``
+- Reduce methods such as :py:func:`DataArray.sum()` now handles object-type array.
+
+  .. ipython:: python
+
+    da = xr.DataArray(np.array([True, False, np.nan], dtype=object), dims='x')
+    da.sum()
+
+  (:issue:`1866`)
+  By `Keisuke Fujii <https://github.com/fujiisoup>`_.
+- Reduce methods such as :py:func:`DataArray.sum()` now accepts ``dtype``
   arguments. (:issue:`1838`)
   By `Keisuke Fujii <https://github.com/fujiisoup>`_.
 - Added nodatavals attribute to DataArray when using :py:func:`~xarray.open_rasterio`. (:issue:`1736`).
diff --git a/xarray/core/dtypes.py b/xarray/core/dtypes.py
index e811d189..8dac3961 100644
--- a/xarray/core/dtypes.py
+++ b/xarray/core/dtypes.py
@@ -1,4 +1,5 @@
 import numpy as np
+import functools
 
 from . import utils
 
@@ -7,6 +8,29 @@ from . import utils
 NA = utils.ReprObject('<NA>')
 
 
+@functools.total_ordering
+class AlwaysGreaterThan(object):
+    def __gt__(self, other):
+        return True
+
+    def __eq__(self, other):
+        return isinstance(other, type(self))
+
+
+@functools.total_ordering
+class AlwaysLessThan(object):
+    def __lt__(self, other):
+        return True
+
+    def __eq__(self, other):
+        return isinstance(other, type(self))
+
+
+# Equivalence to np.inf (-np.inf) for object-type
+INF = AlwaysGreaterThan()
+NINF = AlwaysLessThan()
+
+
 # Pairs of types that, if both found, should be promoted to object dtype
 # instead of following NumPy's own type-promotion rules. These type promotion
 # rules match pandas instead. For reference, see the NumPy type hierarchy:
@@ -18,6 +42,29 @@ PROMOTE_TO_OBJECT = [
 ]
 
 
+@functools.total_ordering
+class AlwaysGreaterThan(object):
+    def __gt__(self, other):
+        return True
+
+    def __eq__(self, other):
+        return isinstance(other, type(self))
+
+
+@functools.total_ordering
+class AlwaysLessThan(object):
+    def __lt__(self, other):
+        return True
+
+    def __eq__(self, other):
+        return isinstance(other, type(self))
+
+
+# Equivalence to np.inf (-np.inf) for object-type
+INF = AlwaysGreaterThan()
+NINF = AlwaysLessThan()
+
+
 def maybe_promote(dtype):
     """Simpler equivalent of pandas.core.common._maybe_promote
 
@@ -66,6 +113,46 @@ def get_fill_value(dtype):
     return fill_value
 
 
+def get_pos_infinity(dtype):
+    """Return an appropriate positive infinity for this dtype.
+
+    Parameters
+    ----------
+    dtype : np.dtype
+
+    Returns
+    -------
+    fill_value : positive infinity value corresponding to this dtype.
+    """
+    if issubclass(dtype.type, (np.floating, np.integer)):
+        return np.inf
+
+    if issubclass(dtype.type, np.complexfloating):
+        return np.inf + 1j * np.inf
+
+    return INF
+
+
+def get_neg_infinity(dtype):
+    """Return an appropriate positive infinity for this dtype.
+
+    Parameters
+    ----------
+    dtype : np.dtype
+
+    Returns
+    -------
+    fill_value : positive infinity value corresponding to this dtype.
+    """
+    if issubclass(dtype.type, (np.floating, np.integer)):
+        return -np.inf
+
+    if issubclass(dtype.type, np.complexfloating):
+        return -np.inf - 1j * np.inf
+
+    return NINF
+
+
 def is_datetime_like(dtype):
     """Check if a dtype is a subclass of the numpy datetime types
     """
diff --git a/xarray/core/duck_array_ops.py b/xarray/core/duck_array_ops.py
index 3e9a2e6d..6f554880 100644
--- a/xarray/core/duck_array_ops.py
+++ b/xarray/core/duck_array_ops.py
@@ -197,6 +197,79 @@ def _ignore_warnings_if(condition):
         yield
 
 
+def _nansum_object(value, axis=None, **kwargs):
+    """ In house nansum for object array """
+    value = fillna(value, 0)
+    return _dask_or_eager_func('sum')(value, axis=axis, **kwargs)
+
+
+def _nan_minmax_object(func, get_fill_value, value, axis=None, **kwargs):
+    """ In house nanmin and nanmax for object array """
+    fill_value = get_fill_value(value.dtype)
+    valid_count = count(value, axis=axis)
+    filled_value = fillna(value, fill_value)
+    data = _dask_or_eager_func(func)(filled_value, axis=axis, **kwargs)
+    if not hasattr(data, 'dtype'):  # scalar case
+        data = dtypes.fill_value(value.dtype) if valid_count == 0 else data
+        return np.array(data, dtype=value.dtype)
+    return where_method(data, valid_count != 0)
+
+
+def _nan_argminmax_object(func, get_fill_value, value, axis=None, **kwargs):
+    """ In house nanargmin, nanargmax for object arrays. Always return integer
+    type """
+    fill_value = get_fill_value(value.dtype)
+    valid_count = count(value, axis=axis)
+    value = fillna(value, fill_value)
+    data = _dask_or_eager_func(func)(value, axis=axis, **kwargs)
+    # dask seems return non-integer type
+    if isinstance(value, dask_array_type):
+        data = data.astype(int)
+
+    if (valid_count == 0).any():
+        raise ValueError('All-NaN slice encountered')
+
+    return np.array(data, dtype=int)
+
+
+def _nanmean_ddof_object(ddof, value, axis=None, **kwargs):
+    """ In house nanmean. ddof argument will be used in _nanvar method """
+    valid_count = count(value, axis=axis)
+    value = fillna(value, 0)
+    # As dtype inference is impossible for object dtype, we assume float
+    # https://github.com/dask/dask/issues/3162
+    dtype = kwargs.pop('dtype', None)
+    if dtype is None and value.dtype.kind == 'O':
+        dtype = value.dtype if value.dtype.kind in ['cf'] else float
+
+    data = _dask_or_eager_func('sum')(value, axis=axis, dtype=dtype, **kwargs)
+    data = data / (valid_count - ddof)
+    return where_method(data, valid_count != 0)
+
+
+def _nanvar_object(value, axis=None, **kwargs):
+    ddof = kwargs.pop('ddof', 0)
+    kwargs_mean = kwargs.copy()
+    kwargs_mean.pop('keepdims', None)
+    value_mean = _nanmean_ddof_object(ddof=0, value=value, axis=axis,
+                                      keepdims=True, **kwargs_mean)
+    squared = (value.astype(value_mean.dtype) - value_mean)**2
+    return _nanmean_ddof_object(ddof, squared, axis=axis, **kwargs)
+
+
+_nan_object_funcs = {
+    'sum': _nansum_object,
+    'min': partial(_nan_minmax_object, 'min', dtypes.get_pos_infinity),
+    'max': partial(_nan_minmax_object, 'max', dtypes.get_neg_infinity),
+    'argmin': partial(_nan_argminmax_object, 'argmin',
+                      dtypes.get_pos_infinity),
+    'argmax': partial(_nan_argminmax_object, 'argmax',
+                      dtypes.get_neg_infinity),
+    'mean': partial(_nanmean_ddof_object, 0),
+    'var': _nanvar_object,
+}
+
+
 def _create_nan_agg_method(name, numeric_only=False, np_compat=False,
                            no_bottleneck=False, coerce_strings=False,
                            keep_dims=False):
@@ -211,27 +284,31 @@ def _create_nan_agg_method(name, numeric_only=False, np_compat=False,
         if coerce_strings and values.dtype.kind in 'SU':
             values = values.astype(object)
 
-        if skipna or (skipna is None and values.dtype.kind in 'cf'):
+        if skipna or (skipna is None and values.dtype.kind in 'cfO'):
             if values.dtype.kind not in ['u', 'i', 'f', 'c']:
-                raise NotImplementedError(
-                    'skipna=True not yet implemented for %s with dtype %s'
-                    % (name, values.dtype))
-            nanname = 'nan' + name
-            if (isinstance(axis, tuple) or not values.dtype.isnative or
-                    no_bottleneck or
-                    (dtype is not None and np.dtype(dtype) != values.dtype)):
-                # bottleneck can't handle multiple axis arguments or non-native
-                # endianness
-                if np_compat:
-                    eager_module = npcompat
-                else:
-                    eager_module = np
+                func = _nan_object_funcs.get(name, None)
+                using_numpy_nan_func = True
+                if func is None or values.dtype.kind not in 'Ob':
+                    raise NotImplementedError(
+                        'skipna=True not yet implemented for %s with dtype %s'
+                        % (name, values.dtype))
             else:
-                kwargs.pop('dtype', None)
-                eager_module = bn
-            func = _dask_or_eager_func(nanname, eager_module)
-            using_numpy_nan_func = (eager_module is np or
-                                    eager_module is npcompat)
+                nanname = 'nan' + name
+                if (isinstance(axis, tuple) or not values.dtype.isnative or
+                        no_bottleneck or (dtype is not None and
+                                          np.dtype(dtype) != values.dtype)):
+                    # bottleneck can't handle multiple axis arguments or
+                    # non-native endianness
+                    if np_compat:
+                        eager_module = npcompat
+                    else:
+                        eager_module = np
+                else:
+                    kwargs.pop('dtype', None)
+                    eager_module = bn
+                func = _dask_or_eager_func(nanname, eager_module)
+                using_numpy_nan_func = (eager_module is np or
+                                        eager_module is npcompat)
         else:
             func = _dask_or_eager_func(name)
             using_numpy_nan_func = False
@@ -240,7 +317,11 @@ def _create_nan_agg_method(name, numeric_only=False, np_compat=False,
                 return func(values, axis=axis, **kwargs)
             except AttributeError:
                 if isinstance(values, dask_array_type):
-                    msg = '%s is not yet implemented on dask arrays' % name
+                    try:  # dask/dask#3133 dask sometimes needs dtype argument
+                        return func(values, axis=axis, dtype=values.dtype,
+                                    **kwargs)
+                    except AttributeError:
+                        msg = '%s is not yet implemented on dask arrays' % name
                 else:
                     assert using_numpy_nan_func
                     msg = ('%s is not available with skipna=False with the '


Test patch:
diff --git a/xarray/tests/test_dtypes.py b/xarray/tests/test_dtypes.py
index 51c1aaa4..1b236e01 100644
--- a/xarray/tests/test_dtypes.py
+++ b/xarray/tests/test_dtypes.py
@@ -46,3 +46,9 @@ def test_result_type_dask_array():
     # would get promoted to float32
     actual = dtypes.result_type(array, np.array([0.5, 1.0], dtype=np.float32))
     assert actual == np.float64
+
+
+@pytest.mark.parametrize('obj', [1.0, np.inf, 'ab', 1.0 + 1.0j, True])
+def test_inf(obj):
+    assert dtypes.INF > obj
+    assert dtypes.NINF < obj
diff --git a/xarray/tests/test_duck_array_ops.py b/xarray/tests/test_duck_array_ops.py
index 54eaf46e..d68a7a38 100644
--- a/xarray/tests/test_duck_array_ops.py
+++ b/xarray/tests/test_duck_array_ops.py
@@ -1,15 +1,19 @@
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
-from pytest import mark
+import pytest
 import numpy as np
 from numpy import array, nan
+from distutils.version import LooseVersion
 from . import assert_array_equal
 from xarray.core.duck_array_ops import (
     first, last, count, mean, array_notnull_equiv, where, stack, concatenate
 )
+from xarray import DataArray
+from xarray.testing import assert_allclose
+from xarray import concat
 
-from . import TestCase, raises_regex
+from . import TestCase, raises_regex, has_dask
 
 
 class TestOps(TestCase):
@@ -97,7 +101,7 @@ class TestOps(TestCase):
 
 
 class TestArrayNotNullEquiv():
-    @mark.parametrize("arr1, arr2", [
+    @pytest.mark.parametrize("arr1, arr2", [
         (np.array([1, 2, 3]), np.array([1, 2, 3])),
         (np.array([1, 2, np.nan]), np.array([1, np.nan, 3])),
         (np.array([np.nan, 2, np.nan]), np.array([1, np.nan, np.nan])),
@@ -115,7 +119,7 @@ class TestArrayNotNullEquiv():
         b = np.array([[1, 2], [np.nan, 4]])
         assert not array_notnull_equiv(a, b)
 
-    @mark.parametrize("val1, val2, val3, null", [
+    @pytest.mark.parametrize("val1, val2, val3, null", [
         (1, 2, 3, None),
         (1., 2., 3., np.nan),
         (1., 2., 3., None),
@@ -125,3 +129,175 @@ class TestArrayNotNullEquiv():
         arr1 = np.array([val1, null, val3, null])
         arr2 = np.array([val1, val2, null, null])
         assert array_notnull_equiv(arr1, arr2)
+
+
+def construct_dataarray(dim_num, dtype, contains_nan, dask):
+    # dimnum <= 3
+    rng = np.random.RandomState(0)
+    shapes = [16, 8, 4][:dim_num]
+    dims = ('x', 'y', 'z')[:dim_num]
+
+    if np.issubdtype(dtype, np.floating):
+        array = rng.randn(*shapes).astype(dtype)
+    elif np.issubdtype(dtype, np.integer):
+        array = rng.randint(0, 10, size=shapes).astype(dtype)
+    elif np.issubdtype(dtype, np.bool_):
+        array = rng.randint(0, 1, size=shapes).astype(dtype)
+    elif dtype == str:
+        array = rng.choice(['a', 'b', 'c', 'd'], size=shapes)
+    else:
+        raise ValueError
+    da = DataArray(array, dims=dims, coords={'x': np.arange(16)}, name='da')
+
+    if contains_nan:
+        da = da.reindex(x=np.arange(20))
+    if dask and has_dask:
+        chunks = {d: 4 for d in dims}
+        da = da.chunk(chunks)
+
+    return da
+
+
+def from_series_or_scalar(se):
+    try:
+        return DataArray.from_series(se)
+    except AttributeError:  # scalar case
+        return DataArray(se)
+
+
+def series_reduce(da, func, dim, **kwargs):
+    """ convert DataArray to pd.Series, apply pd.func, then convert back to
+    a DataArray. Multiple dims cannot be specified."""
+    if dim is None or da.ndim == 1:
+        se = da.to_series()
+        return from_series_or_scalar(getattr(se, func)(**kwargs))
+    else:
+        da1 = []
+        dims = list(da.dims)
+        dims.remove(dim)
+        d = dims[0]
+        for i in range(len(da[d])):
+            da1.append(series_reduce(da.isel(**{d: i}), func, dim, **kwargs))
+
+        if d in da.coords:
+            return concat(da1, dim=da[d])
+        return concat(da1, dim=d)
+
+
+@pytest.mark.parametrize('dim_num', [1, 2])
+@pytest.mark.parametrize('dtype', [float, int, np.float32, np.bool_])
+@pytest.mark.parametrize('dask', [False, True])
+@pytest.mark.parametrize('func', ['sum', 'min', 'max', 'mean', 'var'])
+@pytest.mark.parametrize('skipna', [False, True])
+@pytest.mark.parametrize('aggdim', [None, 'x'])
+def test_reduce(dim_num, dtype, dask, func, skipna, aggdim):
+
+    if aggdim == 'y' and dim_num < 2:
+        return
+
+    if dtype == np.bool_ and func == 'mean':
+        return  # numpy does not support this
+
+    if dask and not has_dask:
+        return
+
+    rtol = 1e-04 if dtype == np.float32 else 1e-05
+
+    da = construct_dataarray(dim_num, dtype, contains_nan=True, dask=dask)
+    axis = None if aggdim is None else da.get_axis_num(aggdim)
+
+    if dask and not skipna and func in ['var', 'std'] and dtype == np.bool_:
+        # TODO this might be dask's bug
+        return
+
+    if (LooseVersion(np.__version__) >= LooseVersion('1.13.0') and
+            da.dtype.kind == 'O' and skipna):
+        # Numpy < 1.13 does not handle object-type array.
+        try:
+            if skipna:
+                expected = getattr(np, 'nan{}'.format(func))(da.values,
+                                                             axis=axis)
+            else:
+                expected = getattr(np, func)(da.values, axis=axis)
+
+            actual = getattr(da, func)(skipna=skipna, dim=aggdim)
+            assert np.allclose(actual.values, np.array(expected), rtol=1.0e-4,
+                               equal_nan=True)
+        except (TypeError, AttributeError, ZeroDivisionError):
+            # TODO currently, numpy does not support some methods such as
+            # nanmean for object dtype
+            pass
+
+    # make sure the compatiblility with pandas' results.
+    actual = getattr(da, func)(skipna=skipna, dim=aggdim)
+    if func == 'var':
+        expected = series_reduce(da, func, skipna=skipna, dim=aggdim, ddof=0)
+        assert_allclose(actual, expected, rtol=rtol)
+        # also check ddof!=0 case
+        actual = getattr(da, func)(skipna=skipna, dim=aggdim, ddof=5)
+        expected = series_reduce(da, func, skipna=skipna, dim=aggdim, ddof=5)
+        assert_allclose(actual, expected, rtol=rtol)
+    else:
+        expected = series_reduce(da, func, skipna=skipna, dim=aggdim)
+        assert_allclose(actual, expected, rtol=rtol)
+
+    # make sure the dtype argument
+    if func not in ['max', 'min']:
+        actual = getattr(da, func)(skipna=skipna, dim=aggdim, dtype=float)
+        assert actual.dtype == float
+
+    # without nan
+    da = construct_dataarray(dim_num, dtype, contains_nan=False, dask=dask)
+    actual = getattr(da, func)(skipna=skipna)
+    expected = getattr(np, 'nan{}'.format(func))(da.values)
+    if actual.dtype == object:
+        assert actual.values == np.array(expected)
+    else:
+        assert np.allclose(actual.values, np.array(expected), rtol=rtol)
+
+
+@pytest.mark.parametrize('dim_num', [1, 2])
+@pytest.mark.parametrize('dtype', [float, int, np.float32, np.bool_, str])
+@pytest.mark.parametrize('contains_nan', [True, False])
+@pytest.mark.parametrize('dask', [False, True])
+@pytest.mark.parametrize('func', ['min', 'max'])
+@pytest.mark.parametrize('skipna', [False, True])
+@pytest.mark.parametrize('aggdim', ['x', 'y'])
+def test_argmin_max(dim_num, dtype, contains_nan, dask, func, skipna, aggdim):
+    # pandas-dev/pandas#16830, we do not check consistency with pandas but
+    # just make sure da[da.argmin()] == da.min()
+
+    if aggdim == 'y' and dim_num < 2:
+        return
+
+    if dask and not has_dask:
+        return
+
+    if contains_nan:
+        if not skipna:
+            # numpy's argmin (not nanargmin) does not handle object-dtype
+            return
+        if skipna and np.dtype(dtype).kind in 'iufc':
+            # numpy's nanargmin raises ValueError for all nan axis
+            return
+
+    da = construct_dataarray(dim_num, dtype, contains_nan=contains_nan,
+                             dask=dask)
+
+    if aggdim == 'y' and contains_nan and skipna:
+        with pytest.raises(ValueError):
+            actual = da.isel(**{
+                aggdim: getattr(da, 'arg'+func)(dim=aggdim,
+                                                skipna=skipna).compute()})
+        return
+
+    actual = da.isel(**{
+        aggdim: getattr(da, 'arg'+func)(dim=aggdim, skipna=skipna).compute()})
+    expected = getattr(da, func)(dim=aggdim, skipna=skipna)
+    assert_allclose(actual.drop(actual.coords), expected.drop(expected.coords))
+
+
+def test_argmin_max_error():
+    da = construct_dataarray(2, np.bool_, contains_nan=True, dask=False)
+    with pytest.raises(ValueError):
+        da.argmin(dim='y')