0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pandasのmergeでJOINの種類を勉強する

0
Posted at

pandas で DataFrame を処理する際、 merge に苦手意識がありうまく使えないでいました。 SQL で簡単な INNER JOIN は経験があるものの、キーが重複していたり LEFT/RIGHT JOIN を使ったりしたらどうなるのかは理解できていなく、変なデータができてしまうのではないかという心配がありました。

うまく使えれば効率よく処理を書けるはずなので、 inner や outer などの種類によってどのようなデータが作られるのか、サンプルデータで試しながら基礎を勉強します。特に、キーが重複する場合や存在しない場合の動きを見れるようにします。

使用する pandas バージョン:3.0.3

サンプルデータ

merge を試す2つの DataFrame a, b について、同じキーを持つ行が a には M 個、 b には N 個だけ存在するようにデータを用意します(M, N ∈ {0, 1, 2})。このキーの名前は aMbN と機械的に付けます。

これにより、 M:N という組み合わせを 1:1 や 0:2 など網羅的に試せるほか、結合後にキーを見るだけで結合前の個数をすぐ特定できます。

import pandas as pd

a = pd.DataFrame(
  [
    ["a1b0", 1],
    ["a1b1", 1],
    ["a1b2", 1],
    ["a2b0", 1], ["a2b0", 2],
    ["a2b1", 1], ["a2b1", 2],
    ["a2b2", 1], ["a2b2", 2],
  ],
  columns=["key", "val_a"]
)
b = pd.DataFrame(
  [
    ["a0b1", 1],
    ["a1b1", 1],
    ["a2b1", 1],
    ["a0b2", 1], ["a0b2", 2],
    ["a1b2", 1], ["a1b2", 2],
    ["a2b2", 1], ["a2b2", 2],
  ],
  columns=["key", "val_b"]
)
DataFrame を表示
>>> a
    key  val_a
0  a1b0      1
1  a1b1      1
2  a1b2      1
3  a2b0      1
4  a2b0      2
5  a2b1      1
6  a2b1      2
7  a2b2      1
8  a2b2      2

>>> b
    key  val_b
0  a0b1      1
1  a1b1      1
2  a2b1      1
3  a0b2      1
4  a0b2      2
5  a1b2      1
6  a1b2      2
7  a2b2      1
8  a2b2      2

JOINの種類

以下のものを試し、 how の違いで結合がどのようになるのか見ていきます。

a.merge(b, how="inner", on="key")
a.merge(b, how="outer", on="key")
a.merge(b, how="left" , on="key")
a.merge(b, how="right", on="key")

a.merge(b, how="cross")

a.merge(b, how="left_anti" , on="key")
a.merge(b, how="right_anti", on="key")

inner

how を指定しなかった場合のデフォルトです。

>>> a.merge(b, how="inner", on="key")
    key  val_a  val_b
0  a1b1      1      1
1  a1b2      1      1
2  a1b2      1      2
3  a2b1      1      1
4  a2b1      2      1
5  a2b2      1      1
6  a2b2      1      2
7  a2b2      2      1
8  a2b2      2      2

列は a, b にあったものが合体しています。 on に指定した列 key は1個に纏まっています。

キーの値でグループ分けして、行の個数や内容を見てみます。

  • a1b1 は元の DataFrame a, b にあったものをそのままマージした形です
  • a1b2a だと 1 個でしたが、結合後は b と同じ 2 個に増えています
  • a2b2 のキーを見ると、 [val_a, val_b] は [1, 1], [1, 2], [2, 1], [2, 2] と全ての組み合わせが登場しています
  • a0b2 のような片方に含まれないキーは消えています

つまり、 M:N のデータを持つキーは M×N 行のデータになります。

outer

>>> a.merge(b, how="outer", on="key")
     key  val_a  val_b
0   a0b1    NaN    1.0
1   a0b2    NaN    1.0
2   a0b2    NaN    2.0
3   a1b0    1.0    NaN
4   a1b1    1.0    1.0
5   a1b2    1.0    1.0
6   a1b2    1.0    2.0
7   a2b0    1.0    NaN
8   a2b0    2.0    NaN
9   a2b1    1.0    1.0
10  a2b1    2.0    1.0
11  a2b2    1.0    1.0
12  a2b2    1.0    2.0
13  a2b2    2.0    1.0
14  a2b2    2.0    2.0

前節の inner と比べると、 M または N が 0 のキーが追加されていて、 val_a, val_b のうち存在しないほうは NaN となっています(この影響で他のデータも整数から浮動小数点数に変化していますが、今回は無視します)。 M, N の両方が1以上のキーについては、 inner と同じ M×N 行のデータです。

もし結合前からデータを埋めたと捉えるなら、 a, b それぞれが、存在しない各キーについて欠損値データを1行ずつ加えたことになります。

データを埋めてから INNER JOIN してみる

a には a0b1a0b2 を、 b には a1b0a2b0 を加えてみます。

