자동차·데이터 실무

여러 시험 조건을 같은 시간축에서 비교하기

세 개의 설명용 합성 CSV를 검증한 뒤, 같은 시간 표본과 가속도 단위를 쓰는 곡선을 한 그래프에 겹쳐 그립니다.

목차 보기

이런 분께여러 조건의 시험 결과를 겹쳐 그리려는 자동차 분야 파이썬 입문자

준비사항
  • Python 3.12와 터미널을 준비합니다. python --version으로 버전을 확인합니다.
  • 다운로드한 ZIP을 모두 풀고 example.py가 있는 폴더에서 터미널을 엽니다.
  • 처음에는 동봉된 연습 데이터로 실행한 뒤 결과와 원본을 비교합니다.
  • inputs 폴더의 case_a.csv, case_b.csv, case_c.csv는 모두 설명용 합성 데이터입니다.
  • 모든 입력은 시간 s, 가속도 m/s^2, 같은 시간 표본을 사용합니다.

01비교에 필요한 조건을 맞추기

여러 곡선을 한 그림에 겹치려면 시간과 세로축 단위의 의미가 같아야 합니다. 이 예제는 설명용 합성 데이터 Case A, Case B, Case C를 시간 s, 가속도 m/s^2의 공통 축에 표시합니다. 조건 이름은 파일 안의 condition 열에서 읽습니다.

02폴더와 입력 형식 준비하기

inputs 폴더 안의 .csv 파일을 이름순으로 읽습니다. 파일은 최소 두 개이며 각 파일은 한 조건만 담아야 합니다. 모든 행에 dataset_note, condition, time, time_unit, acceleration, acceleration_unit 여섯 열이 있어야 합니다.

필드동봉 예시
dataset_note설명용 합성 데이터
conditionCase A (영문·숫자 등 ASCII)
time / time_unit0.0 / s
acceleration / acceleration_unit0.8 / m/s^2

이 예제는 파일끼리 시간 배열이 정확히 같은 경우만 비교합니다. 표본 개수, 시작 시각, 시간 중 하나라도 다르면 중지합니다. 그래프 축에는 같은 단위를 m/s²로 표시합니다.

03파일을 준비하고 실행하기

  1. ZIP을 풀고 README.txt에 적힌 입력 파일과 example.py가 같은 위치인지 확인합니다. ZIP 안에서 바로 실행하지 않습니다.
  2. example.py가 있는 폴더에서 아래 명령을 한 줄씩 실행합니다. Windows에서 python 명령이 없고 py가 있으면 각 명령의 python을 py -3.12로 바꿉니다.
  3. 완료 메시지가 나온 뒤 새 outputs 폴더를 엽니다. 기존 outputs가 있으면 먼저 다른 이름으로 보관하고 다시 실행합니다.
bash
python --version
python -m pip install -r requirements.txt
python example.py

설치는 인터넷 연결이 필요합니다. 코드의 BASE는 현재 터미널 위치가 아니라 example.py의 위치를 기준으로 입력과 결과 경로를 정합니다.

04전체 코드와 비교 전 검사

각 파일에서 시간 순서와 단위를 검사한 뒤 조건 이름의 중복을 확인합니다. 첫 파일의 시간 배열을 기준으로 나머지 배열을 비교하고, 모두 일치할 때 한 Axes에 선을 추가합니다. 정렬·시간 이동·보간·단위 변환은 자동 수행하지 않습니다.

example.py
"""Overlay explanatory synthetic test conditions on the same axes.

All files must have the same explicit units and matching time samples.
No interpolation, clock shifting, filtering, or safety assessment is performed.
"""
import csv
import math
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

BASE = Path(__file__).resolve().parent
HEADERS = ["dataset_note", "condition", "time", "time_unit", "acceleration", "acceleration_unit"]
NOTE = "설명용 합성 데이터"


def read_condition(path):
    times, values = [], []
    label = None
    with path.open(encoding="utf-8-sig", newline="") as file:
        reader = csv.DictReader(file)
        if reader.fieldnames != HEADERS:
            raise ValueError(f"{path.name}: expected headers {HEADERS}")
        for line, row in enumerate(reader, start=2):
            if None in row or any(value is None or not value.strip() for value in row.values()):
                raise ValueError(f"{path.name}:{line}: missing or extra field.")
            if row["dataset_note"] != NOTE:
                raise ValueError(f"{path.name}:{line}: synthetic-data label is required.")
            if row["time_unit"] != "s" or row["acceleration_unit"] != "m/s^2":
                raise ValueError(f"{path.name}:{line}: expected units s and m/s^2; no automatic conversion.")
            if not row["condition"].isascii():
                raise ValueError(f"{path.name}:{line}: use an ASCII condition label for portable plotting.")
            label = row["condition"] if label is None else label
            if row["condition"] != label:
                raise ValueError(f"{path.name}:{line}: condition label changes within one file.")
            time, value = float(row["time"]), float(row["acceleration"])
            if not math.isfinite(time) or not math.isfinite(value) or time < 0:
                raise ValueError(f"{path.name}:{line}: expected finite values and nonnegative time.")
            if times and time <= times[-1]:
                raise ValueError(f"{path.name}:{line}: duplicate or decreasing time.")
            times.append(time)
            values.append(value)
    if len(times) < 2:
        raise ValueError(f"{path.name}: at least two samples are required.")
    return label, times, values


