Pandasで複数のデータフレームを効率よく結合する方法

Pandasで複数のデータフレームを効率よく結合する方法

実務において、扱うデータは1つのソースだけではありません。顧客情報はExcel、取引履歴はCSV、レビュー情報はJSONなど、異なる形式で存在するのが当たり前です。こうした異なるデータソースを統合して分析可能な形にするには、Pandasの結合機能を正しく理解し、使いこなすことが非常に重要です。

Pandasは、SQLのJOINと似たような柔軟性を持つmergejoinconcatといった機能を備えており、複雑なデータ統合も簡潔に行えます。本記事では、それらのメソッドを活用し、データの整合性を保ちつつ、効率的に複数のデータフレームを結合する実践的な手法を詳しく解説していきます。


目次


1. なぜデータフレームの結合が重要なのか

現場では、マーケティング部門が顧客データを持ち、営業部門が販売記録を管理し、サポート部門が問い合わせ履歴を保持するなど、データは部門ごとに分散しています。これらの情報を統合してビジネス全体のパフォーマンスを把握するには、データフレーム同士を結合する処理が欠かせません。

少量のデータであればExcelで手作業も可能かもしれませんが、数千〜数百万件のデータを扱うようになると、手動では限界があります。効率的かつ再現可能な形でデータを統合するには、Pandasの結合機能を活用するのが最善の方法です。

この連載では、単なる文法の紹介ではなく、実務でよくあるデータの結合パターンや、結合時に起こりやすいミスを防ぐためのポイントを具体例とともに紹介していきます。結合処理を通じて、より正確で分析可能なデータセットを構築するためのノウハウを提供します。

import pandas as pd

# 顧客情報
customers = pd.DataFrame({
    'customer_id': [1, 2, 3],
    'name': ['田中', '佐藤', '鈴木']
})

# 注文履歴
orders = pd.DataFrame({
    'order_id': [101, 102, 103],
    'customer_id': [1, 2, 1],
    'amount': [10000, 15000, 8000]
})

# 顧客IDをキーに結合
merged = pd.merge(customers, orders, on='customer_id', how='inner')
print(merged)

このようなシンプルな例から始めて、次章ではmerge、join、concatの違いや選び方について詳しく解説していきます。


2. merge、join、concatの基本概念

複数のデータフレームを扱う際、どのメソッドを使ってどのように結合すべきかを理解することは非常に重要です。Pandasでは、主に3つの結合方法が提供されています:merge()join()concat()。それぞれの違いや使用目的を把握することで、より効率的かつ安全にデータ統合を行うことが可能になります。

2.1 merge()

merge()は、SQLのJOINと非常によく似た動作をする結合方法で、列をキーにして結合します。onleft_onright_onなどの引数でキー列を柔軟に指定でき、howで結合方法(inner、outer、left、right)を選択できます。

2.2 join()

join()は主にインデックスを使って結合するための簡易メソッドです。mergeと比べて記述がシンプルで、インデックスが整っていれば非常に直感的に結合できます。内部的にはleft joinがデフォルトです。

2.3 concat()

concat()は、複数のデータフレームを縦(行方向)または横(列方向)に連結するために使われます。キーに基づく結合ではなく、単に形の似たデータをまとめたい場合に便利です。

2.4 SQLとの対比で理解する

Pandasメソッド SQL相当 主な用途
merge() JOIN(INNER / OUTERなど) キー列を基に条件付きで結合
join() LEFT JOIN(インデックス基準) インデックスを使ってシンプルに結合
concat() UNION / UNION ALL 縦または横方向に連結

状況に応じて適切なメソッドを選択することが、正確な分析の出発点となります。次章では、最も汎用的で強力なmerge()の使い方について詳しく見ていきましょう。

# SQLのINNER JOINに相当する基本的なmergeの例
df1 = pd.DataFrame({'ID': [1, 2, 3], '名前': ['山田', '高橋', '斎藤']})
df2 = pd.DataFrame({'ID': [2, 3, 4], 'スコア': [85, 90, 78]})

