この記事でわかること

  • 2つのExcelファイルの違いを、目で追わずに、キー列で行を照合して見つける考え方
  • Excelの標準機能でできることと、できないこと
  • 実データをAIに渡さずに、比較のコードを作ってもらう頼み方
  • 新規・削除・変更を出すPythonのコードと、実行のしかた・結果の読み方・検算
  • 空欄と「NA」、数値と文字列、日付、数式など、見落としやすい違い

Excelの差分確認が必要になる場面

取引先の一覧・在庫表・名簿のように、同じ形の表を更新するたびに「前回から何が変わったか」を確かめる場面があります。

  • 先月の一覧と今月の一覧で、増えた取引先と、なくなった取引先を知りたい
  • 修正を頼んだファイルが戻ってきたとき、頼んだ箇所だけが直っているかを確かめたい
  • システムから出力した一覧を、手元で管理している一覧と突き合わせたい

どれも、「行が増えた・減った」と「同じ行の値が変わった」を分けて知りたい、という点が共通しています。

目視で見比べると見落とす理由

2つのファイルを並べて上から見比べる方法は、行が少なく、並び順が変わらない表なら使えます。ただ、次のような変化があると、目で追うのは一気に難しくなります。

  • 行の挿入・削除:途中に1行入るだけで、それより下の行がすべてずれて見える
  • 並べ替え:中身が同じでも、並び順が違うだけで別の表に見える
  • 見た目が同じ値:数値の 98000 と文字列の「98000」、日付と日付の形をした文字列、空欄と空白は、画面ではほとんど区別できない

この記事のサンプルで試したところ、行の並びを逆にしただけのファイルを行の位置どうしで比べた場合、10行すべて・54個のセルが違いとして出ました。キー列で行を照合すると、違いは0件です。pandas の DataFrame.compare も、行と列のラベルがそろった2つの表しか比べられないため、行の追加や並べ替えがある一覧には、そのままでは使えません。行の位置ではなく、その行が何の行かを表すキーで照合することが、差分確認の出発点になります。

Excelの標準機能でできること

Excelにも、2つのファイルを見比べるための機能があります。

  • 並べて比較と同時スクロール:表示タブの「並べて比較」をオンにすると「同時にスクロール」が使え、2つのウィンドウを同じ位置で動かせます。見比べやすくする機能で、違いを探して示してはくれません。
  • Spreadsheet Compare:「Inquire」タブの「Compare Files」から使う比較ツールで、入力した値・数式・書式・VBAなどの違いを種類ごとに色分けして表示します。2026年10月時点のMicrosoftのサポートページの説明では、Microsoft 365 Apps for enterpriseと同等のエディションのWindows版Excelだけで使える機能です。

手元のExcelでSpreadsheet Compareが使えないとき、または行の追加・削除・並べ替えがある一覧を、キーを決めて機械的に比べたいときは、この後のPythonの方法が役に立ちます。

AIに比較を頼むときは実データを渡さない

取引先や顧客の一覧をそのままAIのチャットに貼ると、そのデータを社外のサービスへ送ることになります。比較のコードを作ってもらうだけなら、実データは必要ありません。AIに伝えるのは、シート名・列名・列の型・キー列と、架空のサンプルだけにします。コードは自分のPCで実行し、実データはPCの外へ出しません。

社外のAIに渡してよいデータの考え方は、ExcelをAIで自動化する方法の「AIに渡してよいデータ・渡してはいけないデータ」で整理しています。まず勤務先のルールを確認してください。

Claude Codeに頼む場合の依頼文の例です。

【用途】比較のコードを作ってもらう依頼 /【実行場所】Claude Code /【状態変更】なし(実行の前に確認を求める)

2つのExcelファイル(old.xlsx と new.xlsx)の違いを出すPythonのコードを作ってください。
実際のファイルの中身は渡しません。ファイルの形は次のとおりです。
- シート名: customers
- 列: customer_code(キー列・文字列)、company_name、contact、amount(数値)、contract_date(日付)、notes
- 動作の確認には、この形の架空のデータだけを使ってください。

条件:
- 元のファイルは読むだけにして、上書きや保存をしないこと
- customer_code で行を照合し、新規・削除・値の変更の3つに分けて表示すること
- キーの重複・キーの空欄・列の構成の違いがあれば、結果を出さずにエラーで止めること
- 表示した件数が、行数の増減と合っているかを検算する方法も示すこと
- わからない点は推測で補わず、質問すること

