pydata/pandas — pull request #3575 by jreback (2013-05-11 04:08:31+00:00)
Title: ENH: allow to_csv to write multi-index columns, read_csv to read with header=list arg

In theory should close:

#3571, #1651, #3141

Works, but a couple of issues/caveats:

- index_col needs to be specified as an integer list (can be fixed)

- header is a list of rows to read that contain the multi-index,
a row that is skipped (e.g. [0,1,3,5], will just be skipped, like a comment)

- the writing format might be a bit odd: the col names go in the first column,
other index_cols are blanks (they are separated, just == '')
The names of an multi-index on the index are after the columns and before the data,
and are a full row (but blank after the row names).

- I am not sure if we should allow ``df.to_csv('path',index=False)`` when have a multi-index columns, could just ban it I guess (mainly as it screws up the write format, and then where do you put the names?)

- The ``cols`` argument needs testing and prob is broken when using multi-index on the columns (it really should be specified as a tuple I think, but that is work, so maybe just ban it when using multi-index columns)

- all tests pass but 1, ``as_recarray``, needs fixing

- needs more testing

```
In [14]: df = mkdf(5,3,r_idx_nlevels=2,c_idx_nlevels=4)

In [15]: df.to_csv('test.csv')

In [16]: !cat 'test.csv'
C0,,C_l0_g0,C_l0_g1,C_l0_g2
C1,,C_l1_g0,C_l1_g1,C_l1_g2
C2,,C_l2_g0,C_l2_g1,C_l2_g2
C3,,C_l3_g0,C_l3_g1,C_l3_g2
R0,R1,,,
R_l0_g0,R_l1_g0,R0C0,R0C1,R0C2
R_l0_g1,R_l1_g1,R1C0,R1C1,R1C2
R_l0_g2,R_l1_g2,R2C0,R2C1,R2C2
R_l0_g3,R_l1_g3,R3C0,R3C1,R3C2
R_l0_g4,R_l1_g4,R4C0,R4C1,R4C2

In [17]: res = read_csv('test.csv',header=[0,1,2,3],index_col=[0,1])

In [18]: res.index
Out[18]:
MultiIndex
[(u'R_l0_g0', u'R_l1_g0'), (u'R_l0_g1', u'R_l1_g1'), (u'R_l0_g2', u'R_l1_g2'), (u'R_l0_g3', u'R_l1_g3'), (u'R_l0_g4', u'R_l1_g4')]

In [19]: res.columns
Out[19]:
MultiIndex
[(u'C_l0_g0', u'C_l1_g0', u'C_l2_g0', u'C_l3_g0'), (u'C_l0_g1', u'C_l1_g1', u'C_l2_g1', u'C_l3_g1'), (u'C_l0_g2', u'C_l1_g2', u'C_l2_g2', u'C_l3_g2')]

In [20]: res
Out[20]:
C0 C_l0_g0 C_l0_g1 C_l0_g2
C1 C_l1_g0 C_l1_g1 C_l1_g2
C2 C_l2_g0 C_l2_g1 C_l2_g2
C3 C_l3_g0 C_l3_g1 C_l3_g2
R0 R1
R_l0_g0 R_l1_g0 R0C0 R0C1 R0C2
R_l0_g1 R_l1_g1 R1C0 R1C1 R1C2
R_l0_g2 R_l1_g2 R2C0 R2C1 R2C2
R_l0_g3 R_l1_g3 R3C0 R3C1 R3C2
R_l0_g4 R_l1_g4 R4C0 R4C1 R4C2

In [21]: res.index.names
Out[21]: ['R0', 'R1']

In [22]: res.columns.names
Out[22]: ['C0', 'C1', 'C2', 'C3']
```