Repository: dask/dask

Issue:
dd.from_delayed(meta=...) no longer allows dict
`dd.from_delayed(meta=...)` used to allow a `dict` but since #2534 it requires a df or series. Was this intentional?

```py
>>> dd.from_delayed([delayed(pd.DataFrame({'a':[1,2]}))], meta={'a':'int'}).compute()
TypeError: Expected partition to be DataFrame, Series, or Index, got `dict`
```

It does work with an explicit `dd.utils.make_meta`:
```py
>>> dd.from_delayed([delayed(pd.DataFrame({'a':[1,2]}))], meta=dd.utils.make_meta({'a':'int'})).compute()

   a
0  1
1  2
```

But the docstring still thinks that `dict` should be allowed:
```py
>>> dd.from_delayed?
Signature: dd.from_delayed(dfs, meta=None, divisions=None, prefix='from-delayed')
Docstring:
Create Dask DataFrame from many Dask Delayed objects

Parameters
----------
dfs : list of Delayed
    An iterable of ``dask.delayed.Delayed`` objects, such as come from
    ``dask.delayed`` These comprise the individual partitions of the
    resulting dataframe.
meta : pd.DataFrame, pd.Series, dict, iterable, tuple, optional
    An empty ``pd.DataFrame`` or ``pd.Series`` that matches the dtypes and
    column names of the output. This metadata is necessary for many
    algorithms in dask dataframe to work.  For ease of use, some
    alternative inputs are also available. Instead of a ``DataFrame``, a
    ``dict`` of ``{name: dtype}`` or iterable of ``(name, dtype)`` can be
    provided. Instead of a series, a tuple of ``(name, dtype)`` can be
    used. If not provided, dask will try to infer the metadata. This may
    lead to unexpected results, so providing ``meta`` is recommended. For
    more information, see ``dask.dataframe.utils.make_meta``.
divisions : tuple, str, optional
    Partition boundaries along the index.
    For tuple, see http://dask.pydata.org/en/latest/dataframe-design.html#partitions
    For string 'sorted' will compute the delayed values to find index
    values.  Assumes that the indexes are mutually sorted.
    If None, then won't use index information
prefix : str, optional
    Prefix to prepend to the keys.
File:      ~/miniconda3/envs/signals/lib/python3.6/site-packages/dask/dataframe/io/io.py
Type:      function
```

## Versions
```sh
$ cat requirements.txt | egrep 'dask/dask' | grep -v '^#'
git+https://github.com/dask/dask.git@6f8ec47
$ python -V
Python 3.6.0
```


Discussion:
I don't recall a reason for this change.  I wouldn't be surprised if it was accidental.  The only motivation I can see is that the dict doesn't fully define the schema due to column ordering.  

@jcrist would probably know more

Resolving patch:
diff --git a/dask/dataframe/io/io.py b/dask/dataframe/io/io.py
--- a/dask/dataframe/io/io.py
+++ b/dask/dataframe/io/io.py
@@ -16,7 +16,7 @@
 
 from ..core import DataFrame, Series, new_dd_object
 from ..shuffle import set_partition
-from ..utils import insert_meta_param_description, check_meta
+from ..utils import insert_meta_param_description, check_meta, make_meta
 
 from ...utils import M, ensure_dict
 
@@ -502,10 +502,7 @@ def from_delayed(dfs, meta=None, divisions=None, prefix='from-delayed'):
 
     if meta is None:
         meta = dfs[0].compute()
-    if isinstance(meta, (str, pd.Series)):
-        Frame = Series
-    else:
-        Frame = DataFrame
+    meta = make_meta(meta)
 
     name = prefix + '-' + tokenize(*dfs)
     dsk = merge(df.dask for df in dfs)
@@ -519,7 +516,7 @@ def from_delayed(dfs, meta=None, divisions=None, prefix='from-delayed'):
         if len(divs) != len(dfs) + 1:
             raise ValueError("divisions should be a tuple of len(dfs) + 1")
 
-    df = Frame(dsk, name, meta, divs)
+    df = new_dd_object(dsk, name, meta, divs)
 
     if divisions == 'sorted':
         from ..shuffle import compute_divisions


Test patch:
diff --git a/dask/dataframe/io/tests/test_io.py b/dask/dataframe/io/tests/test_io.py
--- a/dask/dataframe/io/tests/test_io.py
+++ b/dask/dataframe/io/tests/test_io.py
@@ -510,6 +510,10 @@ def test_from_delayed():
         assert list(s.map_partitions(my_len).compute()) == [1, 2, 3, 4]
         assert ddf.known_divisions == (divisions is not None)
 
+    meta2 = [(c, 'f8') for c in df.columns]
+    assert_eq(dd.from_delayed(dfs, meta=meta2), df)
+    assert_eq(dd.from_delayed([d.a for d in dfs], meta=('a', 'f8')), df.a)
+
     with pytest.raises(ValueError):
         dd.from_delayed(dfs, meta=meta, divisions=[0, 1, 3, 6])