Repository: dask/dask

Issue:
dataframe.read_hdf raises exception for empty datasets
<!-- 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**:

```ValueError: Start row number (0) is above or equal to stop row number (0)``` when calling `dataframe.read_hdf`

**What you expected to happen**:

An empty dataframe to be loaded without error.

**Minimal Complete Verifiable Example**:

```python
import dask.dataframe as ddf
# Exception is thrown below
ddf.read_hdf('some_data.h5', key='an_empty_dataset')
```

**Environment**:

- Dask version: 2021.10.0
- Python version: 3.8
- Operating System: Linux
- Install method (conda, pip, source): conda




Discussion:
@struktured Thanks for opening this issue, and for including a minimal example! I'll take a look at this :)
@struktured Thanks for your patience!

I couldn't reproduce this error, and I'm wondering if it's specific to your data? I'm also curious if you can read this (or smaller+similar data) successfully with [pandas' read_hdf](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_hdf.html#pandas-read-hdf)?

[A minimal example that shows this erorr](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) will allow us to help you better. :) You can do something like:

```python
import dask
import dask.dataframe as dd

ddf = dask.datasets.timeseries()
ddf.to_hdf("some_data.h5", "data") # create toy dataset (similar to your actual data)
ddf_hdf = dd.read_hdf("some_data.h5", "data'")
```

