二つのCSVやExcelを比べたいのに、pandasのcompareがインデックスのエラーで止まる。エラーを消すために行番号を振り直したら、別の申請を同じ行として比べてしまう。このような場面では、先に同じ業務IDの明細を対応させ、追加・削除を分けてから共通行を比較するのが出発点です。
本稿は、Pythonで表の読み込みと列の指定を練習したことがある社会人向けです。架空の申請一覧を使い、「変更前も変更後も6行、記録済み金額も3500円」という条件で見落とせる違いを確認します。差分の確認と、変更内容が業務上正しいかの判断は別です。
確認日:2026年10月12日。compareの公式仕様、set_indexを参照しました。公式はpandas 3.0.6表示です。掲載コードの参照実行はPython 3.12.14・pandas 2.2.3で確認し、現行版すべてで実行済みとはしていません。
エラーを直す前に、比べる明細の対応を決める
compareが要求するラベルと、仕事で使うIDを分ける
compareは、対応するセルの違いを表示するための機能です。公式仕様では、形状と行・列ラベルが揃っていることが必要です。ここでいうラベルはpandasが表を扱うための情報であり、仕事で同じ申請を示すIDと自動的に一致するものではありません。
例えば、CSVの先頭から0、1、2という行番号が付いているだけなら、両方の0行目が同じ申請である根拠はありません。変更後のCSVが部門順に並び替えられたとき、0行目の内容は別の申請になります。行番号が同じという条件だけで比較を通すと、意味の異なる二行を対応させてしまいます。
一方、申請IDをインデックスにしていても、変更前がR01、R02の順、変更後がR02、R01の順なら、そのまま比較する前に並びを揃える必要があります。「両方に同じIDがある」と「同じ順序で対応している」は別の確認です。どちらが原因かを調べずに値を変更しないでください。
最初のメモには、比較するファイル、対象期間、一行の意味、同じ行を識別する列を書きます。本稿なら「一行は一つの申請、申請IDで対応、比較する値は金額と状態」です。元ファイルの表示順を比較条件へ含める必要がある仕事なら、その目的も別に書きます。
この説明ができない段階で、関数だけを選ぶと判断が行き詰まります。申請番号を毎月使い回す運用なら、番号だけでは前月と今月の申請を区別できません。年度や拠点を含む複合キーが必要か、業務の担当者へ確認してから比較します。
エラーメッセージは、この対応を点検する入口です。対象の列数、列名、IDの集合、行の並びを順に確認し、どの条件が揃っていないかを記録します。エラーを消せたかだけでなく、同じ申請の変更前と変更後を見ているかまで確かめるのが、この練習の目的です。
reset_indexで行位置を揃えても、同じ申請とは限らない
reset_indexを使うこと自体が悪いわけではありません。ただし、インデックスを捨てて連番にした結果、何を対応させるかが変わった場合は注意が必要です。compareが動くようになっても、業務上の比較条件を満たしているとは限りません。
架空例では、変更前の先頭はR01で、変更後の先頭はR04です。この二つを先頭同士で比べると、金額が1200円から700円へ変わり、申請IDも変わったように見えます。しかし実際には、R04の700円は変更前にもあり、R01の変更後は900円です。位置で合わせた差分は、確認したい変更を表しません。
行順だけが変わった表と、本当に値が変わった表を分けて試します。前者は、業務IDで揃えれば差分がなくなる例です。後者は、揃えてもR01の金額などに差が残ります。この二種類の教材を用意すると、「並び順の問題を直した」結果と「値の変更を見つけた」結果を混同しにくくなります。
表示用の行番号が必要なら、IDで照合した後の報告表に付けられます。その番号は、読者が差分を数えるためのもので、元の申請を識別する番号とは別です。差分表に申請IDを残せば、表示の行番号が変わっても元明細へ戻れます。
また、両方を金額順に並べれば同じ行になる、という方法も適しません。同じ金額の申請が複数あると、どれがどれに対応するかを決められないためです。金額は比較したい値であり、その値自身を対応の根拠にすると、値が変更された申請を追いにくくなります。
比較前に整理する順序は、キーの確認、追加と削除の分類、共通キーの順序、対象列の順序です。差分が多すぎる場合は、いきなり変更件数の多さを業務の異常と決めず、この対応条件を見直します。小さい6行で説明できる状態に戻すと、何がずれているかを追いやすくなります。
行数も合計も一致する6行で、見落とす変更を再現する
教材は、編集部が作成した架空の申請一覧です。R01とR02の金額は差し引きで打ち消し合い、R05とR07も同じ300円です。R03は金額を変えずに状態だけを変えています。実在する申請や顧客情報ではありません。
| 申請ID | 変更前の金額・状態 | 変更後の金額・状態 |
|---|---|---|
| R01 | 1200円・申請 | 900円・申請 |
| R02 | 800円・申請 | 1100円・申請 |
| R03 | 500円・差戻 | 500円・承認 |
| R04 | 700円・申請 | 700円・申請 |
| R05 | 300円・申請 | 存在しない |
| R06 | 金額未記録・保留 | 金額未記録・保留 |
| R07 | 存在しない | 300円・申請 |
import pandas as pd
before = pd.DataFrame([
["R01", 1200, "申請"], ["R02", 800, "申請"],
["R03", 500, "差戻"], ["R04", 700, "申請"],
["R05", 300, "申請"], ["R06", None, "保留"],
], columns=["申請ID", "金額", "状態"])
after = pd.DataFrame([
["R04", 700, "申請"], ["R02", 1100, "申請"],
["R01", 900, "申請"], ["R07", 300, "申請"],
["R03", 500, "承認"], ["R06", None, "保留"],
], columns=["申請ID", "金額", "状態"])
before["金額"] = before["金額"].astype("Int64")
after["金額"] = after["金額"].astype("Int64")
assert len(before) == len(after) == 6
assert before["金額"].sum() == after["金額"].sum() == 3500
両方とも6行で、記録済み金額の合計は3500円です。ただし、金額が未記録のR06を含めて「全申請の金額が3500円と確定した」とは言えません。この値は、記録されている金額の集計です。欠損の意味を確認する作業は、差分比較を通した後にも残ります。
R01は300円減り、R02は300円増えています。合計だけを見れば差は0円でも、申請単位では二つの変更です。R03の状態変更は金額の集計に表れません。どの申請が承認されたかを知りたい仕事では、金額だけを比較対象にする設計も不足します。
R05がなくなりR07が加わったことも、行数と合計だけでは見つかりません。同じ300円の申請が一つ入れ替わっているからです。申請IDの集合を比べる確認が、件数や総額の確認を補います。
この教材で得た件数や金額は、実務の目安や効果の実測値ではありません。差が見えなくなる条件を意図的に用意した練習です。自分の入力へ適用するときは3500という固定値を使わず、対象期間と元明細から期待する値を設定してください。
追加・削除・共通行を別々に数える
変更前と変更後に共通するIDは、R01・R02・R03・R04・R06の5件です。変更前だけにあるR05は削除側、変更後だけにあるR07は追加側として分けます。compareへ渡すのは、まず共通する5件の値です。
ここで「削除」と呼ぶのは、比較する変更後の表にIDが存在しないという意味です。業務システムから本当に申請が取り消されたか、抽出条件が変わっただけかは、この表だけでは決められません。報告では「変更後の対象明細にない」と書く方が、原因の調査につなげやすい場合があります。
同様に「追加」は、変更前の対象明細になかったという分類です。新規申請なのか、以前は抽出対象外だった申請が加わったのかは、元の運用や期間を確認します。集合の違いから業務の原因まで推定してしまわないでください。
件数は、変更前6件=共通5件+変更前だけ1件、変更後6件=共通5件+変更後だけ1件という二つの式で照合します。共通5件のうち変更あり3件、変更なし2件も分けて数えます。追加と削除を、共通行の変更件数に混ぜないのがポイントです。
共通行の比較だけを保存すると、R05とR07の情報を報告から落としてしまいます。差分表、追加表、削除表を一組にして残すと、依頼者が「変更なしの行は除かれたのか」「そもそも比較対象に存在しなかったのか」を区別できます。
この分類は、合計の確認とも組み合わせます。共通行の金額変化が打ち消し合っただけなのか、追加と削除の金額が等しかったのかを別々に説明できます。今回の例では両方が起きています。差額0という一つの数字から、明細が一致したとは判断しません。
IDの重複・空欄・列の違いは、比較を続けず保留する
業務IDで対応させるには、一つのIDが一つの明細を表す必要があります。変更前にR01が二行、変更後にR01が一行あれば、どちらの変更前を比べるのかが曖昧です。先頭や末尾の一行を選ぶだけでは、正しい明細を選べたことになりません。
本稿のコードは、IDの重複を見つけたら比較を止めます。空欄、空白だけの文字列、前後に空白があるID、文字列ではないIDも保留します。実務の入力を勝手に修正しないための、小さい教材の条件です。「R01」と「 R01」を同じIDへ直してよいかは、入力仕様の確認が必要です。
重複が正常な仕様なら、一行の意味を見直します。同じ申請に複数の明細が付くなら、「申請ID+明細番号」などの組み合わせを候補にします。ただし、どの列を足せば一意になるかは業務ごとに異なります。金額や状態をキーに加えて重複を消すと、本来比較したい値の変更を追いにくくなります。
列が増減した場合も、共通する列だけを取り出して黙って続けないようにします。新しい確認列を落としてしまうと、変更後だけにある重要な情報が報告へ残りません。列名の集合を確認し、今回比べる範囲と、追加された列を別に記録します。
列の並びだけが違う場合は、同じ列名を同じ順序で取り出す方法が候補になります。ただし、金額の列名が変わった、状態の定義が変わったといった場合は単なる並び替えではありません。名称だけを揃えれば同じ意味になるか、元の仕様から確認します。
入力の不一致を保留できると、後から差分表を直す作業を減らせます。比較前の点検として、行数、列の集合、IDの未記録、IDの重複、比較する値の意味を控えてください。結合で行数が増える問題を調べるときは、pandasのmergeと重複キーの確認も使えます。
IDで揃えた差分を、元明細へ戻れる報告にする
共通IDと対象列を同じ順序へ揃える
下の関数は、先ほどの二つのDataFrameを受け取ります。CSVの読み込み、金額表記の変換、申請IDの採番は含めていません。金額を数値として扱えることと、一行が一申請であることを確認した後の、比較部分だけの教材です。
申請IDをインデックスにした後、共通IDを同じリストで取り出します。金額と状態の列も、同じ順序で選びます。ファイルに入っていた行順や列順を比較の根拠にしないためです。IDの名前順は表示を安定させるためで、申請の時系列を示しているわけではありません。
元データに別の順序が必要なら、比較結果ができた後に表示順を指定できます。受付日時の順へ並べる場合も、どのIDの差分かを保ったまま並べます。行位置だけを揃える方法と違い、表示の順序を変えても対応する申請は変わりません。
また、欠損は比較専用の控えで「未記録」という表示へ置き換えています。変更前・変更後の元DataFrameへは書き戻しません。これは金額の集計方法ではなく、未記録から0円へ変わった場合も、比較表で見えるようにするための表示です。
def keyed_diff(old, new):
expected = {"申請ID", "金額", "状態"}
for name, df in [("変更前", old), ("変更後", new)]:
if set(df.columns) != expected:
raise ValueError(name + ": 列構成を確認")
valid = df["申請ID"].map(
lambda x: isinstance(x, str)
and bool(x.strip()) and x == x.strip()
)
if not valid.all():
raise ValueError(name + ": IDを保留")
if df["申請ID"].duplicated().any():
raise ValueError(name + ": ID重複を保留")
left = old.set_index("申請ID")
right = new.set_index("申請ID")
common = sorted(set(left.index) & set(right.index))
removed = sorted(set(left.index) - set(right.index))
added = sorted(set(right.index) - set(left.index))
cols = ["金額", "状態"]
l = left.loc[common, cols]
r = right.loc[common, cols]
lview = l.astype("object").where(l.notna(), "<未記録>")
rview = r.astype("object").where(r.notna(), "<未記録>")
changes = lview.compare(
rview, result_names=("変更前", "変更後")
)
unknown = sorted(
k for k in common
if pd.isna(left.loc[k, "金額"])
or pd.isna(right.loc[k, "金額"])
)
return {
"common": common, "added": added, "removed": removed,
"changes": changes, "unknown": unknown
}
result = keyed_diff(before, after)
assert result["added"] == ["R07"]
assert result["removed"] == ["R05"]
assert list(result["changes"].index) == ["R01", "R02", "R03"]
assert result["unknown"] == ["R06"]
compareに渡す二表は、この段階で同じ5つのIDと同じ2列になっています。そこで初めて、対応する金額と状態を比べます。追加と削除は関数の別の戻り値に残るため、共通行の差分へ出てこないIDも追跡できます。
実データに使うときは、列名やキーを業務仕様に合わせて設計し直します。この関数は汎用の差分サービスではありません。三列以外を許可しないのも教材の条件です。必要な列を増やすなら、入力点検、比較対象、期待する差分の三つを同時に見直してください。
3件の変更を、金額差と状態変更へ分けて読む
共通行の差分は、R01、R02、R03の3件です。金額の変更は二件、状態の変更は一件です。一行の中で複数の列が変わる場合もあるため、変更したIDの件数と、変更したセルの数は同じものではありません。今回だけは、各IDで一つの値が変わっています。
| 申請ID | 変更した値 | 確認する内容 |
|---|---|---|
| R01 | 金額1200→900円 | 300円の減額根拠 |
| R02 | 金額800→1100円 | 300円の増額根拠 |
| R03 | 状態:差戻→承認 | 承認した日時や記録 |
| R04 | 変更なし | 比較対象として一致 |
| R06 | 両方で金額未記録 | 金額の確認は未完了 |
差分の金額は、増額と減額を別々に数えると意味を説明しやすくなります。R01の減額300円とR02の増額300円が打ち消し合った結果、合計の差が0円です。「金額変更なし」ではなく、「二件の金額変更があり、合計の差は0円」と書きます。
R03については、金額だけを差分対象にしていると見つけられません。申請の状態を報告したい仕事なら、状態列を比較に含める理由があります。逆に、帳票の表示色などを比較へ入れる必要があるかは仕事の目的次第です。何を差分と呼ぶかを、依頼者と揃えてください。
compareの結果では、変更していない値が省かれることがあります。差分表の空欄を、元の金額が不明だったという意味に読み替えないようにします。元明細の未記録と、比較表示が省略したセルは別です。本稿では未記録を表示語へ置き換え、確認対象も別リストで返しています。
報告時は、列見出しに変更前・変更後を明記します。左右が逆のまま読むと、増額と減額の解釈も逆になります。ファイル名と対象日を表の近くへ書き、どちらを前、どちらを後として関数へ渡したかを残すと、後から再確認できます。
差分は確認箇所を絞るための資料です。R01の900円が正しいと、このコードだけで決められるわけではありません。申請書や承認記録など、値の根拠へ戻って内容を確認します。差分が出たことと、その変更を承認してよいことを分けて扱ってください。
同じ未記録と、未記録から0円への変更を区別する
R06は、変更前も変更後も金額が未記録です。比較専用の表示では両方が同じ「未記録」になるため、金額の差分は出ません。しかし、金額が確認できた状態ではありません。そこで関数は、金額を確認すべき共通IDとしてR06を別に返しています。
この例で0円を使って欠損を埋めると、「金額がない」という情報を失います。申請が無償なのか、入力されていないのか、別の資料に金額があるのかは、元明細から確認する必要があります。見た目を揃えることを目的に、元の金額を変更しないでください。
変更後のR06へ0円が記録された場合は、未記録から0円への変更として表示させます。下の練習は別の入力を作って試すもので、先ほどの変更後DataFrameを上書きしません。初期の教材と追加の教材を分けると、どの条件で結果が変わったかを追えます。
variant = after.copy()
variant.loc[variant["申請ID"] == "R06", "金額"] = 0
checked = keyed_diff(before, variant)
assert checked["changes"].loc[
"R06", ("金額", "変更前")
] == "<未記録>"
assert checked["changes"].loc[
"R06", ("金額", "変更後")
] == 0
# 行順だけを変えた表は、IDで揃えると差分なし。
assert keyed_diff(before, before.iloc[::-1])["changes"].empty
0円が記録されたことで差分を検出できても、その0円が根拠のある値かはまだ確認が必要です。元の欄が空だから0円にした、という修正は、入力不足を金額の確定へ置き換えてしまうことがあります。更新した人と根拠の資料を、差分表とは別に控えます。
型の扱いも、比較の前提です。文字列の「900」と数値の900、空文字と欠損、税込と税抜の金額は、見た目が近くても意味が同じとは限りません。本稿の金額は整数円へ揃えた教材です。小数や通貨の違いを扱う汎用の金額比較ではないため、実務では単位と許容する差を先に決めます。
数値表記の整形が必要なら、比較の途中で黙って変換するのではなく、変換前後の控えを残します。「入力の表記が変わった」と「金額が変わった」を分けて報告できるようにします。件数の一致だけで変換の正しさを確定せず、未変換や保留の行も数えてください。
比較の目的と再現条件を、検算メモへ残す
「二つの表が一致したか」と「どの値が違うか」は、目的が異なります。equalsの公式案内は一致の真偽を確認する機能、assert_frame_equalは比較条件を指定して検査する機能です。compareは、差の表示を見たい場面で使い分けます。
| 方法 | 確認すること | その前に決めること |
|---|---|---|
| 件数・金額集計 | 規模や合計の変化 | 欠損、集計対象、単位 |
| IDの集合を比較 | 追加・削除・共通 | 一行を識別するキー |
| compare | 対応するセルの差 | 同じ行・列の対応 |
| equals | 表の一致を真偽で確認 | 比較対象とデータの型 |
| assert_frame_equal | 指定した条件での検査 | 型や数値の比較条件 |
検算メモの記入例は、「変更前6件、変更後6件、共通5件、追加R07、削除R05。共通行の変更IDは3件。記録済み金額は両方3500円。R06は金額確認未完了」です。差分の件数だけでなく、比較対象と残る確認も含めています。
入力を変えて試すときは、一度に一条件に絞ります。行順だけを逆にする、IDを一つ重複させる、列を一つ増やす、金額未記録を0円へ変える、といった小さな変更です。行順だけなら差分なし、重複や列差なら保留、0円への変更なら差分検出という期待を先に書きます。
本稿の参照実行では、これらの条件を確かめています。ただし、自分の環境で使う際も、同じ入力と明示した条件で結果を確認します。エラー文の細かな表示や、欠損の型の扱いは実行環境で違う可能性があるため、使用版と入力の型をメモへ残してください。
検算メモには、正しさを未確認のまま残す項目も書きます。追加と削除の原因、値の変更根拠、金額未記録の確認先などです。比較処理は、こうした調査を代わりに済ませるものではありません。誰が何を確認すれば報告を完了できるかを具体化するために使います。
元ファイル、比較専用の控え、差分報告を分けて保存すると、再実行時に同じ入力へ戻れます。調査中の表を元ファイルに上書きしてしまうと、変更前後の根拠が失われます。小さい教材でも保存先を区別する練習をしておくと、実データへ移したときの確認がしやすくなります。
Excelで確認する範囲と、Pythonを学ぶ範囲を選ぶ
一度だけ数行を見比べる仕事なら、表計算でIDの追加・削除と値を確認する方法も候補になります。本稿の6行を目で照合し、R01・R02・R03の変更とR05・R07の入替を説明できれば、比較処理が何をしているかを理解する練習になります。
毎月同じ形式の二表を比べるなら、IDの点検と差分の確認条件をPythonへ残す方法が候補です。ただし、入力仕様や変更の理由を決める作業は残ります。コードが動いたことを、業務判断の自動化が完成したことへ言い換えないようにします。
独学で進めたい人は、「行順だけの変更で差分が消える理由」「重複IDで止める理由」「3500円が同じでも一致とは言えない理由」を説明してみてください。関数の名前を覚えるだけでなく、入力と期待する分類を自分で作れることが、次の練習へ進む目安になります。
質問しながら学びたい場合は、読み込み、型の整理、キーの設計、比較結果の検算のどこで止まっているかを分けます。相談用の資料は、本番の申請一覧ではなく、このような架空の小さい入力にします。期待する値と実際の値があれば、支援してほしい範囲を説明しやすくなります。
2026年10月12日に確認したPython Winnerの公式講座一覧には、Pythonの基礎・応用やデータ分析を扱うコースがあります。基礎から指導を受けたい人の比較候補ですが、この差分教材の添削が含まれるとは確認していません。演習、質問できる範囲、受講期間と総費用を申込前に確認してください。
一つの比較処理を直したい人と、Python全体を学び直したい人では、必要な支援が違います。独学、単発の質問支援、まとまった講座を、自分が説明できない工程から選びます。受講した結果や転職の成果を、この記事の練習結果から推定することはできません。
無料カウンセリング・受講相談と、有料講座への申込みは別です。架空の入力と期待する差分を用意し、希望する演習範囲を確認してから判断します。
よくある質問
Q. 同じ行数なのにcompareがエラーになるのはなぜですか?
行数だけでなく行・列ラベルの対応を確認します。同じIDがあっても並びが違う場合や、列名が違う場合があります。業務IDと対象列を同じ順序へ揃えてから比較します。
Q. reset_indexでエラーを消してよいですか?
行位置で対応させることが比較目的に合う場合に限って判断します。申請IDを追いたい仕事では、連番にしただけで同じ申請が同じ位置になるとは限りません。
Q. 追加と削除はcompareだけで分かりますか?
本稿では先にIDの集合から追加・削除・共通を分けています。共通行だけの差分表には、比較対象に存在しないR05やR07が出ないため、別の一覧も残します。
Q. 両方の金額が空欄なら確認済みですか?
差分がなくても金額が確認できたとは言えません。本稿のR06は別の確認対象へ残し、未記録を0円に変えて比較を済ませることを避けます。
Q. 合計が同じなら差分確認を省略できますか?
教材では両方3500円でも、二件の金額変更と一件の状態変更、IDの追加・削除があります。合計の一致は明細の一致を証明しません。
次の確認に使える記事です。
- CSVの先頭ゼロと文字コード:IDを読み込む段階の表記を調べる場合。
- カンマ・円付き金額の変換:金額を比べる前の入力整理。
- 最新日付の行と重複の確認:一つのIDに履歴がある場合。
- Excel自動化を独学と講座で学ぶ判断:必要な学習範囲を選ぶ場合。