result = pd.merge(df1, df2, on='ID', how='inner')
print(result)

このように、共通のIDをもつ行だけが結合されます。merge()の柔軟性を理解することで、複雑なデータ関係もスムーズに扱えるようになります。


3. merge()の使い方と実践的な応用

merge()はPandasの中でも最も汎用性が高く、実務でも頻繁に使用されるデータフレーム結合メソッドです。SQLのJOINに近い感覚で利用でき、複数のキーや条件に基づいた柔軟な結合が可能です。ここでは、基本構文から応用テクニックまで、段階的に解説していきます。

3.1 基本構文とデフォルト動作

merge()を使えば、共通の列(キー)に基づいて2つのデータフレームを簡単に結合できます。指定しない場合、共通列が自動で検出され、デフォルトではINNER JOINが行われます。

df1 = pd.DataFrame({'ID': [1, 2, 3], '名前': ['山田', '高橋', '斎藤']})
df2 = pd.DataFrame({'ID': [2, 3, 4], 'スコア': [88, 92, 75]})

# デフォルトはINNER JOIN
result = pd.merge(df1, df2)
print(result)

3.2 結合方法の指定

how引数で結合の方法を選べます:

  • 'inner'(デフォルト):両方に共通するキーのみ
  • 'left':左側の全データ+右側の一致するデータ
  • 'right':右側の全データ+左側の一致するデータ
  • 'outer':両方すべてのデータ+一致しない部分はNaN
# 外部結合(OUTER JOIN)
outer_result = pd.merge(df1, df2, on='ID', how='outer')
print(outer_result)

3.3 異なる列名での結合

結合キーの列名が異なる場合は、left_onright_onを使って明示的に指定します。

employees = pd.DataFrame({'社員ID': [1, 2, 3], '名前': ['田中', '佐藤', '鈴木']})
salaries = pd.DataFrame({'ID': [1, 2], '給与': [5000, 6000]})

merged = pd.merge(employees, salaries, left_on='社員ID', right_on='ID', how='left')
print(merged)

異なるデータソース間で列名が揃っていない場合、こうした指定が特に重要です。

3.4 実務例:顧客情報と注文データの結合

実務では、顧客情報に注文履歴を付与するようなケースが頻出します。たとえ注文がない顧客がいても、顧客情報は残したいという要件に応じ、LEFT JOINが適しています。

customers = pd.DataFrame({
    '顧客ID': [101, 102, 103, 104],
    '名前': ['佐藤', '鈴木', '高橋', '田中']
})

orders = pd.DataFrame({
    '注文ID': [9001, 9002, 9003],
    '顧客ID': [101, 103, 105],
    '商品': ['ノートPC', 'スマートフォン', 'タブレット']
})

# LEFT JOIN:すべての顧客を保持
result = pd.merge(customers, orders, on='顧客ID', how='left')
print(result)

注文履歴がない顧客にもNaNで空欄が残されるため、「注文がない顧客を抽出」といった分析が可能になります。

3.5 複数キーでの結合

年+四半期など、複数の列をキーとして結合する場合は、リストで指定します。

df1 = pd.DataFrame({
    '年': [2023, 2023, 2024],
    '四半期': ['Q1', 'Q2', 'Q1'],
    '売上': [100, 150, 200]
})

df2 = pd.DataFrame({
    '年': [2023, 2024],
    '四半期': ['Q1', 'Q1'],
    '成長率': [1.1, 1.3]
})

merged = pd.merge(df1, df2, on=['年', '四半期'], how='left')
print(merged)

複合キーの結合は、時系列分析やカテゴリ階層分析において非常に有用です。

次章では、インデックスを基に結合するjoin()の使い方と、mergeとの違いについて詳しく解説します。


