Repository: pydata/xarray

Issue:
DataArray.sum does not respect dtype keyword
#### Code Sample, a copy-pastable example if possible

```python
# Your code here
da = xarray.DataArray(arange(5, dtype="i2"))
print(da.sum(dtype="i4").dtype)
```
#### Problem description

The result is int64.  This is a problem because I asked for int32.

#### Expected Output

Expected output `int32`.

#### Output of ``xr.show_versions()``

<details>
# Paste the output here xr.show_versions() here
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.1.final.0
python-bits: 64
OS: Linux
OS-release: 2.6.32-696.6.3.el6.x86_64
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8

xarray: 0.10.0+dev12.gf882a58
pandas: 0.22.0
numpy: 1.14.0
scipy: 1.0.0
netCDF4: 1.3.1
h5netcdf: None
Nio: None
bottleneck: 1.2.1
cyordereddict: None
dask: 0.16.1
matplotlib: 2.1.1
cartopy: 0.15.1
seaborn: 0.8.1
setuptools: 38.4.0
pip: 9.0.1
conda: 4.3.16
pytest: 3.1.2
IPython: 6.1.0
sphinx: 1.6.2

</details>


Discussion:

Patch:
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index ab3d9733..cf0f3fc8 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -27,9 +27,11 @@ Documentation
 
 Enhancements
 ~~~~~~~~~~~~
+- 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`).
   By `Alan Snow <https://github.com/snowman2>`_.
-
 - :py:func:`~plot.contourf()` learned to contour 2D variables that have both a
   1D co-ordinate (e.g. time) and a 2D co-ordinate (e.g. depth as a function of
   time) (:issue:`1737`).
diff --git a/xarray/core/duck_array_ops.py b/xarray/core/duck_array_ops.py
index 2f97de4a..2058ce86 100644
--- a/xarray/core/duck_array_ops.py
+++ b/xarray/core/duck_array_ops.py
@@ -175,11 +175,11 @@ def _create_nan_agg_method(name, numeric_only=False, np_compat=False,
                            no_bottleneck=False, coerce_strings=False,
                            keep_dims=False):
     def f(values, axis=None, skipna=None, **kwargs):
-        # ignore keyword args inserted by np.mean and other numpy aggregators
-        # automatically:
-        kwargs.pop('dtype', None)
-        kwargs.pop('out', None)
+        if kwargs.pop('out', None) is not None:
+            raise TypeError('`out` is not valid for {}'.format(name))
 
+        # If dtype is supplied, we use numpy's method.
+        dtype = kwargs.get('dtype', None)
         values = asarray(values)
 
         if coerce_strings and values.dtype.kind in 'SU':
@@ -192,7 +192,8 @@ def _create_nan_agg_method(name, numeric_only=False, np_compat=False,
                     % (name, values.dtype))
             nanname = 'nan' + name
             if (isinstance(axis, tuple) or not values.dtype.isnative or
-                    no_bottleneck):
+                    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:
@@ -200,6 +201,7 @@ def _create_nan_agg_method(name, numeric_only=False, np_compat=False,
                 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


Test patch:
diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py
index 5524b4c9..39b3109c 100644
--- a/xarray/tests/test_dataarray.py
+++ b/xarray/tests/test_dataarray.py
@@ -1784,6 +1784,24 @@ class TestDataArray(TestCase):
                              dims=['x', 'y']).mean('x')
         assert_equal(actual, expected)
 
+    def test_reduce_dtype(self):
+        coords = {'x': [-1, -2], 'y': ['ab', 'cd', 'ef'],
+                  'lat': (['x', 'y'], [[1, 2, 3], [-1, -2, -3]]),
+                  'c': -999}
+        orig = DataArray([[-1, 0, 1], [-3, 0, 3]], coords, dims=['x', 'y'])
+
+        for dtype in [np.float16, np.float32, np.float64]:
+            assert orig.astype(float).mean(dtype=dtype).dtype == dtype
+
+    def test_reduce_out(self):
+        coords = {'x': [-1, -2], 'y': ['ab', 'cd', 'ef'],
+                  'lat': (['x', 'y'], [[1, 2, 3], [-1, -2, -3]]),
+                  'c': -999}
+        orig = DataArray([[-1, 0, 1], [-3, 0, 3]], coords, dims=['x', 'y'])
+
+        with pytest.raises(TypeError):
+            orig.mean(out=np.ones(orig.shape))
+
     # skip due to bug in older versions of numpy.nanpercentile
     def test_quantile(self):
         for q in [0.25, [0.50], [0.25, 0.75]]: