Repository: dask/dask

Issue:
from_delayed requires metadata
Maybe just a documentation issue? Not clear to me why something like this doesn't work.

```
import pandas as pd
from dask import delayed
import dask.dataframe as dd

XX = [delayed(pd.util.testing.makeDataFrame)() for _ in range(5)]

# doesn't work
df = dd.from_delayed(XX)

# works
df = dd.from_delayed(XX, metadata=['A', 'B', 'C', 'D'])
```



Discussion:
Agreed.  We should probably also just compute the first delayed value to grab metadata if the user doesn't provide it explicitly.  I can get to this sometime tomorrow afternoon if no one beats me to it.


Resolving patch:
diff --git a/dask/dataframe/core.py b/dask/dataframe/core.py
--- a/dask/dataframe/core.py
+++ b/dask/dataframe/core.py
@@ -559,8 +559,8 @@ def to_delayed(self):
 
         Returns a list of values, one value per partition.
         """
-        from ..delayed import Value
-        return [Value(k, [self.dask]) for k in self._keys()]
+        from ..delayed import Delayed
+        return [Delayed(k, [self.dask]) for k in self._keys()]
 
     @classmethod
     def _get_unary_operator(cls, op):
diff --git a/dask/dataframe/io.py b/dask/dataframe/io.py
--- a/dask/dataframe/io.py
+++ b/dask/dataframe/io.py
@@ -645,16 +645,24 @@ def from_delayed(dfs, metadata=None, divisions=None, columns=None,
     Parameters
     ----------
     dfs: list of Values
-        An iterable of dask.delayed.Value objects, such as come from dask.do
-        These comprise the individual partitions of the resulting dataframe
-    metadata: list or string of column names or empty dataframe
-    divisions: list or None
+        An iterable of ``dask.delayed.Delayed`` objects, such as come from
+        ``dask.delayed`` These comprise the individual partitions of the
+        resulting dataframe.
+    metadata: str, list of column names, or empty dataframe, optional
+        Metadata for the underlying pandas object. Can be either column name
+        (if Series), list of column names, or pandas object with the same
+        columns/dtypes. If not provided, will be computed from the first
+        partition.
+    divisions: list, optional
+        Partition boundaries along the index.
+    prefix, str, optional
+        Prefix to prepend to the keys.
     """
     if columns is not None:
         warn("Deprecation warning: Use metadata argument, not columns")
         metadata = columns
-    from dask.delayed import Value
-    if isinstance(dfs, Value):
+    from dask.delayed import Delayed
+    if isinstance(dfs, Delayed):
         dfs = [dfs]
     dsk = merge(df.dask for df in dfs)
 
@@ -665,8 +673,10 @@ def from_delayed(dfs, metadata=None, divisions=None, columns=None,
 
     if divisions is None:
         divisions = [None] * (len(dfs) + 1)
+    if metadata is None:
+        metadata = dfs[0].compute()
 
-    if isinstance(metadata, str):
+    if isinstance(metadata, (str, pd.Series)):
         return Series(merge(dsk, dsk2), name, metadata, divisions)
     else:
         return DataFrame(merge(dsk, dsk2), name, metadata, divisions)


Test patch:
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -1653,31 +1653,39 @@ def test_dataframe_itertuples():
 
 
 def test_from_delayed():
-    from dask import do
-    dfs = [do(tm.makeTimeDataFrame)(i) for i in range(1, 5)]
-    df = dd.from_delayed(dfs, columns=['A', 'B', 'C', 'D'])
+    from dask import delayed
+    dfs = [delayed(tm.makeTimeDataFrame)(i) for i in range(1, 5)]
+    df = dd.from_delayed(dfs, metadata=['A', 'B', 'C', 'D'])
 
     assert (df.compute().columns == df.columns).all()
     assert list(df.map_partitions(len).compute()) == [1, 2, 3, 4]
 
     ss = [df.A for df in dfs]
-    s = dd.from_delayed(ss, columns='A')
+    s = dd.from_delayed(ss, metadata='A')
 
     assert s.compute().name == s.name
     assert list(s.map_partitions(len).compute()) == [1, 2, 3, 4]
+    
+    s = dd.from_delayed(ss)
+    assert s._known_dtype
+    assert s.compute().name == s.name
 
     df = dd.from_delayed(dfs, tm.makeTimeDataFrame(1))
     assert df._known_dtype
     assert list(df.columns) == ['A', 'B', 'C', 'D']
 
+    df = dd.from_delayed(dfs)
+    assert df._known_dtype
+    assert list(df.columns) == ['A', 'B', 'C', 'D']
+
 
 def test_to_delayed():
-    from dask.delayed import Value
+    from dask.delayed import Delayed
     df = pd.DataFrame({'x': [1, 2, 3, 4], 'y': [10, 20, 30, 40]})
     ddf = dd.from_pandas(df, npartitions=2)
     a, b = ddf.to_delayed()
-    assert isinstance(a, Value)
-    assert isinstance(b, Value)
+    assert isinstance(a, Delayed)
+    assert isinstance(b, Delayed)
 
     assert eq(a.compute(), df.iloc[:2])