Repository: dask/dask

Issue:
Passing a list of a list with two column names to dask.read_csv fails with ValueError
``` python
import pandas as pd
import dask.dataframe as df

# this works...
delme1 = pd.read_csv('some_file.txt', delimiter='\t', parse_dates=[['Datum', 'Zeit']])

# this does not
delme2 = df.read_csv('some_file.txt', delimiter='\t', parse_dates=[['Datum', 'Zeit']])

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-41c1ed8fde3e> in <module>()
----> 1 delme = df.read_csv('some_file.txt', delimiter='\t', parse_dates=[['Datum', 'Zeit']])
      2 delme

/Applications/anaconda/lib/python2.7/site-packages/dask/dataframe/io.pyc in read_csv(fn, *args, **kwargs)
    182     kwargs = kwargs.copy()
    183 
--> 184     kwargs = fill_kwargs(fn, args, kwargs)
    185 
    186     # Handle glob strings

/Applications/anaconda/lib/python2.7/site-packages/dask/dataframe/io.pyc in fill_kwargs(fn, args, kwargs)
    147     kwargs = clean_kwargs(kwargs)
    148     try:
--> 149         head = pd.read_csv(fn, *args, **assoc(kwargs, 'nrows', sample_nrows))
    150     except StopIteration:
    151         head = pd.read_csv(fn, *args, **kwargs)

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in parser_f(filepath_or_buffer, sep, dialect, compression, doublequote, escapechar, quotechar, quoting, skipinitialspace, lineterminator, header, index_col, names, prefix, skiprows, skipfooter, skip_footer, na_values, true_values, false_values, delimiter, converters, dtype, usecols, engine, delim_whitespace, as_recarray, na_filter, compact_ints, use_unsigned, low_memory, buffer_lines, warn_bad_lines, error_bad_lines, keep_default_na, thousands, comment, decimal, parse_dates, keep_date_col, dayfirst, date_parser, memory_map, float_precision, nrows, iterator, chunksize, verbose, encoding, squeeze, mangle_dupe_cols, tupleize_cols, infer_datetime_format, skip_blank_lines)
    496                     skip_blank_lines=skip_blank_lines)
    497 
--> 498         return _read(filepath_or_buffer, kwds)
    499 
    500     parser_f.__name__ = name

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in _read(filepath_or_buffer, kwds)
    273 
    274     # Create the parser.
--> 275     parser = TextFileReader(filepath_or_buffer, **kwds)
    276 
    277     if (nrows is not None) and (chunksize is not None):

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in __init__(self, f, engine, **kwds)
    588             self.options['has_index_names'] = kwds['has_index_names']
    589 
--> 590         self._make_engine(self.engine)
    591 
    592     def _get_options_with_defaults(self, engine):

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in _make_engine(self, engine)
    729     def _make_engine(self, engine='c'):
    730         if engine == 'c':
--> 731             self._engine = CParserWrapper(self.f, **self.options)
    732         else:
    733             if engine == 'python':

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in __init__(self, src, **kwds)
   1144                 raise ValueError("Usecols do not match names.")
   1145 
-> 1146         self._set_noconvert_columns()
   1147 
   1148         self.orig_names = self.names

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in _set_noconvert_columns(self)
   1178                 if isinstance(val, list):
   1179                     for k in val:
-> 1180                         _set(k)
   1181                 else:
   1182                     _set(val)

/Applications/anaconda/lib/python2.7/site-packages/pandas/io/parsers.pyc in _set(x)
   1172                 self._reader.set_noconvert(x)
   1173             else:
-> 1174                 self._reader.set_noconvert(names.index(x))
   1175 
   1176         if isinstance(self.parse_dates, list):

ValueError: 'Datum' is not in list
```



Discussion:
Can you provide a tiny sample of such a csv file so that I can reproduce this locally?

[sample.txt](https://github.com/blaze/dask/files/97717/sample.txt)

Thanks!  I'll take a look at this soon.  Hopefully tomorrow.


Resolving patch:
diff --git a/dask/dataframe/io.py b/dask/dataframe/io.py
--- a/dask/dataframe/io.py
+++ b/dask/dataframe/io.py
@@ -187,7 +187,10 @@ def fill_kwargs(fn, **kwargs):
 
     if kwargs.get('parse_dates'):
         for col in kwargs['parse_dates']:
-            del dtype[col]
+            if isinstance(col, (tuple, list)):
+                del dtype['_'.join(col)]
+            else:
+                del dtype[col]
 
     kwargs['dtype'] = dtype
 
@@ -246,11 +249,11 @@ def csv_names(fn, encoding='utf-8', compression=None, names=None,
     try:
         kwargs['nrows'] = 5
         df = pd.read_csv(fn, encoding=encoding, compression=compression,
-                names=names, parse_dates=parse_dates, **kwargs)
+                names=names, **kwargs)
     except StopIteration:
         kwargs['nrows'] = None
         df = pd.read_csv(fn, encoding=encoding, compression=compression,
-                names=names, parse_dates=parse_dates, **kwargs)
+                names=names, **kwargs)
     return list(df.columns)
 
 
diff --git a/dask/dataframe/utils.py b/dask/dataframe/utils.py
--- a/dask/dataframe/utils.py
+++ b/dask/dataframe/utils.py
@@ -225,19 +225,19 @@ def _maybe_sort(a):
     return a.sort_index()
 
 
-def eq(a, b, check_names=True, ignore_index=False):
+def eq(a, b, check_names=True, **kwargs):
     a = _check_dask(a, check_names=check_names)
     b = _check_dask(b, check_names=check_names)
     if isinstance(a, pd.DataFrame):
         a = _maybe_sort(a)
         b = _maybe_sort(b)
-        tm.assert_frame_equal(a, b)
+        tm.assert_frame_equal(a, b, **kwargs)
     elif isinstance(a, pd.Series):
         a = _maybe_sort(a)
         b = _maybe_sort(b)
-        tm.assert_series_equal(a, b, check_names=check_names)
+        tm.assert_series_equal(a, b, check_names=check_names, **kwargs)
     elif isinstance(a, pd.Index):
-        tm.assert_index_equal(a, b)
+        tm.assert_index_equal(a, b, **kwargs)
     else:
         if a == b:
             return True


Test patch:
diff --git a/dask/dataframe/tests/test_io.py b/dask/dataframe/tests/test_io.py
--- a/dask/dataframe/tests/test_io.py
+++ b/dask/dataframe/tests/test_io.py
@@ -817,3 +817,31 @@ def test_none_usecols():
     with filetext(text) as fn:
         df = dd.read_csv(fn, usecols=None)
         eq(df, pd.read_csv(fn, usecols=None))
+
+
+
+pdmc_text = """
+ID,date,time
+10,2003-11-04,180036
+11,2003-11-05,125640
+12,2003-11-01,2519
+13,2003-10-22,142559
+14,2003-10-24,163113
+15,2003-10-20,170133
+16,2003-11-11,160448
+17,2003-11-03,171759
+18,2003-11-07,190928
+19,2003-10-21,84623
+20,2003-10-25,192207
+21,2003-11-13,180156
+22,2003-11-15,131037
+""".strip()
+
+
+def test_parse_dates_multi_column():
+    with filetext(pdmc_text) as fn:
+        ddf = dd.read_csv(fn, parse_dates=[['date', 'time']])
+        df = pd.read_csv(fn, parse_dates=[['date', 'time']])
+
+        assert (df.columns == ddf.columns).all()
+        assert len(df) == len(ddf)