pandas

【pandas】DataFrameを結合する方法 ~ merge, join ~

【pandas】DataFrameを結合する方法 _ merge, join _

pandasのDataFrameを結合する方法について解説します。

DataFrameの結合

データを分析する際にはデータソースとして、DBやcsvなどいろいろなデータソースからデータを取得して分析することがほとんどです。この時、各データを同じ意味を表すキー列をもとに結合してから分析することで様々な意味ある分析をすることができるようになるため、データをうまく結合していくことは非常に重要なデータ操作です。

pandasでは、DataFrameの結合方法としてmerge関数やjoinメソッドが用意されています。

以降では以下図に示したように製品一覧と関連する情報を結合する例を用いてDataFrameのmerge, joinを使用した結合例を紹介します。

DataFrameの結合 merge join

この例では、各製品シリアル一覧と関連する品質情報実績、顧客情報、製品特徴のデータがあるものとします。

製造メーカーなどでは生産管理システムのマスタや製造実績、品質管理システムの品質実績など、いろいろなシステムのデータをつなげて分析したいといったことがよくあります。実際の現場で使われているデータはもっと複雑なデータが多いかと思いますが、上記のような簡単データで結合のイメージをつかんでもらおうと思います。

Note

merge関数の公式ドキュメントの記載はこちらを参照してください。
joinメソッドの公式ドキュメントの記載はこちらを参照してください。

merge関数によるDataFrameの結合の基本

データベースの扱いに慣れている人であればわかる人が多いかと思いますが、データの関係性として「1対1」「多対1」「多対多」という関係性があります。以降ではそれぞれの関係性のデータを例に使いながら、merge関数を使用方法や動作を説明します。

1対1の結合

DataFrameの結合 merge (1対1)

まずは、「1対1」の関係からです。上記の例で「serial_no」をキー列として見てみると、各レコード(各行)は1対1に対応していることがわかります。この例では製品一覧と品質情報をserial_noで結合することで、各製品シリアルの品質情報を一つの表として確認することができるようになります。

具体的にそれぞれのDataFrameを用意して、merge関数で結合する例を見てみましょう。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)

# 品質情報
quality_result = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'quality': [100, 200, 300, 400, 500]
     }
)

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'品質実績(quality_result):\n{quality_result}\n')

# 製品一覧と品質情報をマージ(1対1)
result_df = pd.merge(product_list, quality_result)

print(f'製品一覧と品質情報をマージ(1対1):\n{result_df}')
【実行結果】
製品一覧(product_list):
  serial_no customer_id product_code
0      A001       cid_1          p_a
1      B001       cid_1          p_b
2      C001       cid_2          p_c
3      C002       cid_2          p_c
4      D001       cid_3          p_d

品質実績(quality_result):
  serial_no  quality
0      A001      100
1      B001      200
2      C001      300
3      C002      400
4      D001      500

製品一覧と品質情報をマージ(1対1):
  serial_no customer_id product_code  quality
0      A001       cid_1          p_a      100
1      B001       cid_1          p_b      200
2      C001       cid_2          p_c      300
3      C002       cid_2          p_c      400
4      D001       cid_3          p_d      500

merge関数は、引数として結合するDataFrameを指定します。結果としては、結合されたDataFrameが返ってきます。merge関数は、自動的に1つ以上の同じ名前の列名を探してキーにして結合をしてくれます。今回の例では「serial_no」がキーになります。

多対1の結合

DataFrameの結合 merge (多対1)

次に、「多対1」の関係性です。上記の例で「customer_id」をキー列として見てみると、製品一覧の複数のレコード(行)に1つの顧客情報のレコード(行)が対応しているものがあります。このような関係を「多対1」といいます。この例では製品一覧と顧客情報をcustomer_idで結合することで、製品一覧の各製品がどこの顧客の製品なのかを確認することができるようになります。

具体的にそれぞれのDataFrameを用意して、merge関数で結合する例を見てみましょう。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)

# 顧客情報
customer_info = pd.DataFrame(
    {'customer_id': ['cid_1', 'cid_2', 'cid_3'],
     'customer_name': ['顧客1', '顧客2', '顧客3']
     }
)

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'顧客情報(customer_info):\n{customer_info}\n')

