pandasでstack, unstack, pivotを使ってデータを整形
pandasのstack()
, unstack()
, pivot()
はデータのピボット処理を行うメソッド。
列方向に並んだデータを行方向に並べ替えたり、行方向に並んだデータを列方向に並べ替えたりして、データの構造を再形成できる。
long型(積み上げ型、縦型、縦持ち)とwide型(横型、横持ち)のデータ構造を相互に変換することが可能。
- Reshaping and Pivot Tables — pandas 0.23.0 documentation
- pandas.DataFrame.stack — pandas 0.23.0 documentation
- pandas.DataFrame.unstack — pandas 0.23.0 documentation
- pandas.DataFrame.pivot — pandas 0.23.0 documentation
ここでは以下の内容について説明する。
- 列から行へピボット:
stack()
- 行から列へピボット:
unstack()
pandas.DataFrame
のunstack()
- 行と列を指定してピボット(再形成):
pivot()
- 実際のデータを使った例
並べ替えだけでなく集計操作を行うpivot_table()
については以下の記事を参照。
列から行へピボット: stack()
以下のデータを例とする。
import pandas as pd
df = pd.DataFrame({'A': ['a', 'a', 'a', 'b', 'b', 'b'],
'B': ['x', 'y', 'z', 'x', 'y', 'z'],
'C': [1, 2, 3, 4, 5, 6],
'D': [10, 20, 30, 40, 50, 60]})
print(df)
# A B C D
# 0 a x 1 10
# 1 a y 2 20
# 2 a z 3 30
# 3 b x 4 40
# 4 b y 5 50
# 5 b z 6 60
stack()
メソッドを呼ぶと列方向に並んでいたデータが行方向に並べ替えられる。縦に積み重なる(= stack)イメージ。
s = df.stack()
print(s)
# 0 A a
# B x
# C 1
# D 10
# 1 A a
# B y
# C 2
# D 20
# 2 A a
# B z
# C 3
# D 30
# 3 A b
# B x
# C 4
# D 40
# 4 A b
# B y
# C 5
# D 50
# 5 A b
# B z
# C 6
# D 60
# dtype: object
stack()
メソッドはマルチインデックス(階層型インデックス)のpandas.Series
を返す。
print(type(s))
# <class 'pandas.core.series.Series'>
print(s.index)
# MultiIndex(levels=[[0, 1, 2, 3, 4, 5], ['A', 'B', 'C', 'D']],
# labels=[[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5], [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]])
元のpandas.DataFrame
のcolumns
がマルチインデックスの場合のstack()
の振る舞いは後述。
行から列へピボット: unstack()
stack()
で取得したpandas.Series
からunstack()
を呼ぶと元に戻る。行方向に並んでいたデータが列方向に並べ替えられる。
print(s.unstack())
# A B C D
# 0 a x 1 10
# 1 a y 2 20
# 2 a z 3 30
# 3 b x 4 40
# 4 b y 5 50
# 5 b z 6 60
デフォルトではマルチインデックスの一番下の階層(一番内側の階層)に対して処理される。対象の階層は引数level
で指定可能。0
が一番上の階層(一番外側の階層)。
print(s.unstack(level=0))
# 0 1 2 3 4 5
# A a a a b b b
# B x y z x y z
# C 1 2 3 4 5 6
# D 10 20 30 40 50 60
この場合、元のデータが転置されることになる。転置は.T
でも取得可能。
print(df.T)
# 0 1 2 3 4 5
# A a a a b b b
# B x y z x y z
# C 1 2 3 4 5 6
# D 10 20 30 40 50 60
pandas.DataFrameのunstack()
index
がマルチインデックスのpandas.DataFrame
に対するunstack()
の例。
例として、以下のようにマルチインデックスのpandas.DataFrame
を作成する。
df_m = df.set_index(['A', 'B'])
print(df_m)
# C D
# A B
# a x 1 10
# y 2 20
# z 3 30
# b x 4 40
# y 5 50
# z 6 60
print(df_m.index)
# MultiIndex(levels=[['a', 'b'], ['x', 'y', 'z']],
# labels=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]],
# names=['A', 'B'])
unstack()
を呼ぶとcolumns
がマルチインデックスになる。
df_mu = df_m.unstack()
print(df_mu)
# C D
# B x y z x y z
# A
# a 1 2 3 10 20 30
# b 4 5 6 40 50 60
print(df_mu.columns)
# MultiIndex(levels=[['C', 'D'], ['x', 'y', 'z']],
# labels=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]],
# names=[None, 'B'])
どのインデックス列を対象にするかは引数level
で指定。インデックス列名でも指定可能。
print(df_m.unstack(level=0))
# C D
# A a b a b
# B
# x 1 4 10 40
# y 2 5 20 50
# z 3 6 30 60
print(df_m.unstack(level='A'))
# C D
# A a b a b
# B
# x 1 4 10 40
# y 2 5 20 50
# z 3 6 30 60
マルチインデックスの階層を入れ替えたい場合はswaplevel()
を使う。さらにsort_index()
でソートすると見やすい。
print(df_mu)
# C D
# B x y z x y z
# A
# a 1 2 3 10 20 30
# b 4 5 6 40 50 60
print(df_mu.swaplevel(axis=1))
# B x y z x y z
# C C C D D D
# A
# a 1 2 3 10 20 30
# b 4 5 6 40 50 60
print(df_mu.swaplevel(axis=1).sort_index(axis=1))
# B x y z
# C D C D C D
# A
# a 1 10 2 20 3 30
# b 4 40 5 50 6 60
マルチインデックスについてのもろもろの処理については以下の記事を参照。
stack()
でunstack()
前のデータに戻る。ここでも引数level
で対象を指定可能。
print(df_mu.stack())
# C D
# A B
# a x 1 10
# y 2 20
# z 3 30
# b x 4 40
# y 5 50
# z 6 60
print(df_mu.stack(level=0))
# B x y z
# A
# a C 1 2 3
# D 10 20 30
# b C 4 5 6
# D 40 50 60
行と列を指定してピボット(再形成): pivot()
pivot()
は、引数index
, columns
, values
に列名を指定してデータを再形成するメソッド。
long型(積み上げ型、縦型、縦持ち)からwide型(横型、横持ち)への変換となる。
print(df)
# A B C D
# 0 a x 1 10
# 1 a y 2 20
# 2 a z 3 30
# 3 b x 4 40
# 4 b y 5 50
# 5 b z 6 60
print(df.pivot(index='A', columns='B', values='C'))
# B x y z
# A
# a 1 2 3
# b 4 5 6
引数values
にはリストで複数列を指定可能。values
を省略した場合は、index
, columns
に使われなかった列がvalues
となる。
引数index
, columns
にはリストの指定不可。
print(df.pivot(index='A', columns='B', values=['C', 'D']))
# C D
# B x y z x y z
# A
# a 1 2 3 10 20 30
# b 4 5 6 40 50 60
print(df.pivot(index='A', columns='B'))
# C D
# B x y z x y z
# A
# a 1 2 3 10 20 30
# b 4 5 6 40 50 60
pivot()
の処理はset_index()
でマルチインデックスを設定してからunstack()
する処理に等しい。
print(df.set_index(['A', 'B']).unstack('B'))
# C D
# B x y z x y z
# A
# a 1 2 3 10 20 30
# b 4 5 6 40 50 60
注意点
pivot()
で引数index
, columns
に指定する列の値の組み合わせが重複しているとエラーとなる。以下の例では先頭の2行が重複しているのでエラー。
df.loc[1, 'B'] = 'x'
print(df)
# A B C D
# 0 a x 1 10
# 1 a x 2 20
# 2 a z 3 30
# 3 b x 4 40
# 4 b y 5 50
# 5 b z 6 60
# print(df.pivot(index='A', columns='B'))
# ValueError: Index contains duplicate entries, cannot reshape
重複している場合はpivot_table()
を使うと重複要素を集計した結果が得られる。デフォルトでは重複要素の平均(引数aggfunc
で変更可能)。
print(df.pivot_table(index='A', columns='B'))
# C D
# B x y z x y z
# A
# a 1.5 NaN 3.0 15.0 NaN 30.0
# b 4.0 5.0 6.0 40.0 50.0 60.0
print(df.pivot_table(index='A', columns='B', aggfunc=sum))
# C D
# B x y z x y z
# A
# a 3.0 NaN 3.0 30.0 NaN 30.0
# b 4.0 5.0 6.0 40.0 50.0 60.0
実際のデータを使った例
政府統計ポータルサイトe-Statからダウンロードした2017年の日本の人口のデータを例とする。
csvファイルはこちら。
例として「男女別・性比」、「人口」、「年齢各歳」、「value」の列を取り出して使う。
import pandas as pd
df = pd.read_csv('data/src/estat_0003215840.csv')
df = df[['男女別・性比', '人口', '年齢各歳', 'value']]
print(df.head(10))
# 男女別・性比 人口 年齢各歳 value
# 0 男女計 総人口 総数 126706.0
# 1 男女計 総人口 0歳 963.0
# 2 男女計 総人口 1歳 1000.0
# 3 男女計 総人口 2歳 960.0
# 4 男女計 総人口 3歳 975.0
# 5 男女計 総人口 4歳 1011.0
# 6 男女計 総人口 5歳 1017.0
# 7 男女計 総人口 6歳 1045.0
# 8 男女計 総人口 7歳 1049.0
# 9 男女計 総人口 8歳 1060.0
このデータには「男女別・性比」、「人口」のカテゴリごとに各年齢の値が格納されている。各カテゴリの種類とその組み合わせは以下の通り。
print(df['男女別・性比'].unique())
# ['男女計' '男' '女' '人口性比']
print(df['人口'].unique())
# ['総人口' '日本人人口']
print(df[['男女別・性比', '人口']].drop_duplicates())
# 男女別・性比 人口
# 0 男女計 総人口
# 102 男女計 日本人人口
# 204 男 総人口
# 306 男 日本人人口
# 408 女 総人口
# 510 女 日本人人口
# 612 人口性比 総人口
# 714 人口性比 日本人人口
このデータを「年齢各歳」をindex
として再形成したい。
そのままpivot()
を使うとエラーになる。
# df.pivot(index='年齢各歳', columns='男女別・性比', values='value')
# ValueError: Index contains duplicate entries, cannot reshape
これは、pivot()
では引数index
, columns
に単独の列しか指定できず、index
に「年齢各歳」、columns
に「男女別・性比」、「人口」カテゴリのいずれか一方だけを指定すると、引数index
, columns
に指定した列の値の組み合わせが重複してしまうため。
例のようにcolumns
に男女別・性比
を指定すると、例えば0歳
と男女計
の組み合わせが総人口
と日本人人口
のいずれにも存在し重複する。
先にいずれかのカテゴリを限定すればpivot()
でもOK。
df_jp = df.query('人口 == "日本人人口"')
print(df_jp.pivot(index='年齢各歳', columns='男女別・性比', values='value').head(10))
# 男女別・性比 人口性比 女 男 男女計
# 年齢各歳
# 0歳 105.0 462.0 485.0 947.0
# 100歳以上 15.0 59.0 9.0 67.0
# 10歳 105.0 518.0 544.0 1061.0
# 11歳 104.8 515.0 540.0 1054.0
# 12歳 104.9 516.0 541.0 1057.0
# 13歳 104.6 535.0 560.0 1095.0
# 14歳 105.2 543.0 571.0 1115.0
# 15歳 105.1 560.0 588.0 1148.0
# 16歳 105.3 567.0 597.0 1163.0
# 17歳 106.2 577.0 613.0 1189.0
なお、ここで条件指定にquery()
を使っているが「男女別・性比」は・
を含んでいるためquery()
ではエラーになるので注意。
set_index()
でマルチインデックスを設定してからunstack()
すればカテゴリを限定せずに再形成が可能。columns
がマルチインデックスになる。
print(df.set_index(['年齢各歳', '人口', '男女別・性比']).unstack(['人口', '男女別・性比']).sort_index(axis=1).head(10))
# value
# 人口 日本人人口 総人口
# 男女別・性比 人口性比 女 男 男女計 人口性比 女 男 男女計
# 年齢各歳
# 0歳 105.0 462.0 485.0 947.0 105.0 470.0 493.0 963.0
# 100歳以上 15.0 59.0 9.0 67.0 15.0 59.0 9.0 67.0
# 10歳 105.0 518.0 544.0 1061.0 105.1 523.0 549.0 1072.0
# 11歳 104.8 515.0 540.0 1054.0 104.8 520.0 545.0 1065.0
# 12歳 104.9 516.0 541.0 1057.0 104.9 521.0 546.0 1067.0
# 13歳 104.6 535.0 560.0 1095.0 104.6 540.0 565.0 1105.0
# 14歳 105.2 543.0 571.0 1115.0 105.2 548.0 576.0 1124.0
# 15歳 105.1 560.0 588.0 1148.0 105.1 565.0 594.0 1158.0
# 16歳 105.3 567.0 597.0 1163.0 105.3 572.0 602.0 1174.0
# 17歳 106.2 577.0 613.0 1189.0 106.2 583.0 619.0 1202.0
pivot_table()
を使うともっと簡単。
pivot_table()
では引数index
, columns
にリストで複数の列を指定できる。ただし、もし重複する要素があった場合はそれらの値を集計した結果(デフォルトでは平均値)になってしまうので注意。
df_pt = df.pivot_table(index='年齢各歳', columns=['人口', '男女別・性比'], values='value')
print(df_pt.head(10))
# 人口 日本人人口 総人口
# 男女別・性比 人口性比 女 男 男女計 人口性比 女 男 男女計
# 年齢各歳
# 0歳 105.0 462.0 485.0 947.0 105.0 470.0 493.0 963.0
# 100歳以上 15.0 59.0 9.0 67.0 15.0 59.0 9.0 67.0
# 10歳 105.0 518.0 544.0 1061.0 105.1 523.0 549.0 1072.0
# 11歳 104.8 515.0 540.0 1054.0 104.8 520.0 545.0 1065.0
# 12歳 104.9 516.0 541.0 1057.0 104.9 521.0 546.0 1067.0
# 13歳 104.6 535.0 560.0 1095.0 104.6 540.0 565.0 1105.0
# 14歳 105.2 543.0 571.0 1115.0 105.2 548.0 576.0 1124.0
# 15歳 105.1 560.0 588.0 1148.0 105.1 565.0 594.0 1158.0
# 16歳 105.3 567.0 597.0 1163.0 105.3 572.0 602.0 1174.0
# 17歳 106.2 577.0 613.0 1189.0 106.2 583.0 619.0 1202.0
print(df_pt.columns)
# MultiIndex(levels=[['日本人人口', '総人口'], ['人口性比', '女', '男', '男女計']],
# labels=[[0, 0, 0, 0, 1, 1, 1, 1], [0, 1, 2, 3, 0, 1, 2, 3]],
# names=['人口', '男女別・性比'])
マルチインデックスの行・列の選択については以下の記事を参照。上の階層から順に指定する際はタプルを使う。
print(df_pt.loc[:, ('日本人人口', '男')].head(10))
# 年齢各歳
# 0歳 485.0
# 100歳以上 9.0
# 10歳 544.0
# 11歳 540.0
# 12歳 541.0
# 13歳 560.0
# 14歳 571.0
# 15歳 588.0
# 16歳 597.0
# 17歳 613.0
# Name: (日本人人口, 男), dtype: float64
print(df_pt.loc[:, ('日本人人口', ['男', '女'])].head(10))
# 人口 日本人人口
# 男女別・性比 女 男
# 年齢各歳
# 0歳 462.0 485.0
# 100歳以上 59.0 9.0
# 10歳 518.0 544.0
# 11歳 515.0 540.0
# 12歳 516.0 541.0
# 13歳 535.0 560.0
# 14歳 543.0 571.0
# 15歳 560.0 588.0
# 16歳 567.0 597.0
# 17歳 577.0 613.0