Repository: dask/dask

Issue:
to_parquet() fails with empty partitions
Hi,

I am currently facing the following problem with `dask` (version 0.13.0): `to_parquet()` fails when one of the partitions is empty. The error one gets is:

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-48-faa825ea24c4> in <module>()
     11 # fails as there are empty partitions
     12 ddf2 = ddf[ddf.a <= 5]
---> 13 ddf2.to_parquet("/tmp/test2.parquet")

/opt/conda/lib/python3.5/site-packages/dask/dataframe/core.py in to_parquet(self, path, *args, **kwargs)
    931         """ See dd.to_parquet docstring for more information """
    932         from .io import to_parquet
--> 933         return to_parquet(path, self, *args, **kwargs)
    934 
    935     def to_csv(self, filename, **kwargs):

/opt/conda/lib/python3.5/site-packages/dask/dataframe/io/parquet.py in to_parquet(path, df, compression, write_index, has_nulls, fixed_text, object_encoding, storage_options)
    242 
    243     for fn, rg in zip(filenames, out):
--> 244         for chunk in rg.columns:
    245             chunk.file_path = fn
    246         fmd.row_groups.append(rg)

AttributeError: 'NoneType' object has no attribute 'columns'
```

Reproducible example:

```
import pandas as pd
import dask.dataframe as dd

df = pd.DataFrame({"a": range(10), "b": range(10)})
ddf = dd.from_pandas(df, npartitions=5)


# works
ddf.to_parquet("/tmp/test.parquet")

# fails as there are empty partitions
ddf2 = ddf[ddf.a <= 5]
ddf2.to_parquet("/tmp/test2.parquet")
```

I assume this is not the intended behaviour.


Discussion:
@martindurant do you have time to address this?
On it.

Resolving patch:
diff --git a/dask/dataframe/io/parquet.py b/dask/dataframe/io/parquet.py
--- a/dask/dataframe/io/parquet.py
+++ b/dask/dataframe/io/parquet.py
@@ -288,10 +288,13 @@ def to_parquet(path, df, compression=None, write_index=None, has_nulls=None,
     out = delayed(writes).compute()
 
     for fn, rg in zip(filenames, out):
-        for chunk in rg.columns:
-            chunk.file_path = fn
-        fmd.row_groups.append(rg)
+        if rg is not None:
+            for chunk in rg.columns:
+                chunk.file_path = fn
+            fmd.row_groups.append(rg)
 
+    if len(fmd.row_groups) == 0:
+        raise ValueError("All partitions were empty")
     fastparquet.writer.write_common_metadata(metadata_fn, fmd, open_with=myopen,
                                              no_row_groups=False)
 


Test patch:
diff --git a/dask/dataframe/io/tests/test_parquet.py b/dask/dataframe/io/tests/test_parquet.py
--- a/dask/dataframe/io/tests/test_parquet.py
+++ b/dask/dataframe/io/tests/test_parquet.py
@@ -305,3 +305,20 @@ def test_categories(fn):
     with pytest.raises(ValueError):
         # attempt to load as category unknown column
         ddf2 = dd.read_parquet(fn, categories=['foo'])
+
+
+def test_empty_partition(fn):
+    df = pd.DataFrame({"a": range(10), "b": range(10)})
+    ddf = dd.from_pandas(df, npartitions=5)
+
+    # fails as there are empty partitions
+    ddf2 = ddf[ddf.a <= 5]
+    ddf2.to_parquet(fn)
+
+    ddf3 = dd.read_parquet(fn)
+    assert_eq(ddf2.compute(), ddf3.compute(), check_names=False,
+              check_index=False)
+
+    ddf2 = ddf[ddf.a <= -5]
+    with pytest.raises(ValueError):
+        ddf2.to_parquet(fn)