コードを実行する前に、何をするコードかを説明して、私の確認を待ってください。

AIが作ったコードは、そのまま実データに使わず、サンプルで期待どおりの結果になるかを確かめてから使います。AIの出力の確かめ方はAIの出力をどう確認するかでも扱っています。

比べる前にキー列を確かめる

キー列は、1行を1つに決める列です。取引先の一覧なら取引先コード、在庫表なら品番のような列がキーになります。比べる前に、次の4つを確かめます。

  1. 両方のファイルに同じ名前のキー列がある
  2. キーが空欄の行がない
  3. 同じキーの行が2つ以上ない(重複していると、どの行どうしを比べるかが決まりません)
  4. キーが文字列として保存されている

4は、「00101」のように先頭に0があるコードで問題になります。数値に表示形式を付けて0を見せているだけだと、セルの値は101です。サンプルで確かめた結果でも、Pythonで読むと101になり、先頭の0は戻りませんでした。

この記事のコードは、1〜4のどれかに当てはまると、差分を出さずにエラーで止まります。キーが数値で保存されている表(例:1001のような番号)も、このコードでは止まります。キー列がない表は行の位置でしか比べられず、行の追加や並べ替えに弱くなるため、この記事の方法の対象外です。

この記事のサンプルExcel

架空の取引先の一覧で試します。シート名は customers で、列は次の6つです。

列 内容
customer_code 取引先コード(キー列。C001 など)
company_name 会社名(架空商事01 など)
contact 担当者(担当A など)
amount 金額
contract_date 契約日
notes 備考

old.xlsx は10行の一覧で、new.xlsx は次の3か所を変えたものです。

  • C003 の金額を 120000 から 125000 に変更
  • C005 の行を削除
  • C011 の行を追加

次のコードを make_sample.py という名前で保存すると、この2つのファイルを作れます。実行のしかたは、後の「差分を出すコードと実行のしかた」で説明します。

【用途】サンプルの old.xlsx と new.xlsx を作る /【実行場所】自分のPC(空のフォルダ) /【状態変更】あり(2つのファイルを新しく作る。同じ名前のファイルがあれば作らずに止まる)

"""記事のサンプル old.xlsx と new.xlsx を作る(架空のデータ)。同じ名前のファイルがあれば、作らずに止まる。"""
from datetime import datetime
from pathlib import Path

from openpyxl import Workbook

COLUMNS = ["customer_code", "company_name", "contact", "amount", "contract_date", "notes"]
OLD = [
    ["C001", "架空商事01", "担当A", 100000, datetime(2026, 4, 1), None],
    ["C002", "架空商事02", "担当B", 85000, datetime(2026, 5, 1), "継続"],
    ["C003", "架空商事03", "担当C", 120000, datetime(2026, 5, 15), None],
    ["C004", "架空商事04", "担当D", 98000, datetime(2026, 6, 1), "新規"],
    ["C005", "架空商事05", "担当E", 150000, datetime(2026, 6, 15), None],
    ["C006", "架空商事06", "担当F", 64000, datetime(2026, 6, 30), "継続"],
    ["C007", "架空商事07", "担当G", 210000, datetime(2026, 7, 1), "要確認"],
    ["C008", "架空商事08", "担当H", 47000, datetime(2026, 7, 15), None],
    ["C009", "架空商事09", "担当I", 132000, datetime(2026, 8, 1), "継続"],
    ["C010", "架空商事10", "担当J", 76000, datetime(2026, 8, 15), None],
]
NEW = [list(row) for row in OLD if row[0] != "C005"]  # C005 を削除
for row in NEW:
    if row[0] == "C003":
        row[3] = 125000  # C003 の金額を変更
NEW.append(["C011", "架空商事11", "担当K", 58000, datetime(2026, 9, 1), "新規"])  # C011 を追加

for name in ("old.xlsx", "new.xlsx"):
    if Path(name).exists():
        raise SystemExit(f"{name} が既にあります(上書きしないで止めます)")
for name, rows in (("old.xlsx", OLD), ("new.xlsx", NEW)):
    wb = Workbook()
    sheet = wb.active
    sheet.title = "customers"
    sheet.append(COLUMNS)
    for row in rows:
        sheet.append(row)
    for (cell,) in sheet.iter_rows(min_row=2, min_col=5, max_col=5):
        cell.number_format = "yyyy/mm/dd"
    wb.save(name)
