Repository: dask/dask

Issue:
Dask.bag mean not aggregated with 1 partition
```python
>>>import dask
>>>print(dask.__version__)
0.14.3
```

It seems to me that a call to `aggregate()` is missing from [dask.bag.core.Item.reduction](https://github.com/dask/dask/blob/master/dask/bag/core.py#L734) when the bag has only one partition.

Here's a small example.


```python
import dask.bag as db

data = db.from_sequence([{'a':6, 'b':1}, {'a':6, 'b':1}])

def get_mean(bag, npart):
    new = bag.repartition(npartitions=npart)
    result = (new
              .map(lambda i:i['a'] - i['b'])
              .mean())
   
    return 'npartitions={}; mean={}'.format(npart, result.compute())

for i in 1,2:
    print(get_mean(data, i))
```

```
npartitions=1; mean=(10.0, 2)
npartitions=2; mean=5.0
```


Discussion:

Resolving patch:
diff --git a/dask/bag/core.py b/dask/bag/core.py
--- a/dask/bag/core.py
+++ b/dask/bag/core.py
@@ -722,20 +722,21 @@ def reduction(self, perpartition, aggregate, split_every=None,
             split_every = 8
         if split_every is False:
             split_every = self.npartitions
+
         token = tokenize(self, perpartition, aggregate, split_every)
         a = '%s-part-%s' % (name or funcname(perpartition), token)
         is_last = self.npartitions == 1
-        dsk = dict(((a, i), (empty_safe_apply, perpartition, (self.name, i), is_last))
-                   for i in range(self.npartitions))
+        dsk = {(a, i): (empty_safe_apply, perpartition, (self.name, i), is_last)
+               for i in range(self.npartitions)}
         k = self.npartitions
         b = a
         fmt = '%s-aggregate-%s' % (name or funcname(aggregate), token)
         depth = 0
-        while k > 1:
+
+        while k > split_every:
             c = fmt + str(depth)
-            is_last = k <= split_every
             dsk2 = dict(((c, i), (empty_safe_aggregate, aggregate,
-                                  [(b, j) for j in inds], is_last))
+                                  [(b, j) for j in inds], False))
                         for i, inds in enumerate(partition_all(split_every,
                                                                range(k))))
             dsk.update(dsk2)
@@ -743,11 +744,14 @@ def reduction(self, perpartition, aggregate, split_every=None,
             b = c
             depth += 1
 
+        dsk[(fmt, 0)] = (empty_safe_aggregate, aggregate,
+                         [(b, j) for j in range(k)], True)
+
         if out_type is Item:
-            dsk[b] = dsk.pop((b, 0))
-            return Item(merge(self.dask, dsk), b)
+            dsk[fmt] = dsk.pop((fmt, 0))
+            return Item(merge(self.dask, dsk), fmt)
         else:
-            return Bag(merge(self.dask, dsk), b, 1)
+            return Bag(merge(self.dask, dsk), fmt, 1)
 
     def sum(self, split_every=None):
         """ Sum all elements """


Test patch:
diff --git a/dask/bag/tests/test_bag.py b/dask/bag/tests/test_bag.py
--- a/dask/bag/tests/test_bag.py
+++ b/dask/bag/tests/test_bag.py
@@ -229,8 +229,9 @@ def test_topk():
     assert b.topk(4).name == b.topk(4).name
 
 
-def test_topk_with_non_callable_key():
-    b = db.from_sequence([(1, 10), (2, 9), (3, 8)], npartitions=2)
+@pytest.mark.parametrize('npartitions', [1, 2])
+def test_topk_with_non_callable_key(npartitions):
+    b = db.from_sequence([(1, 10), (2, 9), (3, 8)], npartitions=npartitions)
     assert list(b.topk(2, key=1)) == [(1, 10), (2, 9)]
     assert list(b.topk(2, key=0)) == [(3, 8), (2, 9)]
     assert b.topk(2, key=1).name == b.topk(2, key=1).name
@@ -285,15 +286,20 @@ def test_tree_reductions():
     assert c.key != b.sum().key
 
 
-def test_mean():
-    assert b.mean().compute(get=dask.get) == 2.0
-    assert float(b.mean()) == 2.0
+@pytest.mark.parametrize('npartitions', [1, 3, 4])
+def test_aggregation(npartitions):
+    L = list(range(15))
+    b = db.range(15, npartitions=npartitions)
+    assert b.mean().compute(get=dask.get) == sum(L) / len(L)
+    assert b.sum().compute(get=dask.get) == sum(L)
+    assert b.count().compute(get=dask.get) == len(L)
 
 
-def test_non_splittable_reductions():
+@pytest.mark.parametrize('npartitions', [1, 10])
+def test_non_splittable_reductions(npartitions):
     np = pytest.importorskip('numpy')
     data = list(range(100))
-    c = db.from_sequence(data, npartitions=10)
+    c = db.from_sequence(data, npartitions=npartitions)
     assert c.mean().compute() == np.mean(data)
     assert c.std().compute(get=dask.get) == np.std(data)
 
@@ -1035,13 +1041,14 @@ def test_reduction_empty():
     assert b.filter(lambda x: x % 2 == 0).min().compute(get=dask.get) == 0
 
 
-def test_reduction_empty_aggregate():
-    b = db.from_sequence([0, 0, 0, 1], npartitions=4).filter(None)
+@pytest.mark.parametrize('npartitions', [1, 2, 4])
+def test_reduction_empty_aggregate(npartitions):
+    b = db.from_sequence([0, 0, 0, 1], npartitions=npartitions).filter(None)
     assert b.min(split_every=2).compute(get=dask.get) == 1
     vals = db.compute(b.min(split_every=2), b.max(split_every=2), get=dask.get)
     assert vals == (1, 1)
     with pytest.raises(ValueError):
-        b = db.from_sequence([0, 0, 0, 0], npartitions=4)
+        b = db.from_sequence([0, 0, 0, 0], npartitions=npartitions)
         b.filter(None).min(split_every=2).compute(get=dask.get)
 
 
@@ -1142,3 +1149,11 @@ def test_temporary_directory():
         b2 = b.groupby(lambda x: x % 2)
         b2.compute()
         assert any(fn.endswith('.partd') for fn in os.listdir(os.getcwd()))
+
+
+def test_empty_bag():
+    b = db.from_sequence([])
+    assert b.map(inc).all().compute(get=dask.get)
+    assert not b.map(inc).any().compute(get=dask.get)
+    assert not b.map(inc).sum().compute(get=dask.get)
+    assert not b.map(inc).count().compute(get=dask.get)