【決定版】pandas DataFrameの結合方法まとめ(concat, merge, join)


【決定版】pandas DataFrameの結合方法まとめ(concat, merge, join)

データ分析において、異なるデータソースから取得した情報を統合したり、関連するデータセットを組み合わせたりすることは日常的に行われます。pandasライブラリは、このデータ結合のプロセスを効率的かつ柔軟に行うための強力な機能を提供しています。特に重要なのが、pd.concatpd.merge、そしてDataFrame.joinという3つのメソッドです。

これらのメソッドは似ているようで異なる役割を持ち、状況に応じて適切に使い分けることが重要です。この記事では、「決定版」として、これら3つの主要な結合方法について、その基本的な使い方から応用、さらには使い分けや注意点まで、約5000語の詳細な解説を行います。

データ分析の効率と精度を高めるために、DataFrameの結合スキルは必須です。この記事を読み終える頃には、あなたはpandasにおけるデータ結合の達人となっていることでしょう。

1. はじめに:なぜデータ結合が必要なのか

現代のデータは、しばしば複数の場所に分散して存在しています。例えば、顧客情報はCRMシステムに、注文履歴はトランザクションデータベースに、ウェブサイトの行動データはログファイルに格納されているかもしれません。これらの異なるデータソースを組み合わせて分析することで、より深い洞察を得ることができます。

  • 情報の統合: 複数のデータセットに分散している情報を一つにまとめたい。
  • 関連データの付加: あるデータセットの各レコードに対し、別のデータセットから関連する情報を追加したい。
  • データの増強: 既存のデータに新しい観測値(行)や属性(列)を追加したい。

pandas DataFrameは、このようなデータ操作の中心的な役割を果たします。そして、これらの結合ニーズに応えるために、concatmergejoinが提供されています。

これらの操作は、リレーショナルデータベースにおけるUNION(concatに近い)やJOIN(merge/joinに近い)といった操作に対応しており、データ分析のワークフローにおいて非常に頻繁に登場します。

この記事では、以下の内容を詳細に解説します。

  • pd.concat(): 複数のDataFrameやSeriesを単純に連結する方法
  • pd.merge(): キーに基づいてリレーショナルに結合する方法
  • DataFrame.join(): 主にインデックスに基づいて結合する方法
  • それぞれの使い分け、応用的な引数、注意点

早速、それぞれの方法を詳しく見ていきましょう。

2. DataFrame結合の基本概念

pandasの結合操作は、基本的に2つ以上のDataFrame(あるいはSeries)を組み合わせるものです。組み合わせ方には、大きく分けて「連結 (Concatenation)」と「結合 (Merging/Joining)」の2種類があります。

  • 連結 (Concatenation): 複数のDataFrameを、指定した軸(行方向または列方向)に沿って単純に結合します。元のDataFrameの構造をそのまま引き継ぎつつ、積み重ねるイメージです。これは主に、同じ構造を持つデータセットを一つにまとめたい場合に使用します。pd.concat()がこの役割を担います。SQLのUNION操作に似ています。
  • 結合 (Merging/Joining): 2つのDataFrameを、一つ以上の「キー」となる列やインデックスの値に基づいて関連付け、結合します。キーが一致する行を探し、それらを組み合わせた新しい行を作成します。これは、リレーショナルデータベースにおけるJOIN操作に相当します。pd.merge()およびDataFrame.join()がこの役割を担います。

これらの操作を理解する上で重要なのは、結合の「キー」と「方法 (How)」です。

  • キー (Key): どの列(またはインデックス)の値を基準にデータをマッチングさせるか。例えば、顧客ID、製品コードなどがキーとなり得ます。キーが一致する行同士が結合されます。
  • 方法 (How): キーがすべてのDataFrameに存在するとは限りません。キーが見つからなかった行をどのように扱うかを指定するのが「方法」です。SQLのINNER JOINLEFT JOINRIGHT JOINFULL OUTER JOINに対応する考え方です。

これらの概念を頭に入れた上で、各メソッドの詳細を見ていきましょう。