print("old.xlsx と new.xlsx を作りました")

Pythonの環境を準備する

Pythonと、Excelファイルを読むための pandas と openpyxl を使います。pandasの公式ドキュメントでは、xlsxファイルの読み書きには追加のライブラリとして openpyxl が必要とされています。

【用途】ライブラリのインストール /【実行場所】自分のPCのターミナル /【状態変更】あり(Pythonの環境にライブラリを追加する)

pip install pandas openpyxl

この記事のコードは、Python 3.12.10・pandas 3.0.6・openpyxl 3.1.5 の環境で実行して確かめました。Pythonの入れ方や学び方はPythonを学ぶ方法、Claude Codeの準備はClaude Codeの始め方と使い方を参考にしてください。WindowsのPCで日本語のファイル名や文字化けが気になるときは、WindowsでClaude Codeを使うときの注意点も役に立ちます。

差分を出すコードと実行のしかた

コードの流れは次のとおりです。

  1. 2つのファイルを読む(シート名を指定し、セルに保存された値をそのまま読む)
  2. キー列があるか、キーの空欄・型・重複がないかを確かめる
  3. 2つのファイルの列の構成が同じかを確かめる
  4. 新のファイルだけにあるキーを「新規」、旧のファイルだけにあるキーを「削除」とする
  5. 両方にあるキーの行を列ごとに比べ、値が違うセルを「変更」とする
  6. 件数と一覧を画面に表示する(ファイルには何も書き込まない)

読み込みでは、dtype=object でセルに保存された値を型を変えずに読み、keep_default_na=False で空欄と文字列の「NA」を区別します。どちらも pandas の read_excel の設定です。

【用途】2つのExcelファイルの差分を出す /【実行場所】自分のPC /【状態変更】なし(ファイルを読むだけで、結果は画面に表示する)

"""2つの Excel ファイル(old.xlsx と new.xlsx)の違いを、キー列で行を照合して出す(A8 の記事の最小のコード)。

- 元のファイルは読むだけで、書き込まない。結果は画面に表示する。
- 新規の行・削除された行・値が変わったセルの3つに分ける。行の並びが変わっただけなら差は 0。
- 次の場合は比較せずにエラーで止める(終了コード 2): ファイルが無い・読めない・キー列が無い・
  キーが空欄・キーが文字列でない(先頭の 0 が消えている可能性)・キーの重複・列の構成が違う。

使い方: python excel_diff_minimal.py [旧のファイル 新のファイル]
"""
import sys
from pathlib import Path

import pandas as pd

OLD_FILE = "old.xlsx"
NEW_FILE = "new.xlsx"
SHEET = "customers"      # 比べるシートの名前
KEY = "customer_code"    # 行を照合するキー列の名前


class CompareError(Exception):
    """比較を続けられない問題。成功として扱わない。"""


def load(path, sheet=SHEET, key=KEY):
    path = Path(path)
    if not path.is_file():
        raise CompareError(f"ファイルがありません: {path}")
    try:
        # dtype=object: セルに保存された値をそのまま読む(数値・文字列・日付を勝手に変換しない)
        # keep_default_na=False: 空欄は ''、文字列の 'NA' は 'NA' のまま(空欄と区別する)
        df = pd.read_excel(path, sheet_name=sheet, dtype=object, keep_default_na=False)
    except Exception as e:
        raise CompareError(f"読み込めません: {path}({type(e).__name__}: {e})") from e
    if key not in df.columns:
        raise CompareError(f"キー列 {key} がありません: {path}")
    keys = df[key]
    rows = lambda mask: [i + 2 for i in df.index[mask]]  # Excel の行番号(見出しが1行目の場合)
    blank = keys.map(lambda v: str(v).strip() == "")
    if blank.any():
        raise CompareError(f"キーが空欄の行があります: {path} の {rows(blank)} 行目")
    not_text = keys.map(lambda v: not isinstance(v, str))
    if not_text.any():
        raise CompareError(f"キーが文字列ではない行があります(先頭の 0 が消えている可能性): {path} の {rows(not_text)} 行目")
    dup = keys[keys.duplicated(keep=False)]
    if len(dup):
        raise CompareError(f"キーが重複しています: {path} の {sorted(set(dup))}")
    return df.set_index(key)