# 製品一覧と顧客情報をマージ(多対1)
result_df = pd.merge(product_list, customer_info)

print(f'製品一覧と顧客情報をマージ(多対1):\n{result_df}')
製品一覧(product_list):
  serial_no customer_id product_code
0      A001       cid_1          p_a
1      B001       cid_1          p_b
2      C001       cid_2          p_c
3      C002       cid_2          p_c
4      D001       cid_3          p_d

顧客情報(customer_info):
  customer_id customer_name
0       cid_1           顧客1
1       cid_2           顧客2
2       cid_3           顧客3

製品一覧と顧客情報をマージ(多対1):
  serial_no customer_id product_code customer_name
0      A001       cid_1          p_a           顧客1
1      B001       cid_1          p_b           顧客1
2      C001       cid_2          p_c           顧客2
3      C002       cid_2          p_c           顧客2
4      D001       cid_3          p_d           顧客3

結果を見てみるとcustomer_idをキーとして、customer_name列を結合したDataFrameを取得できていることがわかるかと思います。

多対多の結合

DataFrameの結合 merge (多対多)

次に、「多対多」の関係性です。上記の例で「product_code」をキーにして見てみると、製品一覧の複数のレコード(行)に複数の製品特徴のレコード(行)が対応しているものがあります。このような関係を「多対多」といいます。この例では製品一覧と製品特徴をproduct_codeで結合することで、製品一覧の各製品がどういった製品特徴を持っているかを確認できるようになります。

具体的にそれぞれのDataFrameを用意して、merge関数で結合する例を見てみましょう。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)

# 製品特徴
product_feature = pd.DataFrame(
    {'product_code': ['p_a', 'p_a', 'p_b', 'p_b', 'p_c', 'p_c',
                      'p_d', 'p_d', 'p_d'],
     'feature': ['a-1', 'a-2', 'b-1', 'b-2', 'c-1', 'c-2',
                 'd-1', 'd-2', 'd-3']
     }
)

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'製品特徴(product_feature):\n{product_feature}\n')

# 製品一覧と製品特徴をマージ(多対多)
result_df = pd.merge(product_list, product_feature)

print(f'製品一覧と製品特徴をマージ(多対多):\n{result_df}')
【実行結果】
製品一覧(product_list):
  serial_no customer_id product_code
0      A001       cid_1          p_a
1      B001       cid_1          p_b
2      C001       cid_2          p_c
3      C002       cid_2          p_c
4      D001       cid_3          p_d

製品特徴(product_feature):
  product_code feature
0          p_a     a-1
1          p_a     a-2
2          p_b     b-1
3          p_b     b-2
4          p_c     c-1
5          p_c     c-2
6          p_d     d-1
7          p_d     d-2
8          p_d     d-3

製品一覧と製品特徴をマージ(多対多):
   serial_no customer_id product_code feature
0       A001       cid_1          p_a     a-1
1       A001       cid_1          p_a     a-2
2       B001       cid_1          p_b     b-1
3       B001       cid_1          p_b     b-2
4       C001       cid_2          p_c     c-1
5       C001       cid_2          p_c     c-2
6       C002       cid_2          p_c     c-1
7       C002       cid_2          p_c     c-2
8       D001       cid_3          p_d     d-1
9       D001       cid_3          p_d     d-2
10      D001       cid_3          p_d     d-3

結果を見るとわかりますが、多対多の関係ではある製品シリアルのレコードが複数行に増えることがわかると思います。例えば、serial_no=D001の場合は、製品特徴が3レコード(行)あるので、結果も3行になります。

merge関数でキーを指定したDataFrameの結合

merge関数は、自動的に1つ以上の同じ名前の列名を探してキーとして結合をしてくれますが、実際の場面では結合したいキーは明確になっていることが多く、明示的に結合するキーを指定して結合することが多いかと思います。

以降では、結合するキー列を指定してDataFrameを結合する方法を説明します。

onでキーを指定して結合

merge関数でキー列を指定する場合は、以下の例のように「on」でキーの列名を指定します。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)

# 品質情報
quality_result = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'quality': [100, 200, 300, 400, 500]
     }
)

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'品質実績(quality_result):\n{quality_result}\n')

# onでキーを指定してマージ
result_df = pd.merge(product_list, quality_result, on='serial_no')