“`python
import pandas as pd
import numpy as np

サンプルデータの準備

df1 = pd.DataFrame({
‘A’: [‘A0’, ‘A1’, ‘A2’, ‘A3’],
‘B’: [‘B0’, ‘B1’, ‘B2’, ‘B3’],
‘C’: [‘C0’, ‘C1’, ‘C2’, ‘C3’],
‘D’: [‘D0’, ‘D1’, ‘D2’, ‘D3’]
}, index=[0, 1, 2, 3])

df2 = pd.DataFrame({
‘A’: [‘A4’, ‘A5’, ‘A6’, ‘A7’],
‘B’: [‘B4’, ‘B5’, ‘B6’, ‘B7’],
‘C’: [‘C4’, ‘C5’, ‘C6’, ‘C7’],
‘D’: [‘D4’, ‘D5’, ‘D6’, ‘D7’]
}, index=[4, 5, 6, 7])

df3 = pd.DataFrame({
‘A’: [‘A8’, ‘A9’, ‘A10’, ‘A11’],
‘B’: [‘B8’, ‘B9’, ‘B10’, ‘B11’],
‘C’: [‘C8’, ‘C9’, ‘C10’, ‘C11’],
‘D’: [‘D8’, ‘D9’, ‘D10’, ‘D11’]
}, index=[8, 9, 10, 11])

Merge/Join 用のサンプルデータ

df_left = pd.DataFrame({
‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K3’],
‘A’: [‘A0’, ‘A1’, ‘A2’, ‘A3’],
‘B’: [‘B0’, ‘B1’, ‘B2’, ‘B3’]
})

df_right = pd.DataFrame({
‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K4’],
‘C’: [‘C0’, ‘C1’, ‘C2’, ‘C4’],
‘D’: [‘D0’, ‘D1’, ‘D2’, ‘D4’]
})

df_left_index = pd.DataFrame({
‘A’: [‘A0’, ‘A1’, ‘A2’, ‘A3’],
‘B’: [‘B0’, ‘B1’, ‘B2’, ‘B3’]
}, index=[‘K0’, ‘K1’, ‘K2’, ‘K3’])

df_right_index = pd.DataFrame({
‘C’: [‘C0’, ‘C1’, ‘C2’, ‘C4’],
‘D’: [‘D0’, ‘D1’, ‘D2’, ‘D4’]
}, index=[‘K0’, ‘K1’, ‘K2’, ‘K4’])

衝突列を持つサンプルデータ

df_left_col_conflict = pd.DataFrame({
‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K3’],
‘col1’: [1, 2, 3, 4],
‘col2’: [5, 6, 7, 8]
})

df_right_col_conflict = pd.DataFrame({
‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K4’],
‘col1’: [9, 10, 11, 12],
‘col3’: [13, 14, 15, 16]
})

print(“— サンプルデータ —“)
print(“df1:\n”, df1)
print(“\ndf2:\n”, df2)
print(“\ndf3:\n”, df3)
print(“\ndf_left:\n”, df_left)
print(“\ndf_right:\n”, df_right)
print(“\ndf_left_index:\n”, df_left_index)
print(“\ndf_right_index:\n”, df_right_index)
print(“\ndf_left_col_conflict:\n”, df_left_col_conflict)
print(“\ndf_right_col_conflict:\n”, df_right_col_conflict)
print(“——————–“)
“`

3. pd.concat(): シンプルな連結

pd.concat()関数は、pandasオブジェクト(SeriesまたはDataFrame)を、指定された軸に沿って単純に連結するために使用されます。これは、データベースのUNION ALL操作に最も概念的に近いです。

3.1 基本的な使い方

pd.concat()に連結したいオブジェクトのリストを渡すのが最も基本的な使い方です。デフォルトでは、オブジェクトは行方向(axis=0)に連結されます。

“`python

例: 3つのDataFrameを行方向に連結

result_concat_rows = pd.concat([df1, df2, df3])
print(“\n— pd.concat() (axis=0, デフォルト) —“)
print(result_concat_rows)
“`

出力を見ると、df1, df2, df3が上から順に連結され、元のインデックスがそのまま保持されていることがわかります。

3.2 列方向への連結 (axis=1)

axis=1を指定すると、オブジェクトは列方向に連結されます。この場合、pandasはインデックスを揃えようとします。インデックスが一致しない行については、欠損値(NaN)が生成されます。

“`python

例: 2つのDataFrameを列方向に連結

この例ではdf1とdf2のインデックス範囲が異なるため、注意が必要

result_concat_cols = pd.concat([df1, df2], axis=1)
print(“\n— pd.concat() (axis=1) —“)
print(result_concat_cols)
“`

出力を見ると、インデックス0から3まではdf1の列が、インデックス4から7まではdf2の列が入っていますが、それぞれのDataFrameに存在しないインデックスの行はNaNになっています。

もし、インデックスを揃えて列を追加したい場合は、mergejoinの方が適していることが多いですが、単に隣に並べたいだけであればconcat(axis=1)も使えます。

3.3 インデックスの扱い

concatにおけるインデックスの扱いは重要です。

元のインデックスを保持 (デフォルト)

デフォルトでは、元のDataFrameのインデックスがそのまま保持されます。これにより、同じインデックス値が結果のDataFrameに複数出現する可能性があります。

“`python

df1とdf1自体を連結(インデックス重複の例)

result_concat_duplicate_index = pd.concat([df1, df1])
print(“\n— pd.concat() (元のインデックス保持、重複あり) —“)
print(result_concat_duplicate_index)
“`

新しいインデックスを生成 (ignore_index=True)

連結後のDataFrameに新しい連続したインデックス(0から始まる整数)を付けたい場合は、ignore_index=Trueを指定します。元のインデックスは破棄されます。

“`python

例: ignore_index=True

result_concat_ignore_index = pd.concat([df1, df2, df3], ignore_index=True)
print(“\n— pd.concat() (ignore_index=True) —“)
print(result_concat_ignore_index)
“`

これは、複数のファイルを読み込んで一つのDataFrameにまとめる際など、元のインデックスに関心がない場合によく使用されます。

階層的なインデックスの生成 (keys)

連結元のDataFrameを区別したい場合、keys引数を使って階層的なインデックス(MultiIndex)を生成できます。keysには、連結したいオブジェクトに対応するキーのリストを渡します。