def compare(old, new):
    only_old = [c for c in old.columns if c not in new.columns]
    only_new = [c for c in new.columns if c not in old.columns]
    if only_old or only_new:
        raise CompareError(f"列の構成が違います(旧だけ: {only_old} / 新だけ: {only_new})")
    added = sorted(set(new.index) - set(old.index))
    removed = sorted(set(old.index) - set(new.index))
    changed = []
    for k in sorted(set(old.index) & set(new.index)):
        for col in old.columns:  # 旧のファイルの列の順
            before, after = old.at[k, col], new.at[k, col]
            if before != after:
                changed.append((k, col, before, after))
    return {"added": added, "removed": removed, "changed": changed}


def compare_files(old_file, new_file, sheet=SHEET, key=KEY):
    return compare(load(old_file, sheet, key), load(new_file, sheet, key))


def show(v):
    """文字列は '' で囲む('1000' と 1000、空欄 '' と 'NA' を見分けるため)。"""
    return repr(v) if isinstance(v, str) else str(v)


def main(argv):
    old_file, new_file = (argv[1], argv[2]) if len(argv) == 3 else (OLD_FILE, NEW_FILE)
    try:
        result = compare_files(old_file, new_file)
    except CompareError as e:
        print(f"エラー: {e}", file=sys.stderr)
        print("比較を中止しました(結果は出していません)", file=sys.stderr)
        return 2
    print(f"比較: {old_file} → {new_file}(シート {SHEET}・キー {KEY})")
    print(f"新規 {len(result['added'])}件 / 削除 {len(result['removed'])}件 / 変更 {len(result['changed'])}件")
    for k in result["added"]:
        print(f"[新規] {k}")
    for k in result["removed"]:
        print(f"[削除] {k}")
    for k, col, before, after in result["changed"]:
        print(f"[変更] {k} {col}: {show(before)} → {show(after)}")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))

このコードを excel_diff_minimal.py という名前で保存します。自分のファイルで使うときは、冒頭の OLD_FILE・NEW_FILE・SHEET・KEY を、比べるファイル・シート名・キー列の名前に合わせて書き換えます。

make_sample.py と excel_diff_minimal.py を同じ空のフォルダに置き、ターミナルでそのフォルダへ移動してから実行します。

【用途】サンプルを作って比べる /【実行場所】自分のPCのターミナル(2つのファイルを置いたフォルダ) /【状態変更】1行目だけあり(サンプルの2つのファイルを作る)。2行目は読むだけ

python make_sample.py
python excel_diff_minimal.py

新規・削除・変更の出力の読み方

サンプルで実行すると、次のように表示されます。

比較: old.xlsx → new.xlsx(シート customers・キー customer_code)
新規 1件 / 削除 1件 / 変更 1件
[新規] C011
[削除] C005
[変更] C003 amount: 120000 → 125000
  • [新規]:new.xlsx だけにあるキーです。
  • [削除]:old.xlsx だけにあるキーです。
  • [変更]:両方にあるキーの行で、値が違うセルです。キー・列名・変更前 → 変更後の順に表示します。
  • 値が ' で囲まれているときは文字列です。たとえば 98000 → '98000' は、数値が数字の文字列に変わったことを示します。空欄は '' と表示されます。

キーが重複しているファイルなど、比べる前の確認で問題が見つかると、差分を出さずに次のように止まります(検証用に作った、C007 が2行あるファイルでの表示です)。終了コードは2になります。

エラー: キーが重複しています: case05_new.xlsx の ['C007']
比較を中止しました(結果は出していません)

結果を検算する

差分が出たら、その結果が正しいかを確かめます。

  • 件数を照合する:旧の行数 − 削除 + 新規 が新の行数になるかを見ます。サンプルでは 10 − 1 + 1 = 10 で、new.xlsx の行数と一致します。
  • 元のファイルで数件を確かめる:新規・削除・変更から数件を選び、元のファイルを開いて確かめます。このとき、ファイルは保存せずに閉じます。
  • 違いがないはずのファイルで試す:同じファイルどうし、または並べ替えただけのファイルで比べ、違いが0件になることを確かめます。
  • わざと1か所だけ変えたファイルで試す:コピーの1か所だけを変え、その1件だけが出ることを確かめます。

