Skip to content
トピック
Pandas
Pandas Concat:PythonでDataFrameを連結する方法

Pandas Concat:PythonでDataFrameを連結する方法

Updated on

現実のデータが単一のファイルに収まっていることはほとんどありません。1月の売上データをあるCSVから、2月のデータを別のCSVから、Q1の目標を3つ目のCSVから取得します。複数のWebページをスクレイピングして別々のDataFrameに格納します。大きなデータセットを並列処理のために分割し、後でピースを再組み立てする必要があります。いずれの場合も、行を失ったり、列を混乱させたり、インデックスを壊したりせずにDataFrameを結合する信頼性の高い方法が必要です。

**pandas concat**関数(pd.concat())は、この作業の標準ツールです。DataFrameを縦方向(行の追加)または横方向(列の追加)に積み重ね、異なる列を適切に処理し、1回の呼び出しで任意の数のDataFrameに対応します。このガイドでは、必要なすべてのパラメータを、ノートブックに直接貼り付けられる実動コード例とともに解説します。

📚

pd.concat()の機能 -- 基本構文

pd.concat()はDataFrameのリスト(または辞書)を受け取り、指定された軸に沿って結合します。ブロックを積み重ねるようなイメージです -- 縦方向に積めば行が追加され、横に並べれば列が追加されます。

import pandas as pd
 
result = pd.concat(
    objs,              # list or dict of DataFrames
    axis=0,            # 0 = vertical (rows), 1 = horizontal (columns)
    join='outer',      # 'outer' or 'inner'
    ignore_index=False,
    keys=None,
    sort=False,
    verify_integrity=False
)

主要パラメータ一覧

パラメータ説明デフォルト
objs連結するDataFrame(またはSeries)のリストまたは辞書必須
axis0で行を積み重ね(縦方向)、1で列を積み重ね(横方向)0
join'outer'はすべての列を保持、'inner'は共通の列のみ保持'outer'
ignore_indexTrueの場合、結果のインデックスを0, 1, 2, ...にリセットFalse
keys各行がどの元のDataFrameから来たかを識別するラベルNone
sort非連結軸をソート(axis=0の場合は列名)False
verify_integrity結果に重複するインデックス値がある場合にエラーを発生False

すべての例で使用するサンプルデータ

以下のすべての例では、これらのDataFrameを使用します:

import pandas as pd
 
df_jan = pd.DataFrame({
    'product': ['Widget', 'Gadget', 'Sprocket'],
    'units_sold': [150, 200, 80],
    'revenue': [1500, 3000, 960]
})
 
df_feb = pd.DataFrame({
    'product': ['Widget', 'Gadget', 'Sprocket'],
    'units_sold': [170, 180, 95],
    'revenue': [1700, 2700, 1140]
})
 
print(df_jan)
print(df_feb)

出力:

    product  units_sold  revenue
0    Widget         150     1500
1    Gadget         200     3000
2  Sprocket          80      960

    product  units_sold  revenue
0    Widget         170     1700
1    Gadget         180     2700
2  Sprocket          95     1140

DataFrameを縦方向に連結する(axis=0)

縦方向の連結は最も一般的なユースケースです。あるDataFrameを別のDataFrameの上に積み重ね、行を追加します。月次ファイル、バッチ結果、または同じ列を持つ複数のテーブルに分割されたデータがある場合に使用します。

combined = pd.concat([df_jan, df_feb])
print(combined)

出力:

    product  units_sold  revenue
0    Widget         150     1500
1    Gadget         200     3000
2  Sprocket          80      960
0    Widget         170     1700
1    Gadget         180     2700
2  Sprocket          95     1140

インデックスに注目してください。両方のDataFrameが元のインデックス値(0, 1, 2)を保持しているため、結果には重複するインデックス値があります。これは通常望ましくありません。解決策は次に説明するignore_indexパラメータです。

ignore_indexパラメータ -- インデックスのリセット

ignore_index=Trueを設定すると、元のインデックスを破棄し、0から始まる新しい連番インデックスを割り当てます:

combined = pd.concat([df_jan, df_feb], ignore_index=True)
print(combined)

出力:

    product  units_sold  revenue
0    Widget         150     1500
1    Gadget         200     3000
2  Sprocket          80      960
3    Widget         170     1700
4    Gadget         180     2700
5  Sprocket          95     1140

