Repository: pandas-dev/pandas

Issue:
BUG/ERR: read_csv(header=[0]) should raise/warn
`read_csv` with a single-row header either breaks any names that might be on the index, or reads all data as `NaN`.  This problem might exist because `pd.read_csv` hasn't caught up to #7589.

``` python

In [1]: import pandas as pd

In [2]: from StringIO import StringIO

In [3]: pd.read_csv(StringIO('''\
alpha,,A,B
foo,bar,,
this,x,1,2
this,y,5,6
that,x,3,4
that,y,7,8'''), header=0, index_col=[0,1])
Out[3]: 
                   A   B
alpha Unnamed: 1        
foo   bar        NaN NaN
this  x            1   2
      y            5   6
that  x            3   4
      y            7   8

In [4]: pd.read_csv(StringIO('''\
alpha,,A,B
foo,bar,,
this,x,1,2
this,y,5,6
that,x,3,4
that,y,7,8'''), header=[0], index_col=[0,1])
Out[4]: 
alpha       A    B
foo  bar          
this x    NaN  NaN
     y    NaN  NaN
that x    NaN  NaN
     y    NaN  NaN

In [5]: pd.read_csv(StringIO('''\
alpha,,A,B
beta,,Y,Z
foo,bar,,
this,x,1,2
this,y,5,6
that,x,3,4                                  
that,y,7,8'''), header=[0,1], index_col=[0,1])
Out[5]: 
alpha     A  B
beta      Y  Z
foo  bar      
this x    1  2
     y    5  6
that x    3  4
     y    7  8

```



Discussion:
use header=0 
this should technically work but it violates the guarantees as if header is a list it must have len > 1
And is therefore looking for a multi index header (which of only a single level which is not valid)
so it's prob an easy fix

Notice that `header=0` has different semantics regarding the second row: with `header=[0]`, it's taken as the `result.index.names`, whereas with `header=0` you need to use `skiprows=[1]` and set the names of the index manually.

I am not sure what you have (in the first example) is a valid format. IOW you cannot generate it using `to_csv` to the same interpretation (IIRC this is a separate issue). The index name on a single column is lost

```
In [68]: df = DataFrame(np.arange(1,9).reshape(4,2),columns=Index(['A','B'],name='alpha'),index=MultiIndex.from_product([['this','that'],['x','y']],names=['foo','bar']))

In [69]: df.to_csv('test.csv',mode='w')

In [70]: !cat test.csv
foo,bar,A,B
this,x,1,2
this,y,3,4
that,x,5,6
that,y,7,8
```

Ah, I get it now.  I thought you could because of the appearance of `'alpha'` in `str(Out[4])`, but after digging more it became clear that that's just a broken object anyway.  I think there are three possible solutions to this:
- Make `read_csv` raise when `index_col` or `header` is a singleton
- Make `read_csv` silently unpack such singletons
- Allow `Index`es to have `names` just like `MultiIndex`es (my preference-- I really dislike it when I'm `stack`ing and `unstack`ing levels and suddenly my `names` disappear).

- `Index` _can_ have a name! (see #6482 as its not printing out, but its their).
- the issue is that `header=[a_single_value]` I think should raise (unless `tupleize_cols=False`, iow its a list of tuples, which is de-facto a `MultiIndex` and how things < 0.12 used to parse).
- alternatively, you could have it unpack it (and maybe show a warning) if `tupleize_cols=True` (the default), AND their is no ambiguity.

care to do a pull-request?

If this raises, how should we handle the case where there's a single row index and a single column index, each of which has a name?

I don't think that is representable at the moment (except by a separate line, which `to_csv` doesn't generate properly)

somthing like

```
column_index_name,column_1
index_name
index_0,value_0
```

nice thing about tests is you can change code and see what if anything breaks.
Their are a lot of edge cases for this kind of thing.

This will have to wait until I have some time, I think.

np. @ischwabacher it has your name on it!

Is this purely error reporting or does there need to be some unpacking of singletons @ischwabacher @jreback 

@springcoil I think this could start with showing an error if their is a mismatch between the header and the resulting columns. IOW, if header if not a list-like, then the columns should be an `Index`, conversely if its a list-like, then the result should be a `MultiIndex` (this is subject to `tupleize_cols` though).

Use the cases indicates about, and see what breaks.


Resolving patch:
diff --git a/pandas/io/parsers.py b/pandas/io/parsers.py
--- a/pandas/io/parsers.py
+++ b/pandas/io/parsers.py
@@ -2279,10 +2279,11 @@ def _infer_columns(self):
         if self.header is not None:
             header = self.header
 
-            # we have a mi columns, so read an extra line
             if isinstance(header, (list, tuple, np.ndarray)):
-                have_mi_columns = True
-                header = list(header) + [header[-1] + 1]
+                have_mi_columns = len(header) > 1
+                # we have a mi columns, so read an extra line
+                if have_mi_columns:
+                    header = list(header) + [header[-1] + 1]
             else:
                 have_mi_columns = False
                 header = [header]


Test patch:
diff --git a/pandas/tests/io/parser/header.py b/pandas/tests/io/parser/header.py
--- a/pandas/tests/io/parser/header.py
+++ b/pandas/tests/io/parser/header.py
@@ -286,3 +286,10 @@ def test_non_int_header(self):
             self.read_csv(StringIO(data), sep=',', header=['a', 'b'])
         with tm.assert_raises_regex(ValueError, msg):
             self.read_csv(StringIO(data), sep=',', header='string_header')
+
+    def test_singleton_header(self):
+        # See GH #7757
+        data = """a,b,c\n0,1,2\n1,2,3"""
+        df = self.read_csv(StringIO(data), header=[0])
+        expected = DataFrame({"a": [0, 1], "b": [1, 2], "c": [2, 3]})
+        tm.assert_frame_equal(df, expected)