0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1コーナーの通過タイムは100本でどれくらいバラつくのか — 標準偏差で走りの一貫性を測る

0
Posted at

データサイエンスや統計検定2級で必ず登場するキーワードを、レーシングシミュレータの実データを使って解説します。1記事1テーマで完結しています。


こんな人に役立ちます

  • 標準偏差が「典型的なバラツキ」を表すと知っているが、実データで確かめたことがない
  • センサーデータや繰り返し計測のバラツキを定量化したい
  • 外れ値が標準偏差に与える影響を実感したい

問い:1コーナーの通過タイムは100本でどれくらいバラつくのか?

富士スピードウェイの1コーナーは、約300 km/hからのフルブレーキングが必要な難所です。
100本のラップを走って「1コーナーの通過タイム」を計測したとき、そのバラツキはどれくらいになるのか——データで確かめます。

レーシングシミュレータやテレメトリデータに馴染みのない方は、レーシングシミュレータとは?どんなデータを記録しているのか を先にご覧ください。

00_concept_overview.png


「バラツキ」を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 を通過タイムとします。

00_t1_zone_map.png

オレンジ部分が分析対象区間(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)

01_t1_time_distribution.png

オレンジ帯が±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倍を足した値を閾値とし、それを超えるラップを外れ値とみなします。

03_iqr_explanation.png

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 秒

02_t1_time_outlier_removed.png

左: 全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
  • テレメトリデータのスキーマ詳細:どんなデータを記録しているのか
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?