Pandas は DataFrame のマージを含む、データを操作するための膨大なメソッドや関数を提供しています。
DataFrame をマージすることで、元のデータソースを変更することなく新しい DataFrame を作成したり、元のデータソースを変更したりすることができます。
SQLやそれに類する表形式のデータに慣れている人なら、DataFrameを結合して新しいDataFrameを作る「join」という言葉を知っていることでしょう。
初心者の場合、結合の種類(内側、外側、左、右)を完全に把握するのは難しいかもしれません。
このチュートリアルでは、例を使って結合の種類を説明します。
主な内容は merge()
と concat()
関数の使用法です。
しかし、できるだけ多くの実用的な選択肢を提供するために、他のマージ方法についても説明します。
このチュートリアルでは、Pandas バージョン 1.1.4 と NumPy バージョン 1.19.4 を使用しています。
merge() を使ってデータフレームをマージする
まず最初に、このチュートリアルの残りの部分で使用する DataFrame をセットアップしましょう。
df1` には、名前、メールアドレス、ID を含む架空のユーザーリストが含まれます。
import pandas as pd
df1 = pd.DataFrame({'user_id': ['id001', 'id002', 'id003', 'id004', 'id005', 'id006', 'id007'],
'first_name': ['Rivi', 'Wynnie', 'Kristos', 'Madalyn', 'Tobe', 'Regan', 'Kristin'],
'last_name': ['Valti', 'McMurty', 'Ivanets', 'Max', 'Riddich', 'Huyghe', 'Illis'],
'email': ['rvalti0@example.com', 'wmcmurty1@example.com', 'kivanets2@example.com',
'mmax3@example.com', 'triddich4@example.com', 'rhuyghe@example.com', 'killis4@example.com']
})
データベースを設計するとき、プロファイルの設定(背景色、アバター画像のリンク、フォントサイズなど)をユーザーデータ(メール、追加日など)とは別のテーブルに保存するのがよい習慣とされています。
これらのテーブルは、一対一のリレーションシップを持つことができます。
このシナリオをシミュレートするために、画像の URL とユーザー ID を含む df2
を作成して、同じことを行ってみます。
df2 = pd.DataFrame({'user_id': ['id001', 'id002', 'id003', 'id004', 'id005'],
'image_url': ['http://example.com/img/id001.png', 'http://example.com/img/id002.jpg',
'http://example.com/img/id003.bmp', 'http://example.com/img/id004.jpg',
'http://example.com/img/id005.png']
})
DataFrameはこのようになります。
# df1
user_id first_name last_name email
0 id001 Rivi Valti rvalti0@example.com
1 id002 Wynnie McMurty wmcmurty1@example.com
2 id003 Kristos Ivanets kivanets2@example.com
3 id004 Madalyn Max mmax3@example.com
4 id005 Tobe Riddich triddich4@example.com
5 id006 Regan Huyghe rhuyghe@example.com
6 id007 Kristin Illis killis4@example.com
#df2
user_id image_url
0 id001 http://example.com/img/id001.png
1 id002 http://example.com/img/id002.jpg
2 id003 http://example.com/img/id003.bmp
3 id004 http://example.com/img/id004.jpg
4 id005 http://example.com/img/id005.png
これらのDataFrameを merge()
関数で結合してみましょう。
まず、この関数が受け取ることができるオプションの一覧をご覧ください。
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=True,
suffixes=('_x', '_y'), copy=True, indicator=False,
validate=None)
これらのオプションのほとんどはデフォルト値を持ちますが、left と right は例外です。
この2つのパラメータは、これからマージするDataFrameの名前です。
この関数自体は新しいDataFrameを返すので、それを変数 df3_merged
に格納します。
Pythonシェルに以下のコードを入力してください。
df3_merged = pd.merge(df1, df2)
両方の DataFrame が同じ名前の user_id
カラムを持っているので、 merge()
関数は自動的にそのキーに一致する 2 つのテーブルを結合します。
もし、異なる名前のカラムが2つあれば、 left_on='left_column_name'
と right_on='right_column_name'
を使って、両方の DataFrame で明示的にキーを指定することができます。
それでは、変数 df3_merged
を出力して、その中身を見てみましょう。
user_id first_name last_name email image_url
0 id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png
元の df1
には 7 行あったのに、df3_merged
には 5 行しかないことに気がつくと思います。
howパラメータのデフォルト値が
innerに設定されている場合、新しい DataFrame は左と右の DataFrame の交点から生成されます。
したがって、どちらかのテーブルでuser_id` が欠落していると、マージされたDataFrameには含まれない。
これは、左右の行の位置が入れ替わっても同じです。
df3_merged = pd.merge(df2, df1)
結果は、やはり
user_id image_url first_name last_name email
0 id001 http://example.com/img/id001.png Rivi Valti rvalti0@example.com
1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com
2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com
3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com
4 id005 http://example.com/img/id005.png Tobe Riddich triddich4@example.com
IDが 'id006'
と 'id007'
のユーザーは、両方のテーブルで交差していないので、マージされたDataFrameの一部ではありません。
しかし、どちらかのDataFrameをメインDataFrameとして使用し、たとえすべての行が互いに交差していなくても、その行をすべて含めたい場合があります。
つまり、image_url
はオプションとして、すべてのユーザーを表示させたいということです。
どうやって?merge()を使うことで、
howパラメータに
‘left’` という引数を渡すことができます。
df_left_merge = pd.merge(df1, df2, how='left')
print(df_left_merge)
左結合では、左側のDataFrame (df1
) のすべての要素と右側のDataFrame (df2
) のすべての要素を取り込みました。
上記のコードを実行すると、このように表示されます。
user_id first_name last_name email image_url
0 id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png
5 id006 Regan Huyghe rhuyghe@example.com NaN
6 id007 Kristin Illis killis4@example.com NaN
左のDataFrameと一致する値がないセルは、NaN
で埋め尽くされています。
右結合を試してみましょう。
次のようなマージされたDataFrameを作成します。
df_right_merge = pd.merge(df1, df2, how='right')
print(df_right_merge)
予想通り、右結合は左のDataFrameから右のDataFrameにマッチする全ての値を返します。
user_id first_name last_name email image_url
0 id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png
df2のすべての行は
df1の値を持つので、この場合、右結合は
inner`結合と似ています。
外側joinについて見てみましょう。
左結合と外側結合の両方を行うために、DataFrameの場所を入れ替えて、2つの新しい変数を作成しましょう。
df_left = pd.merge(df2, df1, how='left', indicator=True)
df_outer = pd.merge(df2, df1, how='outer', indicator=True)
print(df_left)
print(df_outer)
左側のデータフレームは df2
で、右側のデータフレームは df1
であることを覚えておいてください。
how=’outer’` を使用すると、キーに一致するDataFrameがマージされますが、欠落している値や一致しない値も含まれます。
また、 indicator
フラグを追加して True
に設定すると、Pandas は DataFrame の末尾に _merge
というカラムを追加してくれます。
この列は、行が左、右、または両方のDataFrameで見つかったかどうかを教えてくれます。
df_left` 変数は以下のようなものです。
user_id image_url first_name last_name email _merge
0 id001 http://example.com/img/id001.png Rivi Valti rvalti0@example.com both
1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com both
2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com both
3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com both
4 id005 http://example.com/img/id005.png Tobe Riddich triddich4@example.com both
しかし、df_outer
はこのようなデータになっています。
user_id image_url first_name last_name email _merge
0 id001 http://example.com/img/id001.png Rivi Valti rvalti0@example.com both
1 id002 http://example.com/img/id002.jpg Wynnie McMurty wmcmurty1@example.com both
2 id003 http://example.com/img/id003.bmp Kristos Ivanets kivanets2@example.com both
3 id004 http://example.com/img/id004.jpg Madalyn Max mmax3@example.com both
4 id005 http://example.com/img/id005.png Tobe Riddich triddich4@example.com both
5 id006 NaN Regan Huyghe rhuyghe@example.com right_only
6 id007 NaN Kristin Illis killis4@example.com right_only
df_outerのデータフレームでは、
id006と
id007は右側のデータフレーム (この場合は
df1`) にのみ存在することに注意してください。
もし、場所を入れ替えずに左結合と外側結合を比較しようとすると、どちらも同じ結果になってしまうでしょう。
join() を使ってデータフレームを結合する
Pandasのインスタンスのメソッドである merge()
とは異なり、 join()
はDataFrame自身のメソッドです。
つまり、DataFrameのスタティックメソッドのように使うことができます。
DataFrame.join(other, on=None, how=’left’, lsuffix=”, rsuffix=”, sort=False)`です。
join()を呼び出したDataFrameは、左側のDataFrameになります。
引数other` の DataFrame は右側の DataFrame になります。
一方、 how
パラメータはハンドル引数 (left, right, outer, inner) のいずれかを取り、デフォルトでは left
がセットされています。
それでは、df2
を df1
に結合してみましょう。
df_join = df1.join(df2, rsuffix='_right')
print(df_join)
merge()関数と同様に、
join()関数も同じ名前のキー (カラム) を自動的にマッチングしようとします。
この例では、user_id` キーがそれにあたります。
上記のコードはこのように出力されます。
user_id first_name last_name email user_id_right image_url
0 id001 Rivi Valti rvalti0@example.com id001 http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com id002 http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com id003 http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com id004 http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com id005 http://example.com/img/id005.png
5 id006 Regan Huyghe rhuyghe@example.com NaN NaN
6 id007 Kristin Illis killis4@example.com NaN NaN
おそらく、user_id_right
という「重複したカラム」があることにお気づきでしょう。
このカラムを表示したくない場合は、user_id
カラムを両方のカラムのインデックスとして設定すれば、サフィックスなしで結合されます。
df_join_no_duplicates = df1.set_index('user_id').join(df2.set_index('user_id'))
print(df_join_no_duplicates)
このようにすることで、user_id
カラムを削除し、代わりにインデックスカラムとして設定することができます。
これにより、結果的にきれいなDataFrameが得られます。
first_name last_name email image_url
user_id
id001 Rivi Valti rvalti0@example.com http://example.com/img/id001.png
id002 Wynnie McMurty wmcmurty1@example.com http://example.com/img/id002.jpg
id003 Kristos Ivanets kivanets2@example.com http://example.com/img/id003.bmp
id004 Madalyn Max mmax3@example.com http://example.com/img/id004.jpg
id005 Tobe Riddich triddich4@example.com http://example.com/img/id005.png
id006 Regan Huyghe rhuyghe@example.com NaN
id007 Kristin Illis killis4@example.com NaN
Append() を使ってデータフレームをマージする。
Pandasの公式ドキュメントでも指摘されているように、concat()
とappend()
メソッドは新しいDataFrameのコピーを返すので、これらのメソッドを使いすぎるとプログラムのパフォーマンスに影響を与える可能性があります。
appendは、2つのDataFrameを行軸だけでマージしたい場合に非常に有効です。
つまり、列単位でデータを合わせるのではなく、2つのDataFrameの全ての行を含む新しいDataFrameを作りたいのです。
それでは、df2
をdf1
に追加して、その結果を表示してみましょう。
df_append = df1.append(df2, ignore_index=True)
print(df_append)
append()`を使用すると、どのキーでもDataFrameにマッチしません。
ただ、最初のデータフレームにもう一つのデータフレームを追加して、そのコピーを返します。
もしDataFrameの形状が一致しない場合、Pandasは一致しないセルをNaNに置き換えます。
2つのDataFrameを足し合わせた出力は以下のようになります。
user_id first_name last_name email image_url
0 id001 Rivi Valti rvalti0@example.com NaN
1 id002 Wynnie McMurty wmcmurty1@example.com NaN
2 id003 Kristos Ivanets kivanets2@example.com NaN
3 id004 Madalyn Max mmax3@example.com NaN
4 id005 Tobe Riddich triddich4@example.com NaN
5 id006 Regan Huyghe rhuyghe@example.com NaN
6 id007 Kristin Illis killis4@example.com NaN
7 id001 NaN NaN NaN http://example.com/img/id001.png
8 id002 NaN NaN NaN http://example.com/img/id002.jpg
9 id003 NaN NaN NaN http://example.com/img/id003.bmp
10 id004 NaN NaN NaN http://example.com/img/id004.jpg
11 id005 NaN NaN NaN http://example.com/img/id005.png
ほとんどのユーザーは append()
よりも concat()
を選びます。
なぜなら、concat()` はキーマッチングと軸のオプションも提供しているからです。
concat() を使ってデータフレームを結合する
連結は、merge()
や join()
に比べて少し柔軟です。
なぜなら、縦方向 (行単位) または横方向 (列単位) にデータフレームを結合することができるからです。
トレードオフは、マッチしないデータはすべて破棄されることです。
以下は、パラメータを含む完全な関数です。
pandas.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,
levels=None, names=None, verify_integrity=False, sort=False, copy=True)
以下は、 concat()
関数で最もよく使用されるパラメータです。
-
objs
は、連結される DataFrame オブジェクト ([df1, df2, …]) のリストです。 -
axis
は連結の方向を指定します。0は行単位、
1` は列単位です。 -
join
はinner
(交差) またはouter
(結合) のいずれかになります。 - デフォルトでは
ignore_index
はFalse
に設定されており、インデックス値を元の DataFrame のまま保持することができます。True` に設定すると、元の値を無視し、インデックス値を順番に再割り当てする。 -
keys
を使用すると、階層型インデックスを構築することができます。これは、DataFrameの左外側に追加されるもう一つのレベルのインデックスで、値が一意でない場合にインデックスを区別するのに役立つと考えることができます。
それでは、df2
と同じカラムタイプのDataFrameを新規に作成します。
ただし、id006
とid007
には image_url
が含まれるようにします。
df2_addition = pd.DataFrame({'user_id': ['id006', 'id007'],
'image_url': ['http://example.com/img/id006.png',
'http://example.com/img/id007.jpg']
})
df2と
df2_additionを行単位で結合するには、
objs` パラメータにリストで渡して、生成された DataFrame を新しい変数に代入します。
df_row_concat = pd.concat([df2, df2_addition])
print(df_row_concat)
無事、欠損値を埋めることができました。
user_id image_url
0 id001 http://example.com/img/id001.png
1 id002 http://example.com/img/id002.jpg
2 id003 http://example.com/img/id003.bmp
3 id004 http://example.com/img/id004.jpg
4 id005 http://example.com/img/id005.png
0 id006 http://example.com/img/id006.png
1 id007 http://example.com/img/id007.jpg
しかし、一番左の列のインデックスを見てください。
インデックス 0
と 1
は繰り返されています。
全く新しいユニークなインデックス値を取得するために、 ignore_index
パラメータに True
を渡します。
df_row_concat = pd.concat([df2, df2_addition], ignore_index=True)
これで、 df_row_concat
はユニークなインデックスを持つようになりました。
user_id image_url
0 id001 http://example.com/img/id001.png
1 id002 http://example.com/img/id002.jpg
2 id003 http://example.com/img/id003.bmp
3 id004 http://example.com/img/id004.jpg
4 id005 http://example.com/img/id005.png
5 id006 http://example.com/img/id006.png
6 id007 http://example.com/img/id007.jpg
先ほど説明したように、連結は水平方向にも垂直方向にも行うことができます。
2つのDataFrameを列方向に結合するには、axis
の値をデフォルトの0
から1
に変更する必要があります。
df_column_concat = pd.concat([df1, df_row_concat], axis=1)
print(df_column_concat)
2つのテーブルをあるキーでマッチさせるマージのようにはいかないことにお気づきでしょう。
user_id first_name last_name email user_id image_url
0 id001 Rivi Valti rvalti0@example.com id001 http://example.com/img/id001.png
1 id002 Wynnie McMurty wmcmurty1@example.com id002 http://example.com/img/id002.jpg
2 id003 Kristos Ivanets kivanets2@example.com id003 http://example.com/img/id003.bmp
3 id004 Madalyn Max mmax3@example.com id004 http://example.com/img/id004.jpg
4 id005 Tobe Riddich triddich4@example.com id005 http://example.com/img/id005.png
5 id006 Regan Huyghe rhuyghe@example.com id006 http://example.com/img/id006.png
6 id007 Kristin Illis killis4@example.com id007 http://example.com/img/id007.jpg
もし、右のDataFrameに user_id
カラムがなかったとしても、この連結は同じ結果を返します。
concat()` 関数は、DataFrame のインデックスの値とテーブルの形状を考慮して、2つの DataFrame をつなぎ合わせます。
この関数は merge()
や join()
のようなキーマッチングを行うものではありません。
join` パラメータを変更して、いろいろな結合の組み合わせを試してみてください。
combine_first() と update() によるデータフレームの結合
場合によっては、DataFrame を別の DataFrame と結合することで、DataFrame の欠損データを埋めたいことがあります。
そうすることで、最初のDataFrameの欠損していない値をすべて保持しながら、すべての NaN
値を2番目のDataFrameの欠損していない値(もしあれば)に置き換えることができます。
この例では、NumPyをインポートして NaN
値を使用することにします。
Pandasを pip
と共にインストールした場合、NumPyは既にインストールされているはずです。
Pythonのシェルやスクリプトファイルに以下のコードを入力します。
import numpy as np
df_first = pd.DataFrame({'COL 1': ['X', 'X', np.nan],
'COL 2': ['X', np.nan, 'X'],
'COL 3': [np.nan, 'X', 'X']},
index=range(0, 3))
df_second = pd.DataFrame({'COL 1': [np.nan, 'O', 'O'],
'COL 2': ['O', 'O', 'O']},
index=range(0, 3))
print(df_first)
print(df_second)
df_first` DataFrame は3つのカラムを持ち、それぞれに1つの欠損値があります。
COL 1 COL 2 COL 3
0 X X NaN
1 X NaN X
2 NaN X X
一方、df_second
は2つのカラムを持ち、最初のカラムに1つの欠損値があります。
COL 1 COL 2
0 NaN O
1 O O
2 O O
df_secondを使って、
df_first` の欠損値に対応するすべての値をパッチすることができます。
df_tictactoe = df_first.combine_first(df_second)
print(df_tictactoe)
前述のように、 combine_first()
メソッドを使用すると、インデックス順に NaN
値のみを置換し、最初の DataFrame の欠損していない値はすべてそのまま残します。
COL 1 COL 2 COL 3
0 X X NaN
1 X O X
2 O X X
一方、 df_first
の値を df_second
の値で上書きしたい場合は (NaN かどうかに関係なく)、 update()
メソッドを使用することになります。
まず、別のDataFrameを追加してみましょう。
df_third = pd.DataFrame({'COL 1': ['O'], 'COL 2': ['O'], 'COL 3': ['O']})
print(df_third)
形状は (1, 3) – 1 行 3 列 (インデックスを除く) です。
COL 1 COL 2 COL 3
0 O O O
それでは、df_third
の値で df_first
を更新してみましょう。
df_first.update(df_third)
print(df_first)
combine_first()とは異なり、
update()は新しい DataFrame を返さないことに注意してください。
これは、df_first` をインプレースで変更し、対応する値を変更します。
COL 1 COL 2 COL 3
0 O O O
1 X NaN X
2 NaN X X
update()関数の
overwriteパラメータは、デフォルトで
Trueに設定されています。
このため、NaN値だけでなく、対応するすべての値が変更されます。
これをFalseに変更することで、
NaN` 値のみを置き換えることができます。
df_tictactoe.update(df_first, overwrite=False)
print(df_tictactoe)
以下は、df_tictactoe
DataFrameの最終的な状態です。
COL 1 COL 2 COL 3
0 X X O
1 X O X
2 O X X
値の更新に成功しただけでなく、三目並べゲームに勝つこともできました。
結論
PandasはDataFrameをマージするための強力なツールを提供しています。
しかし、いつ何を使うか決めるのは難しいかもしれません。
ほとんどの場合は merge()
関数で十分ですが、場合によっては concat()
を使って行単位でマージしたり、 join()
を使って接尾辞を付けたり、 combine_first()
や update()
を使って欠損値を除去したいかもしれません。
さらに append()
でデータ行を追加することもできます。
自分が一番使いやすい、そしてそのタスクに最適な関数を使いましょう。