LoginSignup
2
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-10

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

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

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

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

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

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

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であれば1key2であれば2を指定したカラム名で作成します。
下記コードでは'drop'というカラムが作成されるので、そのあとにdropメソッドで削除しています。

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となる項目に重複がある場合にでるエラーです。

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

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

エラーが出る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へ指定することで解決できます。

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

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)

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

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メソッドを使ったときに
縦に結合してくれるのでそれを利用する方法です。

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'}),
])
2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4