note.nkmk.me

pandasでstack, unstack, pivotを使ってデータを整形

Date: 2018-05-25 / tags: Python, pandas

pandasのstack(), unstack(), pivot()はデータのピボット処理を行うメソッド。

列方向に並んだデータを行方向に並べ替えたり、行方向に並んだデータを列方向に並べ替えたりして、データの構造を再形成できる。

long型(積み上げ型、縦型、縦持ち)とwide型(横型、横持ち)のデータ構造を相互に変換することが可能。

ここでは以下の内容について説明する。

  • 列から行へピボット: stack()
  • 行から列へピボット: unstack()
    • pandas.DataFrameunstack()
  • 行と列を指定してピボット(再形成): 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.DataFramecolumnsがマルチインデックスの場合の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

デフォルトではマルチインデックスの一番下の階層(一番内側の階層)に対して処理される。対象の階層は引数lebelで指定可能。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年の日本の人口のデータを例とする。

e-Stat APIを使ったダウンロードについては以下の記事を参照。

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
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事