Repository: dask/dask

Issue:
BUG/API: Can't use DataFrame.index string methods
``` python
import pandas as pd
import dask.dataframe as dd

df = pd.DataFrame({'a': [1, 2]}, index=['a', 'b'])
a = dd.from_pandas(df, npartitions=2)
a.index.str.lower()
```

raises with

``` pytb
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-b8d2a0689fbe> in <module>()
      1 df = pd.DataFrame({'a': [1, 2]}, index=['a', 'b'])
      2 a = dd.from_pandas(df, npartitions=2)
----> 3 a.index.str.lower()

/Users/tom.augspurger/Envs/py3/lib/python3.5/site-packages/dask/dask/dataframe/core.py in _function_map(self, key, *args)
   3184
   3185     def _function_map(self, key, *args):
-> 3186         out = self.call(self._series._meta, key, *args)
   3187         meta = pd.Series([], dtype=out.dtype, name=getattr(out, 'name', None))
   3188         return map_partitions(self.call, self._series, key, *args, meta=meta)

/Users/tom.augspurger/Envs/py3/lib/python3.5/site-packages/dask/dask/dataframe/core.py in call(obj, attr, *args)
   3237     @staticmethod
   3238     def call(obj, attr, *args):
-> 3239         return getattr(obj.str, attr)(*args)
   3240
   3241

/Users/tom.augspurger/Envs/py3/lib/python3.5/site-packages/pandas-0.19.0rc1+21.ge596cbf-py3.5-macosx-10.11-x86_64.egg/pandas/core/base.py in __get__(self, instance, owner)
    239             # this ensures that Series.str.<method> is well defined
    240             return self.accessor_cls
--> 241         return self.construct_accessor(instance)
    242
    243     def __set__(self, instance, value):

/Users/tom.augspurger/Envs/py3/lib/python3.5/site-packages/pandas-0.19.0rc1+21.ge596cbf-py3.5-macosx-10.11-x86_64.egg/pandas/core/strings.py in _make_str_accessor(self)
   1851                            "(i.e. inferred_type is 'string', 'unicode' or "
   1852                            "'mixed')")
-> 1853                 raise AttributeError(message)
   1854             if self.nlevels > 1:
   1855                 message = ("Can only use .str accessor with Index, not "

AttributeError: Can only use .str accessor with string values (i.e. inferred_type is 'string', 'unicode' or 'mixed')
```

Just a guess, but the inferred type of `a._meta.index` being empty messes things up when we get to pandas land. Not time to look for a fix, so just the bug report for now 😄 



Discussion:
Thanks for the bug report, you're right about the issue with empty series doing funny things. Fixing now.


Resolving patch:
diff --git a/dask/dataframe/core.py b/dask/dataframe/core.py
--- a/dask/dataframe/core.py
+++ b/dask/dataframe/core.py
@@ -3178,13 +3178,15 @@ def __init__(self, series):
         self._series = series
 
     def _property_map(self, key):
-        out = self.getattr(self._series._meta, key)
-        meta = pd.Series([], dtype=out.dtype, name=getattr(out, 'name', None))
+        out = self.getattr(self._series._meta_nonempty, key)
+        meta = self._series._partition_type([], dtype=out.dtype,
+                name=getattr(out, 'name', None))
         return map_partitions(self.getattr, self._series, key, meta=meta)
 
     def _function_map(self, key, *args):
-        out = self.call(self._series._meta, key, *args)
-        meta = pd.Series([], dtype=out.dtype, name=getattr(out, 'name', None))
+        out = self.call(self._series._meta_nonempty, key, *args)
+        meta = self._series._partition_type([], dtype=out.dtype,
+                name=getattr(out, 'name', None))
         return map_partitions(self.call, self._series, key, *args, meta=meta)
 
     def __dir__(self):
diff --git a/dask/dataframe/utils.py b/dask/dataframe/utils.py
--- a/dask/dataframe/utils.py
+++ b/dask/dataframe/utils.py
@@ -419,15 +419,17 @@ def assert_divisions(ddf):
     if not ddf.known_divisions:
         return
 
+    index = lambda x: x if isinstance(x, pd.Index) else x.index
+
     results = get_sync(ddf.dask, ddf._keys())
     for i, df in enumerate(results[:-1]):
         if len(df):
-            assert df.index.min() >= ddf.divisions[i]
-            assert df.index.max() < ddf.divisions[i + 1]
+            assert index(df).min() >= ddf.divisions[i]
+            assert index(df).max() < ddf.divisions[i + 1]
 
     if len(results[-1]):
-        assert results[-1].index.min() >= ddf.divisions[-2]
-        assert results[-1].index.max() <= ddf.divisions[-1]
+        assert index(results[-1]).min() >= ddf.divisions[-2]
+        assert index(results[-1]).max() <= ddf.divisions[-1]
 
 
 def assert_sane_keynames(ddf):


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
@@ -1349,16 +1349,18 @@ def test_datetime_accessor():
 
 
 def test_str_accessor():
-    df = pd.DataFrame({'x': ['a', 'b', 'c', 'D']})
+    df = pd.DataFrame({'x': ['a', 'b', 'c', 'D']}, index=['e', 'f', 'g', 'H'])
 
-    a = dd.from_pandas(df, 2)
+    a = dd.from_pandas(df, 2, sort=False)
 
     assert 'upper' in dir(a.x.str)
-
     assert eq(a.x.str.upper(), df.x.str.upper())
-
     assert a.x.str.upper().dask == a.x.str.upper().dask
 
+    assert 'upper' in dir(a.index.str)
+    assert eq(a.index.str.upper(), df.index.str.upper())
+    assert a.index.str.upper().dask == a.index.str.upper().dask
+
 
 def test_empty_max():
     meta = make_meta({'x': 'i8'})