Repository: dask/dask

Issue:
Join using an index-only 1-partition dataframe raises ValueError
<!-- Please include a self-contained copy-pastable example that generates the issue if possible.

Please be concise with code posted. See guidelines below on how to provide a good bug report:

- Craft Minimal Bug Reports http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports
- Minimal Complete Verifiable Examples https://stackoverflow.com/help/mcve

Bug reports that follow these guidelines are easier to diagnose, and so are often handled much more quickly.
-->

**What happened**:

Merging a dataframe with unknown divisions and some columns (ncols >=1) with an index-only dataframe, with known divisions and only 1 partition raises:

`ValueError: min() arg is an empty sequence`

<details>
<summary> Stacktrace:</summary>

```python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-c0949034b5bf> in <module>
----> 1 df3 = df2.merge(df1, how="outer", left_index=True, right_index=True)
      2 df3

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/core.py in merge(self, right, how, on, left_on, right_on, left_index, right_index, suffixes, indicator, npartitions, shuffle)
   4059             npartitions=npartitions,
   4060             indicator=indicator,
-> 4061             shuffle=shuffle,
   4062         )
   4063 

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/multi.py in merge(left, right, how, on, left_on, right_on, left_index, right_index, suffixes, indicator, npartitions, shuffle, max_branch)
    572             suffixes,
    573             shuffle=shuffle,
--> 574             indicator=indicator,
    575         )
    576 

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/multi.py in hash_join(lhs, left_on, rhs, right_on, how, npartitions, suffixes, shuffle, indicator)
    287         npartitions = max(lhs.npartitions, rhs.npartitions)
    288 
--> 289     lhs2 = shuffle_func(lhs, left_on, npartitions=npartitions, shuffle=shuffle)
    290     rhs2 = shuffle_func(rhs, right_on, npartitions=npartitions, shuffle=shuffle)
    291 

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/shuffle.py in shuffle(df, index, shuffle, npartitions, max_branch, ignore_index, compute)
    299         transform_divisions=False,
    300     )
--> 301     df2 = df.assign(_partitions=partitions)
    302     df2._meta.index.name = df._meta.index.name
    303     df3 = rearrange_by_column(

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/core.py in assign(self, **kwargs)
   3805         # Figure out columns of the output
   3806         df2 = self._meta_nonempty.assign(**_extract_meta(kwargs, nonempty=True))
-> 3807         return elemwise(methods.assign, self, *pairs, meta=df2)
   3808 
   3809     @derived_from(pd.DataFrame, ua_args=["index"])

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/core.py in elemwise(op, *args, **kwargs)
   4699     from .multi import _maybe_align_partitions
   4700 
-> 4701     args = _maybe_align_partitions(args)
   4702     dasks = [arg for arg in args if isinstance(arg, (_Frame, Scalar, Array))]
   4703     dfs = [df for df in dasks if isinstance(df, _Frame)]

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/multi.py in _maybe_align_partitions(args)
    157     `align_partitions`, which will fail if divisions aren't all known"""
    158     _is_broadcastable = partial(is_broadcastable, args)
--> 159     dfs = [df for df in args if isinstance(df, _Frame) and not _is_broadcastable(df)]
    160     if not dfs:
    161         return args

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/multi.py in <listcomp>(.0)
    157     `align_partitions`, which will fail if divisions aren't all known"""
    158     _is_broadcastable = partial(is_broadcastable, args)
--> 159     dfs = [df for df in args if isinstance(df, _Frame) and not _is_broadcastable(df)]
    160     if not dfs:
    161         return args

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/core.py in is_broadcastable(dfs, s)
   4660         and any(
   4661             s.divisions == (min(df.columns), max(df.columns))
-> 4662             for df in dfs
   4663             if isinstance(df, DataFrame)
   4664         )

~/.conda/envs/dc/lib/python3.7/site-packages/dask/dataframe/core.py in <genexpr>(.0)
   4661             s.divisions == (min(df.columns), max(df.columns))
   4662             for df in dfs
-> 4663             if isinstance(df, DataFrame)
   4664         )
   4665     )

ValueError: min() arg is an empty sequence
```

</details>


**What you expected to happen**:
The join to succeed.

**Minimal Complete Verifiable Example**:

```python
import dask

# Dataset with unknown divisions
df1 = dask.datasets.timeseries().clear_divisions()

# Index-only dataset, with one partition and known divisions
df2 = dask.datasets.timeseries().drop(columns=["id","name","x","y"]).repartition(npartitions=1)

# Raises the ValueError
df3 = df2.merge(df1, how="outer", left_index=True, right_index=True)

```

**Anything else we need to know?**:
 It feels like the last line of the stacktrace 
```python
if isinstance(df, DataFrame)
``` 
could be a red herring, as `isinstance(df, dd.DataFrame)` returns true for both my dataframes.
If I set my index-only dataframe to have 2 partitions, this error does not get raised, so I'm wondering if there is some Dask metadata that is `None`'d out if there is only 1 partition?

I do not get this error if:
- The index-only dataset has npartitions>1
- The dataset with 1 partition has at least 1 non-index column (we currently work around this problem by attaching a column that we fill with all boolean True values)

**Environment**:

- Dask version: 2.18.0
- Python version: 3.7.7
- Operating System: Linux
- Install method (conda, pip, source): conda



Discussion:
I'd be happy to contribute a PR to fix this if I could get help diagnosing the failure
Hey @KrishanBhasin -- thanks for reporting and for the tidy MRE!

I _think_ that this can be fixed by having those `min` and `max` calls return `None` if the `df.columns` is an empty list.  There might be a performance hit from that, but compared to non-functional I'd wager it's an improvement.

@TomAugspurger may have more enlightened thoughts on the matter.
Yeah, I think having None or NaN there would work. Easiest fix might be `(df.columns.min(), df.columns.max())`, which returns NaN for empty columns.
Thanks for the comments, I'll get a PR in later today!

Resolving patch:
diff --git a/dask/dataframe/core.py b/dask/dataframe/core.py
--- a/dask/dataframe/core.py
+++ b/dask/dataframe/core.py
@@ -4678,7 +4678,7 @@ def is_broadcastable(dfs, s):
         and s.npartitions == 1
         and s.known_divisions
         and any(
-            s.divisions == (min(df.columns), max(df.columns))
+            s.divisions == (df.columns.min(), df.columns.max())
             for df in dfs
             if isinstance(df, DataFrame)
         )


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
@@ -24,6 +24,7 @@
     has_parallel_type,
     iter_chunks,
     total_mem_usage,
+    is_broadcastable,
 )
 from dask.dataframe import methods
 from dask.dataframe.utils import assert_eq, make_meta, assert_max_deps, PANDAS_VERSION
@@ -2492,6 +2493,15 @@ def test_gh580():
     assert_eq(np.cos(df["x"]), np.cos(ddf["x"]))
 
 
+def test_gh6305():
+    df = pd.DataFrame({"x": np.arange(3, dtype=float)})
+    ddf = dd.from_pandas(df, 1)
+    ddf_index_only = ddf.set_index("x")
+    ds = ddf["x"]
+
+    is_broadcastable([ddf_index_only], ds)
+
+
 def test_rename_dict():
     renamer = {"a": "A", "b": "B"}
     assert_eq(d.rename(columns=renamer), full.rename(columns=renamer))