print(f'onでキー(serial_no)を指定してマージ\n{result_df}')
【実行結果】
製品一覧(product_list):
  serial_no customer_id product_code
0      A001       cid_1          p_a
1      B001       cid_1          p_b
2      C001       cid_2          p_c
3      C002       cid_2          p_c
4      D001       cid_3          p_d

品質実績(quality_result):
  serial_no  quality
0      A001      100
1      B001      200
2      C001      300
3      C002      400
4      D001      500

onでキー(serial_no)を指定してマージ
  serial_no customer_id product_code  quality
0      A001       cid_1          p_a      100
1      B001       cid_1          p_b      200
2      C001       cid_2          p_c      300
3      C002       cid_2          p_c      400
4      D001       cid_3          p_d      500

left_on, right_onでそれぞれのDataFrameのキーを指定して結合

データを結合する際には、結合する元のテーブルで意味は同じなのに異なる列名となっている場合がよくあります。このような場合には「left_on」「right_on」を使ってそれぞれのDataFrameにおけるキーの列名を指定します。

ここで左(left)と言っているのは、merge関数の第1引数に指定するDataFrameで、右(right)と言っているのはmerge関数の第2引数に指定するDataFrameです。

以下の例で見てみましょう。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)

# 品質情報
quality_result = pd.DataFrame(
    {'serial': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'quality': [100, 200, 300, 400, 500]
     }
)

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'品質実績(quality_result):\n{quality_result}\n')

# 左右のDataFrameのキーをそれぞれ指定してマージ
result_df = pd.merge(product_list, quality_result,
                     left_on='serial_no', right_on='serial')

print(f'左右のDataFrameのキーをそれぞれ指定してマージ:\n{result_df}')
【実行結果】
製品一覧(product_list):
  serial_no customer_id product_code
0      A001       cid_1          p_a
1      B001       cid_1          p_b
2      C001       cid_2          p_c
3      C002       cid_2          p_c
4      D001       cid_3          p_d

品質実績(quality_result):
  serial  quality
0   A001      100
1   B001      200
2   C001      300
3   C002      400
4   D001      500

左右のDataFrameのキーをそれぞれ指定してマージ:
  serial_no customer_id product_code serial  quality
0      A001       cid_1          p_a   A001      100
1      B001       cid_1          p_b   B001      200
2      C001       cid_2          p_c   C001      300
3      C002       cid_2          p_c   C002      400
4      D001       cid_3          p_d   D001      500

上記の例では、製品一覧ではシリアルナンバーは「serial_no」であるのに対して、品質情報では、「serial」となっており名前が異なっています。この場合は、merge関数を使用しても一致するキーがなく、自動ではうまく結合できません。

このような場合には「left_on=’serial_no’」「right_on=’serial’」としてそれぞれ結合するキー名称を指定することで指定列をキーにして結合できます。結果では、キー列は一つにまとめられるわけではなく複数列出てくる点も覚えておきましょう。

left_index, right_indexを指定してDataFrameのインデックスで結合

これまでの例では、DataFrameの列名を使ったmerge例を見てきました。しかし、場合によってはDataFrameのインデックスを用いて結合する場合もあると思います。このような場合には「left_index」「right_index」を指定することでDataFrameのインデックスを使用して結合することができます。

以下の例でみてみましょう。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)
# serial_no列をインデックスに設定
product_list = product_list.set_index('serial_no')

# 品質情報
quality_result = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'quality': [100, 200, 300, 400, 500]
     }
)
# serial_no列をインデックスに設定
quality_result = quality_result.set_index('serial_no')

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'品質実績(quality_result):\n{quality_result}\n')

# インデックスを使ってマージ
result_df = pd.merge(product_list, quality_result,
                     left_index=True, right_index=True)

print(f'インデックスを使ってマージ:\n{result_df}')
【実行結果】
製品一覧(product_list):
          customer_id product_code
serial_no                         
A001            cid_1          p_a
B001            cid_1          p_b
C001            cid_2          p_c
C002            cid_2          p_c
D001            cid_3          p_d

品質実績(quality_result):
           quality
serial_no         
A001           100
B001           200
C001           300
C002           400
D001           500

インデックスを使ってマージ:
          customer_id product_code  quality