この記事のコードは、値の変更・行の追加と削除・並べ替え・重複キー・空欄とNA・日付・数値・数式・列・シート・書式の12種類の違いを含む架空のファイルで、期待した結果と実際の出力を照らし合わせて確かめました。

よくある失敗と注意点

空欄と文字列の「NA」

pandas の read_excel は、既定の設定では、空欄も「NA」「N/A」「NULL」などの文字列も欠損値として読みます。サンプルで確かめたところ、既定の設定のままでは、空欄から「NA」への変更も、空欄から「N/A」への変更も、違いとして出ませんでした。この記事のコードは keep_default_na=False で読むため、空欄は ''、「NA」は 'NA' として区別されます。

数値と数字の文字列、日付と日付の文字列

見た目が同じでも、数値の 98000 と文字列の '98000'、日付のセルと「2026/05/01」という文字列は、別の値として「変更」に出ます。この記事のコードは、型を自動ではそろえません。「2026/06/31」のような存在しない日付や「未定」のような変換できない値を、黙って一致や空欄として扱わないためです。どちらの型に合わせるかを決めてから、元のデータを直すか、変換の処理を足します。

数式の計算結果が保存されていないファイル

pandas がExcelファイルから読むのは、数式そのものではなく、ファイルに保存されている計算結果です。Pythonなどで作られ、Excelで開いて保存されていないファイルには計算結果が保存されていないことがあり、その場合、数式のセルは空として読まれます。サンプルで確かめたところ、計算結果が保存されていないファイルどうしでは、金額から計算した列の値が 165000 から 176000 に変わるはずの行でも、その列の違いは出ませんでした(金額の列の変更は出ました)。

計算結果が保存されていないExcelでは、変更を見落とす可能性があります。 数式の列が大事な表では、もとになる値の列の変更もあわせて確かめ、比べるファイルに計算結果が保存されているかを確かめてから比べてください。数式の文字列を比べたいときは、openpyxl の load_workbook の data_only で、数式のまま読むか保存された値を読むかを切り替えられます(この記事のコードの対象外です)。openpyxl は数式を計算せず、保存されている値を読むだけです。

書式だけの違いと、シートの追加・削除

このコードが比べるのは、セルの値だけです。背景色や太字だけを変えたセルは、違いとして出ません(サンプルで確かめた結果も0件でした)。また、指定したシート(customers)だけを読むため、シートの追加や削除は検出しません。シート名を指定せずに読むと先頭のシートが読まれるので、比べるシートは必ず名前で指定します。

列の構成が違う

列が追加・削除されたファイルどうしは、このコードでは比べずに止まります。旧だけにある列と新だけにある列がメッセージに出るので、列をそろえてから比べます。

この方法が向かない表

この記事のコードは、1行目が見出しで、2行目から1行に1件のデータが並ぶ表を前提にしています。次のような場合は、この方法をそのまま使わずに判断します。

  • キーになる列がない表:行の位置でしか比べられず、行の追加や並べ替えがあると正しく比べられません。
  • 見出しが複数行ある表、結合セルや表の外のメモがある表:前提の形と違うため、比べる範囲を整えてからにします。
  • 書式・図形・マクロの違いを知りたい場合:値だけを比べるこのコードでは分かりません。使えるエディションであれば、Spreadsheet Compareのような専用の機能を検討します。

元のファイルを書き換えない

このコードはファイルを読むだけで、何も保存しません。サンプルでの検証でも、実行の前後で元のファイルの中身と更新日時は変わりませんでした。openpyxl の公式ドキュメントには、既存のファイルを開いて同じ名前で保存し直すと図形などが失われる、と書かれています。比べるときも、直すときも、元のファイルは別に残しておきます。Pythonで作ったExcelを確かめる考え方は、Excelの定型レポートを自動化する手順でも紹介しています。

まとめ

  • 2つのExcelの違いは、行の位置ではなくキー列で照合し、新規・削除・変更に分けて見る
  • キーの空欄・重複・型と列の構成を先に確かめ、問題があれば差分を出さずに止める
  • 実データはAIに渡さず、列名と型・架空のサンプルでコードを作ってもらい、自分のPCで実行する
  • 空欄とNA、数値と文字列、日付、数式の計算結果、書式、シートは、見落としやすい違いとして別に確かめる
  • 結果は、件数の照合と元のファイルでの確認で検算する

まずはこの記事のサンプルで実行し、同じ5行が表示されることを確かめてから、自分のファイルで試してみてください。