“`python

例: keys引数を使用

result_concat_keys = pd.concat([df1, df2, df3], keys=[‘df1’, ‘df2’, ‘df3’])
print(“\n— pd.concat() (keys引数) —“)
print(result_concat_keys)

階層的なインデックスを使った選択

print(“\n— 階層インデックスを使った選択 (df2の部分) —“)
print(result_concat_keys.loc[‘df2’])
“`

keys引数は、特に複数のファイルを読み込む際に、どのデータがどのファイル由来かを追跡するのに便利です。axis=1と組み合わせることも可能です。

“`python

例: axis=1 と keys引数を組み合わせる

result_concat_cols_keys = pd.concat([df1, df_right], axis=1, keys=[‘LeftData’, ‘RightData’])
print(“\n— pd.concat() (axis=1, keys引数) —“)
print(result_concat_cols_keys)
“`

3.4 データの揃っていない場合の挙動 (join引数)

連結するDataFrameの列が完全に一致しない場合があります。concat関数は、このような状況をjoin引数で制御できます。

join='outer' (デフォルト)

デフォルトのjoin='outer'は、すべてのDataFrameに存在する列の和集合を結果のDataFrameの列とします。存在しない列の場所にはNaNが入ります。

“`python
df_partial1 = pd.DataFrame({‘A’: [1, 2], ‘B’: [3, 4]})
df_partial2 = pd.DataFrame({‘C’: [5, 6], ‘D’: [7, 8]})

result_concat_outer = pd.concat([df_partial1, df_partial2], axis=0)
print(“\n— pd.concat() (join=’outer’, axis=0) —“)
print(result_concat_outer)

df_partial3 = pd.DataFrame({‘A’: [9, 10], ‘E’: [11, 12]})
result_concat_outer_cols = pd.concat([df_partial1, df_partial3], axis=1)
print(“\n— pd.concat() (join=’outer’, axis=1) —“)
print(result_concat_outer_cols)
“`

join='inner'

join='inner'を指定すると、すべてのDataFrameに共通して存在する列だけが結果のDataFrameに含まれます。

“`python
result_concat_inner = pd.concat([df_partial1, df_partial2], axis=0, join=’inner’)
print(“\n— pd.concat() (join=’inner’, axis=0) —“)
print(result_concat_inner)

result_concat_inner_cols = pd.concat([df_partial1, df_partial3], axis=1, join=’inner’)
print(“\n— pd.concat() (join=’inner’, axis=1) —“)
print(result_concat_inner_cols)
“`

axis=0の場合、join='inner'は全てのDataFrameに存在する列名のみを保持します。axis=1の場合、join='inner'は全てのDataFrameに存在するインデックス名のみを保持します。

3.5 その他の引数

  • names: keysと組み合わせて使用し、階層インデックスの各レベルに名前を付けることができます。
  • verify_integrity: Trueに設定すると、連結後の結果に重複したインデックスがある場合にValueErrorを発生させます。特にignore_index=False(デフォルト)の場合に、意図しないインデックス重複を防ぐのに役立ちます。

“`python

例: verify_integrity=True

try:
pd.concat([df1, df1], verify_integrity=True)
except ValueError as e:
print(“\n— pd.concat() (verify_integrity=True, 重複インデックスエラー) —“)
print(e)
“`

3.6 concatのユースケース

  • 同じスキーマを持つ複数のデータファイル(例: 月ごとの売上データCSV)をまとめて読み込む場合。
  • 複数の実験結果データセットを一つにまとめる場合。
  • 計算や処理によって分割されたデータを再度統合する場合。
  • 既存のDataFrameに行や列を単純に追加する場合(appendメソッドの代わりにconcatが推奨されます)。

4. pd.merge(): キーに基づいた結合

pd.merge()関数は、2つのDataFrameを、一つ以上のキー列(またはインデックス)の値に基づいてリレーショナルに結合します。これはSQLのJOIN操作に最も直接的に対応します。

4.1 基本的な使い方と結合キーの指定

mergeは常に2つのDataFrame (left, right) に対して行われます。結合の基準となるキーを指定する必要があります。

“`python

サンプルデータ (再掲)

df_left: ‘key’, ‘A’, ‘B’

df_right: ‘key’, ‘C’, ‘D’

print(“— df_left:\n”, df_left)
print(“\n— df_right:\n”, df_right)

例: ‘key’ 列をキーとして内部結合 (デフォルト)

result_merge_inner = pd.merge(df_left, df_right, on=’key’)
print(“\n— pd.merge() (on=’key’, how=’inner’) —“)
print(result_merge_inner)
“`

on='key'は、両方のDataFrameに存在する'key'という列を結合キーとして使用することを意味します。デフォルトのhow='inner'により、'key'の値が両方のDataFrameに存在する行だけが結果に含まれます(’K0′, ‘K1’, ‘K2’)。

4.2 異なる名前のキー列での結合 (left_on, right_on)

左右のDataFrameで結合したい列の名前が異なる場合は、left_onright_onを使用します。

