Python pandas 図でみる データ連結 / 結合処理
なんかぼやぼやしているうちにひさびさの pandas エントリになってしまった。基本的な使い方については網羅したい気持ちはあるので、、、。
今回は データの連結 / 結合まわり。この部分 公式ドキュメント がちょっとわかりにくいので改訂したいなと思っていて、自分の整理もかねて書きたい。
公式の方はもう少し細かい使い方も載っているのだが、特に重要だろうというところだけをまとめる。
連結 / 結合という用語は以下の意味で使っている。まず憶えておいたほうがよい関数、メソッドは以下の 4 つだけ。
- 連結: データの中身をある方向にそのままつなげる。
pd.concat,DataFrame.append - 結合: データの中身を何かのキーの値で紐付けてつなげる。
pd.merge,DataFrame.join
連結 (concatenate)
柔軟な連結 pd.concat
ふたつの DataFrame の連結は pd.concat で行う ( DataFrame.concat ではない)。元データとする DataFrame を df1、df2 としてそれぞれ以下のように定義する。
import pandas as pd df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}, index=[0, 1, 2, 3]) df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'], 'B': ['B4', 'B5', 'B6', 'B7'], 'C': ['C4', 'C5', 'C6', 'C7'], 'D': ['D4', 'D5', 'D6', 'D7']}, index=[4, 5, 6, 7])
基本的な連結
この 2 つのデータに対する pd.concat の結果は下図 Result で示すもの = 縦方向の連結になる。pd.concat の引数は連結したい DataFrame のリスト [df1, df2] になる。Result の上半分が df1, 下半分が df2 に対応している。
pd.concat([df1, df2])

引数には 3つ以上の DataFrame からなるリストを渡してもよい。結果は 各 DataFrame が順に縦方向に連結されたものになる。
df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
'B': ['B8', 'B9', 'B10', 'B11'],
'C': ['C8', 'C9', 'C10', 'C11'],
'D': ['D8', 'D9', 'D10', 'D11']},
index=[8, 9, 10, 11])
pd.concat([df1, df2, df3])

列名が異なる場合の連結
上ふたつの例では連結する各 DataFrame の 列名 ( columns )はすべて ['A', 'B', 'C', 'D'] で同一だったので気にする必要はなかったが、内部的には 各列は 列名で紐付けされてから連結されている。
以下のように 列名が異なる場合、元データ両方に存在する 列 ['B', 'D'] はそれそれ紐付けられて縦連結され、片方にしか存在しない列は 空の部分が NaN でパディングされる。
df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'],
'D': ['D2', 'D3', 'D6', 'D7'],
'F': ['F2', 'F3', 'F6', 'F7']},
index=[2, 3, 6, 7])
pd.concat([df1, df4])

補足 連結方向のラベル ( 縦方向の場合 行名 = index ) 同士は紐付けされず 常にそのまま連結される。出力をみると、 df1 の index である [0, 1, 2, 3] と df2 の index である [2, 3, 6, 7] は一部重複しているが、紐付けされず元の順序のまま連結されている。連結方向のラベルを重複させたくない場合の処理は後述。
横方向の連結
横方向に連結したい場合は axis=1 を指定。このとき 直前の例とは逆に 紐付けは 連結方向でないラベル = index について行われる。 連結方向のラベルにあたる columns はそのまま維持される。
pd.concat([df1, df4], axis=1)

連結処理の指定
上でみたとおり、pd.concat による連結では、データを "連結方向でないラベル" で紐付けしてから 連結していた。この紐付けは元データをすべて残す形 = 完全外部結合のような形で行われている。
引数の各データに共通のラベルのみを残して連結したい場合は join='inner' を指定する。下の例では横連結を指定しているため、共通の index である [2, 3] に対応する行のみが残る。
pd.concat([df1, df4], axis=1, join='inner')

また、紐付け時に特定のラベルのみを残したい場合もある。そのときは join_axes で残したいラベルの名前を指定すればよい。
例えば axis=1 横方向に連結するとき、join_axes に ひとつめの DataFrame の index を指定すると、それらだけが残るため 左外部結合のような処理になり、
pd.concat([df1, df4], axis=1, join_axes=[df1.index])

ふたつめの DataFrame の index を指定すると 右外部結合のような処理になる。
pd.concat([df1, df4], axis=1, join_axes=[df4.index])

連結方向のラベルの指定
pd.concat は既定では 連結方向のラベル ( 縦連結の場合は index、横連結の場合は columns ) に対しては特に変更を行わない。そのため、上の例のように 連結結果に同名のラベルが重複してしまうことがある。
こういう場合、連結元のデータごとに keys キーワードで指定したラベルを追加で付与することができる。このとき、結果は 複数のレベルをもつ MultiIndex になる ( MultiIndex については別途)。これは図だけでもわかりにくいので テキストでの出力も添付。
pd.concat([df1, df4], axis=1, keys=['X', 'Y']) # X Y # A B C D B D F # 0 A0 B0 C0 D0 NaN NaN NaN # 1 A1 B1 C1 D1 NaN NaN NaN # 2 A2 B2 C2 D2 B2 D2 F2 # 3 A3 B3 C3 D3 B3 D3 F3 # 6 NaN NaN NaN NaN B6 D6 F6 # 7 NaN NaN NaN NaN B7 D7 F7

