AIの業務活用

AIでCSVデータのクリーニング規則を作り、変更セルをすべて確認する

AIチャットアシスタントに明確なデータクリーニング規則を提案させ、承認した規則だけをCSVのコピーに適用し、セル単位の変更履歴を作ります。例はすべての変換を自分で確認できるようにした合成データです。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者AIを使って再現可能なCSVクリーニング規則を作りつつ、AIにデータを勝手に書き換えさせたくない人向けです。

準備するもの
  • Python 3.12
  • テキストエディタとAIチャットアシスタント

01小さな合成CSVから始める

この例で使う人名とメールアドレスはすべて合成データで、実在の人物ではありません。目的はAIにファイルそのものを直接クリーニングさせることではありません。代わりに、AIに規則を説明させ、人が内容を確認して承認した後で決定的に適用します。

次の内容をsample_people.csvとして保存します。前後の空白、メールアドレスの大文字小文字、departmentの表記、欠損値の表現、数値表記が統一されていません。

text
row_id,name,email,department,hours
1, alice kim ,ALICE.KIM@example.com, sales ,8
2,Bob Lee,bob.lee@example.com,Engineering,7.5
3,Carol Park, carol.park@example.com ,SALES, 8.0
4,Dan Choi,,engineering,NA
5,Eve Han,eve.han@example.com, Sales,9

02AIにはファイル修正ではなく規則を依頼する

AIに各列の意味と代表的な値を伝え、意味を変えない安全な書式調整と、解釈を伴う可能性がある変換を分けてもらいます。

もっともらしく見えるという理由だけで規則を承認してはいけません。たとえば、すべての名前をTitle Caseにすると、正しい大文字小文字表記を壊す可能性があります。hoursの空欄をすべて0に置き換える規則も、元データに存在しない情報を追加することになります。

03AIの回答を明示的な規則表にする

この例に対する保守的なAI回答では、次のような規則が提案される可能性があります。コードを書く前にそれぞれを確認します。

承認する規則理由
name前後の空白だけを削除大文字小文字は変えず、不要な空白だけを除去
email空白を除去し、空でない値だけ小文字化欠損値を作らず、この例のメール表記を統一
department空白除去後、大文字小文字を無視してsalesをSales、engineeringをEngineeringに変換例で確認できる限定的な一覧だけを使用
hours空白除去、NAを空欄に変更、有効な小数表記を正規化選択した欠損表現と数値表現を統一
row_id変更しないレコード識別子を保持

これらの規則は意図的に狭い範囲に限定しています。未知のdepartment値は推測せず、そのまま残します。空のemailも空のまま維持します。行の削除や重複排除も行いません。

04実行前に変更結果を予測する

小さな例なら、実行前に期待される結果を手作業で求められます。この表を後でスクリプトの出力と照合します。

row_id変更前変更後
1name alice kim alice kim
1emailALICE.KIM@example.comalice.kim@example.com
1department sales Sales
3email carol.park@example.com carol.park@example.com
3departmentSALESSales
3hours 8.08
4departmentengineeringEngineering
4hoursNA
5department SalesSales

したがって、変更されるセルは合計9個になるはずです。row 2には変更がなく、出力には元の順序のまま5行すべてが残る必要があります。

05承認した規則だけをコピーに適用する

次の標準ライブラリだけを使うスクリプトは、期待する列を確認し、各行をクリーニングしながら実際に値が変わったセルをすべて記録します。outputsフォルダがすでに存在する場合は停止するため、以前の確認結果を誤って上書きする可能性を減らせます。

python
import csv
import sys
from decimal import Decimal, InvalidOperation
from pathlib import Path

INPUT = Path("sample_people.csv")
OUTPUT_DIR = Path("outputs")
CLEANED = OUTPUT_DIR / "sample_people_cleaned.csv"
CHANGES = OUTPUT_DIR / "changes.csv"

EXPECTED_FIELDS = ["row_id", "name", "email", "department", "hours"]
DEPARTMENT_MAP = {
    "sales": "Sales",
    "engineering": "Engineering",
}


def clean_hours(value):
    trimmed = value.strip()
    if trimmed == "NA":
        return "", "NA converted to blank"
    if trimmed == "":
        return "", "trim whitespace"

    try:
        number = Decimal(trimmed)
    except InvalidOperation:
        return trimmed, "trim whitespace"

    if number == number.to_integral():
        normalized = str(number.quantize(Decimal("1")))
    else:
        normalized = format(number.normalize(), "f")
    return normalized, "normalize numeric notation"


def clean_cell(column, value):
    if column == "row_id":
        return value, "preserve row_id"

    if column == "name":
        return value.strip(), "trim name whitespace"

    if column == "email":
        trimmed = value.strip()
        return trimmed.lower() if trimmed else "", "trim and lowercase email"

    if column == "department":
        trimmed = value.strip()
        normalized = DEPARTMENT_MAP.get(trimmed.lower(), trimmed)
        return normalized, "trim and normalize known department"

    if column == "hours":
        return clean_hours(value)

    return value, "no rule"