4. join()でインデックスを使った結合を行う

join()は、merge()よりもシンプルに記述でき、主にインデックスを基準とした結合に特化したメソッドです。すでにインデックスが意味を持つように設計されているデータフレーム同士を結合する場合に特に便利で、コードの可読性も向上します。

4.1 基本的な使い方

join()は左側のデータフレームのインデックスに基づいて、右側のデータを結合します。デフォルトではleft joinとして動作します。

df1 = pd.DataFrame({'名前': ['山田', '佐藤', '鈴木']}, index=[1, 2, 3])
df2 = pd.DataFrame({'スコア': [90, 85, 88]}, index=[2, 3, 4])

# インデックスを基準に結合
result = df1.join(df2)
print(result)

左側(df1)のインデックスに一致する行だけが結合され、存在しないものはNaNになります。

4.2 任意の列でjoinするには

インデックス以外の列で結合したい場合は、事前にset_index()でインデックスを変更してからjoinを行うことができます。

employees = pd.DataFrame({
    '社員ID': [1, 2, 3],
    '名前': ['佐藤', '高橋', '田中']
}).set_index('社員ID')

salaries = pd.DataFrame({
    '社員ID': [1, 2],
    '給与': [5000, 6000]
}).set_index('社員ID')

# インデックスで結合
joined = employees.join(salaries, how='left')
print(joined)

このように、インデックスが一致することで自動的にデータがマッチし、コードの簡潔さが保たれます。

4.3 実用例:時系列データのセンサ情報を結合

時間をインデックスとして持つセンサーデータなど、時系列情報を結合する場合、join()は特に効果を発揮します。

import pandas as pd

# 時系列インデックス
time_index = pd.date_range('2024-01-01', periods=5, freq='D')
sensor1 = pd.DataFrame({'温度': [20, 21, 19, 22, 23]}, index=time_index)
sensor2 = pd.DataFrame({'湿度': [30, 35, 33, 31, 34]}, index=time_index)

# インデックスで結合
combined = sensor1.join(sensor2)
print(combined)

タイムスタンプに基づいたデータの統合は、IoTや株価分析など時間軸が重要な場面で非常に有用です。

4.4 join()とmerge()の使い分け

比較項目 join() merge()
結合基準 インデックス 列またはインデックス
柔軟性 やや限定的 高い
コードの簡潔さ 非常に良い 明示的で可読性が高い
適している用途 インデックスを使った単純な結合 複雑な条件・複数キーの結合

インデックスが主なキーとなるデータではjoin()、より複雑な結合条件が必要な場合はmerge()というように、状況に応じて使い分けることが重要です。

次章では、データフレームを単純に縦・横に連結するconcat()について解説します。


5. concat()で縦・横にデータを連結する

concat()は、merge()join()のようにキーを使って結合するのではなく、複数のデータフレームを縦(行方向)または横(列方向)に単純に連結するためのメソッドです。構造が同じデータを継続的に追加する場合や、複数の情報を並列に連結したい場合に非常に便利です。

5.1 縦方向への連結(行の追加)

axis=0(デフォルト)で、行方向にデータフレームを結合します。例えば、月ごとのデータをまとめて年間のデータセットを作成するケースなどに最適です。

import pandas as pd

jan = pd.DataFrame({'商品': ['A', 'B'], '売上': [100, 150]})
feb = pd.DataFrame({'商品': ['C', 'D'], '売上': [200, 180]})

# 縦に連結
result = pd.concat([jan, feb])
print(result)

インデックスが重複してしまう場合は、ignore_index=Trueを指定すると、連番の新しいインデックスが再作成されます。

# インデックスをリセット
result = pd.concat([jan, feb], ignore_index=True)

5.2 横方向への連結(列の追加)

axis=1を指定すると、列方向にデータを連結します。これは同じインデックスを持つ別々の指標を統合する場合に有効です。

