データサイエンスや統計検定2級で必ず登場するキーワードを、レーシングシミュレータの実データを使って解説します。1記事1テーマで完結しています。
こんな人に役立ちます
- 標準偏差が「典型的なバラツキ」を表すと知っているが、実データで確かめたことがない
- センサーデータや繰り返し計測のバラツキを定量化したい
- 外れ値が標準偏差に与える影響を実感したい
問い:1コーナーの通過タイムは100本でどれくらいバラつくのか?
富士スピードウェイの1コーナーは、約300 km/hからのフルブレーキングが必要な難所です。
100本のラップを走って「1コーナーの通過タイム」を計測したとき、そのバラツキはどれくらいになるのか——データで確かめます。
レーシングシミュレータやテレメトリデータに馴染みのない方は、レーシングシミュレータとは? と どんなデータを記録しているのか を先にご覧ください。
「バラツキ」を1つの数値で表す
100本の通過タイムを計測したとして、こんな疑問が生まれます。
- 最速と最遅の差は何秒か?
- 典型的には何秒くらいズレているのか?
最大・最小の差は端の2本の話に過ぎません。「典型的なズレ幅」を1つの数値で表すのが標準偏差です。
標準偏差(σ、standard deviation):各データが平均からどれだけ離れているかの典型値
- σが小さい → 毎回ほぼ同じタイムで通過できている(安定)
- σが大きい → ラップごとに通過タイムがバラついている(不安定)
計算式は次のとおりです。
$$\sigma = \sqrt{\frac{1}{n-1} \sum_{i=1}^{n}(x_i - \bar{x})^2}$$
($n-1$ で割るのは不偏標準偏差と呼ばれる推定値。Pythonでは ddof=1 を指定します)
データ準備
GT7テレメトリはUDP経由でCSVに記録しています。1ラップ≈6,000行、サンプリング約60fps。
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
DATA_DIR = "/path/to/TelemetryData"
# Supra18 / RM(レーシングミディアム)/ Dry / 富士スピードウェイ
files = sorted([f for f in os.listdir(DATA_DIR) if "Supra18_RM_Dry" in f])
print(f"対象ファイル数: {len(files)}") # 100
1コーナーの通過タイムを計算する
「1コーナー区間」をスタートラインからの累積距離で次のように定義します。
1コーナー区間:累積距離 500 m〜 1000 m
ブレーキング開始(約565 m付近)の手前から、コーナー出口の加速区間(約189 km/h)までをカバーします。次のコーナーのブレーキング開始は1150 m付近のため、150 mの余裕があります。pos_x/y/z から累積距離を計算し、この区間の入口・出口のフレーム番号の差 ÷ 60fps を通過タイムとします。
オレンジ部分が分析対象区間(500〜1000 m)。メインストレートエンドからブレーキング・コーナリング・立ち上がり加速までをカバーする
T1_START_M = 500
T1_END_M = 1000
FPS = 60.0
def calc_t1_time(filepath):
df = pd.read_csv(filepath).sort_values("packet_id").reset_index(drop=True)
dx = df['pos_x'].diff().fillna(0)
dy = df['pos_y'].diff().fillna(0)
dz = df['pos_z'].diff().fillna(0)
df['dist'] = np.sqrt(dx**2 + dy**2 + dz**2).cumsum()
entry_rows = df[df['dist'] >= T1_START_M]
exit_rows = df[df['dist'] >= T1_END_M]
if entry_rows.empty or exit_rows.empty:
return None
entry_idx = entry_rows.index[0]
exit_idx = exit_rows.index[0]
if exit_idx <= entry_idx:
return None
return (exit_idx - entry_idx) / FPS
t1_times = []
for fname in files:
t = calc_t1_time(os.path.join(DATA_DIR, fname))
if t is not None:
t1_times.append(t)
t1_times = np.array(t1_times)
print(f"計測できたラップ数: {len(t1_times)}") # 100
標準偏差を計算する
mean = t1_times.mean()
std = t1_times.std(ddof=1)
print(f"平均通過タイム: {mean:.3f} 秒")
print(f"標準偏差: {std:.3f} 秒")
print(f"最速: {t1_times.min():.3f} 秒")
print(f"最遅: {t1_times.max():.3f} 秒")
平均通過タイム: 12.453 秒
標準偏差: 0.714 秒
最速: 11.900 秒
最遅: 16.517 秒
標準偏差0.714秒は「典型的には0.7秒くらいズレている」という意味です。
ブレーキング・コーナリング・加速を含む12秒の区間で0.7秒のバラツキは、ラップタイムへの影響として無視できない大きさです。
この0.7秒は、ブレーキング開始のタイミング・コーナーのライン取り・スロットルを開始する位置の違いが積み重なって現れています。σという1つの数値が、コーナーでの一連の操作のバラツキを表しています。
分布を可視化する
fig, ax = plt.subplots(figsize=(10, 5))
ax.hist(t1_times, bins=15, color='steelblue', alpha=0.75, edgecolor='white')
ax.axvspan(mean - std, mean + std, alpha=0.15, color='orange',
label=f'±1σ ({mean-std:.3f}〜{mean+std:.3f} 秒)')
ax.axvline(mean, color='red', linestyle='--', label=f'平均 {mean:.3f} 秒')
ax.set_xlabel('1コーナー通過タイム(秒)')
ax.set_ylabel('ラップ数')
ax.set_title(f'1コーナー通過タイムの分布({len(t1_times)}本)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('t1_time_distribution.png', dpi=120)
オレンジ帯が±1σ(11.739〜13.166秒)、赤破線が平均(12.453秒)
分布の形から読み取れること
within_1s = np.sum((t1_times >= mean - std) & (t1_times <= mean + std))
within_2s = np.sum((t1_times >= mean - 2*std) & (t1_times <= mean + 2*std))
print(f"±1σ以内: {within_1s}本 ({within_1s/len(t1_times):.0%})")
print(f"±2σ以内: {within_2s}本 ({within_2s/len(t1_times):.0%})")
±1σ以内: 89本 (89%)
±2σ以内: 97本 (97%)
正規分布の理論値は±1σで68%ですが、今回は89%と高くなっています。ヒストグラムを見ると、分布が右に歪んでいる(右裾が長い)ことがわかります。大多数のラップは12秒台に密集している一方、14〜16秒台のラップが数本あります。これはスピンやコースオフなど、通常の走行ではないラップが混入しているためと考えられます。
この歪みは標準偏差の重要な性質を示しています。標準偏差は外れ値の影響を受けやすい。14〜16秒台の数本が存在するだけで、σは実態より大きめに見積もられます。その結果、過大に広がったσの枠の中に正常な走行ラップ(密集地帯)がほぼすっぽり収まってしまい、±1σ内が89%という高い値になったのです。「典型的なバラツキ」を正確に知りたい場合は、外れ値を除外したうえで再計算するか、外れ値に頑健な四分位範囲(IQR:データの中央50%の広がりを表す指標)を併用するのが実務的なアプローチです。
外れ値を除いたら標準偏差はどう変わるか
「標準偏差は外れ値の影響を受けやすい」と述べました。では、実際に外れ値を除外してσを再計算するとどうなるでしょうか。
外れ値の検出にはIQR法を使います。第3四分位数(Q3)に四分位範囲(IQR)の1.5倍を足した値を閾値とし、それを超えるラップを外れ値とみなします。
q1, q3 = np.percentile(t1_times, [25, 75])
iqr = q3 - q1
upper_fence = q3 + 1.5 * iqr # → 13.519 秒
filtered = t1_times[t1_times <= upper_fence]
mean_f = filtered.mean()
std_f = filtered.std(ddof=1)
print(f"閾値: {upper_fence:.3f} 秒")
print(f"除外ラップ数: {len(t1_times) - len(filtered)}本")
print(f"平均(除外後): {mean_f:.3f} 秒")
print(f"σ (除外後): {std_f:.3f} 秒")
閾値: 13.519 秒
除外ラップ数: 5本
平均(除外後): 12.332 秒
σ (除外後): 0.410 秒
左: 全100本(σ=0.714秒)、右: 外れ値5本除外後(σ=0.410秒)
除外したのは13.6〜16.5秒の5本のみです。全体の5%にも満たないデータを除くだけで、σが 0.714秒 → 0.410秒(42%減) と大幅に変わりました。これは標準偏差の外れ値感度の高さを端的に示しています。
除外後のσ = 0.410秒が「スピン・コースオフを除いた通常走行のバラツキ」の実態に近い値です。ブレーキング・コーナリング・加速を含む500mの区間で約0.4秒のバラツキがある、という方が実務的な分析の出発点として適切です。
まとめ
| 指標 | 値 |
|---|---|
| 計測ラップ数 | 100本 |
| 平均通過タイム | 12.453 秒 |
| 標準偏差(全ラップ) | 0.714 秒 |
| 標準偏差(外れ値5本除外後) | 0.410 秒(42%減) |
| 最速 | 11.900 秒 |
| 最遅 | 16.517 秒 |
| ±1σ以内 | 89%(理論値68%より高い) |
標準偏差は「典型的なバラツキ幅」を1つの数値で表す強力な指標ですが、外れ値に影響を受けやすいという性質があります。分布の形を可視化して外れ値の有無を確認し、必要に応じてIQR法で除外したうえで再計算するのが実務的なアプローチです。
最後まで読んでいただきありがとうございました。
参考
- データ:GT7テレメトリ(富士スピードウェイ、Supra18、RM、Dry、100ラップ)
- ライブラリ:numpy、pandas、matplotlib
- テレメトリデータのスキーマ詳細:どんなデータを記録しているのか




