pandas merge
関数徹底解説:複数キーでデータ結合する完全ガイド
データ分析において、異なるソースから得られた複数のデータセットを組み合わせる作業は不可欠です。特に、リレーショナルデータベースのように、複数のテーブルが共通のキーを介して関連付けられている場合、これらのテーブルを結合して分析に必要な情報を一つのデータセットにまとめる必要があります。
pandasライブラリは、Pythonでデータ分析を行うための強力なツールであり、データフレーム(DataFrame)という便利なデータ構造を提供しています。pandasには、このようなデータ結合を行うためのいくつかの関数がありますが、最も汎用的で強力なのが merge
関数です。
merge
関数は、SQLにおけるJOIN操作に非常に似た機能を提供します。単一のキーだけでなく、複数のキーを指定してデータフレームを結合できる柔軟性を持っています。これにより、より複雑なリレーションシップを持つデータセットも正確に結合することが可能です。
この記事では、pandasの merge
関数を使用して複数のキーでデータフレームを結合する方法に焦点を当て、その基本的な使い方から応用、注意点までを徹底的に解説します。約5000語にわたる詳細な解説を通じて、読者の皆様が実務で自信を持って複数キー結合を扱えるようになることを目指します。
1. なぜデータ結合が必要なのか? データ分析におけるリレーションシップ
現代のデータはしばしば複数のテーブルやファイルに分散しています。例えば、ECサイトのデータであれば、「顧客情報」「商品リスト」「注文履歴」「配送情報」などが別々のテーブルとして管理されていることが一般的です。
- 顧客情報テーブル: 顧客ID、氏名、住所、連絡先など
- 商品リストテーブル: 商品ID、商品名、価格、カテゴリなど
- 注文履歴テーブル: 注文ID、顧客ID、商品ID、購入数量、注文日時など
- 配送情報テーブル: 注文ID、配送先住所、配送状況、追跡番号など
これらのテーブルは、顧客ID、商品ID、注文IDといった共通の「キー」を介して互いに関連付けられています。例えば、「どの顧客が(顧客ID)、いつ(注文日時)、どの商品を(商品ID)、いくつ購入したか(購入数量)」を知るためには、「顧客情報」と「注文履歴」と「商品リスト」のテーブルを組み合わせる必要があります。
このような、複数のテーブルを共通のキーに基づいて連結し、必要な情報を一つのデータセットにまとめる操作を「データ結合(Data Merging / Joining)」と呼びます。データ分析を行う上で、このデータ結合は最も基本的なステップの一つです。
pandasの merge
関数は、このデータ結合を行うための主要なツールです。
2. pandasにおけるデータ結合手法:merge
, join
, concat
pandasにはデータフレームを組み合わせるためのいくつかの関数があります。それぞれの役割を簡単に見てみましょう。
-
pd.merge()
:- 最も柔軟な結合関数です。
- 指定した1つ以上の列(またはインデックス)をキーとして使用して結合します。
- SQLのJOIN(INNER, LEFT, RIGHT, OUTER, CROSS)に相当する操作が可能です。
- 結合キーは、両方のDataFrameで異なる列名でも指定できます (
left_on
,right_on
)。 - この記事の主要なテーマです。
-
DataFrame.join()
:- DataFrameのメソッドとして呼び出されます (
df1.join(df2)
)。 - デフォルトではインデックスをキーとして結合します。
on
引数を使用することで、呼び出し元のDataFrameの列をキーとし、結合対象のDataFrameのインデックスをキーとして結合することも可能です (df1.join(df2, on='key_col')
)。- 複数キーをインデックスとして結合することも可能ですが、複数列をキーとする場合は
merge
の方が直感的で一般的です。 merge
と比べると、インデックスを重視した結合に適しています。
- DataFrameのメソッドとして呼び出されます (
-
pd.concat()
:- 複数のDataFrameを単に連結(Concatenate)する関数です。
- 結合キーではなく、軸(axis=0で縦方向、axis=1で横方向)に沿ってDataFrameを積み重ねたり並べたりします。
- 横方向に連結する場合 (
axis=1
)、インデックスが一致する行が結合されますが、これは「結合」というより「連結」に近い考え方です。キーに基づいた論理的な関連付けではなく、位置やインデックスの一致に依存します。
これらの関数の中で、リレーショナルなデータ結合(共通のキーによる関連付け)を最も柔軟かつ強力に行えるのが pd.merge()
です。特に複数キーでの結合には、merge
が最も適しています。
3. pandas merge
関数の基本
pd.merge()
関数は、以下のような基本的な形式で使用します。
python
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=False,
suffixes=('_x', '_y'), copy=True, indicator=False, validate=None)
主要な引数は以下の通りです。
left
: 結合する左側のDataFrame。right
: 結合する右側のDataFrame。how
: 結合の種類を指定します。'inner'
(デフォルト),'left'
,'right'
,'outer'
,'cross'
のいずれか。on
: 結合キーとして使用する列名(または列名のリスト)。left
とright
の両方に同じ名前の列が存在する場合に使用します。left_on
:left
DataFrameで結合キーとして使用する列名(または列名のリスト)。on
とは異なり、right_on
と組み合わせて、左右のDataFrameで結合キーの列名が異なる場合に使用します。right_on
:right
DataFrameで結合キーとして使用する列名(または列名のリスト)。left_on
と組み合わせて使用します。left_index
:left
DataFrameのインデックスを結合キーとして使用するかどうか(True
またはFalse
)。right_index
:right
DataFrameのインデックスを結合キーとして使用するかどうか(True
またはFalse
)。suffixes
: 結合後に列名が重複した場合に付加するサフィックスを指定します。デフォルトは('_x', '_y')
です。indicator
: 結合元を示す列を追加するかどうかを指定します。True
または列名を指定します。追加される列には'left_only'
,'right_only'
,'both'
の値が入ります。
これらの引数を組み合わせることで、様々な種類の結合や、列・インデックスをキーとした結合が可能です。
4. 複数キーでのデータ結合:on
引数を使った方法
データ結合において、単一の列だけではデータセット内のレコードを一意に特定できない場合があります。例えば、同じ顧客が複数回注文している場合、顧客IDだけでは個々の注文を区別できません。この場合、「顧客ID」と「注文ID」や「注文日時」などを組み合わせてキーとする必要があります。
merge
関数で複数キーを指定するには、on
、left_on
、right_on
の各引数に列名のリストを渡します。
最も簡単なのは、左右のDataFrameで結合キーとして使用する列名が全て同じ場合です。この場合は on
引数に列名のリストを渡します。
例:顧客の注文詳細データを結合する
仮に、以下のような2つのDataFrameがあるとします。
orders_df
: 注文の概要情報(注文ID, 顧客ID, 注文日, 合計金額)order_details_df
: 各注文に含まれる商品の詳細情報(注文ID, 商品ID, 数量, 単価)
これらのデータから、「どの顧客が、いつ、どの商品を、いくつ、いくらで購入したか」を知るには、orders_df
と order_details_df
を結合する必要があります。ここで共通のキーとなるのは「注文ID」ですが、注文IDだけでは不十分です。例えば、ある注文IDに複数の商品が含まれる場合、その注文IDは order_details_df
で複数回出現します。正確に結合し、各注文明細行にその注文の顧客IDや合計金額を紐づけるには、order_details_df
の各行を特定するキーとして「注文ID」と「商品ID」の組み合わせを考えたり、あるいは orders_df
の情報(注文IDと他の列)を order_details_df
の各注文ID行に単純に紐づけるという考え方ができます。
ここではシンプルに、orders_df
の情報を order_details_df
の各行に紐づけるケースを考えます。この場合、orders_df
の各行は注文IDで一意に特定できますが、order_details_df
の各行は「注文ID」と「商品ID」の組み合わせで一意に特定されます。orders_df
を order_details_df
に結合し、各注文明細行に対応する顧客IDや注文日を付加したいとします。この場合、結合キーは orders_df
の「注文ID」と order_details_df
の「注文ID」になります。キーは単一の「注文ID」ですが、order_details_df
側で注文IDが重複している(1つの注文に複数の商品がある)点がポイントです。merge
はデフォルトでこのような「多対一」または「多対多」の関係を適切に処理します。
しかし、より典型的な複数キーの例として、「特定の顧客の、特定日の、特定の商品に対する評価」のようなデータを考えましょう。
例:顧客評価データと商品リストを結合する
reviews_df
: 顧客評価データ(CustomerID, ProductID, ReviewDate, Rating)products_df
: 商品リスト(ProductID, ProductName, Category)customers_df
: 顧客リスト(CustomerID, CustomerName, Region)
ここで、「特定の顧客が、特定の商品に付けた評価」に、その顧客名と商品名を紐づけたいとします。
まず、reviews_df
と customers_df
を結合して、評価データに顧客名を追加します。キーは CustomerID
という単一キーです。
“`python
import pandas as pd
サンプルデータの作成
reviews_data = {
‘CustomerID’: [1, 1, 2, 3, 1, 2],
‘ProductID’: [‘A’, ‘B’, ‘A’, ‘C’, ‘A’, ‘B’],
‘ReviewDate’: [‘2023-01-15’, ‘2023-01-20’, ‘2023-01-18’, ‘2023-01-25’, ‘2023-02-01’, ‘2023-02-05’],
‘Rating’: [5, 4, 4, 5, 5, 3]
}
reviews_df = pd.DataFrame(reviews_data)
reviews_df[‘ReviewDate’] = pd.to_datetime(reviews_df[‘ReviewDate’])
customers_data = {
‘CustomerID’: [1, 2, 3, 4],
‘CustomerName’: [‘Alice’, ‘Bob’, ‘Charlie’, ‘David’],
‘Region’: [‘East’, ‘West’, ‘North’, ‘South’]
}
customers_df = pd.DataFrame(customers_data)
products_data = {
‘ProductID’: [‘A’, ‘B’, ‘C’, ‘D’],
‘ProductName’: [‘Laptop’, ‘Keyboard’, ‘Mouse’, ‘Monitor’],
‘Category’: [‘Electronics’, ‘Electronics’, ‘Electronics’, ‘Electronics’]
}
products_df = pd.DataFrame(products_data)
print(“reviews_df:”)
print(reviews_df)
print(“\ncustomers_df:”)
print(customers_df)
print(“\nproducts_df:”)
print(products_df)
reviews_df と customers_df を CustomerID で結合
これは単一キー結合
reviews_with_customer = pd.merge(reviews_df, customers_df, on=’CustomerID’, how=’left’)
print(“\nreviews_with_customer (joined with customers_df):”)
print(reviews_with_customer)
“`
実行結果:
“`
reviews_df:
CustomerID ProductID ReviewDate Rating
0 1 A 2023-01-15 5
1 1 B 2023-01-20 4
2 2 A 2023-01-18 4
3 3 C 2023-01-25 5
4 1 A 2023-02-01 5
5 2 B 2023-02-05 3
customers_df:
CustomerID CustomerName Region
0 1 Alice East
1 2 Bob West
2 3 Charlie North
3 4 David South
products_df:
ProductID ProductName Category
0 A Laptop Electronics
1 B Keyboard Electronics
2 C Mouse Electronics
3 D Monitor Electronics
reviews_with_customer (joined with customers_df):
CustomerID ProductID ReviewDate Rating CustomerName Region
0 1 A 2023-01-15 5 Alice East
1 1 B 2023-01-20 4 Alice East
2 2 A 2023-01-18 4 Bob West
3 3 C 2023-01-25 5 Charlie North
4 1 A 2023-02-01 5 Alice East
5 2 B 2023-02-05 3 Bob West
“`
次に、この reviews_with_customer
DataFrameに商品名を追加するために、products_df
と結合します。キーは ProductID
です。これも単一キー結合です。
“`python
reviews_with_customer と products_df を ProductID で結合
これは単一キー結合
final_reviews = pd.merge(reviews_with_customer, products_df, on=’ProductID’, how=’left’)
print(“\nfinal_reviews (joined with products_df):”)
print(final_reviews)
“`
実行結果:
final_reviews (joined with products_df):
CustomerID ProductID ReviewDate Rating CustomerName Region ProductName Category
0 1 A 2023-01-15 5 Alice East Laptop Electronics
1 1 B 2023-01-20 4 Alice East Keyboard Electronics
2 2 A 2023-01-18 4 Bob West Laptop Electronics
3 3 C 2023-01-25 5 Charlie North Mouse Electronics
4 1 A 2023-02-01 5 Alice East Laptop Electronics
5 2 B 2023-02-05 3 Bob West Keyboard Electronics
このように、複数の単一キー結合を順番に行うことで目的のデータセットを作成することも可能ですが、元のデータセットが複雑になると、中間ステップが増えて管理が煩雑になったり、パフォーマンスに影響したりする場合があります。
ここで、もし reviews_df
に「同じ顧客が同じ日に同じ商品に複数回評価を付ける」ようなデータが含まれており、それを区別する必要がある場合、「CustomerID」と「ReviewDate」と「ProductID」を組み合わせたものを一意のキーとして扱いたいと考えるかもしれません。しかし、reviews_df
自体はその3つの組み合わせで一意である必要はありません(上記例でも CustomerID=1, ProductID=’A’ は異なる日付で2回出現しています)。
より明確な複数キー結合の例:
在庫データと販売データを結合する場合を考えましょう。
stock_df
: 在庫データ(StoreID, ProductID, Stock)sales_df
: 販売データ(StoreID, ProductID, SaleDate, QuantitySold)
ある特定の日付(SaleDate)における、特定の店舗(StoreID)での、特定の商品(ProductID)の販売数量を、その時点の在庫情報と紐づけたいとします。
ここで、在庫情報は店舗と商品の組み合わせ (StoreID
, ProductID
) で管理されており、販売データは店舗、商品、日付の組み合わせ (StoreID
, ProductID
, SaleDate
) で記録されています。
sales_df
の各販売記録に対して、対応する stock_df
の在庫数情報を結合したいとします。stock_df
は (StoreID
, ProductID
) の組み合わせで各商品の在庫量を一意に特定しています。sales_df
は (StoreID
, ProductID
, SaleDate
) の組み合わせで各販売記録を特定しています。
この場合、sales_df
の各行 (StoreID
, ProductID
, SaleDate
) に、対応する stock_df
の在庫情報 (StoreID
, ProductID
, Stock) を紐づけます。結合のキーは sales_df
と stock_df
の両方で共通する列である StoreID
と ProductID
の組み合わせになります。
on
引数にリスト形式で複数キーを指定します。
“`python
在庫データと販売データのサンプル作成
stock_data = {
‘StoreID’: [101, 101, 102, 102, 101],
‘ProductID’: [‘X’, ‘Y’, ‘X’, ‘Z’, ‘Z’],
‘Stock’: [50, 30, 25, 10, 15]
}
stock_df = pd.DataFrame(stock_data)
sales_data = {
‘StoreID’: [101, 101, 102, 101, 102, 101],
‘ProductID’: [‘X’, ‘Y’, ‘X’, ‘X’, ‘Z’, ‘Z’],
‘SaleDate’: [‘2023-03-01’, ‘2023-03-01’, ‘2023-03-01’, ‘2023-03-02’, ‘2023-03-02’, ‘2023-03-03’],
‘QuantitySold’: [5, 2, 3, 1, 2, 3]
}
sales_df = pd.DataFrame(sales_data)
sales_df[‘SaleDate’] = pd.to_datetime(sales_df[‘SaleDate’])
print(“\nstock_df:”)
print(stock_df)
print(“\nsales_df:”)
print(sales_df)
sales_df と stock_df を複数キー (‘StoreID’, ‘ProductID’) で結合
stock_df は (‘StoreID’, ‘ProductID’) で一意だが、sales_df は (‘StoreID’, ‘ProductID’) で重複する(異なる SaleDate の行がある)
sales_df の各行に対して、対応する stock_df の情報が紐づく
merged_sales_stock = pd.merge(sales_df, stock_df, on=[‘StoreID’, ‘ProductID’], how=’left’)
print(“\nmerged_sales_stock (multiple keys ‘StoreID’, ‘ProductID’):”)
print(merged_sales_stock)
“`
実行結果:
“`
stock_df:
StoreID ProductID Stock
0 101 X 50
1 101 Y 30
2 102 X 25
3 102 Z 10
4 101 Z 15
sales_df:
StoreID ProductID SaleDate QuantitySold
0 101 X 2023-03-01 5
1 101 Y 2023-03-01 2
2 102 X 2023-03-01 3
3 101 X 2023-03-02 1
4 102 Z 2023-03-02 2
5 101 Z 2023-03-03 3
merged_sales_stock (multiple keys ‘StoreID’, ‘ProductID’):
StoreID ProductID SaleDate QuantitySold Stock
0 101 X 2023-03-01 5 50
1 101 Y 2023-03-01 2 30
2 102 X 2023-03-01 3 25
3 101 X 2023-03-02 1 50
4 102 Z 2023-03-02 2 10
5 101 Z 2023-03-03 3 15
“`
この例では、sales_df
の各行(各販売記録)に対して、対応する stock_df
の在庫数(StoreID
と ProductID
が一致する行の Stock
列の値)が正しく紐づいていることがわかります。on=['StoreID', 'ProductID']
とすることで、「sales_df
の行の StoreID
が stock_df
の行の StoreID
と等しく、かつ sales_df
の行の ProductID
が stock_df
の行の ProductID
と等しい」という条件で行の組み合わせが行われます。
on
引数を使用できるのは、結合キーとなる列名が左右のDataFrameで全く同じである場合のみです。
5. 複数キーでのデータ結合:left_on
と right_on
を使った方法
左右のDataFrameで結合キーとして使用したい列の名前が異なる場合があります。例えば、片方では顧客IDが CustomerID
という列名で、もう片方では CustID
という列名になっているなどです。
この場合は、left_on
引数に左側DataFrameのキー列名のリストを、right_on
引数に右側DataFrameのキー列名のリストを指定します。リスト内の列名の順番は対応している必要があります。
例:注文データと顧客データを結合する(キー列名が異なる場合)
orders_df_v2
: 注文データ(OrderID, CustIdentifier, OrderDate, TotalAmount)customers_df_v2
: 顧客データ(CustomerID, CustomerName, Region)
orders_df_v2
の CustIdentifier
列と、customers_df_v2
の CustomerID
列をキーとして結合したいとします。
“`python
キー列名が異なる場合のサンプルデータ作成
orders_data_v2 = {
‘OrderID’: [1, 2, 3, 4, 5, 6],
‘CustIdentifier’: [101, 102, 103, 101, 102, 101],
‘OrderDate’: [‘2023-04-01’, ‘2023-04-01’, ‘2023-04-02’, ‘2023-04-03’, ‘2023-04-03’, ‘2023-04-04’],
‘TotalAmount’: [1500, 2000, 5000, 3000, 1800, 2500]
}
orders_df_v2 = pd.DataFrame(orders_data_v2)
orders_df_v2[‘OrderDate’] = pd.to_datetime(orders_df_v2[‘OrderDate’])
customers_data_v2 = {
‘CustomerID’: [101, 102, 103, 104],
‘CustomerName’: [‘Alice’, ‘Bob’, ‘Charlie’, ‘David’],
‘Region’: [‘East’, ‘West’, ‘North’, ‘South’]
}
customers_df_v2 = pd.DataFrame(customers_data_v2)
print(“\norders_df_v2:”)
print(orders_df_v2)
print(“\ncustomers_df_v2:”)
print(customers_df_v2)
orders_df_v2 と customers_df_v2 を CustIdentifier と CustomerID で結合
merged_orders_customers_v2 = pd.merge(orders_df_v2, customers_df_v2,
left_on=’CustIdentifier’, right_on=’CustomerID’,
how=’left’)
print(“\nmerged_orders_customers_v2 (using left_on and right_on):”)
print(merged_orders_customers_v2)
“`
実行結果:
“`
orders_df_v2:
OrderID CustIdentifier OrderDate TotalAmount
0 1 101 2023-04-01 1500
1 2 102 2023-04-01 2000
2 3 103 2023-04-02 5000
3 4 101 2023-04-03 3000
4 5 102 2023-04-03 1800
5 6 101 2023-04-04 2500
customers_df_v2:
CustomerID CustomerName Region
0 101 Alice East
1 102 Bob West
2 103 Charlie North
3 104 David South
merged_orders_customers_v2 (using left_on and right_on):
OrderID CustIdentifier OrderDate TotalAmount CustomerID CustomerName Region
0 1 101 2023-04-01 1500 101 Alice East
1 2 102 2023-04-01 2000 102 Bob West
2 3 103 2023-04-02 5000 103 Charlie North
3 4 101 2023-04-03 3000 101 Alice East
4 5 102 2023-04-03 1800 102 Bob West
5 6 101 2023-04-04 2500 101 Alice East
“`
このように、left_on
と right_on
を使うことで、異なる列名を持つキーで結合できます。結合後のDataFrameには、両方のキー列(CustIdentifier
と CustomerID
)がデフォルトで保持されます。どちらか一方のみが必要な場合は、結合後に不要な列を削除します。
複数キーで列名が異なる場合:
例えば、orders_df_v3
には (StoreCode, ItemCode)
というキーがあり、stock_df_v2
には (StoreID, ProductID)
というキーがあるとします。ここで StoreCode
と StoreID
は同じ店舗を指し、ItemCode
と ProductID
は同じ商品を指しているとします。
orders_df_v3
: 注文詳細データ(OrderID, StoreCode, ItemCode, Quantity)stock_df_v2
: 在庫データ(StoreID, ProductID, Stock)
これらを (StoreCode, ItemCode)
と (StoreID, ProductID)
という複数キーで結合するには、left_on
と right_on
にそれぞれリスト形式でキー列名を指定します。リストの要素の順序が重要です。left_on
の StoreCode
は right_on
の StoreID
と対応し、left_on
の ItemCode
は right_on
の ProductID
と対応するように指定します。
“`python
複数キーで列名が異なる場合のサンプルデータ作成
orders_data_v3 = {
‘OrderID’: [1, 2, 3, 4],
‘StoreCode’: [‘S01’, ‘S02’, ‘S01’, ‘S03’],
‘ItemCode’: [‘A1’, ‘B2’, ‘A1’, ‘C3’],
‘Quantity’: [10, 5, 8, 12]
}
orders_df_v3 = pd.DataFrame(orders_data_v3)
stock_data_v2 = {
‘StoreID’: [‘S01’, ‘S02’, ‘S01’, ‘S04’],
‘ProductID’: [‘A1’, ‘B2’, ‘D4’, ‘C3’],
‘Stock’: [100, 50, 200, 150]
}
stock_df_v2 = pd.DataFrame(stock_data_v2)
print(“\norders_df_v3:”)
print(orders_df_v3)
print(“\nstock_df_v2:”)
print(stock_df_v2)
orders_df_v3 と stock_df_v2 を (StoreCode, ItemCode) と (StoreID, ProductID) で複数キー結合
merged_orders_stock_v2 = pd.merge(orders_df_v3, stock_df_v2,
left_on=[‘StoreCode’, ‘ItemCode’],
right_on=[‘StoreID’, ‘ProductID’],
how=’left’)
print(“\nmerged_orders_stock_v2 (multiple keys with different names):”)
print(merged_orders_stock_v2)
“`
実行結果:
“`
orders_df_v3:
OrderID StoreCode ItemCode Quantity
0 1 S01 A1 10
1 2 S02 B2 5
2 3 S01 A1 8
3 4 S03 C3 12
stock_df_v2:
StoreID ProductID Stock
0 S01 A1 100
1 S02 B2 50
2 S01 D4 200
3 S04 C3 150
merged_orders_stock_v2 (multiple keys with different names):
OrderID StoreCode ItemCode Quantity StoreID ProductID Stock
0 1 S01 A1 10 S01 A1 100.0
1 2 S02 B2 5 S02 B2 50.0
2 3 S01 A1 8 S01 A1 100.0
3 4 S03 C3 12 NaN NaN NaN
“`
結果からわかるように、orders_df_v3
の行は、対応する stock_df_v2
の情報(StoreID, ProductID, Stock)と結合されています。キーの組み合わせ ('S03', 'C3')
は stock_df_v2
に存在しないため、結合結果ではこれらの列が NaN
になっています(how='left'
を指定したため)。また、キー列 (StoreCode
, ItemCode
, StoreID
, ProductID
) は全て結果のDataFrameに残ります。
6. 複数キーでのデータ結合:インデックスを使った方法
merge
関数は、列だけでなくDataFrameのインデックスを結合キーとして使用することも可能です。これは、left_index=True
および right_index=True
引数を使用することで実現できます。
特に、DataFrameのインデックスが複数レベル(MultiIndex)である場合、そのインデックス全体を複数キーとして結合することができます。
例:MultiIndex を持つ DataFrame の結合
仮に、店舗と商品の組み合わせをインデックスとして持つ在庫データと、店舗と商品の組み合わせを列として持つ販売目標データがあるとします。
stock_df_idx
: 在庫データ(インデックス:(StoreID, ProductID), 列:Stock)target_df_col
: 販売目標データ(インデックス:店舗名, 列:ProductID の目標値)
この例は直接複数キーのインデックス結合の典型例ではないので、よりシンプルな例を考えます。
例:MultiIndex を持つ DataFrame 同士の結合
df_left_idx
: 在庫データ(インデックス:(StoreID, ProductID), 列:Stock)df_right_idx
: 価格データ(インデックス:(StoreID, ProductID), 列:Price)
これらのDataFrameをインデックスをキーとして結合します。インデックスはMultiIndexなので、実質的には複数キーでの結合となります。
“`python
MultiIndex を持つサンプルデータ作成
stock_data_idx = {
(‘S01’, ‘A1’): 100,
(‘S01’, ‘B2’): 50,
(‘S02’, ‘A1’): 75,
(‘S03’, ‘C3′): 20
}
stock_s_idx = pd.Series(stock_data_idx, name=’Stock’)
stock_df_idx = stock_s_idx.to_frame()
stock_df_idx.index.names = [‘StoreID’, ‘ProductID’] # インデックスに名前を付ける
price_data_idx = {
(‘S01’, ‘A1’): 15.0,
(‘S01’, ‘B2’): 10.0,
(‘S02’, ‘A1’): 15.0,
(‘S04’, ‘D4′): 25.0 # S04はstock_df_idxにない
}
price_s_idx = pd.Series(price_data_idx, name=’Price’)
price_df_idx = price_s_idx.to_frame()
price_df_idx.index.names = [‘StoreID’, ‘ProductID’] # インデックスに名前を付ける
print(“\nstock_df_idx (MultiIndex):”)
print(stock_df_idx)
print(“\nprice_df_idx (MultiIndex):”)
print(price_df_idx)
MultiIndex をキーとして結合
merged_idx = pd.merge(stock_df_idx, price_df_idx, left_index=True, right_index=True, how=’outer’)
print(“\nmerged_idx (merged on MultiIndex):”)
print(merged_idx)
“`
実行結果:
“`
stock_df_idx (MultiIndex):
Stock
StoreID ProductID
S01 A1 100
B2 50
S02 A1 75
S03 C3 20
price_df_idx (MultiIndex):
Price
StoreID ProductID
S01 A1 15.0
B2 10.0
S02 A1 15.0
S04 D4 25.0
merged_idx (merged on MultiIndex):
Stock Price
StoreID ProductID
S01 A1 100.0 15.0
B2 50.0 10.0
S02 A1 75.0 15.0
S03 C3 20.0 NaN
S04 D4 NaN 25.0
“`
left_index=True
および right_index=True
を指定することで、それぞれのDataFrameのインデックス全体が結合キーとして使用されます。MultiIndexの場合は、そのレベル全てが複数キーとして機能します。上記の例では、('S01', 'A1')
, ('S01', 'B2')
, ('S02', 'A1')
といったインデックスの組み合わせで結合が行われています。how='outer'
としているため、どちらかのDataFrameにのみ存在するインデックスの組み合わせ(('S03', 'C3')
と ('S04', 'D4')
)も結果に含まれ、存在しない方のDataFrameの列は NaN
になります。
列とインデックスを組み合わせた複数キー結合:
left_on
, right_on
, left_index
, right_index
は組み合わせて使用することも可能です。例えば、左側のDataFrameは列をキーに、右側のDataFrameはインデックスをキーにして結合する場合です。
df_left_col
: 注文データ(列:(StoreID, ProductID, OrderDate), …)df_right_idx
: 在庫データ(インデックス:(StoreID, ProductID), 列:Stock)
この場合、df_left_col
の StoreID
と ProductID
列をキーに、df_right_idx
の MultiIndex (StoreID, ProductID) をキーとして結合します。
“`python
列とMultiIndex を組み合わせた結合のサンプルデータ作成
orders_data_col = {
‘OrderID’: [1, 2, 3, 4, 5, 6],
‘StoreID’: [‘S01’, ‘S02’, ‘S01’, ‘S01’, ‘S02’, ‘S01’],
‘ProductID’: [‘A1’, ‘B2’, ‘A1’, ‘C3’, ‘B2’, ‘A1’],
‘OrderDate’: [‘2023-05-01’, ‘2023-05-01’, ‘2023-05-02’, ‘2023-05-02’, ‘2023-05-03’, ‘2023-05-03’],
‘Quantity’: [10, 5, 8, 2, 3, 15]
}
orders_df_col = pd.DataFrame(orders_data_col)
orders_df_col[‘OrderDate’] = pd.to_datetime(orders_df_col[‘OrderDate’])
stock_df_idx は前の例で作成したものを使用
print(“\norders_df_col:”)
print(orders_df_col)
print(“\nstock_df_idx (MultiIndex):”)
print(stock_df_idx)
orders_df_col を left_on=[‘StoreID’, ‘ProductID’] で、
stock_df_idx を right_index=True で複数キー結合
merged_col_idx = pd.merge(orders_df_col, stock_df_idx,
left_on=[‘StoreID’, ‘ProductID’],
right_index=True,
how=’left’)
print(“\nmerged_col_idx (merged on columns and MultiIndex):”)
print(merged_col_idx)
“`
実行結果:
“`
orders_df_col:
OrderID StoreID ProductID OrderDate Quantity
0 1 S01 A1 2023-05-01 10
1 2 S02 B2 2023-05-01 5
2 3 S01 A1 2023-05-02 8
3 4 S01 C3 2023-05-02 2
4 5 S02 B2 2023-05-03 3
5 6 S01 A1 2023-05-03 15
stock_df_idx (MultiIndex):
Stock
StoreID ProductID
S01 A1 100
B2 50
S02 A1 75
S03 C3 20
merged_col_idx (merged on columns and MultiIndex):
OrderID StoreID ProductID OrderDate Quantity Stock
0 1 S01 A1 2023-05-01 10 100.0
1 2 S02 B2 2023-05-01 5 50.0
2 3 S01 A1 2023-05-02 8 100.0
3 4 S01 C3 2023-05-02 2 NaN # S01, C3 の組み合わせは stock_df_idx にない
4 5 S02 B2 2023-05-03 3 50.0
5 6 S01 A1 2023-05-03 15 100.0
“`
この例では、orders_df_col
の StoreID
と ProductID
列の組み合わせをキーに、stock_df_idx
の MultiIndex をキーとして結合が実行されています。how='left'
なので、orders_df_col
の全ての行が保持され、一致しないキーの組み合わせ (('S01', 'C3')
) に対応する stock_df_idx
の列 (Stock
) は NaN
になります。
このように、merge
関数は列キー (on
, left_on
, right_on
) とインデックスキー (left_index
, right_index
) を柔軟に組み合わせて使用できます。特に複数キーの場合、列名のリストや MultiIndex が重要な役割を果たします。
7. 異なる結合タイプ(how
引数)と複数キー
how
引数は、SQLのJOINと同じように、結合の種類を指定します。複数キー結合の場合も、これらの結合タイプの考え方は変わりません。結合キーとして指定した複数列の組み合わせが一致するかどうかで行を結合します。
-
how='inner'
(内部結合):- 左右両方のDataFrameに、結合キーとなる複数列の組み合わせが一致する行のみを結果に含めます。
- 最も一般的な結合タイプです。
- 上記サンプル例で
how='inner'
を指定した場合、stock_df_idx
に存在しないキーの組み合わせ (('S01', 'C3')
,('S03', 'C3')
,('S04', 'D4')
) を持つ行は結果に含まれません。
-
how='left'
(左外部結合):- 左側 (
left
) のDataFrameの全ての行を結果に含めます。 - 右側 (
right
) のDataFrameから、結合キーとなる複数列の組み合わせが一致する行があればその情報を含めます。 - 一致する組み合わせがない場合、右側のDataFrame由来の列は
NaN
になります。 - 上記サンプル例で
merged_col_idx
がhow='left'
を使用した結果です。
- 左側 (
-
how='right'
(右外部結合):- 左外部結合の逆です。右側 (
right
) のDataFrameの全ての行を結果に含めます。 - 左側 (
left
) のDataFrameから、結合キーとなる複数列の組み合わせが一致する行があればその情報を含めます。 - 一致する組み合わせがない場合、左側のDataFrame由来の列は
NaN
になります。
- 左外部結合の逆です。右側 (
-
how='outer'
(完全外部結合):- 左側 (
left
) と右側 (right
) のDataFrameの、結合キーとなる複数列の組み合わせの全てのユニークな組み合わせを結果に含めます。 - どちらか一方のDataFrameにのみ存在するキーの組み合わせの場合、存在しない方のDataFrame由来の列は
NaN
になります。 - 上記サンプル例で
merged_idx
がhow='outer'
を使用した結果です。
- 左側 (
-
how='cross'
(クロス結合):- 左右のDataFrameの全ての行の組み合わせを生成します(デカルト積)。
cross
結合ではon
,left_on
,right_on
,left_index
,right_index
のいずれも指定できません(指定しても無視されるか、エラーになる)。- したがって、複数キー結合の文脈では通常使用されません。ただし、
merge
関数のhow
オプションとして存在するため補足しました。
複数キー結合においても、どのタイプの結合を選択するかは分析の目的に依存します。
* 両方のデータに存在する情報だけが必要なら inner
。
* 左のデータを全て使い、それに関連する右の情報を付けたいなら left
。
* 右のデータを全て使い、それに関連する左の情報を付けたいなら right
。
* 両方のデータが持つ全てのキーの組み合わせを確認したいなら outer
。
8. 実用的な考慮事項と高度なトピック
複数キーでのデータ結合は強力ですが、実際のデータでは様々な問題に遭遇することがあります。ここでは、実用的な注意点やより高度なトピックについて解説します。
8.1. データ型の重要性
結合キーとして使用する列は、左右のDataFrameでデータ型が一致している必要があります。たとえ見た目が同じ '101'
という値であっても、片方が文字列型 (object
または string
) で、もう片方が数値型 (int64
) であれば、merge
はそれらを異なる値として扱い、結合キーが一致しないと判断します。
“`python
データ型が異なるキーの例
df1 = pd.DataFrame({‘ID’: [1, 2], ‘Value’: [‘A’, ‘B’]})
df2 = pd.DataFrame({‘ID’: [‘1’, ‘2’], ‘Detail’: [‘X’, ‘Y’]})
print(“\ndf1 (int ID):”)
print(df1)
print(“\ndf2 (string ID):”)
print(df2)
このまま結合しようとすると、キーが一致しないため結果は空になる (inner joinの場合)
merged_wrong_type = pd.merge(df1, df2, on=’ID’, how=’inner’)
print(“\nmerged_wrong_type (inner join on different types):”)
print(merged_wrong_type) # 結果: Empty DataFrame
“`
この問題を解決するには、結合前に一方または両方のキー列のデータ型を一致させる必要があります。astype()
メソッドを使用するのが一般的です。
“`python
データ型を一致させてから結合
df2[‘ID’] = df2[‘ID’].astype(int) # df2 の ID 列を整数型に変換
print(“\ndf2 (ID after type conversion):”)
print(df2)
merged_correct_type = pd.merge(df1, df2, on=’ID’, how=’inner’)
print(“\nmerged_correct_type (inner join after type conversion):”)
print(merged_correct_type)
“`
実行結果:
“`
df1 (int ID):
ID Value
0 1 A
1 2 B
df2 (string ID):
ID Detail
0 ‘1’ X
1 ‘2’ Y
merged_wrong_type (inner join on different types):
Empty DataFrame
Columns: [ID, Value, Detail]
Index: []
df2 (ID after type conversion):
ID Detail
0 1 X
1 2 Y
merged_correct_type (inner join after type conversion):
ID Value Detail
0 1 A X
1 2 B Y
“`
複数キー結合の場合も同様に、指定した全てのキー列の組み合わせについて、それぞれの列のデータ型が左右のDataFrameで一致している必要があります。結合前に df.info()
や df.dtypes
を使ってデータ型を確認し、必要に応じて変換を行いましょう。
8.2. キーにNaNが含まれる場合
デフォルトでは、merge
関数は結合キーに含まれる NaN
(欠損値)を、他の NaN
や有効な値とは一致しないものとして扱います。したがって、キーに NaN
が含まれる行は、NaN
を含む他の行とは結合されず、結合タイプによっては結果から除外されるか、相手側の情報が NaN
となります。
“`python
キーにNaNが含まれる例
df_a = pd.DataFrame({‘key’: [1, 2, np.nan, 4], ‘val_a’: [10, 20, 30, 40]})
df_b = pd.DataFrame({‘key’: [1, np.nan, 3, 4], ‘val_b’: [100, 200, 300, 400]})
print(“\ndf_a (with NaN key):”)
print(df_a)
print(“\ndf_b (with NaN key):”)
print(df_b)
NaNを含むキーでの内部結合
merged_nan_inner = pd.merge(df_a, df_b, on=’key’, how=’inner’)
print(“\nmerged_nan_inner (inner join with NaN keys):”)
print(merged_nan_inner) # NaNを含む行は結合されない
NaNを含むキーでの外部結合
merged_nan_outer = pd.merge(df_a, df_b, on=’key’, how=’outer’, indicator=True)
print(“\nmerged_nan_outer (outer join with NaN keys):”)
print(merged_nan_outer) # NaNを含む行はそれぞれの元DataFrameの情報として残る
“`
実行結果:
“`
df_a (with NaN key):
key val_a
0 1.0 10
1 2.0 20
2 NaN 30
3 4.0 40
df_b (with NaN key):
key val_b
0 1.0 100
1 NaN 200
2 3.0 300
3 4.0 400
merged_nan_inner (inner join with NaN keys):
key val_a val_b
0 1.0 10 100
1 4.0 40 400
merged_nan_outer (outer join with NaN keys):
key val_a val_b _merge
0 1.0 10.0 100.0 both
1 2.0 20.0 NaN left_only
2 NaN 30.0 NaN left_only
3 4.0 40.0 400.0 both
4 NaN NaN 200.0 right_only
5 3.0 NaN 300.0 right_only
“`
how='outer'
の結果を見ると、key
が NaN
の行は、df_a
由来 (left_only
) と df_b
由来 (right_only
) として別々に含まれており、互いに結合されていないことがわかります。
複数キーの場合も同じです。指定した複数キーの組み合わせのいずれかに NaN
が含まれる行は、そのキーの組み合わせが正確に一致しない限り結合されません。NaN
を含むキーで結合したい場合は、事前に NaN
を特定の値で置換するなどの前処理が必要になることがありますが、通常はキーに NaN
が含まれないようにデータクレンジングを行います。
8.3. 重複キーの扱い
merge
関数は、結合キーに重複がある場合(いわゆる「多対一」「一対多」「多対多」のリレーションシップ)、すべての可能な組み合わせを生成します。
- 一対一 (One-to-One): 左右どちらのDataFrameの結合キーも重複がない場合。結果の行数は元のDataFrameの行数以下になります (inner/left/right/outerによる)。
- 一対多 (One-to-Many): 左のDataFrameの結合キーは一意だが、右のDataFrameの結合キーは重複がある場合。左の各行は、右の一致する全ての行と結合されます。結果の行数は右のDataFrameの行数と同等かそれ以上になります。
- 多対一 (Many-to-One): 左のDataFrameの結合キーは重複があるが、右のDataFrameの結合キーは一意である場合。左の各行は、右の一致する唯一の行と結合されます。結果の行数は左のDataFrameの行数と同等かそれ以上になります。
- 多対多 (Many-to-Many): 左右どちらのDataFrameの結合キーも重複がある場合。左の重複する各行は、右の重複する全ての行と結合されます。結果の行数は、一致するキーの組み合わせの「左の重複数 × 右の重複数」の合計となり、非常に大きくなる可能性があります。
複数キー結合の場合もこの考え方は同じです。指定した複数列の組み合わせが結合キーとなり、その組み合わせが左右のDataFrameでそれぞれ何回出現するかに応じて、多対多の結合が発生する可能性があります。
意図しない多対多結合が発生すると、結果のDataFrameが予期せず巨大になり、分析に支障をきたすことがあります。これを防ぐためには、結合前にキーとして使用する複数列の組み合わせに重複がないか確認することが重要です。df.duplicated(subset=['key1', 'key2', ...]).any()
や df.value_counts(['key1', 'key2', ...])
を使用して確認できます。
また、merge
関数には validate
引数があり、期待するリレーションシップを指定できます。
* validate='one_to_one'
* validate='one_to_many'
* validate='many_to_one'
* validate='many_to_many'
(これはデフォルトの動作で、ほとんどチェックを行わない)
例えば、左のDataFrameの複数キーは一意であると期待し、右のDataFrameの複数キーは重複がある(一対多または多対多)と期待する場合、validate='one_to_many'
を指定します。もし実際には左のDataFrameのキーが重複していた場合、merge
はエラーを発生させて処理を中断してくれます。これはデータの問題を早期に発見するのに役立ちます。
8.4. 列名の衝突 (suffixes
引数)
左右のDataFrameに、結合キーではない同じ名前の列が存在する場合、merge
はデフォルトでそれらの列名にサフィックス (_x
と _y
) を付けて区別します。
“`python
列名衝突の例
df_left = pd.DataFrame({‘ID’: [1, 2], ‘Value’: [100, 200], ‘Code’: [‘A’, ‘B’]})
df_right = pd.DataFrame({‘ID’: [1, 3], ‘Value’: [1000, 3000], ‘Name’: [‘X’, ‘Z’]})
print(“\ndf_left:”)
print(df_left)
print(“\ndf_right:”)
print(df_right)
ID をキーに結合。’Value’ 列が衝突する。
merged_conflict = pd.merge(df_left, df_right, on=’ID’, how=’outer’)
print(“\nmerged_conflict (with default suffixes):”)
print(merged_conflict)
“`
実行結果:
“`
df_left:
ID Value Code
0 1 100 A
1 2 200 B
df_right:
ID Value Name
0 1 1000 X
1 3 3000 Z
merged_conflict (with default suffixes):
ID Value_x Code Value_y Name
0 1 100.0 A 1000.0 X
1 2 200.0 B NaN NaN
2 3 NaN NaN 3000.0 Z
“`
Value
列が衝突し、Value_x
と Value_y
として区別されていることがわかります。
suffixes
引数を使うことで、このサフィックスをカスタマイズできます。例えば suffixes=('_left', '_right')
と指定できます。
“`python
サフィックスをカスタマイズ
merged_custom_suffixes = pd.merge(df_left, df_right, on=’ID’, how=’outer’, suffixes=(‘_from_left’, ‘_from_right’))
print(“\nmerged_custom_suffixes (with custom suffixes):”)
print(merged_custom_suffixes)
“`
実行結果:
merged_custom_suffixes (with custom suffixes):
ID Value_from_left Code Value_from_right Name
0 1 100.0 A 1000.0 X
1 2 200.0 B NaN NaN
2 3 NaN NaN 3000.0 Z
複数キー結合の場合も同じです。結合キーに指定した列以外の列で名前が衝突した場合にサフィックスが付与されます。
8.5. 結合元を示す列の追加 (indicator
引数)
indicator
引数に True
を指定すると、結合結果に _merge
という新しい列が追加されます。この列は、各行が左右どちらのDataFrameから来たのか、または両方から来たのかを示します。これは、外部結合の結果を理解したり、結合されなかった行を特定したりするのに非常に便利です。
'left_only'
: その行が左側のDataFrameにのみ存在した(キーが右側になかった)'right_only'
: その行が右側のDataFrameにのみ存在した(キーが左側になかった)'both'
: その行が左右両方のDataFrameに存在した(キーが一致した)
“`python
indicator 引数の使用例 (単一キーですが、複数キーでも同じ挙動)
df_a = pd.DataFrame({‘key’: [1, 2, 4], ‘val_a’: [10, 20, 40]})
df_b = pd.DataFrame({‘key’: [1, 3, 4], ‘val_b’: [100, 300, 400]})
merged_with_indicator = pd.merge(df_a, df_b, on=’key’, how=’outer’, indicator=True)
print(“\nmerged_with_indicator (with indicator):”)
print(merged_with_indicator)
“`
実行結果:
merged_with_indicator (with indicator):
key val_a val_b _merge
0 1.0 10.0 100.0 both
1 2.0 20.0 NaN left_only
2 4.0 40.0 400.0 both
3 3.0 NaN 300.0 right_only
複数キー結合の場合も、キーとなる複数列の組み合わせが左右どちらに存在したかに基づいて _merge
列の値が決まります。これは、外部結合で予期しない NaN
が発生した原因を特定するのに役立ちます。
8.6. パフォーマンスに関する考慮事項
大規模なDataFrameを結合する場合、パフォーマンスが重要になります。
- 適切なキーの選択: 結合キーとして適切な列を選択することは、正確さだけでなくパフォーマンスにも影響します。データ型が統一されている、不必要な重複が少ないなどのキーが望ましいです。
- データ型の最適化: オブジェクト型(文字列)のキーは、数値型やカテゴリ型に比べて比較が遅くなることがあります。可能な場合はデータ型を最適化しましょう。
- インデックスを使った結合:
left_index=True
やright_index=True
を使った結合は、pandasの内部処理が最適化されていることが多く、大規模データで高速になる傾向があります。もし結合キーとして使用したい列が既にインデックスになっている、またはインデックスに設定しても問題ない場合は、インデックス結合を検討しましょう。ただし、列とインデックスを組み合わせたり、MultiIndex を扱う場合は複雑さが増す可能性があります。 - メモリ使用量: 結合は新しいDataFrameを生成するため、メモリを多く消費する可能性があります。特に多対多結合で結果が巨大になる場合は注意が必要です。大きなデータの場合、処理を分割したり、Daskのようなより大規模データ向けのライブラリを検討する必要があるかもしれません。
- ソート: デフォルトでは
merge
は結合結果をキーでソートしません (sort=False
)。ソートが必要なければこのままでOKですが、もし結果をソートしたい場合はsort=True
を指定します。ソートは追加の処理コストがかかります。
8.7. join
関数との使い分け
前述の通り、merge
と join
は似た機能を持っていますが、使い分けの目安があります。
merge
: 主に列をキーとして結合する場合に、より柔軟な指定が可能です (on
,left_on
,right_on
)。複数キーの場合も列名のリストを指定できるため、merge
がより直感的です。左右でキーの列名が異なる場合や、インデックスと列を組み合わせてキーにする場合にも便利です。join
: 主にインデックスをキーとして結合する場合に、より簡潔に記述できます (df1.join(df2)
)。複数DataFrameを同時に結合したい場合 (df1.join([df2, df3])
) にも便利です。ただし、インデックス結合に特化しているため、列をキーにする場合はon
引数での指定が限られます。
複数キー結合では、キーが全て列である場合は merge
(on
または left_on
/right_on
) が最も一般的です。キーが全てMultiIndexである場合は merge
(left_index=True, right_index=True
) または join
(df1.join(df2)
) のどちらでも可能ですが、merge
の方が引数が明示的で分かりやすいと感じる人もいるでしょう。列とインデックスを組み合わせる場合は merge
が必要です。
8.8. SQLのJOINとの比較
pandasの merge
関数は、リレーショナルデータベースのSQLにおける JOIN
句と非常によく似ています。
pandas merge |
SQL JOIN | 説明 |
---|---|---|
how='inner' , on=[k1, k2] |
INNER JOIN ON T1.k1 = T2.k1 AND T1.k2 = T2.k2 |
両方のテーブルにキーの組み合わせが存在する行のみを取得 |
how='left' , on=[k1, k2] |
LEFT JOIN ON T1.k1 = T2.k1 AND T1.k2 = T2.k2 |
左テーブルの全ての行と、一致する右テーブルの行を取得 |
how='right' , on=[k1, k2] |
RIGHT JOIN ON T1.k1 = T2.k1 AND T1.k2 = T2.k2 |
右テーブルの全ての行と、一致する左テーブルの行を取得 |
how='outer' , on=[k1, k2] |
FULL OUTER JOIN ON T1.k1 = T2.k1 AND T1.k2 = T2.k2 |
両方のテーブルの全てのキーの組み合わせの行を取得 |
how='cross' |
CROSS JOIN |
両方のテーブルの全ての行の組み合わせを取得(キー指定なし) |
left_on=[k1_l, k2_l] , right_on=[k1_r, k2_r] |
JOIN ON T1.k1_l = T2.k1_r AND T1.k2_l = T2.k2_r |
左右でキー列名が異なる場合の結合 |
複数キーを指定する場合 (on=['k1', 'k2']
や left_on=[...], right_on=[...]
) は、SQLの ON
句で複数の条件を AND
で連結するのと同様のロジックになります。
SQLの経験がある方にとって、merge
関数の概念は非常に理解しやすいでしょう。
9. 応用例:より複雑な複数キー結合
これまでに説明した概念を用いて、より現実的な応用例を見てみましょう。
例:注文データ、商品データ、店舗データ、配送データを結合して分析する
以下の4つのデータフレームがあるとします。
orders_df
: 注文サマリー(OrderID, CustomerID, StoreID, OrderDate, TotalAmount)items_df
: 注文に含まれる商品(OrderID, ProductID, Quantity, Price)stores_df
: 店舗情報(StoreID, StoreName, City)shipping_df
: 配送情報(OrderID, ShippingDate, Carrier)
これらのデータフレームを結合して、「特定の都市の店舗で行われた注文のうち、あるカテゴリの商品が、いつ、どの配送業者によって配送されたか」といった分析を行いたいとします。
まず、orders_df
と items_df
を結合して、各注文に含まれる商品の詳細を注文サマリーに紐づけます。共通のキーは OrderID
です。items_df
には同じ OrderID
が複数出現する(1つの注文に複数の商品がある)ため、これは一対多(orders_df
: items_df
)の結合になります。
“`python
応用例のサンプルデータ作成
orders_data_ex = {
‘OrderID’: [1, 2, 3, 4, 5],
‘CustomerID’: [101, 102, 101, 103, 102],
‘StoreID’: [‘S01’, ‘S02’, ‘S01’, ‘S03’, ‘S02’],
‘OrderDate’: [‘2023-06-01’, ‘2023-06-01’, ‘2023-06-02’, ‘2023-06-02’, ‘2023-06-03’],
‘TotalAmount’: [2500, 5000, 1800, 7000, 3200]
}
orders_df_ex = pd.DataFrame(orders_data_ex)
items_data_ex = {
‘OrderID’: [1, 1, 2, 2, 3, 4, 4, 5],
‘ProductID’: [‘A’, ‘B’, ‘C’, ‘D’, ‘A’, ‘E’, ‘F’, ‘C’],
‘Quantity’: [2, 1, 1, 1, 3, 2, 1, 2],
‘Price’: [1000, 500, 5000, 2000, 600, 3000, 4000, 1600]
}
items_df_ex = pd.DataFrame(items_data_ex)
stores_data_ex = {
‘StoreID’: [‘S01’, ‘S02’, ‘S03’],
‘StoreName’: [‘Downtown’, ‘Uptown’, ‘Suburban’],
‘City’: [‘Tokyo’, ‘Osaka’, ‘Tokyo’]
}
stores_df_ex = pd.DataFrame(stores_data_ex)
shipping_data_ex = {
‘OrderID’: [1, 2, 3, 4],
‘ShippingDate’: [‘2023-06-02’, ‘2023-06-02’, ‘2023-06-03’, ‘2023-06-03’],
‘Carrier’: [‘Yamato’, ‘Sagawa’, ‘Yamato’, ‘Sagawa’]
}
shipping_df_ex = pd.DataFrame(shipping_data_ex)
shipping_df_ex[‘ShippingDate’] = pd.to_datetime(shipping_df_ex[‘ShippingDate’])
orders_df_ex と items_df_ex を OrderID で結合
items_df_ex は OrderID が重複する(一対多)
merged_orders_items = pd.merge(orders_df_ex, items_df_ex, on=’OrderID’, how=’left’)
print(“\nmerged_orders_items:”)
print(merged_orders_items)
“`
次に、この merged_orders_items
に店舗情報 (stores_df_ex
) を結合します。キーは StoreID
です。
“`python
merged_orders_items に stores_df_ex を StoreID で結合
merged_orders_items_stores = pd.merge(merged_orders_items, stores_df_ex, on=’StoreID’, how=’left’)
print(“\nmerged_orders_items_stores:”)
print(merged_orders_items_stores)
“`
さらに、配送情報 (shipping_df_ex
) を結合します。キーは OrderID
です。
“`python
merged_orders_items_stores に shipping_df_ex を OrderID で結合
final_merged_ex = pd.merge(merged_orders_items_stores, shipping_df_ex, on=’OrderID’, how=’left’)
print(“\nfinal_merged_ex:”)
print(final_merged_ex)
“`
ここまでで、単一キー結合を複数回行うことで目的のDataFrameを作成しました。しかし、もし分析において「特定の店舗で、特定の日に行われた注文」や「特定の顧客が、特定の商品を注文した履歴」のように、複数の条件を組み合わせてレコードを識別する必要がある場合、複数キー結合がより効率的で直感的になる場合があります。
複数キー結合を使うシナリオ:
仮に、以下のようなデータがあったとします。
sales_records_df
: 販売記録(SaleID, StoreID, ProductID, SaleDate, Quantity)daily_targets_df
: 日別・店舗別・商品別の販売目標(StoreID, ProductID, TargetDate, TargetQuantity)
「各販売記録が、対応する目標に対してどの程度達成したか」を知りたいとします。ここで、目標データ (daily_targets_df
) の各行は (StoreID
, ProductID
, TargetDate
) という複数キーで一意に特定されます。販売記録 (sales_records_df
) の各行は通常 SaleID で一意ですが、目標データと紐づけるためにはその販売記録の (StoreID
, ProductID
, SaleDate
) の組み合わせを目標データの (StoreID
, ProductID
, TargetDate
) の組み合わせと一致させる必要があります。
“`python
複数キー結合を使う応用例のサンプルデータ
sales_records_data = {
‘SaleID’: [101, 102, 103, 104, 105],
‘StoreID’: [‘S01’, ‘S01’, ‘S02’, ‘S01’, ‘S02’],
‘ProductID’: [‘A’, ‘B’, ‘A’, ‘A’, ‘B’],
‘SaleDate’: [‘2023-07-01’, ‘2023-07-01’, ‘2023-07-01’, ‘2023-07-02’, ‘2023-07-02’],
‘Quantity’: [5, 2, 3, 1, 2]
}
sales_records_df = pd.DataFrame(sales_records_data)
sales_records_df[‘SaleDate’] = pd.to_datetime(sales_records_df[‘SaleDate’])
daily_targets_data = {
‘StoreID’: [‘S01’, ‘S01’, ‘S02’, ‘S02’, ‘S01’, ‘S01’],
‘ProductID’: [‘A’, ‘B’, ‘A’, ‘B’, ‘A’, ‘B’],
‘TargetDate’: [‘2023-07-01’, ‘2023-07-01’, ‘2023-07-01’, ‘2023-07-01’, ‘2023-07-02’, ‘2023-07-02’],
‘TargetQuantity’: [10, 5, 8, 4, 12, 6]
}
daily_targets_df = pd.DataFrame(daily_targets_data)
daily_targets_df[‘TargetDate’] = pd.to_datetime(daily_targets_df[‘TargetDate’])
print(“\nsales_records_df:”)
print(sales_records_df)
print(“\ndaily_targets_df:”)
print(daily_targets_df)
sales_records_df と daily_targets_df を (StoreID, ProductID, Date) で複数キー結合
sales_records_df の SaleDate と daily_targets_df の TargetDate は列名が異なる
merged_sales_targets = pd.merge(sales_records_df, daily_targets_df,
left_on=[‘StoreID’, ‘ProductID’, ‘SaleDate’],
right_on=[‘StoreID’, ‘ProductID’, ‘TargetDate’],
how=’left’)
結合キーとして使用した TargetDate 列は不要なので削除(あるいは用途に合わせて残す)
merged_sales_targets = merged_sales_targets.drop(columns=[‘TargetDate’])
print(“\nmerged_sales_targets (multiple keys with different date column name):”)
print(merged_sales_targets)
“`
実行結果:
“`
sales_records_df:
SaleID StoreID ProductID SaleDate Quantity
0 101 S01 A 2023-07-01 5
1 102 S01 B 2023-07-01 2
2 103 S02 A 2023-07-01 3
3 104 S01 A 2023-07-02 1
4 105 S02 B 2023-07-02 2
daily_targets_df:
StoreID ProductID TargetDate TargetQuantity
0 S01 A 2023-07-01 10
1 S01 B 2023-07-01 5
2 S02 A 2023-07-01 8
3 S02 B 2023-07-01 4
4 S01 A 2023-07-02 12
5 S01 B 2023-07-02 6
merged_sales_targets (multiple keys with different date column name):
SaleID StoreID ProductID SaleDate Quantity TargetDate TargetQuantity
0 101 S01 A 2023-07-01 5 2023-07-01 10.0
1 102 S01 B 2023-07-01 2 2023-07-01 5.0
2 103 S02 A 2023-07-01 3 2023-07-01 8.0
3 104 S01 A 2023-07-02 1 2023-07-02 12.0
4 105 S02 B 2023-07-02 2 NaT NaN # S02, B, 2023-07-02 の組み合わせは目標データにない
“`
この例では、sales_records_df
の各販売記録が、対応する日別・店舗別・商品別の販売目標 (daily_targets_df
) と正確に紐づけられています。キーとして StoreID
, ProductID
, そして日付列 (SaleDate
と TargetDate
) の3つを指定することで、単一キーでは不可能な正確な紐づけを実現しています。how='left'
の結果、('S02', 'B', '2023-07-02')
という組み合わせは daily_targets_df
に存在しないため、対応する TargetDate
と TargetQuantity
は NaN
になっています。
10. まとめ
この記事では、pandasの merge
関数を使用して複数のキーでデータフレームを結合する方法について、基本から応用まで詳細に解説しました。
- データ分析におけるデータ結合の重要性とその種類(
merge
,join
,concat
)を確認しました。 pd.merge()
関数の主要な引数とその役割を学びました。- 左右のDataFrameでキー列名が同じ場合は
on
引数にリストを、異なる場合はleft_on
とright_on
にそれぞれリストを指定して複数キー結合を行う方法を具体的なコード例で示しました。 - MultiIndex を含むDataFrameを、インデックスを複数キーとして結合する方法や、列とインデックスを組み合わせて複数キー結合する方法を紹介しました。
- 内部結合、外部結合といった異なる結合タイプが複数キー結合でどのように機能するかを説明しました。
- データ型の一致、NaNキーの扱い、重複キー、列名の衝突、パフォーマンスといった実用的な考慮事項や、
indicator
引数、validate
引数の活用方法について解説しました。 join
関数やSQLのJOINとの比較を通じて、merge
関数の位置づけを明確にしました。- より複雑なシナリオでの応用例を通じて、複数キー結合が実際の分析タスクでどのように役立つかを示しました。
merge
関数による複数キー結合は、リレーショナルな構造を持つデータを扱う上で非常に強力なツールです。正確な結合を行うためには、結合キーとして指定する複数列の組み合わせが一意性を保っているか、データ型が一致しているかなどを事前に確認することが重要です。
この記事で得た知識を活用し、皆様のデータ分析タスクがより効率的かつ正確に進むことを願っています。