Automotive & data work

Convert automotive test data between km/h, m/s, g, and m/s²

Convert synthetic vehicle speed and acceleration data into consistent SI units, then convert the results back to check the arithmetic. The script preserves the original CSV and writes the converted values to a new output folder.

Show contents

Who this is forThis guide is for people who work with vehicle or test data containing mixed speed and acceleration units and want simple conversion checks before analysis.

What you need
  • Python 3.12 and a terminal command that starts that version.
  • A text editor that can save UTF-8 CSV and Python files.
  • A working folder where the script can create a new folder beneath outputs.
  • Only the Python standard library is required: csv, math, and pathlib.

01Write the conversion rules before touching the data

This example standardizes speed to metres per second and acceleration to metres per second squared. Speed conversion is based on 1 kilometre = 1000 metres and 1 hour = 3600 seconds, so km/h is divided by 3.6 to obtain m/s. The reverse conversion multiplies m/s by 3.6.

For acceleration, this tutorial uses the conventional standard gravity value g0 = 9.80665 m/s². A value expressed in g is multiplied by 9.80665 to obtain m/s². The reverse conversion divides m/s² by 9.80665.

ConversionRule
km/h to m/skm/h ÷ 3.6
m/s to km/hm/s × 3.6
g to m/s²g × 9.80665
m/s² to gm/s² ÷ 9.80665

02Create a small synthetic test dataset

The following dataset is synthetic and was written specifically for this article. Save it as unit_test_data.csv. The values are chosen so that the speed conversions are easy to verify by hand while the acceleration examples include positive, negative, and fractional g values.

csv
sample_id,speed_kmh,accel_g
T001,0,0
T002,36,1
T003,72,-0.5
T004,90,0.25
T005,108,-1.25

There are 5 records. Negative acceleration values are preserved as negative values; the conversion changes the unit, not the sign. This dataset is only a numerical example and is not intended to represent a specific vehicle manoeuvre or sensor.

03Calculate the expected values by hand

The speed conversions are direct. For example, 72 km/h ÷ 3.6 = 20 m/s, and 90 km/h ÷ 3.6 = 25 m/s. Converting back gives 20 × 3.6 = 72 km/h and 25 × 3.6 = 90 km/h.

For acceleration, 1 g becomes 9.80665 m/s². A value of -0.5 g becomes -4.903325 m/s². A value of 0.25 g becomes 2.4516625 m/s². A value of -1.25 g becomes -12.2583125 m/s².

sample_idspeed_kmhExpected speed_m_saccel_gExpected accel_m_s2
T00100.00000000.000000
T0023610.00000019.806650
T0037220.000000-0.5-4.903325
T0049025.0000000.252.451663
T00510830.000000-1.25-12.258313

The displayed acceleration values are rounded to six decimal places. The calculation itself should use the full 9.80665 factor rather than repeatedly using already rounded output text.

04Convert the CSV and perform round-trip checks

Save the following script as unit_conversion_check.py. It reads the original values, performs both forward conversions, converts the results back to the original units, and checks the round trip with a small numerical tolerance. The original CSV is never rewritten.

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

05Compare the expected converted file

Every row should return to its original speed and acceleration after the reverse conversion. The six-decimal output is shown below. The internal calculations use floating-point values before formatting.

csv
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.451663,0.250000,PASS
T005,108,30.000000,108.000000,-1.25,-12.258313,-1.250000,PASS

The expected console text below was derived by hand from the synthetic data and script. It is not a captured execution log.

text
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

06Perform independent checks and recognize common errors

  • Check 36 km/h manually: 36 ÷ 3.6 = 10 m/s.
  • Check 108 km/h manually: 108 ÷ 3.6 = 30 m/s.
  • Check -0.5 g manually: -0.5 × 9.80665 = -4.903325 m/s².
  • Confirm that signs remain unchanged during unit conversion.
  • Confirm that all five rows show PASS in roundtrip_check.
  • Run the script again without changing OUTPUT_DIR. It should stop with FileExistsError instead of replacing the result.
ProblemWhat to check
Speed differs by a factor of 3.6Check whether a value labeled m/s was mistakenly treated as km/h or the reverse.
Acceleration differs by about a factor of 9.8Check whether g and m/s² were confused.
Negative acceleration becomes positiveDo not apply absolute value unless the analysis specifically requires magnitude.
Unexpected rounding differencesKeep full precision for calculations and round only when displaying or exporting values.
Round trip passes but source units were mislabeledA mathematically reversible conversion cannot detect an incorrect unit label in the original data.

07Understand the limits before using real test data

A successful unit conversion only shows that the arithmetic and declared units are internally consistent. It does not prove that the original sensor channel was calibrated correctly, that the unit label was correct, or that the recorded value represents the physical quantity you think it does.

The g conversion here uses conventional standard gravity, 9.80665 m/s². Local gravitational acceleration can differ from that conventional reference. In many engineering datasets, g is used as a normalized acceleration unit, so the declared convention should be documented.

Real vehicle test datasets may also mix wheel speed, vehicle speed, rotational speed, force, pressure, and temperature units. Build a data dictionary and convert each channel according to its physical dimension. Never infer a unit solely from the magnitude of the numbers.

Execution and verification record

2026-09-20 · hand-checked example · target: Python 3.12 · standard library: csv, math, pathlib · no execution

  • Manually converted the five speeds as 0, 10, 20, 25, and 30 m/s.
  • Manually calculated acceleration values using 9.80665 m/s² per g.
  • Checked -0.5 g as -4.903325 m/s², 0.25 g as 2.4516625 m/s², and -1.25 g as -12.2583125 m/s² before display rounding.
  • Manually checked the reverse speed conversions by multiplying by 3.6.
  • Inspected the code for required columns, duplicate sample IDs, round-trip comparisons, output collision protection, and preservation of the original CSV.
  • Derived the expected CSV and console output by hand.
Verification limits
  • The code was not executed by the author of this response; no converted CSV was created.
  • Sensor calibration, metadata correctness, local gravitational acceleration, and unit labels in real acquisition systems were not tested.
  • Only km/h, m/s, g, and m/s² are covered in this example.
  • The official documentation URLs were provided from known documentation locations but were not checked live.

Site-wide writing and verification principles

References

The explanations and examples were written for this site. See the official sources below for the related behavior and concepts.