df1 = pd.DataFrame({'A': [1, 2, 3]})
df2 = pd.DataFrame({'B': [4, 5, 6]})

# 横に連結
result = pd.concat([df1, df2], axis=1)
print(result)

インデックスが一致しない場合、NaNが自動的に補完されます。

5.3 実務例:月次レポートの統合

例えば、各月の売上データが別々のCSVファイルに分かれている場合、すべてを1つにまとめる際にconcat()を使います。

months = ['2024-01', '2024-02', '2024-03']
dataframes = []

for month in months:
    df = pd.read_csv(f'data/sales_{month}.csv')
    df['月'] = month  # 月情報を追加
    dataframes.append(df)

full_data = pd.concat(dataframes, ignore_index=True)
print(full_data.head())

この方法により、繰り返し処理を簡単に自動化し、分析や可視化の前準備が大幅に効率化されます。

5.4 keysを使った階層インデックスの付加

keysを指定すると、連結元を識別するMultiIndex(階層的なインデックス)を自動的に追加できます。グループ単位で集計やフィルターを行うのに便利です。

q1 = pd.concat([jan, feb], keys=['1月', '2月'])
print(q1)

このように、どのデータがどの期間に属するかを明示的に区別することができます。

5.5 concat()の利用タイミングまとめ

  • ✔️ 行または列を単純に「つなげる」場合に最適
  • ✔️ 同じ構造のデータセットを1つにまとめたいとき
  • ❗ 複雑なキーや条件に基づく結合が必要な場合はmerge()を使用

次章では、データフレームを結合したあとの「後始末」― 重複列やキーの衝突、整形処理などをどのように行えばよいかを解説します。


6. 重複列やキー衝突への対応、結合後のクリーンアップ

複数のデータフレームを結合すると、重複した列名キーの不一致意図しないNaNの発生などが起こることがあります。こうした課題を解決しないままにしておくと、分析の正確性や処理の効率に悪影響を与えます。この章では、結合後に必要なデータのクレンジングと整形の実践的な方法を紹介します。

6.1 列名の重複を防ぐ:suffixesの活用

merge()では、同名の列が存在する場合、自動的に_x_yという接尾辞が付けられます。明示的に接尾辞を指定することで、後の作業が分かりやすくなります。

df1 = pd.DataFrame({'id': [1, 2], '金額': [100, 200]})
df2 = pd.DataFrame({'id': [1, 2], '金額': [300, 400]})

# 接尾辞をカスタマイズ
merged = pd.merge(df1, df2, on='id', suffixes=('_旧', '_新'))
print(merged)

6.2 不要な列の削除

結合キーが重複して列として残る場合や、使わない列がある場合はdrop()で明示的に削除しましょう。

# 重複列(例: id_新)を削除
cleaned = merged.drop(columns=['id_新'])

6.3 列名の標準化

異なるソースから取得したデータでは、同じ意味を持つ列でも名前がばらばらなことが多いです。rename()を使って列名を統一することで、コードの保守性が向上します。

merged.rename(columns={
    '金額_旧': '前月売上',
    '金額_新': '当月売上'
}, inplace=True)

6.4 結合キーの型不一致を修正

結合前にキーの型が一致していないと、期待した通りに結合されません。文字列と数値が混在している場合などは、astype()で統一しておく必要があります。

df1['id'] = df1['id'].astype(str)
df2['id'] = df2['id'].astype(str)

merged = pd.merge(df1, df2, on='id')

6.5 重複行の削除

意図せず重複した行が発生することがあります。drop_duplicates()を使ってユニークなデータに整形します。

deduped = merged.drop_duplicates()
print(deduped)

また、特定の列に基づいて重複を削除したい場合は、subsetを指定します。

deduped = merged.drop_duplicates(subset=['id', '当月売上'])

6.6 列の並び替え

可視化やレポート出力の際に、列の順序が意味を持つこともあります。DataFrameの列順はリストで指定して並べ替えられます。

