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件) を見る