公開データを収集するときに出典・基準日・単位を記録する
title と URL だけを集めるのではなく、reference date、publication date、access date、units、terms of use を1行に記録します。コード不要で使える CSV template と checklist を含みます。
分析を始める前に、データセットの各列について意味、単位、データ型、許容値を文書化します。小さな合成テストデータセットを使い、同じデータ辞書を簡単な自動検証にも利用する方法を示します。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English
対象読者他の人が読んで確認できる、明確な列単位のデータ説明を必要とする研究者向けのガイドです。
列名だけでは、研究データを十分に説明できないことがほとんどです。speed は車速、車輪速度、角速度のいずれを意味する可能性もあります。accel は前後加速度、横加速度、または合成加速度を指すことがあります。データ辞書に意図した意味を記録しておけば、後の分析で推測に頼る必要がなくなります。
このチュートリアルでは、各列について列名、意味、単位、データ型、許容値、例の6項目を記録します。許容値フィールドには、カテゴリ、数値範囲、一意性ルール、簡単なテキストパターンなどを記述できます。
次のデータセットは合成データであり、この記事のために作成したものです。test_runs.csv として保存してください。最初の4行は意図したルールに従っています。5行目には、データ辞書を検証基準として利用できるよう、意図的に4つの問題を含めています。
run_id,speed_kmh,accel_m_s2,road_surface,test_status
R001,30,0.5,dry,complete
R002,50,-1.2,wet,complete
R003,70,0.0,dry,review
R004,40,-0.8,wet,complete
R005,135,fast,snow,done
5行、5列のデータです。R005 には、許容範囲を超えた speed、数値ではない acceleration 値、未対応の road-surface カテゴリ、未対応の test-status 値があります。run_id 自体は有効です。
データ辞書は、分析コードを開かなくても理解できる内容にします。他の研究者が類似した測定項目と区別できる程度まで、意味を具体的に記述してください。
| column_name | 意味 | 単位 | 型 | 許容値 | 例 |
|---|---|---|---|---|---|
| run_id | 1回のテスト実行を識別する一意の識別子 | 文字列 | R に続いて正確に3桁の数字; 一意 | R001 | |
| speed_kmh | 記録開始時の車速 | km/h | 整数 | 0 から 120 までを含む | 50 |
| accel_m_s2 | 前後加速度 | m/s^2 | 浮動小数点数 | -5.0 から 5.0 までを含む | -1.2 |
| road_surface | テストで使用した路面状態 | カテゴリ | dry;wet | wet | |
| test_status | テスト記録のレビュー状態 | カテゴリ | complete;review | complete |
識別子やカテゴリラベルは測定された物理量ではなく無次元のメタデータなので、単位欄が空であることは意図的です。type フィールドは、表計算ソフトが自動推定した型ではなく、意図したデータ表現を示します。
次のスクリプトを data_dictionary_check.py として保存してください。列仕様は SPEC に一度だけ定義します。スクリプトはその仕様を data_dictionary.csv として出力し、同じルールを使って各行を検証します。元の test_runs.csv は読み取り専用です。
import csv
import re
from pathlib import Path
SOURCE = Path("test_runs.csv")
OUTPUT_DIR = Path("outputs") / "data_dictionary_result"
DICTIONARY = OUTPUT_DIR / "data_dictionary.csv"
ISSUES = OUTPUT_DIR / "validation_issues.csv"
SPEC = [
{
"column_name": "run_id",
"meaning": "Unique identifier for one test run",
"unit": "",
"type": "string",
"allowed_values": "R followed by exactly 3 digits; unique",
"example": "R001",
},
{
"column_name": "speed_kmh",
"meaning": "Vehicle speed at the start of the record",
"unit": "km/h",
"type": "integer",
"allowed_values": "0 to 120 inclusive",
"example": "50",
},
{
"column_name": "accel_m_s2",
"meaning": "Longitudinal acceleration",
"unit": "m/s^2",
"type": "float",
"allowed_values": "-5.0 to 5.0 inclusive",
"example": "-1.2",
},
{
"column_name": "road_surface",
"meaning": "Road-surface condition used for the test",
"unit": "",
"type": "category",
"allowed_values": "dry;wet",
"example": "wet",
},
{
"column_name": "test_status",
"meaning": "Review state of the test record",
"unit": "",
"type": "category",
"allowed_values": "complete;review",
"example": "complete",
},
]
def issue(row_number, column, value, problem):
return {
"row_number": row_number,
"column_name": column,
"value": value,
"issue": problem,
}
def main() -> None:
if not SOURCE.is_file():
raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
expected_columns = [item["column_name"] for item in SPEC]
issues = []
seen_run_ids = set()
row_count = 0
with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
if reader.fieldnames != expected_columns:
raise ValueError(
f"Header mismatch. Expected {expected_columns}, got {reader.fieldnames}."
)
for row_number, row in enumerate(reader, start=2):
row_count += 1
run_id = row["run_id"].strip()
if re.fullmatch(r"R[0-9]{3}", run_id) is None:
issues.append(issue(row_number, "run_id", run_id, "PATTERN_ERROR"))
elif run_id in seen_run_ids:
issues.append(issue(row_number, "run_id", run_id, "DUPLICATE_VALUE"))
seen_run_ids.add(run_id)
try:
speed = int(row["speed_kmh"])
except ValueError:
issues.append(issue(row_number, "speed_kmh", row["speed_kmh"], "TYPE_ERROR"))
else:
if not 0 <= speed <= 120:
issues.append(issue(row_number, "speed_kmh", row["speed_kmh"], "OUT_OF_RANGE"))
try:
accel = float(row["accel_m_s2"])
except ValueError:
issues.append(issue(row_number, "accel_m_s2", row["accel_m_s2"], "TYPE_ERROR"))
else:
if not -5.0 <= accel <= 5.0:
issues.append(issue(row_number, "accel_m_s2", row["accel_m_s2"], "OUT_OF_RANGE"))
if row["road_surface"] not in {"dry", "wet"}:
issues.append(issue(row_number, "road_surface", row["road_surface"], "ALLOWED_VALUE_ERROR"))
if row["test_status"] not in {"complete", "review"}:
issues.append(issue(row_number, "test_status", row["test_status"], "ALLOWED_VALUE_ERROR"))
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
dictionary_fields = [
"column_name", "meaning", "unit", "type", "allowed_values", "example"
]
with DICTIONARY.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=dictionary_fields)
writer.writeheader()
writer.writerows(SPEC)
issue_fields = ["row_number", "column_name", "value", "issue"]
with ISSUES.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=issue_fields)
writer.writeheader()
writer.writerows(issues)
print(f"Rows checked: {row_count}.")
print(f"Dictionary columns: {len(SPEC)}.")
print(f"Validation issues: {len(issues)}.")
print(f"Output folder: {OUTPUT_DIR.as_posix()}")
if __name__ == "__main__":
main()
問題が発生するのは R005 のみです。CSV ヘッダーが1行目なので、R005 は物理的な CSV の6行目です。4件の問題行が出力されるはずです。
row_number,column_name,value,issue
6,speed_kmh,135,OUT_OF_RANGE
6,accel_m_s2,fast,TYPE_ERROR
6,road_surface,snow,ALLOWED_VALUE_ERROR
6,test_status,done,ALLOWED_VALUE_ERROR
以下の想定コンソール出力は、合成データセットとスクリプトから手作業で導出したものです。実際の実行ログを取得したものではありません。
Rows checked: 5.
Dictionary columns: 5.
Validation issues: 4.
Output folder: outputs/data_dictionary_result| 問題 | 確認すること |
|---|---|
| 新しいデータセット列にデータ辞書の項目がない | 分析前にデータ辞書を更新し、意味と単位を文書化します。 |
| 同じ概念が異なる単位で存在する | 列を分けるか、単位を明示的に標準化します。記憶に頼らないでください。 |
| カテゴリラベルが時間とともに変化する | 許容値を定義し、新しい路面条件など意図的な追加を文書化します。 |
| 表計算ソフトが整数 ID を数値に変換する | 先頭のゼロやパターンに意味がある場合は、識別子を文字列として保存します。 |
| 検証には合格するが意味が間違っている | 構造的なチェックでは、科学的定義の誤りやセンサーチャンネルの誤ラベルは検出できません。 |
実務的な研究用データ辞書では、ここで示した6項目以上の情報が必要になることがよくあります。プロジェクトに応じて、source system、sensor location、coordinate direction、sampling rate、nullable status、precision、calculation method、valid range、missing-value code、responsible owner などを追加してください。
許容値は、現在観測されている最小値と最大値だけではなく、科学的な意味を反映する必要があります。あるファイルで speed が 30 から 70 km/h の範囲に収まっていたとしても、30 から 70 が自動的に有効な工学的範囲になるわけではありません。
データ辞書は、データセットまたは分析コードと一緒にバージョン管理してください。列の定義、単位、符号規約、カテゴリが変更された場合は、その変更がいつ発生したかを記録します。データ辞書は、分析対象データの正確なバージョンを記述しているときに最も有用です。
2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, pathlib, re · 未実行
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。