伝票番号が同じでも、取引先が違えば別の明細という業務があります。伝票番号だけで重複を探すと、正常な行まで確認対象へ入ります。反対に、行の全項目が同じかで比べると、金額だけ異なる二重入力を見落とすかもしれません。
pandasのduplicatedには、判定に使う列を複数指定できます。何を一件とするかを列の組で表すと、明細の内容と識別用の情報を分けて検査できます。
取引先と伝票番号を組にする
次の架空データは、取引先AとBがそれぞれ伝票001を持つ例です。Aの001だけが二行あります。行IDは結果を確認するための番号で、重複判定のキーには入れません。
import pandas as pd
df = pd.DataFrame({
"行ID": ["R1", "R2", "R3", "R4"],
"取引先": ["A", "B", "A", "A"],
"伝票": ["001", "001", "002", "001"],
"金額": [100, 200, 50, 110],
})
keys = ["取引先", "伝票"]
for column in keys:
if df[column].isna().any() or df[column].eq("").any():
raise ValueError("キーの空欄を確認してください")
single = df.duplicated(["伝票"], keep=False)
combined = df.duplicated(keys, keep=False)
candidates = df.loc[combined].sort_values(keys + ["行ID"])
print("伝票だけの候補:", int(single.sum()))
print("取引先と伝票の候補:", candidates["行ID"].tolist())
print("元の行数:", len(df))
assert candidates["行ID"].tolist() == ["R1", "R4"]
assert len(df) == 4
伝票番号だけなら三行、取引先との組ならR1とR4の二行が候補になります。金額が100と110で違っていても、識別する組が同じなので確認へ出ます。Windows・Python 3.12.10・pandas 3.0.2で、この結果を確認しました。
keep=Falseは同じ組のすべての行を選ぶ指定です。既定のままでは最初の一行を候補へ含めないため、二つの値を並べて確認したい用途には違いがあります。
キーを文字列で連結しない
取引先と伝票番号を単純に足して一つの文字列にすると、区切り方によって別の組が同じ文字になることがあります。列の配列をそのままsubsetへ渡せば、この例で連結用の規則を新たに作る必要はありません。
読み込み時に001が1へ変わっている場合は、判定前に元の識別子を保持できているかも確認します。空白や全角文字をそろえる処理も、業務上同じと認める範囲で行います。
候補を消す前に理由を見る
このコードは重複候補を取り出すだけです。R1とR4のどちらを残すかは決めていません。訂正前後の履歴なのか、二重登録なのかで、その後の扱いが変わります。
伝票内に複数の品目がある場合は、取引先と伝票の組だけでは一意にならないのが正常です。明細番号を加える、伝票単位に集計してから比べるなど、一行の単位を確認してキーを設計します。
まず元の表を変更せず、同じ組の候補と金額などの確認項目を別表へ出してください。除外や統合は、その一覧を使って条件を確定してからの操作です。
キーを決める考え方は取得データの重複判定、表の操作はpandas入門で補えます。
参考資料
pandas.DataFrame.duplicated公式資料を2026年11月1日に確認しました。