a_ = pd.DataFrame(
  [
    ["a0b1", None],
    ["a0b2", None],
    ["a1b0", 1],
    ["a1b1", 1],
    ["a1b2", 1],
    ["a2b0", 1], ["a2b0", 2],
    ["a2b1", 1], ["a2b1", 2],
    ["a2b2", 1], ["a2b2", 2],
  ],
  columns=["key", "val_a"]
)
b_ = pd.DataFrame(
  [
    ["a1b0", None],
    ["a2b0", None],
    ["a0b1", 1],
    ["a1b1", 1],
    ["a2b1", 1],
    ["a0b2", 1], ["a0b2", 2],
    ["a1b2", 1], ["a1b2", 2],
    ["a2b2", 1], ["a2b2", 2],
  ],
  columns=["key", "val_b"]
)

これらのデータを INNER JOIN すると、以下のように outer と同じ結果が得られます。例えば a0b2 は元々 0:2 でしたが、事前に1行加えたことで 1:2 になり2行できています。

>>> a_.merge(b_, how="inner", on="key")
     key  val_a  val_b
0   a0b1    NaN    1.0
1   a0b2    NaN    1.0
2   a0b2    NaN    2.0
3   a1b0    1.0    NaN
4   a1b1    1.0    1.0
5   a1b2    1.0    1.0
6   a1b2    1.0    2.0
7   a2b0    1.0    NaN
8   a2b0    2.0    NaN
9   a2b1    1.0    1.0
10  a2b1    2.0    1.0
11  a2b2    1.0    1.0
12  a2b2    1.0    2.0
13  a2b2    2.0    1.0
14  a2b2    2.0    2.0

left, right

outer が分かればこれらは楽です。

>>> a.merge(b, how="left", on="key")
     key  val_a  val_b
0   a1b0      1    NaN
1   a1b1      1    1.0
2   a1b2      1    1.0
3   a1b2      1    2.0
4   a2b0      1    NaN
5   a2b0      2    NaN
6   a2b1      1    1.0
7   a2b1      2    1.0
8   a2b2      1    1.0
9   a2b2      1    2.0
10  a2b2      2    1.0
11  a2b2      2    2.0

>>> a.merge(b, how="right", on="key")
     key  val_a  val_b
0   a0b1    NaN      1
1   a1b1    1.0      1
2   a2b1    1.0      1
3   a2b1    2.0      1
4   a0b2    NaN      1
5   a0b2    NaN      2
6   a1b2    1.0      1
7   a1b2    1.0      2
8   a2b2    1.0      1
9   a2b2    2.0      1
10  a2b2    1.0      2
11  a2b2    2.0      2

outer と比べると、指定した左(a)または右(b)では元のデータを尊重し NaN を追加しないようになっています。

cross

他と異なり、 on などキーに関する指定があるとエラーとなります。

>>> a.merge(b, how="cross", on="key")
pandas.errors.MergeError: Can not pass on, right_on, left_on or set right_index=True or left_index=True

指定をやめると大量の行が作られます。

>>> a.merge(b, how="cross")
   key_x  val_a key_y  val_b
0   a1b0      1  a0b1      1
1   a1b0      1  a1b1      1
2   a1b0      1  a2b1      1
3   a1b0      1  a0b2      1
4   a1b0      1  a0b2      2
..   ...    ...   ...    ...
76  a2b2      2  a0b2      2
77  a2b2      2  a1b2      1
78  a2b2      2  a1b2      2
79  a2b2      2  a2b2      1
80  a2b2      2  a2b2      2

[81 rows x 4 columns]

これは両方のデータの行の組み合わせを全て作っているためです。今回は9行同士なので 9×9 = 81 行となります。

ここからキーが一致するものだけ抽出すると、 inner と同様の結果が得られます。

>>> a.merge(b, how="cross").query("key_x == key_y")
   key_x  val_a key_y  val_b
10  a1b1      1  a1b1      1
23  a1b2      1  a1b2      1
24  a1b2      1  a1b2      2
47  a2b1      1  a2b1      1
56  a2b1      2  a2b1      1
70  a2b2      1  a2b2      1
71  a2b2      1  a2b2      2
79  a2b2      2  a2b2      1
80  a2b2      2  a2b2      2

left_anti, right_anti

pandas 3.0 で追加されたということで、この機会に試してみます。

>>> a.merge(b, how="left_anti", on="key")
    key  val_a  val_b
0  a1b0      1    NaN
4  a2b0      1    NaN
5  a2b0      2    NaN

>>> a.merge(b, how="right_anti", on="key")
    key  val_a  val_b
0  a0b1    NaN      1
4  a0b2    NaN      1
5  a0b2    NaN      2

left, right の結果のうち、相手が存在しないものだけが抽出されています。 inner にあるものを取り除いたとも言えます。

まとめ

pandas で DataFrame の merge を色々と試し、 JOIN の種類について勉強しました。

cross を除くと、各 JOIN は同じキーの中で左右のデータを組み合わせて新しいデータを作ります。

inner と outer (および left/right)の違いは、キーが片方にしか無いところに現れます。

  • 両方にあるキーについては同じで、元が M:N なら M×N 行のデータになります
  • 片方にしか無いキーは、
    • inner なら消えます(M×0 行または 0×N 行)
    • outer なら欠損値データで補います(M×1 行または 1×N 行)
    • left/right なら相手側だけ欠損値データで補います
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?