業務用スクリプトの依頼文に入力・出力・エラー処理のルールを入れる
「これを自動化して」を、実行可能な task description に変えます。sensitive data を含まない合成 sample と、手作業で確認した expected result を添えて、team ごとの work logs を集計する script request を完成させます。
AIチャットアシスタントに明確なデータクリーニング規則を提案させ、承認した規則だけをCSVのコピーに適用し、セル単位の変更履歴を作ります。例はすべての変換を自分で確認できるようにした合成データです。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English
対象読者AIを使って再現可能なCSVクリーニング規則を作りつつ、AIにデータを勝手に書き換えさせたくない人向けです。
この例で使う人名とメールアドレスはすべて合成データで、実在の人物ではありません。目的はAIにファイルそのものを直接クリーニングさせることではありません。代わりに、AIに規則を説明させ、人が内容を確認して承認した後で決定的に適用します。
次の内容をsample_people.csvとして保存します。前後の空白、メールアドレスの大文字小文字、departmentの表記、欠損値の表現、数値表記が統一されていません。
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
AIに各列の意味と代表的な値を伝え、意味を変えない安全な書式調整と、解釈を伴う可能性がある変換を分けてもらいます。
もっともらしく見えるという理由だけで規則を承認してはいけません。たとえば、すべての名前をTitle Caseにすると、正しい大文字小文字表記を壊す可能性があります。hoursの空欄をすべて0に置き換える規則も、元データに存在しない情報を追加することになります。
この例に対する保守的なAI回答では、次のような規則が提案される可能性があります。コードを書く前にそれぞれを確認します。
| 列 | 承認する規則 | 理由 |
|---|---|---|
| name | 前後の空白だけを削除 | 大文字小文字は変えず、不要な空白だけを除去 |
| 空白を除去し、空でない値だけ小文字化 | 欠損値を作らず、この例のメール表記を統一 | |
| department | 空白除去後、大文字小文字を無視してsalesをSales、engineeringをEngineeringに変換 | 例で確認できる限定的な一覧だけを使用 |
| hours | 空白除去、NAを空欄に変更、有効な小数表記を正規化 | 選択した欠損表現と数値表現を統一 |
| row_id | 変更しない | レコード識別子を保持 |
これらの規則は意図的に狭い範囲に限定しています。未知のdepartment値は推測せず、そのまま残します。空のemailも空のまま維持します。行の削除や重複排除も行いません。
小さな例なら、実行前に期待される結果を手作業で求められます。この表を後でスクリプトの出力と照合します。
| row_id | 列 | 変更前 | 変更後 |
|---|---|---|---|
| 1 | name | alice kim | alice kim |
| 1 | ALICE.KIM@example.com | alice.kim@example.com | |
| 1 | department | sales | Sales |
| 3 | carol.park@example.com | carol.park@example.com | |
| 3 | department | SALES | Sales |
| 3 | hours | 8.0 | 8 |
| 4 | department | engineering | Engineering |
| 4 | hours | NA | |
| 5 | department | Sales | Sales |
したがって、変更されるセルは合計9個になるはずです。row 2には変更がなく、出力には元の順序のまま5行すべてが残る必要があります。
次の標準ライブラリだけを使うスクリプトは、期待する列を確認し、各行をクリーニングしながら実際に値が変わったセルをすべて記録します。outputsフォルダがすでに存在する場合は停止するため、以前の確認結果を誤って上書きする可能性を減らせます。
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です。
クリーニング済みファイルを使う前に、まずoutputs/changes.csvを開きます。各行にはrow_id、列名、元の値、変更後の値、適用された規則が入り、1つの変換内容を説明できる必要があります。
ここでのAIは、データの意味を最終判断する主体ではなく、クリーニング規則の草案を作る補助役です。列定義、有効なカテゴリ、欠損値の扱い、許可される変換は、データ所有者または別の信頼できる基準から確認する必要があります。
2026-09-21 · hand-checked example · Python 3.12
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。