株式分析では「価格データ」「銘柄マスタ」「財務データ」「指標データ」など、別々のテーブルに分かれて配信されるデータを キーで結合 する場面が頻繁にあります。pandas には merge / concat / join の 3 種類の結合手段があり、用途で使い分けます。
本記事では、3 つの結合関数の違いと、株価データを題材にした実用的な結合パターンを示します。
目次
- インストール
- 3 つの結合関数の違い
- サンプルデータの準備
- merge の基本(内部結合)
- 4 種類の結合方式
- キー名が違う場合
- merge_asof — 最近傍の日付で結合
- concat — 縦方向の連結
- join — インデックスベースの結合
- ワイド ↔ ロングの変換
- 結合でよくあるトラブル
インストール
pip install pandas検証バージョン: Python 3.12.5 / pandas 2.2.3
3 つの結合関数の違い
まず全体像を表で押さえます。
| 関数 | 主な用途 | 結合キー |
|---|---|---|
pd.merge(a, b, on=...) | SQL の JOIN 相当。列名で結合 | 列(on / left_on / right_on) |
pd.concat([a, b]) | 縦方向 / 横方向の単純な連結 | キーなし(位置で連結) |
df.join(other) | インデックスをキーにした結合 | インデックス |
実務では merge が中心です。concat は「銘柄ごとの DataFrame を縦に積む」「期間ごとの DataFrame を時間軸で連結する」場面で使います。join は両者がインデックスを揃えて持っているときに簡潔に書けます。
サンプルデータの準備
価格テーブルと銘柄マスタを別々に用意します。
import pandas as pd
prices = pd.DataFrame({ "Date": pd.to_datetime([ "2026-04-01", "2026-04-01", "2026-04-01", "2026-04-02", "2026-04-02", "2026-04-02", ]), "Code": [7203, 9984, 8306, 7203, 9984, 8306], "C": [2900, 9800, 1450, 2925, 9750, 1448],})
masters = pd.DataFrame({ "Code": [7203, 9984, 8306, 6758], "CoName": ["トヨタ", "ソフトバンクG", "三菱UFJ", "ソニーG"], "S33Nm": ["輸送用機器", "情報・通信業", "銀行業", "電気機器"],})merge の基本(内部結合)
両方のテーブルにキーが存在する行だけを残すのが 内部結合(inner join) です。merge のデフォルトはこれです。
joined = pd.merge(prices, masters, on="Code")print(joined.head()) Date Code C CoName S33Nm0 2026-04-01 7203 2900 トヨタ 輸送用機器1 2026-04-01 9984 9800 ソフトバンクG 情報・通信業2 2026-04-01 8306 1450 三菱UFJ 銀行業3 2026-04-02 7203 2925 トヨタ 輸送用機器...prices には 6758(ソニーG)が無いため、masters 側にあっても結果には現れません。
4 種類の結合方式
how で結合方式を切り替えます。
| how | 残る行 |
|---|---|
"inner" | 両方にキーが存在する行のみ(既定) |
"left" | 左側の全行(右側に無いキーは NaN) |
"right" | 右側の全行(左側に無いキーは NaN) |
"outer" | 片方にだけ存在する行も含む |
left = pd.merge(prices, masters, on="Code", how="left")outer = pd.merge(prices, masters, on="Code", how="outer")print(left)print(outer)how="left" は「価格データを軸に、対応するマスタ情報を引っ張ってくる」用途で多用します。マスタ側に該当銘柄が無ければ NaN になり、後段の処理で気付けます。
キー名が違う場合
両側でキーの列名が異なるときは left_on / right_on を使います。たとえば自分で用意した銘柄リストの列名が ticker、株価データ側が Code のように食い違う場合です。
watchlist = pd.DataFrame({"ticker": [7203, 6758], "memo": ["主力", "監視"]})joined = pd.merge(prices, watchlist, left_on="Code", right_on="ticker")結合後は Code と ticker の両方が残ります。不要なら .drop(columns="ticker") で落とします。
merge_asof — 最近傍の日付で結合
「価格データの日付に対して、その日以前で最も近い財務発表日のデータを引く」という時系列特有の結合は merge_asof で書けます。
ratings = pd.DataFrame({ "Date": pd.to_datetime(["2026-03-15", "2026-04-01"]), "Code": [7203, 7203], "rating": ["BUY", "HOLD"],})
prices_sorted = prices.sort_values(["Code", "Date"])ratings_sorted = ratings.sort_values(["Code", "Date"])
merged = pd.merge_asof( prices_sorted, ratings_sorted, on="Date", by="Code", direction="backward",)print(merged)direction="backward" は「価格日付以前で最も近いキー」を選びます。先読みバイアスを防ぐ用途で重要です。両入力とも結合キーで ソート済み が必須です。
concat — 縦方向の連結
複数銘柄の DataFrame を縦に積むには concat が使えます。
df_7203 = prices[prices["Code"] == 7203]df_9984 = prices[prices["Code"] == 9984]
stacked = pd.concat([df_7203, df_9984], ignore_index=True)print(stacked)ignore_index=True を付けないと、元の行番号がそのまま残るため、結合後の行番号が重複します。
横方向の連結は axis=1 です。インデックスが一致していることが前提になります。
horizontal = pd.concat([df_7203.reset_index(drop=True), df_9984.reset_index(drop=True)], axis=1)join — インデックスベースの結合
両方の DataFrame が同じ意味のインデックスを持っているとき、join が簡潔です。
prices_indexed = prices.set_index("Code")masters_indexed = masters.set_index("Code")
joined = prices_indexed.join(masters_indexed, how="left")print(joined.head())価格データを (Code, Date) のマルチインデックスにしておけば、銘柄マスタとの突合が join 一発で書けます。
ワイド ↔ ロングの変換
結合とセットで覚えたいのが、データの形を変える melt と pivot です。
ワイド(列に銘柄が並ぶ)→ ロング(銘柄列を持つ縦長)への変換。
wide = pd.DataFrame({ "Date": pd.to_datetime(["2026-04-01", "2026-04-02"]), "7203": [2900, 2925], "9984": [9800, 9750],})long = wide.melt(id_vars="Date", var_name="Code", value_name="C")print(long)ロング → ワイドの変換は pivot または pivot_table(#4-3「pandas で集計・グループ化」)で行います。
結合でよくあるトラブル
実務でハマりやすい点を 3 つ挙げます。
- キーの型不一致: 片方が
int64、もう片方がobject(文字列)だと結合されません。astypeで揃えます。 - 重複キーによる行数の爆発: 両側に同じキーが複数行ある場合、デカルト積で行数が掛け算されます。
merge(..., validate="one_to_many")で検査できます。 - 欠損行の見落とし:
how="left"で結合した結果、右側列に NaN がどれだけあるかをresult.isna().sum()で必ず確認します。
# 検査オプションの例pd.merge(prices, masters, on="Code", validate="many_to_one")生成AI へのプロンプト例
結合は意図を間違えると黙って壊れるため、要件を明確に書きます。
pandas で次の結合を行うコードを書いてください。
入力:- prices(列: Date, Code, C)- statements(列: DiscDate, Code, EPS, bps)
要件:- 各価格行に対して、DiscDate <= Date を満たす最新の財務行を結合する (発表前のデータを使わないこと)- statements の銘柄コードは Code、prices は Code(結合時に対応づける)- 価格 / EPS から PER、価格 / BPS から PBR を計算する- Date は datetime64、銘柄コードは同じ型に揃える- 検証として、結合後に NaN が発生した行の件数を集計して print する- pandas 2.2 系で merge_asof を使う「先読み禁止」のような制約を明示するのが、時系列結合で間違いを減らすコツです。
まとめ
mergeは SQL の JOIN 相当、concatは単純連結、joinはインデックス結合- 結合方式(
how)は inner / left / right / outer の 4 種 merge_asofは最近傍日付の結合に使い、先読み防止に有用- 結合後は NaN の発生件数と行数 を必ず確認する
- ワイド ↔ ロング変換は
melt/pivotで