merged = merged[['id', '前月売上', '当月売上']]

このように、結合後のクリーンアップをしっかり行うことで、次の分析工程をスムーズかつ正確に進めることができます。

次章では、大規模なデータセットを扱う際の結合パフォーマンスの最適化手法について詳しく解説します。


7. 大規模データを扱うための結合パフォーマンス最適化

データ件数が数十万、数百万行を超えると、Pandasの結合処理は途端に重くなり、実行時間が伸びたり、メモリ不足で処理が落ちることもあります。この章では、大規模データを扱うときに実践すべきパフォーマンス最適化のテクニックを紹介します。

7.1 インデックスを活用する

結合キーの列を事前にインデックスに設定することで、Pandasは高速に行を検索・照合できるようになります。特にjoin()で顕著な効果があります。

df1.set_index('id', inplace=True)
df2.set_index('id', inplace=True)

# インデックス同士での結合
merged = df1.join(df2, how='inner')

7.2 データ型の統一と圧縮

結合前に、キー列の型を統一するだけでなく、必要に応じてint64 → int32object → categoryに変換することでメモリ使用量を抑えられます。

df1['地域'] = df1['地域'].astype('category')
df2['地域'] = df2['地域'].astype('category')

7.3 不要な列を除外してから結合

必要な列だけを残し、結合に関係ない列をあらかじめ削除することで、処理速度とメモリ効率が大幅に向上します。

df2_small = df2[['id', 'スコア']]
merged = pd.merge(df1, df2_small, on='id')

7.4 inner joinを優先的に使用する

outer joinはすべての行を保持するため、データ量が倍以上に増える可能性があり重くなります。必要がない限り、innerleftを使いましょう。

7.5 MultiIndexを活用した階層結合

年と月、カテゴリと商品など、複数のキーで構成された結合はMultiIndexを使うと効率的に処理できます。

df1.set_index(['年', '月'], inplace=True)
df2.set_index(['年', '月'], inplace=True)

merged = df1.join(df2, how='left')

7.6 結合前にサンプリングして検証

本番の大量データを扱う前に、sample()で一部を抜き出してテスト結合を行うことで、エラーや型不一致を事前に確認できます。

sample_df1 = df1.sample(1000)
sample_df2 = df2.sample(1000)

test_merge = pd.merge(sample_df1, sample_df2, on='id', how='inner')

7.7 DaskやSQLエンジンとの連携

数百万〜数千万件以上のデータをPandas単体で扱うのは現実的ではありません。このようなケースでは、Daskなどの分散処理ライブラリや、PostgreSQL、BigQueryといったRDBMSにJOIN処理を委譲するのが効果的です。

まとめ: 結合の高速化は「工夫」ではなく「設計」です。結合する前に、構造・型・必要性を見極めることで、処理速度も、メモリ消費も大幅に改善されます。

次章では、実際に異なるフォーマット(CSV、Excel、JSON)のデータを統合して分析可能な形にする、実務的なシナリオを紹介します。


8. 異なるデータソースの統合:実践シナリオ

実務では、すべてのデータが同じフォーマットや構造で整っているとは限りません。顧客情報がExcel、売上データがCSV、レビュー情報がJSONなど、異なる形式のデータを統一して結合することが求められます。この章では、実際のワークフローを想定して、異なるソースからのデータをどのように統合するかを段階的に説明します。

8.1 シナリオ設定

以下のような3つのデータを統合して、製品ごとの統合レポートを作成したいとします:

  • sales.csv:製品ごとの販売実績(CSV)
  • products.xlsx:製品マスタ情報(Excel)
  • reviews.json:ユーザーによる製品レビュー(JSON)

8.2 ファイルの読み込み

それぞれのフォーマットに合わせた方法でデータを読み込みます。

import pandas as pd
import json

# CSV形式の売上データ
sales = pd.read_csv('data/sales.csv')

