pandasで横持ちデータを縦持ちデータへ変換する方法

まずは下記でデータフレームの用意をします。

```python:DateFrameの用意
import pandas as pd
 
values = [['1', 'John', 'somekey1-1', 'somevalue1-1', 'time1-1', 'somekey2-1', 'somevalue2-1', 'time2-1'],
          ['2', 'Tom', 'somekey1-2', 'somevalue1-2', 'time1-2', 'somekey2-2', 'somevalue2-2', 'time2-2'],]
df = pd.DataFrame(values, columns=['id', 'name', 'key1', 'value1', 'time1', 'key2', 'value2', 'time2'])
df
```

上記コードで下記のデータが作られます。

|  | id | name | key1 | value1 | time1 | key2 | value2 | time2 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 1 | John | somekey1-1 | somevalue1-1 | time1-1 | somekey2-1 | somevalue2-1 | time2-1 |
| 1 | 2 | Tom | somekey1-2 | somevalue1-2 | time1-2 | somekey2-2 | somevalue2-2 | time2-2 |


こちらの情報を下記のように縦持ちデータへ変換するコード4つを紹介していきます。

|  | id | name | key | value | time |
| --- | --- | --- | --- | --- | --- |
| 0 | 1 | John | somekey1-1 | somevalue1-1 | time1-1 |
| 1 | 2 | Tom | somekey1-2 | somevalue1-2 | time1-2 |
| 2 | 1 | John | somekey2-1 | somevalue2-1 | time2-1 |
| 3 | 2 | Tom | somekey2-2 | somevalue2-2 | time2-2 |

# meltメソッドで実施する方法

meltメソッドを使うのが一般的らしく、この情報はたくさん載っていました。
カラムの配列を作るときはいくつか方法がありますので、それも載せておきます。

```python:カラム名の配列を作成
# パターン①
columns = df.columns.tolist()
[value for value in columns if value.startswith('key')]

# パターン②
df.columns[df.columns.str.startswith('key')].tolist()

# 結果
# ['key1', 'key2']
```

一度、カラムの配列を作成して動かすパターン①の方が動きは軽そうなので、
下記ではそちらを使っています。


```python:meltを使って縦持ちデータへ変換
columns = df.columns.tolist()

pd.concat(
    [pd.melt(df, id_vars=['id', 'name'], value_vars=[value for value in columns if value.startswith('key')], value_name='key'),
     pd.melt(df, value_vars=[value for value in columns if value.startswith('value')], value_name='value'),
     pd.melt(df, value_vars=[value for value in columns if value.startswith('time')], value_name='time')
     ],
     axis=1
     ).drop('variable', axis=1)
```

# wide_to_longメソッドで実施する方法

wide_to_longを使うと1行で作成ができるのでかなりシンプルです。
下記サイトを見ても最初はよくわからなかったのですが、
https://pandas.pydata.org/docs/reference/api/pandas.wide_to_long.html

2つ目の引数で指定している配列では`特定の文字から始めるカラムを縦持ちへ変換する`
という動きをするので1行で完結させることが可能です。

`j` で指定するのはカラムの余った部分
`key1`であれば`1`、`key2`であれば`2`を指定したカラム名で作成します。
下記コードでは'drop'というカラムが作成されるので、そのあとに`drop`メソッドで削除しています。


```python:wide_to_longを使って縦持ちデータへ変換
pd.wide_to_long(df, ['key','value','time'], i='id', j='drop').reset_index().drop('drop', axis=1)
```

## wide_to_longでエラーが出た場合

下記のエラーが出た場合の対処法
下記のエラーはidとなる項目に重複がある場合にでるエラーです。

```python:error
ValueError: the id variables need to uniquely identify each row
```

例えば最初のデータフレームを少し変えて、idをどちらも`1`にして実行した場合にエラーがでます。

```python:エラーが出るDataFrame
import pandas as pd
 
values = [['1', 'John', 'somekey1-1', 'somevalue1-1', 'time1-1', 'somekey2-1', 'somevalue2-1', 'time2-1'],
          ['1', 'Tom', 'somekey1-2', 'somevalue1-2', 'time1-2', 'somekey2-2', 'somevalue2-2', 'time2-2'],]
df = pd.DataFrame(values, columns=['id', 'name', 'key1', 'value1', 'time1', 'key2', 'value2', 'time2'])

pd.wide_to_long(df,['key','value','time'], i='id', j='drop').reset_index().drop('drop', axis=1)
```

その時は`reset_index()`でindexの項目を作ってidへ指定することで解決できます。

```python:wide_to_longを使って縦持ちデータへ変換(エラー回避方法)
pd.wide_to_long(df.reset_index(), ['key','value','time'], i='index', j='drop').reset_index().drop('drop', axis=1).drop('index', axis=1)
```

# lreshapeメソッドで実施する方法

`lreshape`はgoogleで検索しても`reshape`に直されるぐらいマイナーなメソッドみたいです。
個人的にはシンプルで好きなのですが、下記サイトに将来消えると書いてあるので、そのうち使えなくなりそうです。残念。
https://pandas.pydata.org/pandas-docs/version/1.0.0/whatsnew/v1.0.0.html


```python:lreshapeを使って縦持ちデータへ変換
d = {'key': df.columns[df.columns.str.startswith('key')].tolist(),
     'value': df.columns[df.columns.str.startswith('value')].tolist(),
     'time': df.columns[df.columns.str.startswith('time')].tolist(),}

pd.lreshape(df, d)
```

また実務で使用していると書き方はあっているはずなのになぜか下記の
エラーが起きることがあるのであまり使用しない方がよさそうです。

```python:error
/usr/local/lib/python3.6/dist-packages/pandas/core/reshape/melt.py in <dictcomp>(.0)
    188             mask &= notna(mdata[c])
    189         if not mask.all():
--> 190             mdata = {k: v[mask] for k, v in mdata.items()}
    191 
    192     return data._constructor(mdata, columns=id_cols + pivot_cols)

IndexError: boolean index did not match indexed array along dimension 0; dimension is 1210 but corresponding boolean dimension is 24200
```


# concatで頑張ってやる方法

多分かなりいけてないです。
最初上記のメソッドを知らない時はこれでやっていました。

カラム名が同じだとconcatメソッドを使ったときに
縦に結合してくれるのでそれを利用する方法です。


```python:concatでの実施
pd.concat([
    df[['id', 'name', 'key1', 'value1', 'time1']].rename(columns={'key1': 'key', 'value1': 'value', 'time1': 'time'}),
    df[['id', 'name', 'key2', 'value2', 'time2']].rename(columns={'key2': 'key', 'value2': 'value', 'time2': 'time'}),
])
```