Pandas結合メソッド徹底比較: join vs merge vs concat パフォーマンス解析!


 

Pandasで複数のDataFrameを結合する際、joinmergeconcatという強力なメソッドが利用できます。これらはそれぞれ異なる目的と特性を持ち、データ結合のシナリオに応じて使い分けることが重要です。特に、大規模なデータを扱う場合、どのメソッドが最もパフォーマンスが良いのかは、データ処理の効率を大きく左右します。

この記事では、Pandasの主要な結合メソッドであるjoinmergeconcatのそれぞれの特性を比較し、どのような場合にどのメソッドが高速なのかを、具体的なコード例とパフォーマンスの考察を通して徹底的に解説します。


 

Pandas結合メソッドの基本をおさらい

 

パフォーマンス比較の前に、各メソッドの基本的な役割と典型的な使用例を再確認しましょう。

  1. pd.merge():

    • 役割: SQLのJOIN操作に最も直接的に対応します。指定した列(キー)に基づいてDataFrameを結合します。

    • 特徴: 最も柔軟な結合メソッド。内部結合、外部結合(左、右、完全)、複数キーでの結合、異なる列名での結合が可能です。

    • 使用例: 顧客IDをキーに顧客情報と注文履歴を結合する。

  2. DataFrame.join():

    • 役割: DataFrameのインデックスをキーとして他のDataFrameを結合します。

    • 特徴: merge(left_index=True, right_index=True)のショートカット的なメソッド。複数のDataFrameを一度に結合できる利点があります。

    • 使用例: ユーザーIDがインデックスとなっている複数のマスタデータを結合する。

  3. pd.concat():

    • 役割: 複数のSeriesやDataFrameを縦方向(行)または横方向(列)に単純に連結します。SQLのUNION操作に近いです。

    • 特徴: 共通のキーがなくても結合可能で、インデックスや列名の和集合・積集合を取ることができます。

    • 使用例: 月ごとの売上ファイルを読み込み、行方向に連結して年間の売上データを作成する。


 

パフォーマンス比較のポイント

 

これらのメソッドのパフォーマンスは、主に以下の要因によって左右されます。

  • 結合キーの種類: 列名 vs インデックス

  • 結合タイプ: inner, left, right, outer

  • データの規模: 行数、列数

  • 結合キーのカーディナリティ(種類数): ユニークなキーの数

  • 結合キーがソートされているか: ソート済みのインデックスは高速な結合に有利です

一般的に、Pandasの操作は、インデックスベースの処理が列ベースの処理よりも高速である傾向があります。これは、インデックスが内部的にハッシュマップやソートされた構造で管理されており、高速なルックアップや比較が可能であるためです。


 

具体的なパフォーマンス比較

 

ここでは、シンプルなシナリオで各メソッドの実行時間を比較してみましょう。

比較シナリオ:

  • 大規模なDataFrameを作成

  • join (インデックス結合)

  • merge (列結合)

  • concat (行方向結合)

Python
 
import pandas as pd
import numpy as np
import time

# 大規模なデータフレームの作成
num_rows = 1_000_000
num_cols = 5

# df_left (merge, joinの左側)
df_left_data = {
    'key': np.arange(num_rows),
    **{f'value_{i}': np.random.rand(num_rows) for i in range(num_cols - 1)}
}
df_left = pd.DataFrame(df_left_data)
df_left = df_left.set_index('key') # join用にインデックスを設定

