まずは下記でデータフレームの用意をします。
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メソッドを使うのが一般的らしく、この情報はたくさん載っていました。
カラムの配列を作るときはいくつか方法がありますので、それも載せておきます。
# パターン①
columns = df.columns.tolist()
[value for value in columns if value.startswith('key')]
# パターン②
df.columns[df.columns.str.startswith('key')].tolist()
# 結果
# ['key1', 'key2']
一度、カラムの配列を作成して動かすパターン①の方が動きは軽そうなので、
下記ではそちらを使っています。
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
メソッドで削除しています。
pd.wide_to_long(df, ['key','value','time'], i='id', j='drop').reset_index().drop('drop', axis=1)
wide_to_longでエラーが出た場合
下記のエラーが出た場合の対処法
下記のエラーはidとなる項目に重複がある場合にでるエラーです。
ValueError: the id variables need to uniquely identify each row
例えば最初のデータフレームを少し変えて、idをどちらも1
にして実行した場合にエラーがでます。
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へ指定することで解決できます。
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
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)
また実務で使用していると書き方はあっているはずなのになぜか下記の
エラーが起きることがあるのであまり使用しない方がよさそうです。
/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メソッドを使ったときに
縦に結合してくれるのでそれを利用する方法です。
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'}),
])