CHAPTER 6 ・ pandas を一歩進める / Lesson 23
表と表を結合する(merge・concat)
売上に得意先マスタを付け足す、縦に積む。Excelの VLOOKUP を、安全に置き換えます。
このレッスンが終わったら
「merge は、共通の列を手がかりに表を横に結ぶ。how の違いと、結合後に行数が増えていないかの確認が大切」と言える。
こんな場面、ありませんか?
売上明細を、地域別に集計したい。ところが、売上明細に書いてあるのは「C004」のような得意先コードだけで、地域は別の表(得意先の一覧)に書いてあります。Excelなら、VLOOKUP で得意先名と地域を引いてきて、列を足す場面です。
サンプルを確かめてみましょう。sales_detail.csv は売上明細(2025年4〜9月、128行)、customers.csv は得意先の一覧です。(商品の一覧 products.csv も、あとで使います)
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
売上明細には「得意先コード」と「商品コード」があり、得意先の一覧には、同じ「得意先コード」に、名前・地域・支払条件が結びついています。このように、コードから名前などを引くための一覧を、マスタと呼びます。この回は、pandas の merge(結合)で、売上明細にマスタの情報を横に付け足す方法を学びます。
merge で、表を横に結ぶ
まず、売上明細に、得意先の一覧を結びます。共通の列「得意先コード」を手がかりに、同じコードの行どうしを横に並べます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
列が、「得意先名」「地域」「支払条件」の3つ増えました。行数は、結合の前も後も 128 行で、変わっていません。この「行数が変わっていないか」の確認が、結合ではとても大切です(Step 4 で理由が分かります)。
次は、商品の一覧も結んで、商品名を付けます。そして金額(数量×単価)の列を作り、地域別に合計します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
大阪・東京・福岡の合計が出ました。products[["商品コード", "商品名"]] のように、必要な列だけを選んで結ぶのが、実務のコツです。理由は、Step 4 の②で見ます。
仕組みを読む
merge の1行は、「どの表と、どの表を」「どの列を手がかりに」「どう結ぶか」の3点でできています。
sales左の表(基準にしたい表)customers右の表(付け足したい表)on="得意先コード"手がかりにする共通の列(キー)how="left"左の表の行を、すべて残すpd.merge(sales, customers,売上明細(左)と得意先の一覧(右)を結ぶ on="得意先コード",「得意先コード」が同じ行どうしを、横に並べる how="left")左(売上明細)の行は、1行も減らさない。右に相手がいない行は、空欄(NaN)にする日本語にすると
手がかりにする列をキー、how="left" のように「左の表の行を全部残して結ぶ」方法を左結合と呼びます。ExcelのVLOOKUPと、対応させておきましょう。
| Excel(VLOOKUP) | pandas(merge) |
|---|---|
| 検索値(C列の得意先コード) | キー(on="得意先コード") |
| 検索する範囲(得意先の一覧) | 右の表(customers) |
見つからないと #N/A | 見つからないと NaN(how="left" の場合) |
| 同じコードが複数あると、最初の1つだけを返す | 行が増える(気づきやすいが、確認が必要) |
変えて、試す
① how を変えると、残る行が変わる。 売上が3行、得意先の一覧が3行の小さな表で、確かめます。売上の「C999」は一覧になく、一覧の「C003」は売上にありません。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
inner:両方にあるものだけ(2行)left:左の表を全部残す(3行)。相手がなければ空欄outer:左右どちらかにあるもの全部(4行)
売上明細にマスタを付けるときは、how="left" を使います。inner だと、マスタにないコードの売上が、黙って消えて、合計が合わなくなるからです。
② 同じ名前の列があると、_x と _y が付く。 sales_detail.csv にも products.csv にも「単価」の列があります。products を丸ごと結ぶと、列名が「単価_x」(左の表の単価)と「単価_y」(右の表の単価)に分かれてしまい、どちらを使うか迷います。Step 2 のように、付けたい列だけを選んでから結ぶと安全です。
③ 一覧に同じキーが2回あると、行が増える。 これが、結合の最大の落とし穴です。得意先の一覧に、C001 の行が、うっかり2行入ってしまったとしましょう。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
128 行が 139 行に増えました。C001 の売上が 11 行あり、それぞれが2行に分かれたからです。このまま集計すると、C001 の売上が二重に数えられます。エラーも出ません。だから、結合の前と後で、行数を見比べます。
見比べる代わりに、validate="many_to_one"(左は重複してよいが、右のキーは重複してはいけない)を付けると、重複したときにその場で止めてくれます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
MergeError: Merge keys are not unique in right dataset(右の表のキーが、重複しています)と出ます。止まるのは、困ったことではなく、間違った集計を防げたということです。
④ 縦に積む。 同じ形の表を縦に積むには pd.concat を使います(基礎編で、支店別のExcelをまとめたときに使いました)。前半と後半の2つの表(別々のファイルを読むと、どちらも行番号が 0 から始まります)を、1つにします。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
そのまま積むと、行の番号が 0 から2回始まって重複します(False)。ignore_index=True を付けると、番号が振り直されます(True)。キーを手がかりに横に足すのが merge、同じ形の表を縦に足すのが concat です。
キーの列名が左右で違うとき(例:右の表が「コード」)は、left_on と right_on で、別々に指定します。
pd.merge(sales, customers, left_on="得意先コード", right_on="コード", how="left")自分で書く
お題① 売上明細に得意先の一覧を結び、地域が「福岡」の、売上金額(数量×単価)の合計を表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
pd.merge(sales, customers, on="得意先コード", how="left") で結びます。金額の列を作ったら、地域が福岡の行だけを取り出して、金額の合計(.sum())を求めます。
答えの例を見る
import pandas as pd
sales = pd.read_csv("sales_detail.csv")
customers = pd.read_csv("customers.csv")
df = pd.merge(sales, customers, on="得意先コード", how="left")
df["金額"] = df["数量"] * df["単価"]
print(df[df["地域"] == "福岡"]["金額"].sum())お題② 次の得意先の一覧(customers2)には、C001 が2回入っています。結合したとき、売上明細より何行増えるかを表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
merged = pd.merge(sales, customers2, on="得意先コード", how="left") で結んで、len(merged) - len(sales) を表示します。
答えの例を見る
import pandas as pd
sales = pd.read_csv("sales_detail.csv")
customers = pd.read_csv("customers.csv")
customers2 = pd.concat([customers, customers.head(1)], ignore_index=True)
merged = pd.merge(sales, customers2, on="得意先コード", how="left")
print(len(merged) - len(sales))お題③ 次の2つの表を、how="outer" と indicator=True で結び、「得意先の一覧にないコード」を持つ伝票番号を、リストで表示してください。(_merge の列が left_only の行が、左の表にしかない行です)
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
r = pd.merge(a, b, on="得意先コード", how="outer", indicator=True) で結ぶと、_merge の列ができます。r[r["_merge"] == "left_only"]["伝票"].tolist() で取り出します。
答えの例を見る
import pandas as pd
a = pd.DataFrame({"伝票": ["S1", "S2", "S3"], "得意先コード": ["C001", "C002", "C999"]})
b = pd.DataFrame({"得意先コード": ["C001", "C002", "C003"], "得意先名": ["アオイ", "ミドリ", "サクラ"]})
r = pd.merge(a, b, on="得意先コード", how="outer", indicator=True)
print(r[r["_merge"] == "left_only"]["伝票"].tolist())仕事ならどう使う?
理解チェック売上明細にマスタを付けるとき、how="left" を使う理由は、どれでしょう?
理解チェックmerge のあとで行数が増えていました。いちばん疑うべき原因は、どれでしょう?