def main():
    if not INPUT.is_file():
        sys.exit(f"Input file not found: {INPUT}")

    if OUTPUT_DIR.exists():
        sys.exit(f"Stop: {OUTPUT_DIR} already exists. Review or rename it first.")

    OUTPUT_DIR.mkdir()

    cleaned_rows = []
    changes = []

    with INPUT.open("r", encoding="utf-8", newline="") as source:
        reader = csv.DictReader(source)
        if reader.fieldnames != EXPECTED_FIELDS:
            sys.exit(
                f"Unexpected columns. Expected {EXPECTED_FIELDS}, got {reader.fieldnames}"
            )

        for row in reader:
            cleaned = {}
            for column in EXPECTED_FIELDS:
                before = row[column]
                after, rule = clean_cell(column, before)
                cleaned[column] = after

                if before != after:
                    changes.append(
                        {
                            "row_id": row["row_id"],
                            "column": column,
                            "before": before,
                            "after": after,
                            "rule": rule,
                        }
                    )

            cleaned_rows.append(cleaned)

    with CLEANED.open("x", encoding="utf-8", newline="") as target:
        writer = csv.DictWriter(target, fieldnames=EXPECTED_FIELDS)
        writer.writeheader()
        writer.writerows(cleaned_rows)

    with CHANGES.open("x", encoding="utf-8", newline="") as target:
        writer = csv.DictWriter(
            target,
            fieldnames=["row_id", "column", "before", "after", "rule"],
        )
        writer.writeheader()
        writer.writerows(changes)

    print(f"Rows written: {len(cleaned_rows)}")
    print(f"Changed cells: {len(changes)}")
    print(f"Cleaned copy: {CLEANED}")
    print(f"Change log: {CHANGES}")


if __name__ == "__main__":
    main()

clean_csv.pyとsample_people.csvを同じフォルダに置き、python clean_csv.pyで実行します。この例で期待される要約は5 rows writtenと9 changed cellsです。

06変更されたセルをすべて確認する

クリーニング済みファイルを使う前に、まずoutputs/changes.csvを開きます。各行にはrow_id、列名、元の値、変更後の値、適用された規則が入り、1つの変換内容を説明できる必要があります。

  1. この合成例で変更履歴が正確に9行あることを確認します。
  2. row_id自体が変更セルとして記録されていないことを確認します。
  3. 9件の変更を、事前に手作業で作った表と照合します。
  4. row 2が変更履歴に含まれていないことを確認します。
  5. outputs/sample_people_cleaned.csvを開き、元の順序のまま5データ行が残っていることを確認します。
  6. 想定外の変更があれば、AIが提案した規則が正しいと決めつけず、原因を確認します。

07よくあるミスと限界

  • 別の根拠がない限り、欠けているhours、department、emailをAIに推測させないでください。
  • 正しい値まで変える可能性があるため、すべての名前を一律にTitle Caseへ変換するような広範な文字列変換は避けます。
  • 2行が似て見えるだけで重複を削除しないでください。重複排除には明示的なレコード照合規則が必要です。
  • 元のCSVを上書きしないでください。未変更の入力と確認済みの出力の両方を保存します。
  • 9 changesのような合計数だけを確認せず、どのセルがなぜ変更されたかも確認します。
  • 大規模データや機密データでは、まず代表サンプルで規則を試し、全体へ適用する前に分野固有の検証を追加します。

ここでのAIは、データの意味を最終判断する主体ではなく、クリーニング規則の草案を作る補助役です。列定義、有効なカテゴリ、欠損値の扱い、許可される変換は、データ所有者または別の信頼できる基準から確認する必要があります。

実行・検証の記録

2026-09-21 · hand-checked example · Python 3.12

  • 合成CSVと承認した変換規則を手作業で確認しました。
  • 期待されるセル単位の変更は合計9件で、row 1が3件、row 3が3件、row 4が2件、row 5が1件です。
  • row 2には期待される変更がありません。
  • クリーニング後も全5行、元の行順、row_id値が維持されることを手作業で確認しました。
  • スクリプトはoutputs/sample_people_cleaned.csvとoutputs/changes.csvだけに書き込み、sample_people.csvは上書きしません。
  • outputsフォルダがすでに存在する場合は停止する構成です。
検証範囲の限界
  • Pythonコードは実行しておらず、小さな例と期待結果を手作業で確認しました。
  • departmentのマッピングは意図的にSalesとEngineeringだけに限定しており、一般的な部門名正規化規則ではありません。
  • メールアドレスの小文字化はこのチュートリアルで明示的に採用した例示規則です。実際の組織データに広く適用する前に、その組織のデータポリシーを確認する必要があります。
  • この例では、不正なCSV、特殊な文字エンコーディング、複数行フィールド、スキーマ変更、重複レコードの判定、分野固有の妥当性規則は扱っていません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。