【concat】連結と追加|Pandas データ結合の基礎
merge は「横方向の結合」(キーで列をマッチング)を行うのに対し、concat は「縦・横方向の連結」(行や列を積み重ねる)を行います。12ヶ月分の売上データを1つの年間テーブルにまとめたり、3つの支店のレポートをマスターレポートに統合したりするのが concat の役割です。本セクションでは、concat の axis 方向、インデックス処理、整合(アラインメント)、keys による多段階ラベル、そして concat と merge の使い分け戦略について解説します。
⚠️ 注意: 以下のコードを実行するには、ローカルの Python 環境が必要です。
1. 学習内容
- ❶ concat による軸ベースの連結
- ❷ axis=0 と axis=1 の違い
- ❸ ignore_index によるインデックスの再構築
- ❹ keys による多段階ラベル
- ❺ join による整合(アラインメント)戦略
2. チャーリーの12ヶ月データ連結
(1) 課題:12個の CSV を1つずつ読み込む手間
チャーリーは月別の売上 CSV を12個持っており、手作業でつなぎ合わせるのは非常に面倒です:
PYTHON
import pandas as pd
# 12個の別々のデータフレームを想像してください
jan = pd.DataFrame({'date': ['2024-01-15'], 'sales': [5000]})
feb = pd.DataFrame({'date': ['2024-02-20'], 'sales': [4500]})
mar = pd.DataFrame({'date': ['2024-03-10'], 'sales': [5200]})
# ... 残り9ヶ月分
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
(2) 解決策:concat で一括連結
▶ サンプル:concat による縦方向の連結(難易度 ⭐)
PYTHON
import pandas as pd
jan = pd.DataFrame({'date': ['2024-01-15'], 'sales': [5000]})
feb = pd.DataFrame({'date': ['2024-02-20'], 'sales': [4500]})
mar = pd.DataFrame({'date': ['2024-03-10'], 'sales': [5200]})
# 縦方向に積み重ねる(axis=0 がデフォルト)
year = pd.concat([jan, feb, mar], ignore_index=True)
print(year)
# date sales
# 0 2024-01-15 5000
# 1 2024-02-20 4500
# 2 2024-03-10 5200
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
3. concat の基本パラメータ
(1) 縦方向と横方向
▶ サンプル
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
:axis=0 と axis=1 の違い(難易度 ⭐)
PYTHON
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
# axis=0:行を積み重ねる(縦方向)
print("axis=0(縦方向):")
print(pd.concat([df1, df2], axis=0))
# A B
# 0 1 3
# 1 2 4
# 0 5 7 ← インデックスが重複!
# 1 6 8
# axis=1:列を並べる(横方向)
print("\naxis=1(横方向):")
print(pd.concat([df1, df2], axis=1))
# A B A B
# 0 1 3 5 7
# 1 2 4 6 8
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
(2) ignore_index によるインデックスの再構築
▶ サンプル
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
:ignore_index によるインデックスの再構築(難易度 ⭐)
PYTHON
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'A': [3, 4]})
df3 = pd.DataFrame({'A': [5, 6]})
# ignore_index なし — 元のインデックスが保持される(重複の可能性あり)
result1 = pd.concat([df1, df2, df3])
print(result1.index.tolist()) # [0, 1, 0, 1, 0, 1] — 重複!
# ignore_index あり — きれいな連番インデックス
result2 = pd.concat([df1, df2, df3], ignore_index=True)
print(result2.index.tolist()) # [0, 1, 2, 3, 4, 5] — きれい!
print(result2)
# A
# 0 1
# 1 2
# 2 3
# 3 4
# 4 5
# 5 6
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
4. keys による多段階ラベル
(1) データソースのタグ付け
▶ サンプル
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
:keys によるソースのタグ付け(難易度 ⭐⭐)
PYTHON
import pandas as pd
q1 = pd.DataFrame({'sales': [100, 200, 300]})
q2 = pd.DataFrame({'sales': [150, 250, 350]})
q3 = pd.DataFrame({'sales': [180, 280, 380]})
# keys でマルチインデックスを作成 — 各行がどのデータフレーム由来か追跡可能
result = pd.concat([q1, q2, q3], keys=['Q1', 'Q2', 'Q3'])
print(result)
# sales
# Q1 0 100
# 1 200
# 2 300
# Q2 0 150
# 1 250
# 2 350
# Q3 0 180
# 1 280
# 2 380
# キーで選択
print(result.loc['Q2'])
# sales
# 0 150
# 1 250
# 2 350
# マルチインデックスの代わりにソース列を追加
result2 = pd.concat([q1, q2, q3], keys=['Q1', 'Q2', 'Q3'], names=['quarter'])
result2 = result2.reset_index(level=0)
print(result2)
# quarter sales
# 0 Q1 100
# 1 Q1 200
# ...
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
5. join による整合(アラインメント)戦略
(1) 列名が完全に一致しない場合
▶ サンプル
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
:join='outer' と 'inner' の違い(難易度 ⭐⭐)
PYTHON
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4], 'C': [5, 6]})
df2 = pd.DataFrame({'B': [7, 8], 'C': [9, 10], 'D': [11, 12]})
# join='outer'(デフォルト)— すべての列を保持し、欠損は NaN で埋める
print("outer:")
print(pd.concat([df1, df2], axis=0, join='outer'))
# A B C D
# 0 1.0 3 5 NaN
# 1 2.0 4 6 NaN
# 0 NaN 7 9 11.0
# 1 NaN 8 10 12.0
# join='inner' — 共通の列のみ保持
print("\ninner:")
print(pd.concat([df1, df2], axis=0, join='inner'))
# B C
# 0 3 5
# 1 4 6
# 0 7 9
# 1 8 10
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
6. concat と merge の比較
| 特徴 | concat | merge |
|---|---|---|
| 操作 | 積み重ね(縦・横方向) | 結合(キーによるマッチング) |
| キー | 不要 | on/left_on が必要 |
| 行数 | 元データの行数の合計(axis=0) | 元データの行数の合計以下 |
| 列数 | 元データの列数の合計(axis=1) | 元データの列の和集合 |
| 用途 | 同一構造のデータの積み重ね | 異なる構造のデータの結合 |
| 典型シナリオ | 12ヶ月分を年間テーブルに統合 | ユーザー + 注文をキーで結合 |
▶ サンプル
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
:concat と merge の使い分け(難易度 ⭐⭐)
PYTHON
import pandas as pd
# concat を使う場合:同一構造を縦方向に積み重ねる
jan_sales = pd.DataFrame({'product': ['A', 'B'], 'sales': [100, 200]})
feb_sales = pd.DataFrame({'product': ['A', 'B'], 'sales': [150, 250]})
annual = pd.concat([jan_sales, feb_sales], keys=['Jan', 'Feb'])
# merge を使う場合:異なる構造をキーで連結する
products = pd.DataFrame({'product': ['A', 'B'], 'price': [10, 20]})
categories = pd.DataFrame({'product': ['A', 'B'], 'category': ['Electronics', 'Home']})
enriched = pd.merge(products, categories, on='product')
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
7. 総合例:複数ソースのデータ連結と分析
(5) ▶ concat の連結タイプ
graph TB
A[複数のデータフレーム] --> B{連結方向は?}
B -->|縦方向 axis=0| C[行を上から下へ積み重ねる]
B -->|横方向 axis=1| D[列を横に並べる]
C --> E{列名は一致する?}
E -->|はい| F[完全な連結]
E -->|いいえ| G[join='outer' は NaN で埋める / join='inner' は共通部分のみ保持]
D --> H{行インデックスは一致する?}
H -->|はい| I[整合された連結]
H -->|いいえ| J[NaN で相互補完]
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
▶ サンプル
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
:複数ソース連結の完全なワークフロー(難易度 ⭐⭐⭐)
PYTHON
import pandas as pd
import numpy as np
# ============================================
# 総合例:複数ソースの concat
# 3つの地域レポート → 年間分析
# ============================================
# 1. 地域別の月次レポート
np.random.seed(42)
regions = {
'North': pd.DataFrame({
'month': pd.date_range('2024-01', periods=6, freq='MS'),
'sales': np.random.randint(3000, 8000, 6),
'orders': np.random.randint(50, 150, 6)
}),
'South': pd.DataFrame({
'month': pd.date_range('2024-01', periods=6, freq='MS'),
'sales': np.random.randint(2000, 6000, 6),
'orders': np.random.randint(30, 120, 6)
}),
'East': pd.DataFrame({
'month': pd.date_range('2024-01', periods=6, freq='MS'),
'sales': np.random.randint(1500, 5000, 6),
'orders': np.random.randint(20, 100, 6)
})
}
# 2. keys で地域をタグ付けして連結
all_data = pd.concat(regions, names=['region', 'idx'])
all_data = all_data.reset_index(level=0).reset_index(drop=True)
print(f"総行数: {len(all_data)}")
# 3. 派生列の追加
all_data['avg_order_value'] = (all_data['sales'] / all_data['orders']).round(2)
# 4. 地域別の分析
region_summary = all_data.groupby('region').agg(
total_sales=('sales', 'sum'),
total_orders=('orders', 'sum'),
avg_monthly_sales=('sales', 'mean')
).round(0)
print("\n=== 地域別サマリー ===")
print(region_summary)
# 5. 全地域の月次トレンド
monthly = all_data.groupby('month')['sales'].sum()
print(f"\nピーク月: {monthly.idxmax().strftime('%Y-%m')}")
TEXT
> 出力: ローカルの Python 環境(pandas 2.x)で実行してください。Piston サーバーには pandas がプリインストールされていないため、ローカルにインストール(`pip install pandas`)してから実行してください。実際の値は pandas のバージョンにより若干異なる場合があります。
❓ よくある質問
Q concat と merge の違いは何ですか?
A concat はデータを積み重ねます — 行を縦方向に、列を横方向に連結し、マッチング用のキーは不要です。merge はデータを結合します — SQL の JOIN のように、キーを使って2つのテーブルの行をマッチングします。同一構造の複数テーブルを1つにまとめる → concat。異なる構造のテーブルを列で連結する → merge。目安:「同一構造なら concat で積み重ね、異なる構造なら merge で結合する」です。
Q axis=0 と axis=1 は何をしますか?
A axis=0(デフォルト)は行を縦方向に連結します — 上から下へ積み重ね、行数が増加します。axis=1 は列を横方向に連結します — 横に並べ、列数が増加します。縦方向の連結では列が自動整合され(欠損は NaN で埋められる)、横方向の連結では行が自動整合されます(欠損は NaN で埋められる)。
Q ignore_index はいつ使うべきですか?
A 縦方向の連結では、ほぼ常に ignore_index=True を指定すべきです — 連番のインデックスを再構築し、インデックスの重複([0,1,0,1,0,1])を回避します。元のインデックスに意味がある場合(日付インデックスなど)のみ保持します。横方向の連結では ignore_index は不要です(行の整合はインデックスに依存するため)。
Q なぜ append は非推奨になったのですか?
A df.append() は Pandas 1.4 で非推奨(deprecated)となり、2.0 で完全に削除されました。理由:append は呼び出しのたびに新しいオブジェクトを作成するため、ループ内では O(n²) のパフォーマンスになります。pd.concat はすべてを一度に連結するため O(n) です。代替:
pd.concat([df1, df2])。ループで追加する場合:まずリストに集め、最後に一度だけ concat してください。Q 列名が異なる場合はどうなりますか?
A 縦方向の連結では、一致しない列名は NaN を生みます — 同じ名前の列のみデータが統合されます。解決策:① 連結前に列名を rename で統一する;② join='inner' で共通の列のみ保持する;③ 連結後に NaN 列を手動で処理する。ベストプラクティス:concat を呼び出す前に列名を統一することです。
Q concat のパフォーマンスを最適化するには?
A ループ内で concat を繰り返し呼び出すのは避けてください — 呼び出しのたびに新しいデータフレームが作成されます。正しい方法:すべてのデータフレームをリストに集め、一度だけ concat します。
frames = [df1, df2, ..., df100]; result = pd.concat(frames)。これにより、ループベースの追加と比べて O(n) と O(n²) の差が生まれます。Q verify_integrity は何をしますか?
A verify_integrity=True は連結後のインデックスに重複がないかチェックし、重複があれば ValueError を発生させます。データの整合性を保証するために使用します — 例えば、一意の ID が2回現れてはいけない場合などです。ただし、チェックにはパフォーマンスコストがあるため、大規模データではスキップしてください。日常的な用途では ignore_index=True の方がシンプルです。
📖 まとめ
- concat はデータを積み重ねる(縦方向の行連結/横方向の列連結)もので、マッチング用のキーは不要です
- axis=0 は行を縦方向に連結(デフォルト)、axis=1 は列を横方向に連結します
- ignore_index=True は連番のインデックスを再構築します — 縦方向の連結ではほぼ常に必要です
- keys はデータソースにタグを付け、各行がどのソース由来か追跡できるマルチインデックスを生成します
- join='outer' はすべての列を保持(NaN で埋める)、join='inner' は共通の列のみ保持します
- concat と merge の使い分け:同一構造の積み重ねには concat、異なる構造の結合には merge を使います
- ループ内での concat は避ける — まずリストに集め、最後に一度だけ concat してください
📝 練習問題
- 基礎(難易度 ⭐):同一構造のデータフレームを3つ作成し(Q1/Q2/Q3 の売上データ)、concat で縦方向に連結(ignore_index=True)して、売上の合計を計算してください。
- 応用(難易度 ⭐⭐):列名が一部異なるデータフレームを2つ作成し、それぞれ join='outer' と join='inner' で連結して、列の違いを比較してください。
- 挑戦(難易度 ⭐⭐⭐):4つの地域について6ヶ月分のデータをシミュレートし、concat(keys=regions) で連結 → reset_index で整理 → 地域別の合計を計算 → 月次トレンドを確認してください。



