同じ売上データでも、出力元によって「部門」が「部署」になったり、列名の前後に空白が付いたりします。集計コードで列が見つからないときは、入力の列名を一度表示すると原因を確認できます。
全てを似た名前として推測すると、「売上金額」と「入金額」まで混ざりかねません。文字の正規化と、業務上の別名の対応を分ける方法なら、どこを同じ意味として扱ったかを残せます。
そろえる範囲を決める
この例では、NFKCで全角英数字などの表記を正規化し、stripで前後の空白を除きます。その後、「部署」「department」を「部門」、「amount」を「金額」に対応させます。
NFKCはUnicodeの互換正規化です。日本語の同義語を理解する処理ではありません。社内の略語や違う意味の金額をまとめる判断は、別名表に明示します。また、製品コードなどでは字形の違いに意味がある場合もあるため、この変換を全てのセルへ広げません。
架空のCSVを用意する
次をinput.csvとしてUTF-8で保存します。金額は円の整数という例です。
部署, 金額
A,100
B,70
A,50
期待する集計はAが150、Bが70です。実データで試す前に、列の順番を変えた場合や、同じ意味の列を二つ入れた場合も確認できます。
列名の衝突を止めてから集計する
次をnormalize_csv.pyとして保存し、input.csvと同じフォルダーで実行します。Python標準ライブラリだけを使います。
import csv
import unicodedata
from collections import defaultdict
from pathlib import Path
aliases = {"部署": "部門", "department": "部門", "amount": "金額"}
def canonical(name):
name = unicodedata.normalize("NFKC", name).strip()
return aliases.get(name, name)
totals = defaultdict(int)
with Path("input.csv").open(encoding="utf-8-sig", newline="") as f:
rows = csv.reader(f)
original = next(rows, None)
if original is None:
raise ValueError("ヘッダーがありません")
columns = [canonical(name) for name in original]
if len(columns) != len(set(columns)):
raise ValueError(f"列名が重複しました: {columns}")
if set(columns) != {"部門", "金額"}:
raise ValueError(f"想定外の列構成です: {columns}")
print(list(zip(original, columns)))
for line_no, values in enumerate(rows, start=2):
if len(values) != len(columns):
raise ValueError(f"{line_no}行目の列数が違います")
row = dict(zip(columns, values))
department = row["部門"].strip()
if not department:
raise ValueError(f"{line_no}行目の部門が空です")
try:
amount = int(row["金額"])
except ValueError as error:
raise ValueError(f"{line_no}行目の金額が整数ではありません") from error
totals[department] += amount
with Path("normalized-total.csv").open("x", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
writer.writerow(["部門", "金額"])
writer.writerows(sorted(totals.items()))
print(dict(sorted(totals.items())))
処理前には、元の列名と変換後の列名の対応が表示されます。集計結果は別のCSVへ保存し、同名ファイルが既にあれば上書きせず止まります。
止まった入力を確認する
「部門」と「部署」が両方ある入力は、変換後にどちらも「部門」になります。このコードは片方を勝手に選ばず、重複として止めます。列名を変更するだけの処理でも、衝突を見落とすと値を失うためです。
この例は部門と金額の二列だけを受け付けます。備考列もある業務では、許可する列を仕様として追加し、必要列の不足と追加列を分けて確認する形へ変えます。金額のカンマ、通貨記号、小数も自動で推測せず、入力側の約束に合わせて変換します。
確認用のデータでは、正常入力の合計に加え、列名衝突、想定外の列、列数の不一致が止まることを確かめます。別名表を増やしたときには、以前の列名が別の意味へ変わっていないかも読み直します。
関連する作業
列名がそろった後は、複数ファイルの結合や合計・平均の計算へ進めます。