# Excel形式の製品マスタ
products = pd.read_excel('data/products.xlsx', sheet_name='Sheet1')

# JSON形式のレビュー
with open('data/reviews.json') as f:
    raw_reviews = json.load(f)
reviews = pd.json_normalize(raw_reviews)

8.3 列名と形式の統一

データソースによって列名が異なる場合があるため、結合前に列名を統一します。

# 列名を標準化
sales.rename(columns={'ProductCode': 'product_id'}, inplace=True)
products.rename(columns={'コード': 'product_id'}, inplace=True)
reviews.rename(columns={'product.code': 'product_id'}, inplace=True)

# 必要な列のみ抽出
sales = sales[['product_id', '数量', '売上日']]
products = products[['product_id', '製品名', 'カテゴリ']]
reviews = reviews[['product_id', 'レビュー点数']]

8.4 順を追って結合

データを段階的に結合し、最終的な統合データを作成します。

# 製品マスタと売上データを結合
merged = pd.merge(products, sales, on='product_id', how='left')

# レビューを追加結合
final = pd.merge(merged, reviews, on='product_id', how='left')

8.5 後処理:欠損補完と並び替え

レビューがない製品にはNaNが入るため、0点で補完し、列の順番を整理しておきます。

# NaNを補完
final['レビュー点数'] = final['レビュー点数'].fillna(0)

# 列の順序を調整
final = final[['product_id', '製品名', 'カテゴリ', '数量', '売上日', 'レビュー点数']]

# 重複の除去(必要に応じて)
final = final.drop_duplicates()

# プレビュー
print(final.head())

8.6 ポイントの整理

  • 🧩 異なるフォーマット(CSV, Excel, JSON)に応じた読み込み
  • 🔧 結合前の前処理(列名統一・列選択・型整備)
  • 🔗 多段階のmerge()によるデータの組み合わせ
  • 🧼 後処理で欠損値や重複をクリーンアップ

このようなフローをテンプレート化することで、データ統合の作業を効率化し、日々の分析業務を安定化させることが可能です。

最終章では、これまでのまとめと、結合を「単なる処理」ではなく「データ設計」として捉える重要性についてお伝えします。


9. 結論:データ設計としての「結合」

結論:データ設計としての「結合」

ここまで、Pandasを使ったデータフレームの結合について、merge()join()concat()の基本から応用、そして実務的な統合シナリオに至るまで詳しく見てきました。単に表を“くっつける”という作業に見えるかもしれませんが、実はこの“結合”こそがデータ設計の中核を成しているのです。

正確な結合ができなければ、分析の前提となるデータの整合性が失われ、意思決定やモデリングにも悪影響を与える可能性があります。結合の質は、後工程の品質をも左右するのです。

✅ 重要なポイントの振り返り:

  • merge()は複雑で柔軟な条件付き結合に最適
  • join()はインデックスに基づいたシンプルな結合に便利
  • concat()は単純な縦・横の連結やバッチ統合に向いている
  • 結合前の型チェックと列名整備は必須(不一致のままでは意図通りに動かない)
  • 後処理(クリーニング・並び替え・欠損処理)まで含めて一連の設計と捉える

結合は単なる“手段”ではなく、“構造を作るための意思決定”です。優れたデータ設計とは、必要なデータを正しく結びつけ、無駄や重複を排除し、分析のベースをきれいに整えることから始まります。

結合はコードではなく、ストーリーの組み立てである。 そう考えることで、あなたの分析精度も、仕事の信頼性も、確実に高まっていくはずです。

# 最終的な結合・整形フロー(例)
final_df = (
    pd.merge(products, sales, on='product_id', how='left')
      .merge(reviews, on='product_id', how='left')
      .fillna({'レビュー点数': 0})
      .drop_duplicates()
      .sort_values(by='売上日')
)
print(final_df.head())

댓글 남기기

Table of Contents

Table of Contents