“`python
df_left_rename = df_left.rename(columns={‘key’: ‘left_key’})
df_right_rename = df_right.rename(columns={‘key’: ‘right_key’})

print(“\n— df_left_rename:\n”, df_left_rename)
print(“\n— df_right_rename:\n”, df_right_rename)

例: left_on=’left_key’, right_on=’right_key’

result_merge_on_diff_names = pd.merge(df_left_rename, df_right_rename, left_on=’left_key’, right_on=’right_key’)
print(“\n— pd.merge() (left_on, right_on) —“)
print(result_merge_on_diff_names)
“`

結果のDataFrameには、デフォルトでは両方のキー列が含まれます。どちらか一方、または両方が不要な場合は、結合後に列を削除するなどの処理が必要です。

4.3 複数のキー列での結合

複数の列を組み合わせて結合キーとすることも可能です。on, left_on, right_onには列名のリストを渡します。

“`python
df_multi_key1 = pd.DataFrame({
‘key1’: [‘A’, ‘A’, ‘B’, ‘B’],
‘key2’: [‘One’, ‘Two’, ‘One’, ‘Two’],
‘value1’: [1, 2, 3, 4]
})

df_multi_key2 = pd.DataFrame({
‘key1’: [‘A’, ‘B’, ‘B’, ‘A’],
‘key2’: [‘One’, ‘One’, ‘Two’, ‘One’],
‘value2’: [5, 6, 7, 8]
})

print(“\n— df_multi_key1:\n”, df_multi_key1)
print(“\n— df_multi_key2:\n”, df_multi_key2)

例: key1とkey2の組み合わせで結合

result_merge_multi_key = pd.merge(df_multi_key1, df_multi_key2, on=[‘key1’, ‘key2’])
print(“\n— pd.merge() (複数のキー) —“)
print(result_merge_multi_key)
“`

キーの組み合わせ (‘A’, ‘One’), (‘B’, ‘One’), (‘B’, ‘Two’) が両方のDataFrameに存在するため、これらの行が結合されています。

4.4 インデックスをキーとして使用 (left_index, right_index)

DataFrameのインデックスを結合キーとして使用することもできます。

“`python

サンプルデータ (再掲)

df_left_index: index=’K0′..’K3′, columns=’A’, ‘B’

df_right_index: index=’K0′..’K4′, columns=’C’, ‘D’

print(“\n— df_left_index:\n”, df_left_index)
print(“\n— df_right_index:\n”, df_right_index)

例: 両方のDataFrameのインデックスをキーとして結合

result_merge_index = pd.merge(df_left_index, df_right_index, left_index=True, right_index=True)
print(“\n— pd.merge() (left_index=True, right_index=True) —“)
print(result_merge_index)
“`

これはDataFrame.join()のデフォルトの挙動と同じです。

片方だけインデックス、もう片方は列をキーにする場合

一方のDataFrameではインデックスをキーとし、もう一方では特定の列をキーとする場合は、left_index=Trueright_on='列名'、またはその逆を使用します。

“`python

例: df_left_index (インデックス) と df_right (列’key’) を結合

result_merge_index_on = pd.merge(df_left_index, df_right, left_index=True, right_on=’key’)
print(“\n— pd.merge() (left_index=True, right_on=’key’) —“)
print(result_merge_index_on)
“`

この場合、結果のインデックスはデフォルトでは自動生成されたものになります。元のインデックスやキー列をインデックスとして保持したい場合は、結合後にset_index()などを使用します。

4.5 結合方法の指定 (how引数)

how引数により、キーが片方のDataFrameにしか存在しない場合の挙動を制御できます。これはSQLのJOINタイプに対応します。

how='inner' (デフォルト)

両方のDataFrameにキーが存在する行のみを含めます(共通部分)。

python
result_merge_inner = pd.merge(df_left, df_right, on='key', how='inner')
print("\n--- pd.merge() (how='inner') ---")
print(result_merge_inner)

キー ‘K0’, ‘K1’, ‘K2′ は両方に存在しますが、’K3’ は df_left のみ、’K4′ は df_right のみに存在するため、結果には ‘K0’, ‘K1’, ‘K2’ のみが含まれます。

how='left'

左側のDataFrame (left) のすべてのキーを含め、右側のDataFrameにマッチするキーがあればその情報を付加します。右側にマッチするキーがない場合は、右側の列にはNaNが入ります。

python
result_merge_left = pd.merge(df_left, df_right, on='key', how='left')
print("\n--- pd.merge() (how='left') ---")
print(result_merge_left)

df_leftのすべてのキー(‘K0’, ‘K1’, ‘K2’, ‘K3′) が含まれています。’K3’ に対応する行はdf_rightに存在しないため、’C’と’D’の列はNaNになっています。

how='right'

右側のDataFrame (right) のすべてのキーを含め、左側のDataFrameにマッチするキーがあればその情報を付加します。左側にマッチするキーがない場合は、左側の列にはNaNが入ります。

python
result_merge_right = pd.merge(df_left, df_right, on='key', how='right')
print("\n--- pd.merge() (how='right') ---")
print(result_merge_right)

df_rightのすべてのキー(‘K0’, ‘K1’, ‘K2’, ‘K4′) が含まれています。’K4’ に対応する行はdf_leftに存在しないため、’A’と’B’の列はNaNになっています。

how='outer'

