Repository: pydata/xarray

Issue:
[Bug] Reduce fails when no axis given
`DataArray.reduce()` fails if you try to reduce using a function which doesn't accept any axis arguments.

```python
import numpy as np
from xarray import DataArray

da = DataArray(np.array([[1, 3, 3], [2, 1, 5]]))

def total_sum(data):
    return np.sum(data.flatten())

sum = da.reduce(total_sum)
print(sum)
```

This should print a dataarray with just the number 15 in it, but instead it throws the error
```
Traceback (most recent call last):
  File "mwe.py", line 9, in <module>
    sum = da.reduce(total_sum)
  File "/home/tegn500/Documents/Work/Code/xarray/xarray/core/dataarray.py", line 1605, in reduce
    var = self.variable.reduce(func, dim, axis, keep_attrs, **kwargs)
  File "/home/tegn500/Documents/Work/Code/xarray/xarray/core/variable.py", line 1365, in reduce
    axis=axis, **kwargs)
TypeError: total_sum() got an unexpected keyword argument 'axis'
```

This contradicts what the docstring of `.reduce()` says:
```
axis: int or sequence of int, optional
    Axis(es) over which to repeatedly apply func. Only one of the ‘dim’ and ‘axis’ 
    arguments can be supplied. If neither are supplied, then the reduction is 
    calculated over the flattened array (by calling f(x) without an axis argument).
```

The problem is that in `variable.py` an `axis=None` kwarg is always passed to func, even if no axis argument is given by the user in `reduce`.  

Discussion:

Patch:
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 01cdca7a..fb9d9dfa 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -94,6 +94,10 @@ Bug fixes
 - Masking data arrays with :py:meth:`xarray.DataArray.where` now returns an
   array with the name of the original masked array (:issue:`2748` and :issue:`2457`).
   By `Yohai Bar-Sinai <https://github.com/yohai>`_.
+- Fixed error when trying to reduce a DataArray using a function which does not
+  require an axis argument. (:issue:`2768`)
+  By `Tom Nicholas <http://github.com/TomNicholas>`_.
+
 - Per `CF conventions
   <http://cfconventions.org/cf-conventions/cf-conventions.html#calendar>`_,
   specifying ``'standard'`` as the calendar type in
diff --git a/xarray/core/variable.py b/xarray/core/variable.py
index 85eab294..b675317d 100644
--- a/xarray/core/variable.py
+++ b/xarray/core/variable.py
@@ -1361,8 +1361,11 @@ class Variable(common.AbstractArray, arithmetic.SupportsArithmetic,
 
         if dim is not None:
             axis = self.get_axis_num(dim)
-        data = func(self.data if allow_lazy else self.values,
-                    axis=axis, **kwargs)
+        input_data = self.data if allow_lazy else self.values
+        if axis is not None:
+            data = func(input_data, axis=axis, **kwargs)
+        else:
+            data = func(input_data, **kwargs)
 
         if getattr(data, 'shape', ()) == self.shape:
             dims = self.dims


Test patch:
diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py
index e4ffdad4..c26968b1 100644
--- a/xarray/tests/test_dataset.py
+++ b/xarray/tests/test_dataset.py
@@ -3633,11 +3633,28 @@ class TestDataset(object):
         actual = ds.reduce(mean_only_one_axis, 'y')
         assert_identical(expected, actual)
 
-        with raises_regex(TypeError, 'non-integer axis'):
+        with raises_regex(TypeError, "missing 1 required positional argument: "
+                                     "'axis'"):
             ds.reduce(mean_only_one_axis)
 
         with raises_regex(TypeError, 'non-integer axis'):
-            ds.reduce(mean_only_one_axis, ['x', 'y'])
+            ds.reduce(mean_only_one_axis, axis=['x', 'y'])
+
+    def test_reduce_no_axis(self):
+
+        def total_sum(x):
+            return np.sum(x.flatten())
+
+        ds = Dataset({'a': (['x', 'y'], [[0, 1, 2, 3, 4]])})
+        expected = Dataset({'a': ((), 10)})
+        actual = ds.reduce(total_sum)
+        assert_identical(expected, actual)
+
+        with raises_regex(TypeError, "unexpected keyword argument 'axis'"):
+            ds.reduce(total_sum, axis=0)
+
+        with raises_regex(TypeError, "unexpected keyword argument 'axis'"):
+            ds.reduce(total_sum, dim='x')
 
     def test_quantile(self):