Repository: pandas-dev/pandas

Issue:
BUG: A zero length series written to HDF cannot be read back.
This happens with and empty series with numpy arrays for both the values and the index.

``` python
>>> import pandas as pd
>>> import numpy as np
>>> 
>>> with pd.get_store('foo.h5') as store:
...     s = pd.Series(np.array([], dtype=np.int64), index=np.array([], dtype=np.int64))
...     store['s'] = s
...     s = store['s']
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/io/pytables.py", line 349, in __getitem__
    return self.get(key)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/io/pytables.py", line 507, in get
    return self._read_group(group)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/io/pytables.py", line 1093, in _read_group
    return s.read(**kwargs)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/io/pytables.py", line 2247, in read
    return Series(values, index=index, name=self.name)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/core/series.py", line 657, in __init__
    data = SingleBlockManager(data, index, fastpath=True)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/core/internals.py", line 2942, in __init__
    block = make_block(block, axis, axis, ndim=1, fastpath=True)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/core/internals.py", line 1535, in make_block
    return klass(values, items, ref_items, ndim=ndim, fastpath=fastpath, placement=placement)
  File "/users/is/pross/workspace/pandas/git/pandas/pandas/core/internals.py", line 62, in __init__
    % (len(items), len(values)))
ValueError: Wrong number of items passed 1, indices imply 0
```

I have fixed this locally and will put in a PR.



Discussion:

Resolving patch:
diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py
--- a/pandas/io/pytables.py
+++ b/pandas/io/pytables.py
@@ -2213,6 +2213,11 @@ def read_multi_index(self, key):
 
     def read_index_node(self, node):
         data = node[:]
+        # If the index was an empty array write_array_empty() will
+        # have written a sentinel. Here we relace it with the original.
+        if 'shape' in node._v_attrs \
+        and self._is_empty_array(getattr(node._v_attrs, 'shape')):
+            data = np.empty(getattr(node._v_attrs, 'shape'), dtype=getattr(node._v_attrs, 'value_type'))
         kind = _ensure_decoded(node._v_attrs.kind)
         name = None
 
@@ -2251,12 +2256,16 @@ def write_array_empty(self, key, value):
         getattr(self.group, key)._v_attrs.value_type = str(value.dtype)
         getattr(self.group, key)._v_attrs.shape = value.shape
 
+    def _is_empty_array(self, shape):
+        """Returns true if any axis is zero length."""
+        return any(x == 0 for x in shape)
+
     def write_array(self, key, value, items=None):
         if key in self.group:
             self._handle.removeNode(self.group, key)
 
         # Transform needed to interface with pytables row/col notation
-        empty_array = any(x == 0 for x in value.shape)
+        empty_array = self._is_empty_array(value.shape)
         transposed = False
 
         if not empty_array:
@@ -2305,17 +2314,18 @@ def write_array(self, key, value, items=None):
             vlarr = self._handle.createVLArray(self.group, key,
                                                _tables().ObjectAtom())
             vlarr.append(value)
-        elif value.dtype.type == np.datetime64:
-            self._handle.createArray(self.group, key, value.view('i8'))
-            getattr(self.group, key)._v_attrs.value_type = 'datetime64'
-        elif value.dtype.type == np.timedelta64:
-            self._handle.createArray(self.group, key, value.view('i8'))
-            getattr(self.group, key)._v_attrs.value_type = 'timedelta64'
         else:
             if empty_array:
                 self.write_array_empty(key, value)
             else:
-                self._handle.createArray(self.group, key, value)
+                if value.dtype.type == np.datetime64:
+                    self._handle.createArray(self.group, key, value.view('i8'))
+                    getattr(self.group, key)._v_attrs.value_type = 'datetime64'
+                elif value.dtype.type == np.timedelta64:
+                    self._handle.createArray(self.group, key, value.view('i8'))
+                    getattr(self.group, key)._v_attrs.value_type = 'timedelta64'
+                else:
+                    self._handle.createArray(self.group, key, value)
 
         getattr(self.group, key)._v_attrs.transposed = transposed
 
@@ -2362,11 +2372,7 @@ def shape(self):
     def read(self, **kwargs):
         self.validate_read(kwargs)
         index = self.read_index('index')
-        if len(index) > 0:
-            values = self.read_array('values')
-        else:
-            values = []
-
+        values = self.read_array('values')
         return Series(values, index=index, name=self.name)
 
     def write(self, obj, **kwargs):


Test patch:
diff --git a/pandas/io/tests/test_pytables.py b/pandas/io/tests/test_pytables.py
--- a/pandas/io/tests/test_pytables.py
+++ b/pandas/io/tests/test_pytables.py
@@ -2358,6 +2358,11 @@ def test_empty_series_frame(self):
         self._check_roundtrip(df1, tm.assert_frame_equal)
         self._check_roundtrip(df2, tm.assert_frame_equal)
 
+    def test_empty_series(self):
+        for dtype in [np.int64, np.float64, np.object, 'm8[ns]', 'M8[ns]']:
+            s = Series(dtype=dtype)
+            self._check_roundtrip(s, tm.assert_series_equal)
+
     def test_can_serialize_dates(self):
 
         rng = [x.date() for x in bdate_range('1/1/2000', '1/30/2000')]