使用するタイミング: 縦方向の連結ではほぼ常に使用します。インデックスに意味のある情報(タイムスタンプやユニークIDなど)が含まれていない限り、後の混乱を避けるためにリセットしましょう。

keysパラメータ -- 階層インデックスの作成

keysパラメータは、各行がどのソースから来たかをラベル付けするレベルをインデックスに追加します。これによりMultiIndex(階層インデックス)が作成されます:

combined = pd.concat([df_jan, df_feb], keys=['January', 'February'])
print(combined)

出力:

               product  units_sold  revenue
January  0      Widget         150     1500
         1      Gadget         200     3000
         2    Sprocket          80      960
February 0      Widget         170     1700
         1      Gadget         180     2700
         2    Sprocket          95     1140

.locを使用して特定のソースのデータを選択できます:

# Get only January data
jan_data = combined.loc['January']
print(jan_data)

出力:

    product  units_sold  revenue
0    Widget         150     1500
1    Gadget         200     3000
2  Sprocket          80      960

使用するタイミング: 各行がどの元のDataFrameに属するかを追跡する必要がある場合にkeysを使用します -- 例えば、異なる実験、期間、またはデータソースからのデータです。

横方向の連結(axis=1) -- 横に並べる

axis=1を設定すると、DataFrameを横に並べて列を追加します。Pandasはインデックス値によって行を揃えます。

targets = pd.DataFrame({
    'target_units': [160, 190, 90],
    'target_revenue': [1600, 2850, 1080]
})
 
result = pd.concat([df_jan, targets], axis=1)
print(result)

出力:

    product  units_sold  revenue  target_units  target_revenue
0    Widget         150     1500           160            1600
1    Gadget         200     3000           190            2850
2  Sprocket          80      960            90            1080

これは両方のDataFrameが同じインデックス(0, 1, 2)を共有しているため、きれいに動作します。インデックスが一致しない場合、一致しない行にはNaN値が入ります:

df_a = pd.DataFrame({'value_a': [10, 20, 30]}, index=[0, 1, 2])
df_b = pd.DataFrame({'value_b': [40, 50, 60]}, index=[1, 2, 3])
 
result = pd.concat([df_a, df_b], axis=1)
print(result)

出力:

   value_a  value_b
0     10.0      NaN
1     20.0     40.0
2     30.0     50.0
3      NaN     60.0

行0にはvalue_bがありません(df_bに一致するインデックスがないため)。行3にはvalue_aがありません(df_aに一致するインデックスがないため)。

joinパラメータ -- Inner vs Outer

joinパラメータは、DataFrameが異なる列(axis=0の場合)または異なるインデックス値(axis=1の場合)を持つ場合の動作を制御します。

outer join(デフォルト) -- すべてを保持

df_with_extra = pd.DataFrame({
    'product': ['Widget', 'Gadget'],
    'units_sold': [200, 250],
    'region': ['East', 'West']
})
 
result = pd.concat([df_jan, df_with_extra], join='outer', ignore_index=True)
print(result)

出力:

    product  units_sold  revenue region
0    Widget         150   1500.0    NaN
1    Gadget         200   3000.0    NaN
2  Sprocket          80    960.0    NaN
3    Widget         200      NaN   East
4    Gadget         250      NaN   West

両方のDataFrameのすべての列が表示されます。欠損値はNaNで埋められます。

inner join -- 共通の列のみ保持

result = pd.concat([df_jan, df_with_extra], join='inner', ignore_index=True)
print(result)

出力:

   product  units_sold
0   Widget         150
1   Gadget         200
2 Sprocket          80
3   Widget         200
4   Gadget         250

両方のDataFrameに存在する列のみが残ります。revenue列(df_with_extraにない)とregion列(df_janにない)は両方とも削除されます。

inner joinを使用するタイミング: NaN値のないクリーンな結果が欲しく、すべてのDataFrameに存在しない列を失っても構わない場合に使用します。

DataFrameのリストを連結する

pd.concat()が他の結合方法に比べて持つ最大の利点の1つは、1回の呼び出しで任意の数のDataFrameを処理できることです。これはループで読み込んだファイルを結合するための標準パターンです:

import pandas as pd
 
# Simulate loading monthly CSV files
months = {
    'Jan': {'product': ['Widget', 'Gadget'], 'units': [150, 200]},
    'Feb': {'product': ['Widget', 'Gadget'], 'units': [170, 180]},
    'Mar': {'product': ['Widget', 'Gadget'], 'units': [190, 210]},
}
 
dfs = []
for month, data in months.items():
    df = pd.DataFrame(data)
    df['month'] = month
    dfs.append(df)
 
all_data = pd.concat(dfs, ignore_index=True)
print(all_data)

出力:

  product  units month
0  Widget    150   Jan
1  Gadget    200   Jan
2  Widget    170   Feb
3  Gadget    180   Feb
4  Widget    190   Mar
5  Gadget    210   Mar

このパターン -- DataFrameのリストを作成し、最後に一度だけconcatを呼び出す -- は、ループ内で1つずつDataFrameを追加するよりもはるかに高速です。各appendは完全なコピーを作成しますが、1回のpd.concat()呼び出しはメモリを1回だけ確保します。

concat vs merge vs append -- 比較表

Pandasには DataFrameを結合する方法がいくつかあります。正しいものを選ぶには、どのように結合したいかによります:

機能pd.concat()pd.merge()DataFrame.append()
主な用途DataFrameの積み重ね(行または列)共有列の値で結合(SQLのように)DataFrameに行を追加
入力数任意の数を一度に一度に2つ一度に2つ
マッチングロジックインデックス(または列名)で揃えるキー列の値でマッチング列名で揃える
結合タイプouterinnerinnerleftrightoutercrossouterのみ
デフォルト動作outer join、縦方向に積み重ねinner join、共有列で結合outer join、行を追加
最適な用途月次ファイルの結合、バッチ結果、同じスキーマのテーブルリレーショナル結合(顧客+注文)pandas 2.0で非推奨
パフォーマンス多数のDataFrameに対して高速2テーブル結合に最適化低速(毎回データをコピー)

それぞれの使い分け

  • pd.concat()を使用する -- DataFrameが同じ構造(同じ列)を持ち、積み重ねたい場合。インデックスで揃える横方向の連結にも使用します。
  • pd.merge()を使用する -- 列の値に基づいて行をマッチングする必要がある場合 -- 例えば、売上テーブルと製品テーブルをproduct_idで結合する場合。詳細はpandas mergeガイドをご覧ください。
  • DataFrame.append()は避ける -- pandas 1.4で非推奨となり、pandas 2.0で削除されました。代わりにpd.concat([df1, df2])を使用してください。

よくあるエラーと対処法

1. 列が一致しない

異なる列名のDataFrameを連結する場合、デフォルトのouter joinは欠損値をNaNで埋めます。予期しない場合は、列名を確認してください:

# Diagnose: compare column names
print(df1.columns.tolist())
print(df2.columns.tolist())
 
# Fix: rename columns to match before concatenating
df2 = df2.rename(columns={'sales': 'revenue', 'qty': 'units_sold'})
combined = pd.concat([df1, df2], ignore_index=True)

2. 連結後のデータ型の不一致

あるDataFrameが列をint64として保存し、別のDataFrameがfloat64として保存している場合、pandasはfloatにアップキャストします。さらに悪いことに、一方が文字列として保存している場合、object型の列が作成されます:

# Check dtypes after concat
combined = pd.concat([df1, df2], ignore_index=True)
print(combined.dtypes)
 
# Fix: cast before concatenating
df2['units_sold'] = df2['units_sold'].astype(int)
combined = pd.concat([df1, df2], ignore_index=True)

3. 重複するインデックス値

ignore_index=Trueを使用しないと、縦方向の連結は元のインデックスを保持し、重複値が発生します。これは.locでのルックアップで問題を引き起こします:

combined = pd.concat([df1, df2])
# combined.loc[0] returns TWO rows, not one
 
# Fix option 1: use ignore_index
combined = pd.concat([df1, df2], ignore_index=True)
 
# Fix option 2: use verify_integrity to catch the issue early
combined = pd.concat([df1, df2], verify_integrity=True)  # raises ValueError

4. 誤った軸での連結

結果が行の2倍ではなく列の2倍になっている場合(またはその逆)、axisパラメータを確認してください:

# Wrong: this adds columns side by side
wrong = pd.concat([df1, df2], axis=1)
 
# Right: this stacks rows vertically
right = pd.concat([df1, df2], axis=0)

PyGWalkerで連結したDataFrameを可視化する

複数のソースからデータを連結した後、結果を検証し、結合されたデータセット内のパターンを探索する必要がよくあります。matplotlibやseabornで手動のプロットコードを書く代わりに、PyGWalker (opens in a new tab)を使用できます -- これは任意のpandas DataFrameをJupyter Notebook内で直接、インタラクティブなTableauライクの視覚的探索インターフェースに変換するオープンソースPythonライブラリです。

import pandas as pd
import pygwalker as pyg
 
# Combine monthly sales data
df_jan = pd.DataFrame({
    'product': ['Widget', 'Gadget', 'Sprocket'],
    'units_sold': [150, 200, 80],
    'revenue': [1500, 3000, 960],
    'month': ['Jan', 'Jan', 'Jan']
})
 
df_feb = pd.DataFrame({
    'product': ['Widget', 'Gadget', 'Sprocket'],
    'units_sold': [170, 180, 95],
    'revenue': [1700, 2700, 1140],
    'month': ['Feb', 'Feb', 'Feb']
})
 
combined = pd.concat([df_jan, df_feb], ignore_index=True)
 
# Launch interactive visualization
walker = pyg.walk(combined)

PyGWalkerでは、productをx軸に、revenueをy軸にドラッグし、monthで分割することで、期間間の収益トレンドを即座に比較できます -- チャートコードは不要です。フィールドをドラッグするだけで、棒グラフ、散布図、折れ線グラフなどを作成できます。連結が正しく機能したか、異なるソースからのデータが期待通りに揃っているかを検証するのに特に便利です。

pip install pygwalkerでPyGWalkerをインストールするか、Google Colab (opens in a new tab)またはKaggle (opens in a new tab)でお試しください。

FAQ

pandas concatとmergeの違いは何ですか?

pd.concat()はインデックスに基づいてDataFrameを縦方向(行の追加)または横方向(列の追加)に積み重ねます。pd.merge()は特定の列の値をマッチングして2つのDataFrameを結合します(SQL JOINのように)。DataFrameが同じ列を持ち、行を結合したい場合はconcatを使用してください。共有キー列に基づいて行をマッチングする必要がある場合はmergeを使用してください。

pd.concat()は元のDataFrameを変更しますか?

いいえ。pd.concat()は常に新しいDataFrameを返します。元のDataFrameは変更されません。これは、操作がデータをその場で変更するのではなく、新しいオブジェクトを返すというpandasの設計原則に一致しています。

異なる列を持つDataFrameをどのように連結しますか?

デフォルトのjoin='outer'pd.concat()を使用してください -- すべてのDataFrameのすべての列を保持し、欠損値をNaNで埋めます。すべてのDataFrameに存在する列のみが必要な場合は、join='inner'を設定してください。連結前に列名を変更して揃えることもできます。

pd.concat()はDataFrame.append()より高速ですか?

はい。DataFrame.append()はpandas 1.4で非推奨となり、pandas 2.0で削除されました。内部的にpd.concat()を呼び出していましたが、毎回コピーを作成していました。多数のDataFrameを結合する場合、リストに集めて一度だけpd.concat()を呼び出す方が、メモリを1回だけ確保するため大幅に高速です。

連結後にインデックスをリセットするにはどうすればよいですか?

pd.concat()ignore_index=Trueを渡してください:pd.concat([df1, df2], ignore_index=True)。これにより、元のインデックス値が0から始まる新しい連番インデックスに置き換えられます。または、結果に対して.reset_index(drop=True)を呼び出すこともできます。

まとめ

pandas concat()関数は、同じ構造を共有するDataFrameを結合するための定番ツールです。主なポイントは以下の通りです:

  • 縦方向の連結axis=0)は行を積み重ねる最も一般的なユースケースです -- 月次ファイル、バッチ結果、分割データセットの結合に最適です。
  • 横方向の連結axis=1)はDataFrameを横に並べ、インデックスで揃えます。
  • **ignore_index=True**を使用して、クリーンな連番インデックスを取得します(ほとんどの場合に推奨)。
  • **keys**を使用して、各行がどのソースから来たかを追跡する階層インデックスを作成します。
  • **join**パラメータは、一致しない列の処理方法を制御します:'outer'はすべてを保持、'inner'は共通の列のみ保持。
  • 常にDataFrameをリストに集めて、ループ内でappendする代わりに、pd.concat()を一度だけ呼び出してください。
  • 列の値でSQL風の結合が必要な場合は、代わりにpd.merge()を使用してください。

データを連結した後、PyGWalker (opens in a new tab)などのツールを使えば、チャートコードを書くことなく結合結果を視覚的に探索でき、データパイプラインの検証やソース間のパターン発見がより迅速になります。

📚