Also, since this is a usage question, I'd encourage you to move it to [Dask's Discourse forum](https://dask.discourse.group/) and close this issue. We can open a dedicated issue if this turns out to be a bug in Dask!
It's definitely a bug but I can only manage to produce the empty dataset shape using proprietary tools.  Here's an example h5 schema that would generate the error. It has one dataset inside called `maybe_empty` with two columns `x` and `y`:

```
h5ls -v empty.h5
Opened "empty.h5" with sec2 driver.
maybe_empty              Dataset {0/0}
    Location:  1:800
    Links:     1
    Chunks:    {512} 8192 bytes
    Storage:   0 logical bytes, 0 allocated bytes
    Filter-0:  blosc-32001 OPT {2, 2, 16, 8192, 6, 1, 1}
    Type:      struct {
                   "x"                +0    native double
                   "y"                +8    native double
               } 16 bytes
```

```python
import dask.dataframe as ddf
r = ddf.read_hdf("empty.h5", key='maybe_empty')
```

will then produce:

```python
ValueError: Start row number (0) is above or equal to stop row number (0)
```

I can upload the empty h5 file to github if that helps debug it. I might also take a stab at fixing the bug myself next week if time permits.

I tried creating an empty dataset  and wrote it to a file like this:

```python
import pandas as pd
import dask.dataframe as dd

df = pd.DataFrame({"A": [], "B": []}, index=[])
df.to_hdf("data.h5", format="fixed", key="df", mode="w")
dd.read_hdf("data.h5", "df")
```

<details>
<summary>Error from trying to read file written with <code>format="fixed"</code></summary>

```python-traceback
IndexError                                Traceback (most recent call last)
Input In [3], in <module>
      4 df = pd.DataFrame({"A": [], "B": []}, index=[])
      5 df.to_hdf("data.h5", format="fixed", key="df", mode="w")
----> 6 dd.read_hdf("data.h5", "df")

File ~/dask/dask/dataframe/io/hdf.py:412, in read_hdf(pattern, key, start, stop, columns, chunksize, sorted_index, lock, mode)
    410 with pd.HDFStore(paths[0], mode=mode) as hdf:
    411     meta_key = _expand_key(key, hdf)[0]
--> 412 meta = pd.read_hdf(paths[0], meta_key, mode=mode, stop=0)
    413 if columns is not None:
    414     meta = meta[columns]

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:455, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    450                 raise ValueError(
    451                     "key must be provided when HDF5 "
    452                     "file contains multiple datasets."
    453                 )
    454         key = candidate_only_group._v_pathname
--> 455     return store.select(
    456         key,
    457         where=where,
    458         start=start,
    459         stop=stop,
    460         columns=columns,
    461         iterator=iterator,
    462         chunksize=chunksize,
    463         auto_close=auto_close,
    464     )
    465 except (ValueError, TypeError, KeyError):
    466     if not isinstance(path_or_buf, HDFStore):
    467         # if there is an error, close the store if we opened it.

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:875, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
    861 # create the iterator
    862 it = TableIterator(
    863     self,
    864     s,
   (...)
    872     auto_close=auto_close,
    873 )
--> 875 return it.get_result()

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:1943, in TableIterator.get_result(self, coordinates)
   1940     where = self.where
   1942 # directly return the result
-> 1943 results = self.func(self.start, self.stop, where)
   1944 self.close()
   1945 return results

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:859, in HDFStore.select.<locals>.func(_start, _stop, _where)
    858 def func(_start, _stop, _where):
--> 859     return s.read(start=_start, stop=_stop, where=_where, columns=columns)

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:3197, in BlockManagerFixed.read(self, where, columns, start, stop)
   3194 for i in range(self.ndim):
   3196     _start, _stop = (start, stop) if i == select_axis else (None, None)
-> 3197     ax = self.read_index(f"axis{i}", start=_start, stop=_stop)
   3198     axes.append(ax)
   3200 items = axes[0]

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:2904, in GenericFixed.read_index(self, key, start, stop)
   2902 elif variety == "regular":
   2903     node = getattr(self.group, key)
-> 2904     index = self.read_index_node(node, start=start, stop=stop)
   2905     return index
   2906 else:  # pragma: no cover

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:3008, in GenericFixed.read_index_node(self, node, start, stop)
   2999     index = factory(
   3000         _unconvert_index(
   3001             data, kind, encoding=self.encoding, errors=self.errors
   (...)
   3004         **kwargs,
   3005     )
   3006 else:
   3007     index = factory(
-> 3008         _unconvert_index(
   3009             data, kind, encoding=self.encoding, errors=self.errors
   3010         ),
   3011         **kwargs,
   3012     )
   3014 index.name = name
   3016 return index

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:4928, in _unconvert_index(data, kind, encoding, errors)
   4924     index = _unconvert_string_array(
   4925         data, nan_rep=None, encoding=encoding, errors=errors
   4926     )
   4927 elif kind == "object":
-> 4928     index = np.asarray(data[0])
   4929 else:  # pragma: no cover
   4930     raise ValueError(f"unrecognized index type {kind}")

IndexError: list index out of range

```
</details>

When you read the same file with pandas you get back an empty dataframe. 

Note that by default pandas was using `format="fixed"`. I tried changing that to `format="table"` and I got a different error about the datafile not containing the dataset in question. 

<details>
<summary>Error from trying to read file written with <code>format="table"</code></summary>

```python-traceback
File ~/dask/dask/dataframe/io/hdf.py:412, in read_hdf(pattern, key, start, stop, columns, chunksize, sorted_index, lock, mode)
    410 with pd.HDFStore(paths[0], mode=mode) as hdf:
    411     meta_key = _expand_key(key, hdf)[0]
--> 412 meta = pd.read_hdf(paths[0], meta_key, mode=mode, stop=0)
    413 if columns is not None:
    414     meta = meta[columns]

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:455, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    450                 raise ValueError(
    451                     "key must be provided when HDF5 "
    452                     "file contains multiple datasets."
    453                 )
    454         key = candidate_only_group._v_pathname
--> 455     return store.select(
    456         key,
    457         where=where,
    458         start=start,
    459         stop=stop,
    460         columns=columns,
    461         iterator=iterator,
    462         chunksize=chunksize,
    463         auto_close=auto_close,
    464     )
    465 except (ValueError, TypeError, KeyError):
    466     if not isinstance(path_or_buf, HDFStore):
    467         # if there is an error, close the store if we opened it.

File ~/conda/envs/dask-dev/lib/python3.9/site-packages/pandas/io/pytables.py:850, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
    848 group = self.get_node(key)
    849 if group is None:
--> 850     raise KeyError(f"No object named {key} in the file")
    852 # create the storer and axes
    853 where = _ensure_term(where, scope_level=1)

KeyError: 'No object named /df in the file'
```

</details>


Resolving patch:
diff --git a/dask/dataframe/io/hdf.py b/dask/dataframe/io/hdf.py
--- a/dask/dataframe/io/hdf.py
+++ b/dask/dataframe/io/hdf.py
@@ -409,7 +409,10 @@ def read_hdf(
     # Build metadata
     with pd.HDFStore(paths[0], mode=mode) as hdf:
         meta_key = _expand_key(key, hdf)[0]
-    meta = pd.read_hdf(paths[0], meta_key, mode=mode, stop=0)
+    try:
+        meta = pd.read_hdf(paths[0], meta_key, mode=mode, stop=0)
+    except IndexError:  # if file is empty, don't set stop
+        meta = pd.read_hdf(paths[0], meta_key, mode=mode)
     if columns is not None:
         meta = meta[columns]
 


Test patch:
diff --git a/dask/dataframe/io/tests/test_hdf.py b/dask/dataframe/io/tests/test_hdf.py
--- a/dask/dataframe/io/tests/test_hdf.py
+++ b/dask/dataframe/io/tests/test_hdf.py
@@ -908,3 +908,14 @@ class Table3(tables.IsDescription):
         dd.read_hdf(path, "/group/table2")
         dd.read_hdf(path, "/group/table3")
         dd.read_hdf(path, "/bar")
+
+
+def test_hdf_empty_dataframe():
+    pytest.importorskip("tables")
+    # https://github.com/dask/dask/issues/8707
+    from dask.dataframe.io.hdf import dont_use_fixed_error_message
+
+    df = pd.DataFrame({"A": [], "B": []}, index=[])
+    df.to_hdf("data.h5", format="fixed", key="df", mode="w")
+    with pytest.raises(TypeError, match=dont_use_fixed_error_message):
+        dd.read_hdf("data.h5", "df")