左右いずれかのDataFrameにキーが存在するすべての行を含めます(和集合)。いずれかのDataFrameにしか存在しないキーについては、他方のDataFrameの列はNaNになります。

python
result_merge_outer = pd.merge(df_left, df_right, on='key', how='outer')
print("\n--- pd.merge() (how='outer') ---")
print(result_merge_outer)

すべてのキー (‘K0’, ‘K1’, ‘K2’, ‘K3’, ‘K4’) が含まれています。それぞれのキーが存在しない側の列はNaNになっています。

how='cross' (pandas 1.2.0以降)

両方のDataFrameのすべての行の組み合わせを生成します(デカルト積)。キーは使用されません。非常に大きな結果になる可能性があるため、注意が必要です。

“`python
df_small1 = pd.DataFrame({‘A’: [1, 2], ‘B’: [3, 4]})
df_small2 = pd.DataFrame({‘C’: [5, 6]})

print(“\n— df_small1:\n”, df_small1)
print(“\n— df_small2:\n”, df_small2)

result_merge_cross = pd.merge(df_small1, df_small2, how=’cross’)
print(“\n— pd.merge() (how=’cross’) —“)
print(result_merge_cross)
“`

df_small1の各行がdf_small2の各行と組み合わされています (2行 * 2行 = 4行)。

4.6 同じ列名がある場合の衝突解決 (suffixes)

結合する2つのDataFrameに、結合キー以外の同じ名前の列が存在する場合、デフォルトでは列名にサフィックス(例: _x, _y)が付加されて区別されます。このサフィックスはsuffixes引数で変更できます。

“`python

サンプルデータ (再掲)

df_left_col_conflict: ‘key’, ‘col1’, ‘col2’

df_right_col_conflict: ‘key’, ‘col1’, ‘col3’

print(“\n— df_left_col_conflict:\n”, df_left_col_conflict)
print(“\n— df_right_col_conflict:\n”, df_right_col_conflict)

例: 同じ列名 ‘col1’ がある場合

result_merge_conflict = pd.merge(df_left_col_conflict, df_right_col_conflict, on=’key’)
print(“\n— pd.merge() (列名衝突, デフォルトサフィックス) —“)
print(result_merge_conflict)

例: カスタムサフィックスを指定

result_merge_conflict_suffixes = pd.merge(df_left_col_conflict, df_right_col_conflict, on=’key’, suffixes=(‘_left’, ‘_right’))
print(“\n— pd.merge() (列名衝突, カスタムサフィックス) —“)
print(result_merge_conflict_suffixes)
“`

'col1'列にデフォルトのサフィックス_x, _yが付いていることがわかります。suffixes引数でこれをカスタマイズできます。

4.7 マージに関する追加情報 (indicator=True)

indicator=Trueを指定すると、結果のDataFrameに_mergeという名前の特別な列が追加されます。この列は、各行が左右どちらのDataFrameから来たのか(または両方から来たのか)を示します。値は'left_only', 'right_only', 'both'のいずれかです。

“`python

例: indicator=True

result_merge_indicator = pd.merge(df_left, df_right, on=’key’, how=’outer’, indicator=True)
print(“\n— pd.merge() (indicator=True) —“)
print(result_merge_indicator)
“`

これは、外部結合の結果をデバッグしたり、どのデータが結合されなかったかを分析したりするのに非常に便利です。

4.8 データ型の考慮

結合キーとして使用する列のデータ型は、一致しているか、少なくとも互換性がある必要があります。例えば、一方のDataFrameのキー列が整数型で、もう一方が文字列型の場合、期待通りに結合されないことがあります。必要に応じて、結合前にastype()などでデータ型を揃えることが推奨されます。

4.9 重複キーの扱い

mergeは、キーに重複がある場合、そのすべての組み合わせを結果として生成します。これはリレーショナル結合の標準的な挙動ですが、予期せぬ結果(行数の増加)につながる可能性があります。

“`python
df_dup1 = pd.DataFrame({‘key’: [‘A’, ‘B’, ‘A’], ‘value1’: [1, 2, 3]})
df_dup2 = pd.DataFrame({‘key’: [‘A’, ‘A’, ‘C’], ‘value2’: [10, 11, 12]})

print(“\n— df_dup1 (重複キーあり):\n”, df_dup1)
print(“\n— df_dup2 (重複キーあり):\n”, df_dup2)

result_merge_duplicates = pd.merge(df_dup1, df_dup2, on=’key’)
print(“\n— pd.merge() (重複キーの結合) —“)
print(result_merge_duplicates)
“`

df_dup1のキー’A’の2行と、df_dup2のキー’A’の2行が組み合わされ、結果として4行 (‘A’について 2 * 2 = 4) が生成されています。このように、重複キーの結合は行数を大きく増やす可能性があるため、結合前にキー列の重複をチェックすることが重要です (df['key'].duplicated().any())。

4.10 mergeのユースケース

  • リレーショナルデータベースのテーブル間の結合と同様の操作を行いたい場合。
  • 顧客IDや商品コードなど、明確なキー列に基づいて異なる情報を紐付けたい場合。
  • 特定の条件(キーの一致)を満たす行同士を組み合わせたい場合。
  • SQLのINNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, CROSS JOINに相当する操作を行いたい場合。