serial_no                                  
A001            cid_1          p_a      100
B001            cid_1          p_b      200
C001            cid_2          p_c      300
C002            cid_2          p_c      400
D001            cid_3          p_d      500

この例で、上記までの例と違うのは、set_indexで’serial_no’列をインデックスとして設定しているところです。

インデックスを使用してマージする場合は、merge関数で「left_index=True」「right_index=True」と指定します。Trueを指定することでインデックスを使ってマージすることを明示しています。

joinメソッドを用いたインデックスでの結合

DataFrameでは、インデックスをキーとして結合を実行するためのjoinメソッドがデフォルトで用意されています。joinメソッドは以下のように使用します。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)
# serial_no列をインデックスに設定
product_list = product_list.set_index('serial_no')

# 品質情報
quality_result = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'quality': [100, 200, 300, 400, 500]
     }
)
# serial_no列をインデックスに設定
quality_result = quality_result.set_index('serial_no')

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'品質実績(quality_result):\n{quality_result}\n')

# joinメソッドを使ってインデックスで結合
result_df = product_list.join(quality_result)

print(f'joinメソッドを使ってインデックスで結合:\n{result_df}')
【実行結果】
製品一覧(product_list):
          customer_id product_code
serial_no                         
A001            cid_1          p_a
B001            cid_1          p_b
C001            cid_2          p_c
C002            cid_2          p_c
D001            cid_3          p_d

品質実績(quality_result):
           quality
serial_no         
A001           100
B001           200
C001           300
C002           400
D001           500

joinメソッドを使ってインデックスで結合:
          customer_id product_code  quality
serial_no                                  
A001            cid_1          p_a      100
B001            cid_1          p_b      200
C001            cid_2          p_c      300
C002            cid_2          p_c      400
D001            cid_3          p_d      500

joinメソッドは左側となるDataFrameのメソッドで呼び出して、結合する右側となるDataFrameを引数に指定します。結果はmerge関数で「left_index=True」「right_index=True」とした場合と同じになります。

mergeはpandasの関数ですので、pd.merge()というように呼び出すことができますが、joinはDataFrameのメソッドのためDataFrameのオブジェクトを使って実行します。そのため、pd.join()というような呼び出し方はできませんので注意してください。

left_on, right_on, left_index, right_indexの混在での結合

列を指定する「left_on」「right_on」、インデックスでの結合を指定する「left_index」「right_index」の使い方を見てきましたが、これらは同時に使用することができ、列とインデックスを混在させて結合することができます。

以下の例で見てみましょう。

import pandas as pd

# 製品一覧
product_list = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'customer_id': ['cid_1', 'cid_1', 'cid_2', 'cid_2', 'cid_3'],
     'product_code': ['p_a', 'p_b', 'p_c', 'p_c', 'p_d']
     }
)
# serial_no列をインデックスに設定
product_list = product_list.set_index('serial_no')

# 品質情報
quality_result = pd.DataFrame(
    {'serial_no': ['A001', 'B001', 'C001', 'C002', 'D001'],
     'quality': [100, 200, 300, 400, 500]
     }
)

print(f'製品一覧(product_list):\n{product_list}\n')
print(f'品質実績(quality_result):\n{quality_result}\n')

# インデックスを使ってマージ(左:インデックスを使用、右:列名を指定)
result_df = pd.merge(product_list, quality_result,
                     left_index=True, right_on='serial_no')

print(f'インデックスを使ってマージ(左:インデックス、右:列名)\n{result_df}')
【実行結果】
製品一覧(product_list):
          customer_id product_code
serial_no                         
A001            cid_1          p_a
B001            cid_1          p_b
C001            cid_2          p_c
C002            cid_2          p_c
D001            cid_3          p_d

品質実績(quality_result):
  serial_no  quality
0      A001      100
1      B001      200
2      C001      300
3      C002      400
4      D001      500

インデックスを使ってマージ(左:インデックス、右:列名)
  customer_id product_code serial_no  quality
0       cid_1          p_a      A001      100
1       cid_1          p_b      B001      200
2       cid_2          p_c      C001      300
3       cid_2          p_c      C002      400
4       cid_3          p_d      D001      500

この例では、左側となる製品一覧はDataFrameのインデックスを、右側となる品質情報は、列名をキーにして結合しています。このようにインデックスと列を混在させて結合することも可能です。