def main():
    destination = BASE / "outputs"
    if destination.exists():
        raise ValueError("outputs already exists; rename it before running again.")
    paths = sorted((BASE / "inputs").glob("*.csv"))
    if len(paths) < 2:
        raise ValueError("At least two CSV files are required in inputs.")
    conditions = [read_condition(path) for path in paths]
    if len({label for label, _, _ in conditions}) != len(conditions):
        raise ValueError("Condition labels must be unique across files.")
    reference_times = conditions[0][1]
    if any(times != reference_times for _, times, _ in conditions[1:]):
        raise ValueError("Time samples do not match; check alignment before comparison.")

    figure, axis = plt.subplots(figsize=(8, 4.6), layout="constrained")
    for label, times, values in conditions:
        axis.plot(times, values, marker="o", linewidth=2, label=label)
    axis.set(title="Test conditions | Explanatory synthetic data", xlabel="Elapsed time (s)", ylabel="Acceleration (m/s²)")
    axis.spines[["top", "right"]].set_visible(False)
    axis.grid(alpha=0.2)
    axis.legend(frameon=False)
    destination.mkdir(exist_ok=False)
    with (destination / "comparison_summary.csv").open("x", encoding="utf-8-sig", newline="") as file:
        writer = csv.writer(file)
        writer.writerow(["dataset_note", "condition", "sample_count", "min_acceleration", "max_acceleration", "unit"])
        for label, _, values in conditions:
            writer.writerow([NOTE, label, len(values), min(values), max(values), "m/s^2"])
    figure.savefig(destination / "synthetic_comparison.png", dpi=160)
    plt.close(figure)
    print(f"Explanatory synthetic data: {len(conditions)} conditions, {len(reference_times)} matching time samples each.")
    print("Created outputs/comparison_summary.csv and outputs/synthetic_comparison.png.")


if __name__ == "__main__":
    try:
        main()
    except (OSError, ValueError, csv.Error) as error:
        raise SystemExit(f"Stopped: {error}") from error

05그래프와 요약 CSV 읽기

outputs/synthetic_comparison.png에 세 조건이 겹쳐 그려집니다. outputs/comparison_summary.csv에는 조건별 표본 수와 가속도 최솟값·최댓값이 저장됩니다. 이 숫자는 설명용 합성 데이터의 단순 요약입니다.

조건표본 수최솟값최댓값
Case A60.0 m/s²1.4 m/s²
Case B60.0 m/s²1.1 m/s²
Case C60.0 m/s²1.7 m/s²
설명용 합성 데이터 Case A, B, C의 가속도 비교. 동일한 시간 0~1초와 m/s² 축을 사용한 세 곡선.
설명용 합성 데이터 Case A, B, C의 가속도 비교. 동일한 시간 0~1초와 m/s² 축을 사용한 세 곡선.

세 곡선의 0.4초 표본에서 값은 각각 1.4, 1.1, 1.7 m/s²입니다. 그래프의 선은 표본 사이를 연결한 것으로, 그 사이의 실제 계측값을 새로 생성한 것은 아닙니다.

06같은 축인지 확인하는 순서

  1. 그래프 범례의 Case A, Case B, Case C가 입력 파일의 condition 값과 맞는지 확인합니다.
  2. x축의 Elapsed time (s), y축의 Acceleration (m/s²)를 확인합니다.
  3. 0.4초에서 세 CSV의 값을 그래프·요약표와 비교합니다.
  4. 새 연습 복사본에서 case_b.csv의 time 하나를 0.41로 바꿉니다. Time samples do not match 메시지로 중지하는지 확인합니다.
  5. 다른 새 복사본에서 acceleration_unit을 g로 바꾸면 자동 변환 없이 중지하는지 확인합니다.

07비교를 멈추는 이유 찾기

메시지확인 순서
expected units s and m/s^2내보낸 파일의 실제 단위와 표기를 확인합니다.
duplicate or decreasing time해당 파일·행의 시간 순서를 확인합니다.
Time samples do not match시작 시각, 표본 주기, 누락 표본을 확인합니다. 원인을 모른 채 시간만 덮어쓰지 않습니다.
Condition labels must be unique파일마다 서로 다른 조건 이름을 사용합니다.
condition label changes한 파일 안에 여러 조건이 섞이지 않았는지 확인합니다.
At least two CSV files입력 파일이 inputs 폴더에 있는지 확인합니다.

08그래프만으로 판단할 수 없는 것

  • 같은 숫자 시간축이어도 서로 다른 트리거·기준 시각일 수 있습니다. 실제 시험의 정렬 근거는 별도로 확인해야 합니다.
  • 정확히 같은 시간 배열만 허용합니다. 근사 일치, 리샘플링, 보간, 시간 지연 보정은 범위에 포함하지 않습니다.
  • 서로 다른 센서 위치, 좌표계, 샘플링 필터, 시험 조건의 비교 가능성은 코드가 판단하지 않습니다.
  • 표본 최댓값은 표본 사이의 실제 최댓값을 보장하지 않습니다. 그래프는 시험 합격·불합격, 성능 우열, 안전 판단을 제공하지 않습니다.

실행·검증 기록

Windows 11 (10.0.26200), CPython 3.12.14 (64비트). openpyxl 3.1.5, Matplotlib 3.10.8. 예제별 사용 라이브러리는 requirements.txt에 고정했습니다.

  • 설명용 합성 데이터 3조건 × 6표본 및 최솟값·최댓값 확인
  • 단위 불일치·중복/역행 시간·시간 배열 불일치·조건명 중복 오류 검사
  • 원본 해시 불변, 기존 출력 거부, PNG 생성 확인
검증 범위의 한계
  • 실제 시험 조건의 비교 가능성, 시간 동기화, 측정 불확도 및 안전성을 평가하지 않았습니다.

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

직접 실행할 예제 파일

코드·입력 데이터·실행 안내가 포함되어 있습니다. 압축을 풀고 README.txt부터 읽어 주세요.

예제 ZIP 다운로드

직접 작성한 연습 자료 · 원본을 따로 보관한 뒤 실행하세요.

참고 출처

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