もしくは、ignore_index=True を指定して 連結方向のラベルを 0 から振りなおすことができる。これは縦連結のときに index を連番で振りなおす場合に便利。
pd.concat([df1, df4], ignore_index=True)

縦方向のシンプルな連結 DataFrame.append
pd.concat でたいていの連結はできる。うち、よく使う 縦方向の連結については DataFrame.append でよりシンプルに書ける。
df1.append(df2)

引数には DataFrame のリストも渡せる。
df1.append([df2, df4])

また、データに一行追加したい、なんて場合も DataFrame.append。このとき、引数は 追加する行に対応した Series になる。
このとき、連結対象の DataFrame の columns と Series の index どうしが紐付けられて連結される。そのため、行として追加する Series は以下のような形で作る。Series が name 属性を持っている場合は 連結後の行の index は name で指定されたもの (ここでは 10 ) になる。
s1 = pd.Series(['X0', 'X1', 'X2', 'X3'], index=['A', 'B', 'C', 'D'], name=10) df1.append(s1)

name 属性のない Series を連結したい場合は ignore_index=True を指定しないとエラーになる。
s2 = pd.Series(['X0', 'X1', 'X2', 'X3'], index=['A', 'B', 'C', 'D']) # NG! df1.append(s2) # TypeError: Can only append a Series if ignore_index=True or if the Series has a name # OK df1.append(s2, ignore_index=True)

補足 append の際、内部では毎回 元データも含めた全体のコピー処理が走るので、ループで一行ずつ追加するような処理は避けたほうがよい。
結合 (merge)
列の値による結合 pd.merge
ふたつの DataFrame の結合は pd.merge もしくは DataFrame.merge で行う。結合もとの DataFrame を left、right としてそれぞれ以下のように定義する。
left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key': ['K1', 'K3', 'K5', 'K7'],
'C': ['C1', 'C3', 'C5', 'C7'],
'D': ['D1', 'D3', 'D5', 'D7']},
index=[1, 3, 5, 7])
この例では 作成した DataFrame 中の key カラムを結合時のキーとし、この列の値が同じ行どうしを結合したい。結合時のキーとなる列名は on キーワードで指定する。既定では内部結合となり、両方のデータに共通の ['K1', 'K3'] に対応する行どうしが結合されて残る。
pd.merge(left, right, on='key')

結合方法は how キーワードで指定する。指定できるのは、
inner: 既定。内部結合。両方のデータに含まれるキーだけを残す。left: 左外部結合。ひとつめのデータのキーをすべて残す。right: 右外部結合。ふたつめのデータのキーをすべて残す。outer: 完全外部結合。すべてのキーを残す。
それぞれの出力を順に図示する。
pd.merge(left, right, on='key', how='left')

pd.merge(left, right, on='key', how='right')

pd.merge(left, right, on='key', how='outer')

複数のキーによる結合
キーとして複数の列を指定したい場合は、on キーワードにキーとする列名のリストを渡せばよい。
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
pd.merge(left, right, on=['key1', 'key2'])

index による結合 DataFrame.join
各データの index をキーとして結合したい場合は、DataFrame.join が便利。既定は左外部結合となり、結合方法は how で変更できる。指定できるオプションは pd.merge と同じ。
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
'B': ['B0', 'B1', 'B2']},
index=['K0', 'K1', 'K2'])
right = pd.DataFrame({'C': ['C0', 'C2', 'C3'],
'D': ['D0', 'D2', 'D3']},
index=['K0', 'K2', 'K3'])
left.join(right)

left.join(right, how='inner')

left.join(right, how='right')

left.join(right, how='outer')

補足 pd.merge でも left_index ならびに right_index キーワードによって index をキーとした結合はできる。
まとめ
pandas でのデータ連結 / 結合まわりを整理した。これ以外の データ変形 (行持ち / 列持ち変換とか) は R の {dplyr}、{tidyr} との対比でまとめたやつがあるのだが、列名 や行名が複数のレベルを持つ = MultiIndex の場合など pandas 固有のものもあるのでまた別途。
- Python pandas でのグルーピング/集約/変換処理まとめ - StatsFragments
- R dplyr, tidyr でのグルーピング/集約/変換処理まとめ - StatsFragments
2015/05/12追記
公式ドキュメントに反映した。こちらのほうが網羅的。
Merge, join, and concatenate — pandas 0.16.2 documentation

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (10件) を見る