リサーチ・情報源

意味・単位・型・許容値を含むデータ辞書を作成する

分析を始める前に、データセットの各列について意味、単位、データ型、許容値を文書化します。小さな合成テストデータセットを使い、同じデータ辞書を簡単な自動検証にも利用する方法を示します。

目次を表示

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

対象読者他の人が読んで確認できる、明確な列単位のデータ説明を必要とする研究者向けのガイドです。

準備するもの
  • Python 3.12 と、そのバージョンを起動できるターミナルコマンド。
  • UTF-8 CSV ファイルを保存できるテキストエディタまたは表計算ソフト。
  • スクリプトが outputs の下に新しいフォルダを作成できる作業フォルダ。
  • 必要なのは Python 標準ライブラリのみです: csv, pathlib, re.

01データ辞書をデータセットの一部として扱う

列名だけでは、研究データを十分に説明できないことがほとんどです。speed は車速、車輪速度、角速度のいずれを意味する可能性もあります。accel は前後加速度、横加速度、または合成加速度を指すことがあります。データ辞書に意図した意味を記録しておけば、後の分析で推測に頼る必要がなくなります。

このチュートリアルでは、各列について列名、意味、単位、データ型、許容値、例の6項目を記録します。許容値フィールドには、カテゴリ、数値範囲、一意性ルール、簡単なテキストパターンなどを記述できます。

02小さな合成研究データセットを作成する

次のデータセットは合成データであり、この記事のために作成したものです。test_runs.csv として保存してください。最初の4行は意図したルールに従っています。5行目には、データ辞書を検証基準として利用できるよう、意図的に4つの問題を含めています。

csv
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 自体は有効です。

03想定するデータ辞書を手作業で作成する

データ辞書は、分析コードを開かなくても理解できる内容にします。他の研究者が類似した測定項目と区別できる程度まで、意味を具体的に記述してください。

column_name意味単位許容値
run_id1回のテスト実行を識別する一意の識別子文字列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;wetwet
test_statusテスト記録のレビュー状態カテゴリcomplete;reviewcomplete

識別子やカテゴリラベルは測定された物理量ではなく無次元のメタデータなので、単位欄が空であることは意図的です。type フィールドは、表計算ソフトが自動推定した型ではなく、意図したデータ表現を示します。

04データ辞書を生成し、データセットを検証する

次のスクリプトを data_dictionary_check.py として保存してください。列仕様は SPEC に一度だけ定義します。スクリプトはその仕様を data_dictionary.csv として出力し、同じルールを使って各行を検証します。元の test_runs.csv は読み取り専用です。

python
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()

05想定される検証上の問題を比較する

問題が発生するのは R005 のみです。CSV ヘッダーが1行目なので、R005 は物理的な CSV の6行目です。4件の問題行が出力されるはずです。

csv
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

以下の想定コンソール出力は、合成データセットとスクリプトから手作業で導出したものです。実際の実行ログを取得したものではありません。

text
Rows checked: 5.
Dictionary columns: 5.
Validation issues: 4.
Output folder: outputs/data_dictionary_result

06データ辞書とデータが一致していることを確認する

  • データセットのすべての列に、データ辞書の行が正確に1つずつ存在することを確認します。
  • 測定量について物理単位が明示されていることを確認します。
  • カテゴリの許容値が、データセットと同じ綴りおよび大文字・小文字で記載されていることを確認します。
  • 不良行を自動削除するのではなく、すべての検証上の問題を確認します。
  • OUTPUT_DIR を変更せずにスクリプトを再実行します。以前のデータ辞書とレポートを置き換えるのではなく、FileExistsError で停止するはずです。
問題確認すること
新しいデータセット列にデータ辞書の項目がない分析前にデータ辞書を更新し、意味と単位を文書化します。
同じ概念が異なる単位で存在する列を分けるか、単位を明示的に標準化します。記憶に頼らないでください。
カテゴリラベルが時間とともに変化する許容値を定義し、新しい路面条件など意図的な追加を文書化します。
表計算ソフトが整数 ID を数値に変換する先頭のゼロやパターンに意味がある場合は、識別子を文字列として保存します。
検証には合格するが意味が間違っている構造的なチェックでは、科学的定義の誤りやセンサーチャンネルの誤ラベルは検出できません。

07プロジェクトの成長に合わせてデータ辞書を拡張する

実務的な研究用データ辞書では、ここで示した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 · 未実行

  • 合成データが5行、データセットが5列であることを手作業で数えました。
  • R001 から R004 が、記載したデータ辞書のルールを満たしていることを手作業で確認しました。
  • R005 の4つの問題を特定しました: speed 135 が 0-120 の範囲を超えている、accel の値 fast が数値ではない、road_surface の snow が許可されていない、test_status の done が許可されていない。
  • R005 自体が、R に続いて3桁の数字という run_id パターンに一致することを確認しました。
  • R005 の4件すべての検証上の問題について、物理的な CSV 行番号が6であることを手作業で導出しました。
  • 完全一致のヘッダー確認、一意な run ID、数値範囲チェック、カテゴリチェック、出力衝突防止、元の CSV の保持について、スクリプトを確認しました。
検証範囲の限界
  • この回答の作成者はコードを実行しておらず、データ辞書または検証用 CSV は作成されていません。
  • 合成データの範囲とカテゴリはこの演習専用であり、一般的な自動車工学上の制限値ではありません。
  • 科学的意味、センサー校正、座標規約、欠損値ポリシー、メタデータのバージョン管理は自動検証されていません。
  • 公式ドキュメントの URL は既知のドキュメント所在地に基づいて記載していますが、ライブで確認していません。

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

参考資料

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