Repository: dask/dask

Issue:
dask.array aggregation results compute as scalars, not 0d arrays
On Python 2.7:

```
In [11]: da.mean(da.ones(4, chunks=4), axis=0)
Out[11]: dask.array<x_16, shape=(), chunks=(), dtype=float64>

In [9]: type(da.mean(da.ones(4, chunks=4), axis=0).compute())
Out[9]: numpy.float64
```

On Python 3.4:

```
In [4]: type(da.mean(da.ones(4, chunks=4), axis=0).compute())
Out[4]: float
```

For consistency between the pre- and post-computed results, these should arguably both be 0-dimensional numpy arrays, because there are no dask.array scalars (maybe there should be?). At the very least, the result should be the same type on both Python versions.

Why this is a problem? `float` cannot be indexed with `None`, which is used in `da.stack`:

```
# python 3.4
In [14]: x = da.ones(4, chunks=4)

In [15]: da.stack([x.mean(), x.mean()])
Out[15]: dask.array<stack-6, shape=(2,), chunks=((1, 1)), dtype=float64>

In [16]: da.stack([x.mean(), x.mean()]).compute()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-52554981f590> in <module>()
----> 1 da.stack([x.mean(), x.mean()]).compute()

/Users/shoyer/dev/dask/dask/array/core.py in compute(self, **kwargs)
    721     @wraps(compute)
    722     def compute(self, **kwargs):
--> 723         result, = compute(self, **kwargs)
    724         return result
    725

/Users/shoyer/dev/dask/dask/array/core.py in compute(*args, **kwargs)
    511     dsk = merge(*[arg.dask for arg in args])
    512     keys = [arg._keys() for arg in args]
--> 513     results = get(dsk, keys, **kwargs)
    514
    515     results2 = tuple(concatenate3(x) if arg.shape else unpack_singleton(x)

/Users/shoyer/dev/dask/dask/array/core.py in get(dsk, keys, get, **kwargs)
   1242     get = get or _globals['get'] or threaded.get
   1243     dsk2 = optimize(dsk, keys, **kwargs)
-> 1244     return get(dsk2, keys, **kwargs)
   1245
   1246

/Users/shoyer/dev/dask/dask/threaded.py in get(dsk, result, cache, **kwargs)
     52     results = get_async(pool.apply_async, len(pool._pool), dsk, result,
     53                         cache=cache, queue=queue, get_id=_thread_get_id,
---> 54                         **kwargs)
     55
     56     return results

/Users/shoyer/dev/dask/dask/async.py in get_async(apply_async, num_workers, dsk, result, cache, queue, get_id, raise_on_exception, start_callback, end_callback, **kwargs)
    460         if isinstance(res, Exception):
    461             raise type(res)(" Exception in remote process\n\n"
--> 462                 + str(res) + "\n\nTraceback:\n" + tb)
    463         state['cache'][key] = res
    464         finish_task(dsk, key, state, results)

TypeError:  Exception in remote process

'float' object is not subscriptable

Traceback:
  File "/Users/shoyer/dev/dask/dask/async.py", line 260, in execute_task
    result = _execute_task(task, data)
  File "/Users/shoyer/dev/dask/dask/async.py", line 243, in _execute_task
    return func(*args2)
  File "/Users/shoyer/dev/dask/dask/array/core.py", line 41, in getarray
    c = a[b]
```

This is the cause of recent build failures in xray on Python 3.4.



Discussion:
My recommendation is to leave them as 0-dimensional arrays.


Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -1969,11 +1969,21 @@ def chunks_from_arrays(arrays):
     >>> x = np.array([[1, 2]])
     >>> chunks_from_arrays([[x, x]])
     ((1,), (2, 2))
+
+    >>> chunks_from_arrays([1, 1])
+    ((1, 1),)
     """
     result = []
     dim = 0
+
+    def shape(x):
+        try:
+            return x.shape
+        except AttributeError:
+            return (1,)
+
     while isinstance(arrays, (list, tuple)):
-        result.append(tuple(deepfirst(a).shape[dim] for a in arrays))
+        result.append(tuple(shape(deepfirst(a))[dim] for a in arrays))
         arrays = arrays[0]
         dim += 1
     return tuple(result)
@@ -2021,11 +2031,19 @@ def concatenate3(arrays):
         return arrays
     chunks = chunks_from_arrays(arrays)
     shape = tuple(map(sum, chunks))
-    result = np.empty(shape=shape, dtype=deepfirst(arrays).dtype)
+
+    def dtype(x):
+        try:
+            return x.dtype
+        except AttributeError:
+            return type(x)
+
+    result = np.empty(shape=shape, dtype=dtype(deepfirst(arrays)))
 
     for (idx, arr) in zip(slices_from_chunks(chunks), core.flatten(arrays)):
-        while arr.ndim < ndim:
-            arr = arr[None, ...]
+        if hasattr(arr, 'ndim'):
+            while arr.ndim < ndim:
+                arr = arr[None, ...]
         result[idx] = arr
 
     return result
diff --git a/dask/array/numpy_compat.py b/dask/array/numpy_compat.py
--- a/dask/array/numpy_compat.py
+++ b/dask/array/numpy_compat.py
@@ -36,18 +36,7 @@ def divide(x1, x2, out=None, dtype=None):
 
         Temporary compatibility fix for a bug in numpy's version. See
         https://github.com/numpy/numpy/issues/3484 for the relevant issue."""
-
-        out_orig = out
-        if out is None:
-            out = np.asarray(x1, dtype=dtype)
-            if out is x1:
-                out = x1.copy()
-        else:
-            if out is not x1:
-                out[:] = x1
-        if dtype is not None and out.dtype != dtype:
-            out = out.astype(dtype)
-        out /= x2
-        if out_orig is None and np.isscalar(x1):
-            out = np.asscalar(out)
-        return out
+        x = np.divide(x1, x2, out)
+        if dtype is not None:
+            x = x.astype(dtype)
+        return x


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -74,6 +74,10 @@ def test_concatenate3():
                             [x, x, x]]).shape == (2, 6)
 
 
+def test_concatenate3_on_scalars():
+    assert eq(concatenate3([1, 2]), np.array([1, 2]))
+
+
 def eq(a, b):
     if isinstance(a, Array):
         adt = a._dtype
@@ -254,6 +258,14 @@ def test_short_stack():
     assert get(s.dask, s._keys())[0][0].shape == (1, 1)
 
 
+def test_stack_scalars():
+    d = da.arange(4, chunks=2)
+
+    s = da.stack([d.mean(), d.sum()])
+
+    assert s.compute().tolist() == [np.arange(4).mean(), np.arange(4).sum()]
+
+
 def test_concatenate():
     a, b, c = [Array(getem(name, chunks=(2, 3), shape=(4, 6)),
                      name, shape=(4, 6), chunks=(2, 3))
diff --git a/dask/array/tests/test_reductions.py b/dask/array/tests/test_reductions.py
--- a/dask/array/tests/test_reductions.py
+++ b/dask/array/tests/test_reductions.py
@@ -217,3 +217,13 @@ def test_nan():
     assert eq(np.nanargmax(x, axis=0), da.nanargmax(d, axis=0))
     with ignoring(AttributeError):
         assert eq(np.nanprod(x), da.nanprod(d))
+
+
+def test_0d_array():
+    x = da.mean(da.ones(4, chunks=4), axis=0).compute()
+    y = np.mean(np.ones(4))
+    assert type(x) == type(y)
+
+    x = da.sum(da.zeros(4, chunks=1)).compute()
+    y = np.sum(np.zeros(4))
+    assert type(x) == type(y)