자료·리서치

열 의미, 단위, 자료형, 허용값을 포함한 데이터 사전 만들기

분석 전에 데이터셋의 각 열이 무엇을 의미하는지, 어떤 단위와 자료형을 사용하는지, 어떤 값을 허용하는지 문서화합니다. 작은 합성 시험 데이터로 같은 데이터 사전을 간단한 자동 검증에도 사용하는 방법을 확인합니다.

목차 보기

이런 분께 맞아요다른 연구자도 읽고 검토할 수 있도록 데이터셋의 열 정의를 명확하게 정리하려는 연구자를 위한 안내입니다.

준비할 것
  • Python 3.12와 해당 버전을 실행하는 터미널 명령이 준비되어 있어야 합니다.
  • UTF-8 CSV를 저장할 수 있는 텍스트 편집기 또는 스프레드시트 프로그램이 필요합니다.
  • 스크립트가 outputs 아래에 새 폴더를 만들 수 있는 작업 폴더가 필요합니다.
  • Python 표준 라이브러리인 csv, pathlib, re만 사용합니다.

01데이터 사전을 데이터셋의 일부로 취급하기

열 이름만으로는 연구 데이터를 충분히 설명하기 어렵습니다. speed는 차량 속도일 수도 있고 휠 속도나 각속도일 수도 있습니다. accel도 종방향, 횡방향, 전체 가속도 중 무엇인지 알 수 없습니다. 데이터 사전은 이후 분석자가 의미를 추측하지 않도록 의도한 정의를 기록합니다.

이 글에서는 각 열에 대해 column name, meaning, unit, data type, allowed values, example의 여섯 가지 정보를 기록합니다. allowed values에는 범주, 수치 범위, 고유성 규칙, 간단한 문자열 패턴 등을 적을 수 있습니다.

02작은 합성 연구 데이터셋 만들기

아래 데이터는 이 글을 위해 작성한 합성 데이터입니다. test_runs.csv로 저장하세요. 처음 네 행은 의도한 규칙을 따르고, 다섯 번째 행에는 데이터 사전을 이용해 검출할 수 있는 문제 네 개를 의도적으로 넣었습니다.

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에는 허용 범위를 초과한 속도, 숫자가 아닌 가속도, 허용되지 않은 노면 범주, 허용되지 않은 시험 상태가 들어 있습니다. run_id 자체는 정상입니다.

03예상 데이터 사전을 손으로 작성하기

데이터 사전은 분석 코드를 열어보지 않아도 이해할 수 있어야 합니다. 비슷한 측정값과 구분할 수 있을 정도로 meaning을 구체적으로 작성하세요.

column_namemeaningunittypeallowed_valuesexample
run_id한 번의 시험 run을 구분하는 고유 식별자stringR 다음에 정확히 숫자 3개; 고유값R001
speed_kmh해당 레코드 시작 시 차량 속도km/hinteger0 이상 120 이하50
accel_m_s2종방향 가속도m/s^2float-5.0 이상 5.0 이하-1.2
road_surface시험에 사용한 노면 조건categorydry;wetwet
test_status시험 레코드의 검토 상태categorycomplete;reviewcomplete

식별자와 범주형 라벨은 물리적 측정량이 아니므로 unit을 비워 두는 것이 의도된 상태입니다. 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데이터 사전과 실제 데이터가 계속 일치하는지 확인하기

  • 데이터셋의 모든 열에 데이터 사전 행이 정확히 하나씩 있는지 확인합니다.
  • 측정 물리량에는 단위를 명확하게 적었는지 확인합니다.
  • 범주형 허용값의 철자와 대소문자가 실제 데이터와 같은지 확인합니다.
  • 검증 문제를 자동으로 삭제하지 말고 각각 검토합니다.
  • OUTPUT_DIR을 바꾸지 않고 다시 실행합니다. 이전 데이터 사전과 검증 보고서를 덮어쓰지 않고 FileExistsError로 중단되어야 합니다.
문제확인할 사항
새 데이터 열에 데이터 사전 항목이 없음분석 전에 데이터 사전을 업데이트해 의미와 단위를 기록하세요.
같은 개념이 서로 다른 단위로 존재열을 분리하거나 단위를 명시적으로 통일하세요. 기억에 의존하지 마세요.
시간이 지나며 범주 라벨이 바뀜허용값을 정의하고 새로운 노면 조건 같은 의도된 추가 항목을 문서화하세요.
스프레드시트가 정수형 ID로 바꿈앞자리 0이나 문자열 패턴이 중요하면 식별자를 string으로 관리하세요.
검증은 통과하지만 의미가 틀림구조 검사는 잘못된 과학적 정의나 잘못 붙은 센서 채널 이름을 자동으로 찾지 못합니다.

07프로젝트가 커지면 데이터 사전도 확장하기

실제 연구 데이터 사전에는 여기서 사용한 여섯 필드보다 더 많은 정보가 필요할 수 있습니다. 프로젝트에 따라 source system, sensor location, coordinate direction, sampling rate, nullable 여부, 정밀도, 계산 방법, valid range, missing-value code, 담당자 등을 추가하세요.

허용값은 현재 파일에서 우연히 관찰된 최솟값과 최댓값이 아니라 과학적 의미를 기준으로 정의해야 합니다. 한 파일의 speed가 30에서 70 km/h 사이에만 있다고 해서 자동으로 30에서 70이 공학적 유효 범위가 되는 것은 아닙니다.

데이터 사전은 데이터셋이나 분석 코드와 함께 버전을 관리하세요. 열 정의, 단위, 부호 규약, 범주가 바뀌면 언제 변경되었는지 기록해야 합니다. 데이터 사전은 실제 분석 중인 데이터 버전을 정확히 설명할 때 가장 유용합니다.

실행·검증 기록

2026-09-20 · 예제 수동 검토 · 대상: Python 3.12 · 표준 라이브러리: csv, pathlib, re · 실행하지 않음

  • 합성 데이터가 5개 행과 5개 열로 구성됨을 수동으로 확인했습니다.
  • R001부터 R004까지 명시한 데이터 사전 규칙을 만족하는지 손으로 확인했습니다.
  • R005에서 speed 135의 범위 초과, accel fast의 숫자형 오류, road_surface snow의 허용값 오류, test_status done의 허용값 오류 등 4개 문제를 확인했습니다.
  • R005 자체는 R 다음에 숫자 3개가 오는 run_id 규칙을 만족함을 확인했습니다.
  • R005의 네 검증 문제가 실제 CSV 6행에 해당함을 수동으로 도출했습니다.
  • 정확한 헤더 검사, 고유 run_id, 숫자 범위 검사, 범주 검사, 출력 충돌 방지, 원본 CSV 보존 로직을 코드로 검토했습니다.
검증 한계
  • 이 응답의 작성자는 코드를 실행하지 않았으며, 데이터 사전이나 검증 CSV를 실제로 만들지 않았습니다.
  • 합성 데이터의 범위와 범주는 연습용이며 일반적인 자동차공학 한계값이 아닙니다.
  • 과학적 의미, 센서 교정, 좌표 규약, 결측치 정책, 메타데이터 버전은 자동으로 검증하지 않았습니다.
  • 공식 문서 URL은 알려진 문서 위치를 사용했지만 실시간으로 접속해 확인하지 않았습니다.

사이트 전체 작성·검증 원칙

참고 출처

설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.