5. DataFrame.join(): 主にインデックスに基づいた結合

DataFrame.join()メソッドは、DataFrameのメソッドとして提供されており、主にそのインデックス(または特定の列)を使用して他のDataFrameと結合します。pd.merge()と機能が重複する部分がありますが、インデックスベースの結合においてはjoinの方が簡潔に記述できることが多いです。

5.1 基本的な使い方

joinは、呼び出し元のDataFrameのインデックスと、結合されるDataFrameのインデックスをデフォルトでキーとして結合します。デフォルトの結合方法は左外部結合 (how='left') です。

“`python

サンプルデータ (再掲)

df_left_index: index=’K0′..’K3′, columns=’A’, ‘B’

df_right_index: index=’K0′..’K4′, columns=’C’, ‘D’

print(“\n— df_left_index:\n”, df_left_index)
print(“\n— df_right_index:\n”, df_right_index)

例: df_left_index に df_right_index をインデックスで結合 (デフォルト how=’left’)

result_join_basic = df_left_index.join(df_right_index)
print(“\n— df_left_index.join(df_right_index) (デフォルト how=’left’, インデックス結合) —“)
print(result_join_basic)
“`

df_left_index (左側) のインデックス (‘K0’, ‘K1’, ‘K2’, ‘K3’) がすべて保持され、それにマッチする df_right_index のデータ (‘K0’, ‘K1’, ‘K2′) が結合されています。’K3’ は df_right_index に存在しないため、’C’と’D’列はNaNになっています。’K4′ は df_right_index にしか存在しないため、結果には含まれません(左外部結合のため)。

5.2 キーの指定

joinにおけるキーの指定方法はいくつかあります。

インデックス vs インデックス (デフォルト)

前述の通り、これがデフォルトの挙動です。left_index=True, right_index=True を指定した pd.merge() と等価です。

“`python

mergeで書いた場合

result_merge_eq_join = pd.merge(df_left_index, df_right_index, left_index=True, right_index=True, how=’left’)
print(“\n— pd.merge() equivalent to join (left_index=True, right_index=True, how=’left’) —“)
print(result_merge_eq_join)

joinのデフォルトと同じ結果になる

“`

呼び出し元の列 vs 結合される側のインデックス (on引数)

呼び出し元のDataFrameの特定の列をキーとして、結合されるDataFrameのインデックスと結合したい場合は、on引数に呼び出し元の列名を指定します。

“`python

df_left: ‘key’, ‘A’, ‘B’ (keyは列)

df_right_index: index=’K0′..’K4′, columns=’C’, ‘D’ (indexはキー)

print(“\n— df_left:\n”, df_left) # keyは列
print(“\n— df_right_index:\n”, df_right_index) # indexはキー

例: df_left の ‘key’ 列をキーとして、df_right_index のインデックスと結合

df_left を呼び出し元として join を使う

result_join_on = df_left.join(df_right_index, on=’key’)
print(“\n— df_left.join(df_right_index, on=’key’) (how=’left’, 列 vs インデックス結合) —“)
print(result_join_on)
“`

これは pd.merge(df_left, df_right_index, left_on='key', right_index=True, how='left') と等価です。結果のDataFrameのインデックスは、デフォルトでは呼び出し元のDataFrame (df_left) のインデックスが引き継がれます。キーとして使用した列 ('key') は結果にそのまま残ります。

呼び出し元のインデックス vs 結合される側の列

結合される側のDataFrameの特定の列をキーとして使用したい場合は、そのDataFrameを事前にその列でインデックス化しておく必要があります。

“`python

df_left_index: index=’K0′..’K3′, columns=’A’, ‘B’ (indexはキー)

df_right: ‘key’, ‘C’, ‘D’ (keyは列)

print(“\n— df_left_index:\n”, df_left_index) # indexはキー
print(“\n— df_right:\n”, df_right) # keyは列

df_right を ‘key’ 列でインデックス化

df_right_indexed = df_right.set_index(‘key’)
print(“\n— df_right_indexed:\n”, df_right_indexed)

例: df_left_index のインデックスをキーとして、df_right_indexed のインデックスと結合

result_join_set_index = df_left_index.join(df_right_indexed)
print(“\n— df_left_index.join(df_right_indexed) (how=’left’, インデックス vs インデックス結合) —“)
print(result_join_set_index)
“`

これは pd.merge(df_left_index, df_right, left_index=True, right_on='key', how='left') と等価です。

5.3 結合方法の指定 (how引数)

joinメソッドもhow引数で結合方法を指定できます。'left' (デフォルト), 'right', 'outer', 'inner'が使用可能です。'cross'joinメソッドにはありません。

“`python

例: df_left_index と df_right_index を完全外部結合 (outer join)

result_join_outer = df_left_index.join(df_right_index, how=’outer’)
print(“\n— df_left_index.join(df_right_index, how=’outer’) —“)
print(result_join_outer)

例: df_left_index と df_right_index を内部結合 (inner join)

result_join_inner = df_left_index.join(df_right_index, how=’inner’)
print(“\n— df_left_index.join(df_right_index, how=’inner’) —“)
print(result_join_inner)
“`

