pandas

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

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

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

DataFrameの結合

データ分析の際には、データベースやCSVファイル等、様々なデータソースからデータを取得して分析することがほとんどです。この時、各データで同じ意味を表すキー列をもとに結合して分析できるようにすることは、データに対する深い知見を得るために必要不可欠です。

Pythonのデータ分析で使用されるpandasでは、DataFrameの結合方法としてmerge関数やjoinメソッドといった非常に便利な機能が提供されています。

以降では、以下図に示したような製品一覧と関連する情報を結合する例でmerge関数やjoinメソッドを用いたDataFrameの結合方法を紹介していきます。

DataFrameの結合用データ例 merge join

上記例では、各製品シリアル一覧と関連する品質情報実績、顧客情報、製品特徴のデータがあるものとします。製造メーカー等では生産管理システムのマスタや製造実績、品質管理システムの品質実績など、様々なシステムのデータをつなげて分析したいといったことがよくあります。実際の現場で使われているデータはもっと複雑なデータが多いと思いますが、上記のような簡単データで結合のイメージをつかんでもらえればと思います。

同じ構造をもつデータを単純に連結する場合には、concatを使うことができます。以下を参考にしていただければと思います。

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

データを結合する場合には、データの関係性をよく考慮することが重要です。データの関係に応じて「1対1」「多対1」「多対多」という関係性があります。以降では、それぞれの関係性のデータを使いながらmerge関数を使用してデータを結合する方法を紹介します。

1対1の結合

DataFrameの結合 merge (1対1)

上記図の例は「1対1」の関係があるデータです。「serial_no」をキー列として見てみると、各レコード(各行)は1対1に対応していることがわかります。ここでは、製品一覧と品質情報をserial_noをキーに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つの顧客情報のレコードが対応しています。ここでは、製品一覧と顧客情報をcustomer_idをキーにして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をキーにして製品一覧に顧客情報が結合されていることが分かります。製品一覧の方が多であるため、顧客情報は複製されて各行に結合されていることが分かるかと思います。

多対多の結合

DataFrameの結合 merge (多対多)

上記図の例は「多対多」の関係があるデータです。「product_code」をキー列としてみてみると、製品一覧の複数レコードに、複数の製品特徴のレコードが対応しているということが分かります。ここでは、製品一覧と製品特徴をproduct_codeをキーにして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

上記結果を見てみると、多対多の関係であるため、ある製品シリアルに関するレコードが複数行に増えていることが分かるかと思います。

上記で見てきた通り、merge関数は自動的に1つ以上のキーを探して結合してくれます。データの関係性による結合の違いを見てもらうために自動でのキー結合をで結果を見てもらいましたが、実際のデータ分析の場面では、結合したいキーを明確に指定して結合することがほとんどです。以降では、merge関数で、キーを指定してDataFrameを結合する方法を紹介していきます。

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

ここでは、merge関数でキー列を指定して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

上記例では、明示的に"serial_no"をキーとして結合しています。

この例では、それぞれのDataFrameが同一名の列を持っているため、簡単に結合できます。しかし、データ分析では同じ意味でも異なる列名であることがよくあります。この場合には、以降で説明するleft_onright_onを使用します。

left_onとright_onでそれぞれのDataFrameのキーを指定する

データを結合する際には、それぞれのDataFrameで意味は同じなのに異なる列名となっている場合がよくあります。このような場合にはleft_onright_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"」としてそれぞれ結合するキー名称を指定することで指定列をキーにして結合することができます。

上記結果を見ても分かるように、キー列は一つにまとめられるわけではなく複数列出てくる点も覚えておきましょう。もし、キー列が複数出てこないようにしたい場合は、事前にDataFrameの列名を揃えておく必要があります。

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

これまでの例では、DataFrameの列名を使用したmerge関数の実行例を見てきました。pandasのDataFrameでは、インデックスを持っているためインデックスを用いての結合をすることも可能です。

インデックスを使用して結合する場合には、以下の例のように「left_index=True」「right_index=True」を指定することで、インデックスを使用しての結合が可能です。

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"列をインデックスとして登録していることに注意してください。

結果を見ると分かりますが「left_index=True」「right_index=True」を指定することで、インデックスを使った結合ができていることが分かるかと思います。

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

merge関数でインデックスを用いた結合の例を紹介しましたが、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

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

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

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

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のインデックスを使用し、右側となる品質情報は、列名をキーとして結合しています。このようにインデックスや列名を混在させて、柔軟に結合することが可能です。

まとめ

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

pandasでは、DataFrameの結合方法としてmerge関数やjoinメソッドが用意されています。結合時には、1対1、多対1、多対多といったことを考慮して結合をすることが適切な結合のためには重要です。それぞれのパターンに関する結合を例を使って紹介しました。

また、merge関数で結合する際のキーの指定は非常に重要です。列名を指定するonleft_onright_onやインデックスで結合するleft_index=Trueright_index=Trueといった指定方法について説明しました。

データ分析では、異なるデータソースのデータを適切に結合できることが、データから深い知見を得るために必要不可欠です。是非、merge関数やjoinメソッドを適切に使用できるようになってデータ分析に活かしてください。

Note

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