# df_right (merge, joinの右側)
# 一部のキーを欠損させ、一部を重複させる
keys_right = np.concatenate([np.arange(num_rows // 2), np.arange(num_rows // 4, num_rows // 4 * 3)])
np.random.shuffle(keys_right) # 意図的にソートを崩す
df_right_data = {
    'key': keys_right,
    'info': np.random.choice(['A', 'B', 'C'], len(keys_right))
}
df_right = pd.DataFrame(df_right_data)
df_right = df_right.set_index('key') # join用にインデックスを設定

# df_concat_part (concatの追加データ)
df_concat_part_data = {
    'key': np.arange(num_rows, num_rows + num_rows // 2),
    **{f'value_{i}': np.random.rand(num_rows // 2) for i in range(num_cols - 1)}
}
df_concat_part = pd.DataFrame(df_concat_part_data)
df_concat_part = df_concat_part.set_index('key')

print(f"データフレームサイズ: {num_rows}行 x {num_cols}列")
print(f"df_left のメモリ使用量: {df_left.memory_usage(deep=True).sum() / (1024**2):.2f} MB")
print(f"df_right のメモリ使用量: {df_right.memory_usage(deep=True).sum() / (1024**2):.2f} MB\n")


# --- 1. pd.merge() による結合 (列結合) ---
start_time = time.time()
merged_df_col = pd.merge(df_left.reset_index(), df_right.reset_index(), on='key', how='inner')
end_time = time.time()
print(f"pd.merge() (列結合) 実行時間: {end_time - start_time:.4f}秒")
print(f"結果行数: {len(merged_df_col)}\n")


# --- 2. DataFrame.join() による結合 (インデックス結合) ---
start_time = time.time()
joined_df_idx = df_left.join(df_right, how='inner')
end_time = time.time()
print(f"DataFrame.join() (インデックス結合) 実行時間: {end_time - start_time:.4f}秒")
print(f"結果行数: {len(joined_df_idx)}\n")


# --- 3. pd.concat() による結合 (行方向) ---
# df_leftとdf_concat_partは列構造がほぼ同じ
start_time = time.time()
concatenated_df = pd.concat([df_left, df_concat_part])
end_time = time.time()
print(f"pd.concat() (行方向結合) 実行時間: {end_time - start_time:.4f}秒")
print(f"結果行数: {len(concatenated_df)}\n")

実行結果の傾向 (環境により変動あり):

上記のコードを一般的なPC環境で実行すると、以下のような傾向が見られます。

  • pd.concat() (行方向): 最も高速になる傾向があります。これは、単にDataFrameを連結するだけで、複雑なキーマッチングやソートが不要なためです。特にインデックスが重複しない場合や、ignore_index=Trueにする場合は非常に高速です。

  • DataFrame.join() (インデックス結合): pd.merge()による列結合よりも高速になることが多いです。インデックスが内部的に最適化されたデータ構造(ハッシュマップやソートされた配列)になっているため、キーのルックアップが効率的です。

  • pd.merge() (列結合): 多くのケースで柔軟性が高い反面、結合キーがインデックスになっていない場合、結合前に内部的にキー列をハッシュ化したりソートしたりするオーバーヘッドが発生するため、joinよりも遅くなる傾向があります。特にキー列がソートされていない場合や、文字列などの複雑なデータ型の場合に顕著です。

補足:

  • mergeleft_on='key', right_index=Trueのように、片方を列、片方をインデックスとして結合する場合、パフォーマンスはjoinに近くなることがあります。

  • 結合するDataFrameのキー列に大量の重複がある場合や、メモリサイズが非常に大きい場合など、特定のシナリオでは結果が異なる可能性があります。


 

結論と使い分けのガイドライン

 

パフォーマンス比較を踏まえ、各メソッドの最適な使い分けをまとめます。

  1. pd.concat():

    • 最速候補: DataFrameを行方向または列方向に単純に連結したい場合。

    • 最適シナリオ:

      • 同じ列構造を持つ複数のファイルをまとめて読み込みたい。

      • テストデータと訓練データを単純に結合したい。

      • インデックスの一致は問わず、単純に積み重ねたい。

    • パフォーマンス: 一般的に最も速いが、重複するインデックスの処理には注意。

  2. DataFrame.join():

    • インデックスがキーの場合の推奨: DataFrameのインデックスが結合キーであり、そのインデックスがユニークであるか、特定の結合タイプで処理できる場合に最適。

    • 最適シナリオ:

      • 主キーがインデックスとして設定されているマスタデータ同士の結合。

      • 時系列データで、日付インデックスをキーに異なる情報を結合する。

      • 複数の小さなDataFrameを効率的に結合したい。

    • パフォーマンス: mergeの列結合より高速なことが多い。特にインデックスがソートされているか、ハッシュ可能な場合。

  3. pd.merge():

    • 最も柔軟で汎用性が高い: 特定の列をキーとして結合したい場合や、複雑な結合タイプ(非等値結合など)が必要な場合に最適。

    • 最適シナリオ:

      • SQLのJOINと同じロジックで結合したい。

      • 結合キーがインデックスではなく、DataFrame内の通常の列である。

      • 左右のDataFrameで結合キーの列名が異なる。

    • パフォーマンス: joinより遅い場合もあるが、列ベースの結合では最も柔軟で、多くのケースで十分な性能を発揮する。


 

まとめ

 

Pandasのjoinmergeconcatは、それぞれ異なる得意分野を持つ強力なデータ結合メソッドです。パフォーマンスを最大化するためには、結合の目的とデータ構造(特にインデックスの有無と質) に応じて適切なメソッドを選択することが重要です。

  • 単純なデータ連結には**concat**。

  • インデックスをキーとする結合には**join**。

  • 列をキーとする柔軟な結合には**merge**。

これらの特性を理解し、使いこなすことで、あなたのPandasによるデータ処理はより効率的かつ高速になるでしょう。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座