5.4 複数のDataFrameを一度に結合

joinメソッドの便利な機能として、複数のDataFrameを一度に結合できる点があります。結合したいDataFrameのリストを渡します。この場合、呼び出し元のDataFrameのインデックスと、リスト内のすべてのDataFrameのインデックスがキーとなります。

“`python
df_add1 = pd.DataFrame({‘E’: [10, 11, 12]}, index=[‘K0’, ‘K1’, ‘K5’])
df_add2 = pd.DataFrame({‘F’: [20, 21]}, index=[‘K1’, ‘K2’])

print(“\n— df_add1:\n”, df_add1)
print(“\n— df_add2:\n”, df_add2)

例: df_left_index に df_add1 と df_add2 を一度に結合

df_left_index のインデックス(‘K0’..’K3′) がキー

result_join_multiple = df_left_index.join([df_add1, df_add2], how=’outer’) # 例として outer に
print(“\n— df_left_index.join([df_add1, df_add2], how=’outer’) —“)
print(result_join_multiple)
“`

この機能は、同じインデックスを持つ複数のDataFrame(例えば、同じ時系列データだが異なる変数を持つDataFrame)を組み合わせる場合に非常に便利です。

5.5 同じ列名がある場合の衝突解決 (lsuffix, rsuffix)

joinメソッドでも列名の衝突が発生する可能性があります。mergeと同様にサフィックスを付加しますが、joinではlsuffix(左側のサフィックス)とrsuffix(右側のサフィックス)という引数を使用します。どちらか一方、または両方を指定する必要があります。

“`python
df_left_col_join = pd.DataFrame({‘A’: [1, 2, 3], ‘B’: [4, 5, 6]}, index=[‘K0’, ‘K1’, ‘K2’])
df_right_col_join = pd.DataFrame({‘A’: [7, 8, 9], ‘C’: [10, 11, 12]}, index=[‘K0’, ‘K1’, ‘K3’])

print(“\n— df_left_col_join:\n”, df_left_col_join)
print(“\n— df_right_col_join:\n”, df_right_col_join)

例: 列名 ‘A’ の衝突

try:
df_left_col_join.join(df_right_col_join)
except ValueError as e:
print(“\n— DataFrame.join() (列名衝突エラー) —“)
print(e)

例: lsuffix, rsuffix を指定

result_join_suffixes = df_left_col_join.join(df_right_col_join, lsuffix=’_left’, rsuffix=’_right’)
print(“\n— DataFrame.join() (lsuffix, rsuffix 指定) —“)
print(result_join_suffixes)
“`

5.6 joinmergeの使い分け

  • join: 主にインデックスをキーとして結合する場合に便利です。特に、呼び出し元のDataFrameのインデックスと結合したい相手のDataFrameのインデックスをキーとする場合は、最も簡潔に記述できます。複数のDataFrameを一度に結合したい場合もjoinが便利です。デフォルトが左外部結合である点も、マスターデータにトランザクションデータを紐付けるようなケースで使いやすいです。
  • merge: 特定の列をキーとして結合する場合に、より柔軟で明示的です。インデックスをキーにする場合も使用できますが、left_index=True, right_index=Trueのように指定が必要です。クロス結合を行いたい場合はmergeを使用します。キー列の名前が左右で異なる場合もmergeが適しています。

概念的には、DataFrame.join()pd.merge()の一部機能を、よりインデックスベースの操作に特化させ、かつDataFrameのメソッドとして提供することで使いやすくしたものです。多くのインデックスベースの結合はjoinで、列ベースの結合やより複雑な結合はmergeで行うと考えると分かりやすいでしょう。

5.7 joinのユースケース

  • 同じインデックス(例: 日付、時刻、カテゴリIDなど)を持つ複数のDataFrameを横に連結したい場合。
  • マスターデータ(例: 顧客リスト、商品マスタ)をメインのDataFrame(例: 注文履歴)に、キー(インデックスまたは特定の列)を基準に付加したい場合。
  • 時系列データなど、インデックス自体が重要な意味を持つデータセットの結合。

6. 応用的なテクニックと注意点

DataFrameの結合は、大規模データではパフォーマンスやメモリ消費に影響を与えることがあります。いくつかの応用的なテクニックと注意点を解説します。

6.1 パフォーマンス

  • インデックスの利用: mergejoinでインデックスをキーとして使用する場合、またはonで指定した列が既にインデックスになっている場合、pandasは内部的にインデックスを利用して高速に結合を行います。結合キーとして頻繁に使用する列は、事前にset_index()でインデックスにしておくと、特に大きなDataFrameでの結合が高速化される可能性があります。
  • データ型の最適化: キーとなる列のデータ型を一致させ、適切な型(特に文字列の場合、カテゴリ型に変換できるなら変換する)にすることで、メモリ効率が向上し、比較演算が速くなることがあります。
  • 不要な列の削減: 結合前に不要な列を削除しておくことで、結合操作自体の負荷や結果のDataFrameのメモリ消費を抑えられます。

6.2 メモリ消費

