pandasで、複数列に対応するヘッダをCSVで出力したいです。


### 実行環境

  * pandas 0.25.3
  * Python 3.7.4

### やりたいこと

pandasのDataFramdeを使って、下図のような表をCSVとして出力したいです。

[![画像の説明をここに入力](https://i.stack.imgur.com/Ayr05.png)](https://i.stack.imgur.com/Ayr05.png)

### 試したこと

以下のPythonコードを実行しました。

[code]

        dict_table = {("user1", "plan"): [1, 11], 
             ("user1", "work"): [2, 12],
             ("user2", "plan"): [3, 13],
             ("user2", "work"): [4, 14]
             }
        df = pandas.DataFrame(dict_table)
        df.to_csv("foo.csv", index=False)
    
[/code]

出力結果(foo.csv)

[code]

    user1,user1,user2,user2
    plan,work,plan,work
    1,2,3,4
    11,12,13,14
    
[/code]

### 期待していた結果

本当は以下のように、ヘッダには同じユーザを一度しか表示しないフォーマットで出力したいです。

[code]

    user1,,user2,
    plan,work,plan,work
    1,2,3,4
    11,12,13,14
    
[/code]

### 質問

pandasを使って、上記のようなCSVは出力する方法を教えていただきたいです。




`levels` に空文字列(`''`)をセットした MultiIndex オブジェクトを作成して、df のインデックスにセットします。

[code]

    >>> midx = pd.MultiIndex(
      levels = [
        list(v) + [''] for v in df.columns.levels
      ],
      codes = [
        [
          y if n == 0 else
            (len(df.columns.levels[i]) if y == (list(v))[n-1] else y)
          for n, y in enumerate(list(v))
        ]
        for i, v in enumerate(df.columns.codes)
      ]
    )
    
    >>> midx
    MultiIndex(levels=[['user1', 'user2', ''], ['plan', 'work', '']],
               codes=[[0, 2, 1, 2], [0, 1, 0, 1]])
    
    >>> df.T.set_index(midx).T.to_csv('output.csv', index=False)
    
[/code]

**output.csv**

[code]

    user1,,user2,
    plan,work,plan,work
    1,2,3,4
    11,12,13,14
    
[/code]

**追記**

対象データはカラム数が少ないので、MultiIndex オブジェクトを手で書き下しても良いかと思います。

[code]

    >>> df.columns
    MultiIndex(levels=[['user1', 'user2'], ['plan', 'work']],
               codes=[[0, 0, 1, 1], [0, 1, 0, 1]])
    
[/code]

`df.columns.codes` で、同じインデックス番号が続く部分を変更します。以下のコードにおける、変更されている値 `2` は
`levels[0][2]`(空文字) に対応しています。

[code]

    >>> df.T.set_index(
          pd.MultiIndex(
            levels = [['user1', 'user2', ''], ['plan', 'work']],
             codes = [[0, 2, 1, 2], [0, 1, 0, 1]]
        )).T.to_csv('output.csv', index=False)
    
[/code]

**追記(2019/11/21)**

> to_csvメソッドなどのオプションなどでは対応できないのですね。

無理をすればできなくはないですが、お勧めはしません。

[code]

    import csv
    
    output = StringIO()
    df.to_csv(output,
      index      = False,
      header     = ['user1', '', 'user2', '\nplan,work,plan,work'],
      quoting    = csv.QUOTE_NONE,
      escapechar = '\01')
    
    with open('output.csv', 'w', encoding='utf-8') as f:
      f.write(output.getvalue().replace('\01', ''))
    
[/code]

`csv.QUOTE_NONE`
を設定していますので、数値データのみの場合は問題ないのですが、`,`(カンマ/セパレータ)が含まれる文字列データの場合にはクォート処理が行われませんので、CSV
としては不正なデータになってしまいます。




`levels` に空文字列(`''`)をセットした MultiIndex オブジェクトを作成して、df のインデックスにセットします。

[code]

    >>> midx = pd.MultiIndex(
      levels = [
        list(v) + [''] for v in df.columns.levels
      ],
      codes = [
        [
          y if n == 0 else
            (len(df.columns.levels[i]) if y == (list(v))[n-1] else y)
          for n, y in enumerate(list(v))
        ]
        for i, v in enumerate(df.columns.codes)
      ]
    )
    
    >>> midx
    MultiIndex(levels=[['user1', 'user2', ''], ['plan', 'work', '']],
               codes=[[0, 2, 1, 2], [0, 1, 0, 1]])
    
    >>> df.T.set_index(midx).T.to_csv('output.csv', index=False)
    
[/code]

**output.csv**

[code]

    user1,,user2,
    plan,work,plan,work
    1,2,3,4
    11,12,13,14
    
[/code]

**追記**

対象データはカラム数が少ないので、MultiIndex オブジェクトを手で書き下しても良いかと思います。

[code]

    >>> df.columns
    MultiIndex(levels=[['user1', 'user2'], ['plan', 'work']],
               codes=[[0, 0, 1, 1], [0, 1, 0, 1]])
    
[/code]

`df.columns.codes` で、同じインデックス番号が続く部分を変更します。以下のコードにおける、変更されている値 `2` は
`levels[0][2]`(空文字) に対応しています。

[code]

    >>> df.T.set_index(
          pd.MultiIndex(
            levels = [['user1', 'user2', ''], ['plan', 'work']],
             codes = [[0, 2, 1, 2], [0, 1, 0, 1]]
        )).T.to_csv('output.csv', index=False)
    
[/code]

**追記(2019/11/21)**

> to_csvメソッドなどのオプションなどでは対応できないのですね。

無理をすればできなくはないですが、お勧めはしません。

[code]

    import csv
    
    output = StringIO()
    df.to_csv(output,
      index      = False,
      header     = ['user1', '', 'user2', '\nplan,work,plan,work'],
      quoting    = csv.QUOTE_NONE,
      escapechar = '\01')
    
    with open('output.csv', 'w', encoding='utf-8') as f:
      f.write(output.getvalue().replace('\01', ''))
    
[/code]

`csv.QUOTE_NONE`
を設定していますので、数値データのみの場合は問題ないのですが、`,`(カンマ/セパレータ)が含まれる文字列データの場合にはクォート処理が行われませんので、CSV
としては不正なデータになってしまいます。

