시험 CSV의 시간축과 단위를 먼저 확인하기
설명용 합성 데이터에서 단위, 누락값, 유한수, 시간 중복·역행을 확인하고 점검 결과와 그래프를 저장합니다.
합성 차량 속도와 가속도 데이터를 일관된 SI 단위로 변환한 뒤 역변환하여 계산을 확인합니다. 원본 CSV는 그대로 유지하고 변환된 데이터는 새 출력 폴더에 저장합니다.
이런 분께속도와 가속도 단위가 섞인 차량 또는 시험 데이터를 분석하기 전에 간단한 단위 변환과 검증을 수행하려는 사람을 위한 안내입니다.
이 예제에서는 속도를 m/s, 가속도를 m/s²로 통일합니다. 1 km는 1000 m이고 1시간은 3600초이므로 km/h 값을 3.6으로 나누면 m/s가 됩니다. 반대로 m/s에 3.6을 곱하면 km/h가 됩니다.
가속도는 관례적인 표준중력 값 g0 = 9.80665 m/s²를 사용합니다. g 단위의 값에 9.80665를 곱하면 m/s²가 됩니다. 반대로 m/s²를 9.80665로 나누면 g 단위가 됩니다.
| 변환 | 계산식 |
|---|---|
| km/h → m/s | km/h ÷ 3.6 |
| m/s → km/h | m/s × 3.6 |
| g → m/s² | g × 9.80665 |
| m/s² → g | m/s² ÷ 9.80665 |
아래 데이터는 이 글을 위해 작성한 합성 데이터입니다. unit_test_data.csv로 저장하세요. 속도는 손으로 변환하기 쉬운 값을 사용하고, 가속도에는 양수, 음수, 소수 g 값을 포함했습니다.
sample_id,speed_kmh,accel_g
T001,0,0
T002,36,1
T003,72,-0.5
T004,90,0.25
T005,108,-1.25
레코드는 5개입니다. 음수 가속도는 변환 후에도 음수로 유지됩니다. 단위 변환은 단위만 바꾸며 부호를 바꾸지 않습니다. 이 데이터는 수치 예제일 뿐 특정 차량 거동이나 센서를 나타내는 값이 아닙니다.
속도 변환은 직접 계산할 수 있습니다. 예를 들어 72 km/h ÷ 3.6 = 20 m/s이고, 90 km/h ÷ 3.6 = 25 m/s입니다. 다시 변환하면 20 × 3.6 = 72 km/h, 25 × 3.6 = 90 km/h가 됩니다.
가속도는 1 g가 9.80665 m/s²입니다. -0.5 g는 -4.903325 m/s², 0.25 g는 2.4516625 m/s², -1.25 g는 -12.2583125 m/s²가 됩니다.
| sample_id | speed_kmh | 예상 speed_m_s | accel_g | 예상 accel_m_s2 |
|---|---|---|---|---|
| T001 | 0 | 0.000000 | 0 | 0.000000 |
| T002 | 36 | 10.000000 | 1 | 9.806650 |
| T003 | 72 | 20.000000 | -0.5 | -4.903325 |
| T004 | 90 | 25.000000 | 0.25 | 2.451662 |
| T005 | 108 | 30.000000 | -1.25 | -12.258312 |
표의 가속도 값은 소수점 이하 6자리로 표시했습니다. 실제 계산에서는 이미 반올림된 출력 문자열을 다시 사용하지 말고 9.80665의 전체 값을 사용해야 합니다.
다음 스크립트를 unit_conversion_check.py로 저장하세요. 원본 값을 읽고 두 종류의 정방향 변환을 수행한 뒤 다시 원래 단위로 역변환하여 작은 수치 허용오차 안에서 일치하는지 확인합니다. 원본 CSV는 다시 저장하지 않습니다.
import csv
import math
from pathlib import Path
SOURCE = Path("unit_test_data.csv")
OUTPUT_DIR = Path("outputs") / "unit_conversion_result"
OUTPUT = OUTPUT_DIR / "converted_test_data.csv"
STANDARD_GRAVITY = 9.80665
TOLERANCE = 1e-12
def kmh_to_ms(value: float) -> float:
return value / 3.6
def ms_to_kmh(value: float) -> float:
return value * 3.6
def g_to_ms2(value: float) -> float:
return value * STANDARD_GRAVITY
def ms2_to_g(value: float) -> float:
return value / STANDARD_GRAVITY
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}")
output_rows = []
with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
required = {"sample_id", "speed_kmh", "accel_g"}
if reader.fieldnames is None or not required.issubset(reader.fieldnames):
raise ValueError("CSV is missing a required column.")
seen_ids = set()
for row in reader:
sample_id = row["sample_id"].strip()
if not sample_id:
raise ValueError("sample_id must not be blank.")
if sample_id in seen_ids:
raise ValueError(f"Duplicate sample_id: {sample_id}")
seen_ids.add(sample_id)
speed_kmh = float(row["speed_kmh"])
accel_g = float(row["accel_g"])
speed_ms = kmh_to_ms(speed_kmh)
accel_ms2 = g_to_ms2(accel_g)
speed_roundtrip = ms_to_kmh(speed_ms)
accel_roundtrip = ms2_to_g(accel_ms2)
speed_ok = math.isclose(
speed_kmh, speed_roundtrip,
rel_tol=TOLERANCE, abs_tol=TOLERANCE
)
accel_ok = math.isclose(
accel_g, accel_roundtrip,
rel_tol=TOLERANCE, abs_tol=TOLERANCE
)
if not speed_ok or not accel_ok:
raise RuntimeError(f"Round-trip check failed: {sample_id}")
output_rows.append({
"sample_id": sample_id,
"speed_kmh": f"{speed_kmh:g}",
"speed_m_s": f"{speed_ms:.6f}",
"speed_roundtrip_kmh": f"{speed_roundtrip:.6f}",
"accel_g": f"{accel_g:g}",
"accel_m_s2": f"{accel_ms2:.6f}",
"accel_roundtrip_g": f"{accel_roundtrip:.6f}",
"roundtrip_check": "PASS",
})
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
fields = [
"sample_id", "speed_kmh", "speed_m_s", "speed_roundtrip_kmh",
"accel_g", "accel_m_s2", "accel_roundtrip_g", "roundtrip_check"
]
with OUTPUT.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=fields)
writer.writeheader()
writer.writerows(output_rows)
print(f"Converted records: {len(output_rows)}.")
print(f"Round-trip checks passed: {len(output_rows)}.")
print(f"Standard gravity used: {STANDARD_GRAVITY} m/s^2.")
print(f"Output: {OUTPUT.as_posix()}")
if __name__ == "__main__":
main()
모든 행은 역변환 후 원래 속도와 가속도로 돌아와야 합니다. 아래에는 소수점 이하 6자리로 표시한 예상 결과가 있습니다. 내부 계산은 문자열로 서식화하기 전의 부동소수점 값을 사용합니다.
sample_id,speed_kmh,speed_m_s,speed_roundtrip_kmh,accel_g,accel_m_s2,accel_roundtrip_g,roundtrip_check
T001,0,0.000000,0.000000,0,0.000000,0.000000,PASS
T002,36,10.000000,36.000000,1,9.806650,1.000000,PASS
T003,72,20.000000,72.000000,-0.5,-4.903325,-0.500000,PASS
T004,90,25.000000,90.000000,0.25,2.451662,0.250000,PASS
T005,108,30.000000,108.000000,-1.25,-12.258312,-1.250000,PASS
아래 예상 콘솔 출력은 합성 데이터와 스크립트를 바탕으로 손으로 도출한 결과이며 실제 실행 로그가 아닙니다.
Converted records: 5.
Round-trip checks passed: 5.
Standard gravity used: 9.80665 m/s^2.
Output: outputs/unit_conversion_result/converted_test_data.csv| 문제 | 확인할 사항 |
|---|---|
| 속도가 약 3.6배 차이남 | m/s 값을 km/h로 잘못 처리했거나 그 반대인지 확인하세요. |
| 가속도가 약 9.8배 차이남 | g와 m/s²를 혼동했는지 확인하세요. |
| 음수 가속도가 양수로 바뀜 | 분석에서 크기만 필요한 경우가 아니라면 절댓값을 적용하지 마세요. |
| 예상하지 못한 반올림 차이 | 계산에는 충분한 정밀도를 유지하고 표시나 내보내기 단계에서만 반올림하세요. |
| 역변환은 통과하지만 원본 단위 라벨이 틀림 | 수학적으로 가역적인 변환은 원본의 잘못된 단위 표기를 탐지할 수 없습니다. |
단위 변환이 정상적으로 끝났다는 것은 계산과 선언된 단위가 내부적으로 일관된다는 뜻일 뿐입니다. 원본 센서가 제대로 교정되었는지, 단위 라벨이 정확했는지, 기록된 값이 의도한 물리량을 나타내는지는 증명하지 않습니다.
여기서는 g 변환에 관례적인 표준중력 9.80665 m/s²를 사용합니다. 실제 지역의 중력가속도는 이 관례적인 기준과 다를 수 있습니다. 많은 공학 데이터에서는 g를 정규화된 가속도 단위로 사용하므로 어떤 기준을 사용했는지 문서화해야 합니다.
실제 차량 시험 데이터에는 휠 속도, 차량 속도, 회전속도, 힘, 압력, 온도 등 여러 단위가 함께 있을 수 있습니다. 각 채널의 물리 차원을 데이터 사전에 기록하고 그에 맞게 변환하세요. 숫자의 크기만 보고 단위를 추정해서는 안 됩니다.
ChatGPT Python tool · Python 3.13.5 · standard library: csv, math, pathlib
설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.