結合操作は、特に完全外部結合 (how='outer') や、重複キーによる結合 (mergeの重複キーの挙動) などで結果のDataFrameが元のDataFrameよりも大幅に大きくなる場合、多くのメモリを消費する可能性があります。大規模データでメモリ不足が発生する場合は、以下の対策が考えられます。

  • データ型を最適化する (例: 数値をより小さい型にする、文字列をカテゴリ型にする)。
  • チャンクごとに処理する: 非常に大きなファイルを結合する場合、一度に全データを読み込まず、ファイルを分割してチャンクごとに読み込み、結合、処理を行う。
  • 結合前にフィルタリング: 結合対象の行や列を必要最小限に絞り込む。
  • DaskやPySparkなど、より大規模なデータ処理フレームワークの利用を検討する。

6.3 キーのデータ型と値の確認

結合がうまくいかない原因の多くは、キーのデータ型が一致していなかったり、キーの値に予期せぬ表記揺れや欠損値があったりすることです。

  • データ型の確認: df.info()df.dtypes でキー列のデータ型を確認し、必要に応じて df['key_col'].astype(str)pd.to_numeric(), pd.to_datetime() などで型を揃えます。
  • 値の一致確認: 文字列のキーの場合、大文字/小文字の違い、前後のスペース、全角/半角などが原因でマッチしないことがあります。.str.strip(), .str.lower(), .str.replace() などで正規化が必要です。
  • 欠損値の確認: キー列にNaNが含まれている場合、その行は多くの場合結合対象になりません。欠損値をどのように扱うかを事前に検討し、必要であれば補完や除外を行います。
  • 重複キーの確認: df['key_col'].duplicated().sum()df['key_col'].value_counts() で重複キーの有無や頻度を確認します。意図しない重複がある場合は、結合前に原因を調査・修正する必要があります。

6.4 pd.merge_asof() について (簡潔に)

標準的なmergeはキーの完全一致に基づいて結合しますが、時系列データなどでは「最も近いキー」に基づいて結合したい場合があります。このようなニアマッチ結合にはpd.merge_asof()が使用できます。これは特に、異なる頻度でサンプリングされた時系列データを結合する際に便利ですが、今回の主要な結合方法(完全一致)とは異なるため、ここでは存在を紹介するにとどめます。

6.5 pd.concat vs DataFrame.append

以前のpandasのバージョンでは、DataFrameに行を追加する際にappendメソッドがよく使われました (df1.append(df2))。しかし、appendは内部的にpd.concatを呼び出しており、パフォーマンス上の理由や冗長性の問題から、現在では非推奨 (deprecated) となっています。行方向の単純な連結は、pd.concat([df1, df2], ignore_index=True) のようにpd.concatを使用することが推奨されます。

“`python

非推奨だが参考として

result_append = df1.append(df2, ignore_index=True) # これは非推奨

print(“\n— DataFrame.append() (非推奨) —“)

print(result_append)

“`

7. まとめ

この記事では、pandas DataFrameを結合するための主要な3つの方法、pd.concat, pd.merge, DataFrame.joinについて詳細に解説しました。

  • pd.concat(): 複数のDataFrameやSeriesを、行方向(axis=0)または列方向(axis=1)に単純に連結します。SQLのUNION ALLに似ています。インデックスの扱い(保持、リセット、階層化)や、存在しない列/行の扱い(join='outer' or 'inner')を制御できます。主に同じ構造のデータを積み重ねる場合や、単に隣に並べる場合に使用します。
  • pd.merge(): 2つのDataFrameを、一つ以上のキー列(またはインデックス)の値に基づいてリレーショナルに結合します。SQLのJOIN操作に直接対応し、inner, left, right, outer, crossといった豊富な結合方法を指定できます。キーを指定してデータを紐付けたい場合に最も強力で柔軟なツールです。
  • DataFrame.join(): DataFrameのメソッドであり、主にそのインデックス(または呼び出し元DataFrameの特定の列)をキーとして他のDataFrameと結合します。デフォルトは左外部結合 (how='left') で、インデックスベースの結合や複数のDataFrameを一度に結合する場合に簡潔に記述できます。pd.mergeのインデックス結合におけるサブセット機能のような位置づけですが、特定のユースケースでは非常に便利です。

それぞれのメソッドは異なる目的と得意とする状況があります。

  • 単純な積み重ねや横並べ: pd.concat
  • 特定のキーに基づいてリレーショナルに紐付け: pd.merge (列キーの場合、または複雑な結合方法の場合)
  • インデックスをキーとして紐付け: DataFrame.join (特に呼び出し元のインデックスがキーの場合)、または pd.merge (より明示的に書きたい場合や、呼び出し元でない方のインデックスをキーにする場合)

データ分析においては、これらの結合方法を適切に使い分けるスキルが非常に重要です。データの構造、結合の目的、そしてパフォーマンスの要件を考慮して、最適な方法を選択してください。

この記事で解説した内容(基本的な使い方、様々な引数、使い分け、注意点)を理解すれば、ほとんどのDataFrame結合タスクに対応できるようになるはずです。ぜひ実際にコードを実行し、結果を確認しながら理解を深めてください。


これで約5000語の詳細な解説記事となりました。この情報が、あなたのpandasを使ったデータ分析に役立つことを願っています。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール