※この記事は、既存記事への追記・リライト案として作成しています。
複数の月別データを上下にまとめたいときや、別々の列を左右に並べたいときは、pandasのconcat()が役立ちます。
ただし、axis=0とaxis=1の違いや、ignore_index=Trueを付ける場面が分からないと、コードは動いてもインデックスが重複したり、思いがけないNaNが入ったりします。
先に結論を示すと、単純に表を上下または左右へ並べるならconcat()、顧客IDや商品コードなどの共通キーで対応する行を結び付けるならmerge()を選びます。
この記事では、同じ売上データを使って結合前後を比較します。axis・ignore_index・インデックスの対応を理解し、結合後の表を予測してからpd.concat()を使えるようになることがゴールです。
- この記事でわかること
- まず結論:上下・左右・キーのどれで結合するかを決める
- サンプルデータを用意する
- pandas concat()でDataFrameを縦結合する
- ignore_index=Trueでインデックスを振り直す
- pandas concat()でDataFrameを横結合する
- 横結合でNaNが出る理由:行数ではなくインデックスを確認する
- 縦結合でNaNが出る理由:列名の不一致を確認する
- concat()とmerge()の違い
- 複数CSVをconcat()で縦結合する
- concat()のよくある失敗と確認方法
- 結合後はshape・head()・列名・インデックスを確認する
- まとめ:concat()は方向と対応基準を確認して使う
- 関連記事
この記事でわかること
pd.concat([df1, df2])の基本的な使い方axis=0による縦結合とaxis=1による横結合の違いignore_index=Trueを付けるべき場面- 結合後にインデックスが重複したり
NaNが入ったりする理由 concat()とmerge()の使い分け- 同じ列構成の複数CSVを1つのDataFrameにまとめる流れ
- 結合後に確認するべきポイント
concat()は、CSVやExcelから読み込んだ複数のDataFrameを1つにまとめ、前処理・集計・可視化へ進むための中間工程でよく使います。
まず結論:上下・左右・キーのどれで結合するかを決める
最初に、「どの方向にデータを増やしたいか」と「何を対応の基準にするか」を確認しましょう。
| やりたいこと | 選ぶ機能 | 基本指定 | 選ぶ場面 | 注意点 |
|---|---|---|---|---|
| 同じ列構成の表を上下に積む | concat() |
axis=0 |
月別売上を年間売上にまとめる | 元のインデックスが引き継がれる |
| 同じインデックスの表を左右に並べる | concat() |
axis=1 |
店舗別の売上と目標を横に並べる | 行数ではなくインデックスで対応する |
| 共通キーで対応する行を結ぶ | merge() |
on="キー列" |
顧客IDで顧客表と購入表を結ぶ | キーの重複や結合方法を確認する |
concat()のaxisは、次のように覚えると迷いにくくなります。
| 指定 | 結合方向 | 増えるもの | 主な確認点 |
|---|---|---|---|
axis=0 |
縦方向 | 行 | 列名とインデックス |
axis=1 |
横方向 | 列 | インデックス |
axis=0はデフォルトなので省略できます。一方、横結合ではaxis=1を明示します。
サンプルデータを用意する
まず、同じ列構成を持つ「上半期売上」と「下半期売上」を作ります。この記事のコードはGoogle Colabで上から順に実行できます。
import pandas as pd
sales_first = pd.DataFrame({
"期間": ["上半期", "上半期", "上半期"],
"店舗": ["東京", "大阪", "福岡"],
"売上": [120, 100, 80]
})
sales_second = pd.DataFrame({
"期間": ["下半期", "下半期", "下半期"],
"店舗": ["東京", "大阪", "福岡"],
"売上": [140, 110, 90]
})
display(sales_first)
display(sales_second)
| 期間 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 上半期 | 東京 | 120 |
| 1 | 上半期 | 大阪 | 100 |
| 2 | 上半期 | 福岡 | 80 |
| 期間 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 下半期 | 東京 | 140 |
| 1 | 下半期 | 大阪 | 110 |
| 2 | 下半期 | 福岡 | 90 |
2つのDataFrameは、どちらも「期間・店舗・売上」の3列です。列名と役割が同じなので、下半期の3行を上半期の下へ追加できます。
処理前の形は次のとおりです。
| DataFrame | 行数 | 列 | インデックス |
|---|---|---|---|
sales_first |
3 | 期間・店舗・売上 | 0、1、2 |
sales_second |
3 | 期間・店舗・売上 | 0、1、2 |
pandas concat()でDataFrameを縦結合する
行を追加する縦結合では、pd.concat()へDataFrameのリストを渡します。axis=0がデフォルトなので、次の2つは同じ意味です。
pd.concat([sales_first, sales_second])pd.concat([sales_first, sales_second], axis=0)
まずは、インデックスをそのまま残して結合します。
なお、pd.concat()は結合した新しいDataFrameを返します。sales_firstやsales_secondなど、結合元のDataFrame自体は変更されません。そのため、結合結果は新しい変数へ代入して使います。
sales_with_original_index = pd.concat(
[sales_first, sales_second],
axis=0
)
sales_with_original_index
| 期間 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 上半期 | 東京 | 120 |
| 1 | 上半期 | 大阪 | 100 |
| 2 | 上半期 | 福岡 | 80 |
| 0 | 下半期 | 東京 | 140 |
| 1 | 下半期 | 大阪 | 110 |
| 2 | 下半期 | 福岡 | 90 |
6行に増えていますが、インデックスは0、1、2、0、1、2です。concat()はデフォルトで各DataFrameのインデックスを引き継ぐため、同じ番号が重複します。
これはデータ行の重複ではありません。東京・大阪・福岡の上半期と下半期は別の行ですが、行ラベルとして同じ番号を持っている状態です。
| 結合前 | 結合後 |
|---|---|
| それぞれのインデックスが0、1、2 | 元の番号を保ったまま0、1、2、0、1、2 |
| 各DataFrameは3行 | 結合後は6行 |
元の行ラベルを残す必要がなく、0からの連番にしたい場合は、次のignore_index=Trueを使います。
ignore_index=Trueでインデックスを振り直す
月別や期間別のデータを1つの表として扱う場合は、元のインデックスが不要なことが多いため、ignore_index=Trueが分かりやすい選択です。
sales_all = pd.concat(
[sales_first, sales_second],
axis=0,
ignore_index=True
)
sales_all
| 期間 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 上半期 | 東京 | 120 |
| 1 | 上半期 | 大阪 | 100 |
| 2 | 上半期 | 福岡 | 80 |
| 3 | 下半期 | 東京 | 140 |
| 4 | 下半期 | 大阪 | 110 |
| 5 | 下半期 | 福岡 | 90 |
結合後のインデックスが0から5までの連番になりました。データの値は変わっていません。変わったのは、結合後に付けられた行ラベルです。
| 指定 | 結合後のインデックス | 選ぶ場面 |
|---|---|---|
ignore_index=False(デフォルト) |
0、1、2、0、1、2 | 元の行ラベルを保持したい |
ignore_index=True |
0、1、2、3、4、5 | 結合後を新しい1つの表として扱いたい |
ignore_index=Trueは、重複しているデータ行を削除する機能ではありません。元のインデックスを使わず、新しい連番を付ける指定です。
すでに結合したDataFrameのインデックスをあとから振り直す場合は、reset_index(drop=True)を使えます。
pandas concat()でDataFrameを横結合する
列を増やす横結合ではaxis=1を指定します。ここでは、店舗をインデックスにした売上表と目標表を左右に並べます。
正常に横結合するため、2つのDataFrameで同じ「東京・大阪・福岡」というインデックスを使います。
sales_by_store = pd.DataFrame(
{"年間売上": [260, 210, 170]},
index=["東京", "大阪", "福岡"]
)
target_by_store = pd.DataFrame(
{"年間目標": [250, 220, 180]},
index=["東京", "大阪", "福岡"]
)
display(sales_by_store)
display(target_by_store)
| 年間売上 | |
|---|---|
| 東京 | 260 |
| 大阪 | 210 |
| 福岡 | 170 |
| 年間目標 | |
|---|---|
| 東京 | 250 |
| 大阪 | 220 |
| 福岡 | 180 |
sales_and_target = pd.concat(
[sales_by_store, target_by_store],
axis=1
)
sales_and_target
| 年間売上 | 年間目標 | |
|---|---|---|
| 東京 | 260 | 250 |
| 大阪 | 210 | 220 |
| 福岡 | 170 | 180 |
「東京」には東京の年間売上と年間目標、「大阪」には大阪の値が並びました。
| 結合前:売上表 | 結合前:目標表 | 結合後 |
|---|---|---|
| インデックス=店舗 | インデックス=店舗 | 同じ店舗の行が横に並ぶ |
| 年間売上の1列 | 年間目標の1列 | 年間売上・年間目標の2列 |
ここで重要なのは、axis=1のconcatが「上から1行目同士を機械的に並べる」のではなく、同じインデックスラベルの行同士を対応させることです。
横結合でNaNが出る理由:行数ではなくインデックスを確認する
次は、コードは正常に動くのに結果を誤解しやすい例です。
売上表と同じ3行の目標表を用意しますが、目標表の店舗を「東京・大阪・名古屋」に変えます。行数はどちらも3行です。
target_mismatched = pd.DataFrame(
{"年間目標": [250, 220, 160]},
index=["東京", "大阪", "名古屋"]
)
sales_and_target_mismatched = pd.concat(
[sales_by_store, target_mismatched],
axis=1
)
sales_and_target_mismatched
| 年間売上 | 年間目標 | |
|---|---|---|
| 東京 | 260.0 | 250.0 |
| 大阪 | 210.0 | 220.0 |
| 福岡 | 170.0 | NaN |
| 名古屋 | NaN | 160.0 |
結果には「福岡」と「名古屋」の両方が残り、対応するデータがない場所にNaNが入ります。
- 福岡:売上表にはあるが目標表にはないため、「年間目標」が
NaN - 名古屋:目標表にはあるが売上表にはないため、「年間売上」が
NaN
NaNは0円という意味ではなく、対応する値が存在しないことを表す欠損値です。行数が同じでも、インデックスが一致しなければこの結果になります。
| 症状 | 原因 | 確認方法 | 修正方法 |
|---|---|---|---|
| 横結合後にNaNが入る | インデックスラベルが一致していない | 両方のindexを確認する |
店舗名の表記やインデックスを揃えてから結合する |
| 想定より行が増える | 一方にしかないインデックスがある | 結合前後のshapeとindexを比較する |
対象行を確認し、必要なら共通範囲へ整える |
自分のデータへ置き換えるときは、行数だけでなく「インデックスが何を表しているか」「表記が完全に一致しているか」を確認してください。欠損値を処理する必要がある場合は、原因を確認してからfillna()を検討します。
縦結合でNaNが出る理由:列名の不一致を確認する
縦結合では、インデックスだけでなく列名にも注意が必要です。次のDataFrameでは、「売上」の列名を「売上金額」に変えています。
sales_second_wrong_column = pd.DataFrame({
"期間": ["下半期", "下半期", "下半期"],
"店舗": ["東京", "大阪", "福岡"],
"売上金額": [140, 110, 90]
})
sales_with_different_columns = pd.concat(
[sales_first, sales_second_wrong_column],
ignore_index=True
)
sales_with_different_columns
| 期間 | 店舗 | 売上 | 売上金額 | |
|---|---|---|---|---|
| 0 | 上半期 | 東京 | 120.0 | NaN |
| 1 | 上半期 | 大阪 | 100.0 | NaN |
| 2 | 上半期 | 福岡 | 80.0 | NaN |
| 3 | 下半期 | 東京 | NaN | 140.0 |
| 4 | 下半期 | 大阪 | NaN | 110.0 |
| 5 | 下半期 | 福岡 | NaN | 90.0 |
concat()はエラーで停止せず、「売上」と「売上金額」を別の列として残します。そのため、上半期の「売上金額」と下半期の「売上」がNaNになります。
| 結合前 | 結合後 |
|---|---|
| 上半期は「売上」列 | 「売上」と「売上金額」が別々に作られる |
| 下半期は「売上金額」列 | 対応しない部分にNaNが入る |
この例はコードのエラーではありません。しかし、同じ意味の列を1列にまとめる意図なら、結果は誤りです。
結合前に次を確認します。
- 列名が完全に一致しているか
- 各列が同じ意味・単位を持っているか
- 不要な前後空白や表記ゆれがないか
同じ意味の列名を統一する場合は、rename()で整えてから結合します。
sales_second_fixed = sales_second_wrong_column.rename(
columns={"売上金額": "売上"}
)
sales_fixed = pd.concat(
[sales_first, sales_second_fixed],
ignore_index=True
)
sales_fixed
| 期間 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 上半期 | 東京 | 120 |
| 1 | 上半期 | 大阪 | 100 |
| 2 | 上半期 | 福岡 | 80 |
| 3 | 下半期 | 東京 | 140 |
| 4 | 下半期 | 大阪 | 110 |
| 5 | 下半期 | 福岡 | 90 |
列名を「売上」に統一したため、結合後は「期間・店舗・売上」の3列になり、列名不一致によるNaNは発生していません。
concat()とmerge()の違い
concat()とmerge()はどちらもpandasでDataFrameを結合する機能ですが、結合の考え方が違います。
| 機能名 | 何を基準に結合するか | 選ぶ場面 | 注意点 |
|---|---|---|---|
concat() |
結合方向とインデックス | 同じ構造の表を上下に積む、同じインデックスの表を左右に並べる | 列名・インデックスの不一致を確認する |
merge() |
顧客IDなどの共通キー | 異なる情報をキーで対応付ける | キーの重複と結合方法を確認する |
たとえば、上半期と下半期の売上を上下に積むならconcat()です。一方、顧客IDを使って顧客情報と購入履歴を対応付けるならmerge()を選びます。
この記事では、concat()による上下・左右の連結に範囲を絞っています。内部結合・左結合など、キーによる結合方法はpandas mergeの使い方で確認してください。
複数CSVをconcat()で縦結合する
実際の分析では、1月・2月・3月のように分かれたCSVを1つにまとめることがあります。
ここではColab上で同じ結果を再現できるように、3つの月別CSVを一時的に作成します。その後、CSVをDataFrameとして読み込み、pd.concat()で縦結合します。
monthly_data = {
"sales_01.csv": pd.DataFrame({"月": [1, 1], "店舗": ["東京", "大阪"], "売上": [20, 18]}),
"sales_02.csv": pd.DataFrame({"月": [2, 2], "店舗": ["東京", "大阪"], "売上": [22, 19]}),
"sales_03.csv": pd.DataFrame({"月": [3, 3], "店舗": ["東京", "大阪"], "売上": [21, 20]})
}
for file_name, monthly_df in monthly_data.items():
monthly_df.to_csv(file_name, index=False)
file_names = ["sales_01.csv", "sales_02.csv", "sales_03.csv"]
monthly_frames = [pd.read_csv(file_name) for file_name in file_names]
sales_quarter = pd.concat(monthly_frames, ignore_index=True)
sales_quarter
| 月 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 1 | 東京 | 20 |
| 1 | 1 | 大阪 | 18 |
| 2 | 2 | 東京 | 22 |
| 3 | 2 | 大阪 | 19 |
| 4 | 3 | 東京 | 21 |
| 5 | 3 | 大阪 | 20 |
3つのCSVに分かれていた各2行のデータが、6行のDataFrameにまとまりました。
ここでも重要なのは、結合前のCSVが同じ列構成・同じ意味・同じ単位になっていることです。自分のファイルを使う場合は、次を確認してください。
- 対象ファイルだけを読み込んでいるか
- 列名と列の意味が各CSVで一致しているか
- 日付や数値などのデータ型が揃っているか
- 結合後の行数が想定どおりか
Google ColabへのファイルアップロードやGoogle Driveからの読み込みは、Google ColabでCSVを読み込む方法で詳しく解説しています。本記事では、読み込み後のDataFrameを`concat()`でまとめる処理を中心にしています。
concat()のよくある失敗と確認方法
concat()では、エラーが出ないまま意図と違う表ができることがあります。実行できたかだけでなく、結果の形と対応関係を確認しましょう。
| 症状 | 主な原因 | 確認方法 | 修正方法 |
|---|---|---|---|
| 横に並べたかったのに行が増えた | axis=1を指定していない |
結合後の行数・列数を見る | 横結合ならaxis=1を指定する |
| 縦結合後にインデックスが重複した | 元のインデックスを引き継いだ | indexを見る |
不要ならignore_index=Trueを指定する |
| 縦結合後に列が増えてNaNが入った | 同じ意味の列名が一致していない | columnsを見る |
列名と意味を揃える |
| 横結合後にNaNや余分な行が出た | インデックスが一致していない | 両方のindexを見る |
インデックスの値と意味を揃える |
特に、axis=1の横結合は行数ではなくインデックスラベルで対応する点が重要です。
結合後はshape・head()・列名・インデックスを確認する
結合は、データ分析のゴールではありません。結合したDataFrameを前処理・集計・可視化へ渡せる状態か確認します。
次のコードでは、複数CSVをまとめたsales_quarterについて、行数と列数、先頭行、列名、インデックス、データ型を確認します。
print("行数・列数:", sales_quarter.shape)
print("列名:", sales_quarter.columns.tolist())
print("インデックス:", sales_quarter.index.tolist())
display(sales_quarter.head())
sales_quarter.info()
| 月 | 店舗 | 売上 | |
|---|---|---|---|
| 0 | 1 | 東京 | 20 |
| 1 | 1 | 大阪 | 18 |
| 2 | 2 | 東京 | 22 |
| 3 | 2 | 大阪 | 19 |
| 4 | 3 | 東京 | 21 |
行数・列数: (6, 3) 列名: ['月', '店舗', '売上'] インデックス: [0, 1, 2, 3, 4, 5] <class 'pandas.core.frame.DataFrame'> RangeIndex: 6 entries, 0 to 5 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 月 6 non-null int64 1 店舗 6 non-null object 2 売上 6 non-null int64 dtypes: int64(2), object(1) memory usage: 276.0+ bytes
この例では、次の点を確認します。
shapeが(6, 3)で、3つのCSVの合計6行になっている- 列名が「月・店舗・売上」の3列で、余計な列が増えていない
- インデックスが0から5までの連番になっている
head()で値の並びを確認できるinfo()で各列のデータ型と欠損の有無を確認できる
DataFrameの基本構造はPandas DataFrame入門、先頭行の確認はpandas head()の使い方、型・欠損値・統計量の確認はpandas describe()の使い方で整理しています。
確認後は、必要に応じて前処理を行い、groupby()による集計やMatplotlibによる可視化へ進みます。
まとめ:concat()は方向と対応基準を確認して使う
この記事では、pandasのconcat()でDataFrameを縦結合・横結合する方法を解説しました。
- 行を上下に積むなら
axis=0を使う - 列を左右に並べるなら
axis=1を使う - 縦結合で元のインデックスが不要なら
ignore_index=Trueを使う - 横結合は行数ではなくインデックスラベルで対応する
- 縦結合では、同じ意味の列名が一致しているか確認する
- 共通キーで対応する行を結ぶ場合は
merge()を選ぶ - 結合後は
shape・head()・列名・インデックス・データ型を確認する
concat()は、分割されたデータを1つにまとめ、前処理・集計・可視化へ進めるための重要な処理です。コードが動いたことだけで終わらず、結合後の行・列・インデックス・NaNの意味まで確認しましょう。
関連記事
pandas mergeの使い方|DataFrame結合(inner, left, outer)の違いと実例:共通キーでDataFrameを結合したい場合pandas reset_index()の使い方:結合後のインデックスをあとから振り直したい場合Google ColabでCSVを読み込む方法:CSVのアップロードやGoogle Driveからの読み込みを確認したい場合pandas read_excel()の使い方:ExcelファイルをDataFrameとして読み込みたい場合Pandas DataFrame入門:DataFrameの構造と基本操作を確認したい場合pandasで新しい列を追加する方法:1つのDataFrameへ計算結果などの列を追加したい場合pandas MultiIndex完全ガイド:階層型インデックスを学びたい場合Pandasロードマップ:読み込み・前処理・集計・可視化の学習順を確認したい場合
axis=0とaxis=1の違いは何ですか?
axis=0はDataFrameを上下に積み、行を増やします。axis=1はDataFrameを左右に並べ、列を増やします。axis=0はデフォルトなので省略できます。
ignore_index=Trueはいつ必要ですか?
縦結合後に元のインデックスを残す必要がなく、0から始まる新しい連番にしたいときに使います。データ行の重複を削除する機能ではありません。
concat後にNaNが出るのはなぜですか?
縦結合では列名、横結合ではインデックスが一致していない可能性があります。NaNは0ではなく、対応する値が存在しないことを表します。結合前のcolumnsとindexを確認してください。
concat()とmerge()はどう使い分けますか?
表を単純に上下または左右へ並べるならconcat()、顧客IDなどの共通キーで対応する行を結ぶならmerge()を使います。
append()の代わりに何を使いますか?
DataFrameのappend()はpandas 2.0で削除されています。DataFrameを縦に追加する場合は、pd.concat([df1, df2], ignore_index=True)のようにconcat()を使います。
列名が違うDataFrameを縦結合するとどうなりますか?
異なる列は別の列として残り、対応する値がない場所へNaNが入ります。エラーにならない場合があるため、同じ意味の列は結合前に列名を統一してください。
concat後のインデックスをあとから振り直せますか?
はい。df.reset_index(drop=True)で0からの連番へ振り直せます。結合時点で元のインデックスが不要だと分かっている場合は、concat(..., ignore_index=True)のほうが簡潔です。
共通する列や行だけを残して結合できますか?
はい。pd.concat()でjoin="inner"を指定すると、縦結合では共通する列、横結合では共通するインデックスだけを残せます。ただし、必要な列や行まで除外される可能性があります。初心者は、まず列名やインデックスの不一致を確認して整える方法を優先すると、意図しないデータの欠落を防ぎやすくなります。
複数CSVをまとめて結合できますか?
はい。同じ列構成のCSVをそれぞれread_csv()で読み込み、DataFrameのリストをpd.concat(..., ignore_index=True)へ渡します。結合前に列名・データ型・対象ファイルを確認してください。
コメント