0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

相関係数を信じる前に散布図を見る — 外れ値5本で r = -0.77 が崩れた話

0
Posted at

レーシングシミュレータの実データを使って、データサイエンスの概念を実装とともに解説しています。
この記事では相関係数を扱います。

統計検定2級では、相関係数・散布図・外れ値・p値を学びます。しかし、教科書の例題だけでは「実データで相関係数がどう壊れるのか」はなかなか実感しにくいです。この記事では、レーシングシミュレータの実データを使って、相関係数を実務的にどう確認するかを見ていきます。


こんな人に役立ちます

  • 相関係数を計算したが「本物の関係か外れ値の影響か」を判断する方法を知りたい
  • 複数の変数のうち「どれが最も効いているか」を定量的に比べたい
  • 散布図と数値を組み合わせたEDA(探索的データ分析:仮説を立てる前にデータの全体像を把握するプロセス)の流れを実例で見たい

結論

  • 「r = -0.77」は外れ値5本が作り出した偽の相関だった
  • 外れ値を除外すると入口速度・最大制動Gの相関は消失(r ≈ -0.13、有意性なし)
  • 正常ラップ内でT1通過タイムと最も強く関係していたのは スロットル全開距離(r = -0.463)
  • 相関係数は散布図による外れ値確認とセットで使う

問い:T1を速く通過するのに何が一番効いているのか?

富士スピードウェイ1コーナー(T1)の区間タイムに影響する要因は複数考えられます。

  • 入口に飛び込む速度が高いほど速い?
  • ブレーキを強く踏むほど速い?
  • コーナリング中のGが高いほど速い?
  • スロットルを早く開けるほど速い?

「なんとなくそう思う」を相関係数で定量化します。

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


相関係数とは

2つの変数の間に「一方が増えると他方も増える/減る」傾向があるかを -1〜+1 の1つの数値で表したものです。

ピアソン相関係数 r

$$r = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i-\bar{x})^2} \cdot \sqrt{\sum(y_i-\bar{y})^2}}$$

  • r ≈ +1:一方が増えると他方も増える(正の相関)
  • r ≈ −1:一方が増えると他方は減る(負の相関)
  • r ≈ 0 :関係なし

今回はT1通過タイムを $y$、各走行指標を $x$ として計算します。通過タイムが短い(速い)ほど良いので、負の相関が「効いている」関係を意味します。

「相関係数が大きい = 重要な変数」というのが直感的な解釈ですが、この記事ではその解釈の落とし穴も実データで確かめます。


4つの走行指標の定義

T1区間(500〜1000 m)を「アプローチ・制動・コーナリング・立ち上がり」の4フェーズに分け、それぞれ1指標を定義します。

フェーズ 指標 計算方法
アプローチ 入口速度 500 m地点の speed_kmh
制動 最大制動G 区間内の lon_g 最小値を符号反転(大きいほど強くブレーキ)
コーナリング 最大横G 区間内の lat_g 絶対値の最大
立ち上がり スロットル全開距離 700 m以降で throttle_pct ≥ 70% になる最初の地点(値が大きい=遅い地点で踏む)

データ準備と指標の抽出

import os
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

DATA_DIR   = "/path/to/TelemetryData"
T1_START_M = 500
T1_END_M   = 1000
APEX_M     = 700
FPS        = 60.0

def extract_t1_metrics(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()

    seg = df[(df['dist'] >= T1_START_M) & (df['dist'] <= T1_END_M)].copy()

    # T1通過タイム
    entry_idx = df[df['dist'] >= T1_START_M].index[0]
    exit_idx  = df[df['dist'] >= T1_END_M].index[0]
    t1_time   = (exit_idx - entry_idx) / FPS

    # 4指標
    entry_speed       = seg.iloc[0]['speed_kmh']
    max_brake_g       = -seg['lon_g'].min()
    max_lat_g         = seg['lat_g'].abs().max()
    apex_seg          = seg[seg['dist'] >= APEX_M]
    throttle_open_dist = apex_seg[apex_seg['throttle_pct'] >= 70].iloc[0]['dist']

    return {
        't1_time': t1_time, 'entry_speed': entry_speed,
        'max_brake_g': max_brake_g, 'max_lat_g': max_lat_g,
        'throttle_open_dist': throttle_open_dist,
    }

files = sorted([f for f in os.listdir(DATA_DIR) if "Supra18_RM_Dry" in f])
records = [extract_t1_metrics(os.path.join(DATA_DIR, f)) for f in files]
df = pd.DataFrame(records)
print(f"計測ラップ数: {len(df)}")  # 100

相関係数を計算する

metrics = [
    ('entry_speed',        'T1入口速度(km/h)',    '入口速度'),
    ('max_brake_g',        '最大制動G(G)',         '最大制動G'),
    ('max_lat_g',          '最大横G(G)',           '最大横G'),
    ('throttle_open_dist', 'スロットル全開距離(m)', 'スロットル全開距離'),
]

for col, label, short in metrics:
    r, p = stats.pearsonr(df[col], df['t1_time'])
    print(f"{short:<16}: r = {r:+.3f}  (p = {p:.4f})")
入口速度            : r = -0.773  (p < 0.0001)
最大制動G           : r = -0.761  (p < 0.0001)
最大横G            : r = +0.044  (p = 0.6614)
スロットル全開距離   : r = -0.192  (p = 0.0562)

入口速度と最大制動Gで r ≈ -0.77 という強い負の相関が出ました。「速く飛び込み、強くブレーキするほど速い」——直感に合う結果です。


散布図を確認する

数値だけ見ると納得感がありますが、必ず散布図を確認します。

fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for i, (col, label, short) in enumerate(metrics):
    r, _ = stats.pearsonr(df[col], df['t1_time'])
    ax = axes.flatten()[i]
    ax.scatter(df[col], df['t1_time'], alpha=0.5, s=25)
    slope, intercept, *_ = stats.linregress(df[col], df['t1_time'])
    x_line = np.linspace(df[col].min(), df[col].max(), 100)
    ax.plot(x_line, slope * x_line + intercept, color='tomato', linewidth=1.5)
    ax.set_xlabel(label)
    ax.set_ylabel('T1通過タイム(秒)')
    ax.set_title(f'{short}  r = {r:+.3f}')

01_correlation_scatter.png

入口速度・最大制動Gで強い負の相関が見えるが...

左上・右上のプロットを見ると、回帰直線が急勾配になっているのは 左上隅にある少数の点 のためです。100〜250 km/h付近の入口速度、0.8〜2.0G付近の制動Gの点が数本あり、それが直線を大きく傾けています。

前の記事でIQR法によって特定した**5本の外れ値ラップ(スピン・コースオフ等)**です。スピンしたラップは入口速度が低く・制動Gも低く・T1タイムも長い——3変数がそろって異常値を示しているため、相関係数を強く引き上げていました。


外れ値を除外して再計算する

q1, q3       = np.percentile(df['t1_time'], [25, 75])
upper_fence  = q3 + 1.5 * (q3 - q1)   # → 13.519 秒
df_f         = df[df['t1_time'] <= upper_fence]   # 95本

print(f"外れ値除外後: n = {len(df_f)}")
for col, label, short in metrics:
    r, p = stats.pearsonr(df_f[col], df_f['t1_time'])
    print(f"{short:<16}: r = {r:+.3f}  (p = {p:.4f})")
外れ値除外後: n = 95
入口速度            : r = -0.129  (p = 0.2144)
最大制動G           : r = -0.131  (p = 0.2066)
最大横G            : r = +0.245  (p = 0.0168)
スロットル全開距離   : r = -0.463  (p < 0.0001)

03_filtered_correlation_scatter.png

外れ値除外後の散布図。スロットル全開距離に明確な傾向が見える

結果が大きく変わりました。


外れ値除去前後の比較

# 除去前後の相関係数を並べて可視化

02_correlation_bar.png

グレーが全100本、青が外れ値除外後(n=95)

04_before_after.png

左:全100本(r=-0.77)、右:外れ値除外後(r≈-0.13)。5本の外れ値が相関を作り出していた

3点の発見を整理します。

① 入口速度・最大制動G:偽の相関だった

r = -0.77 → -0.13 に激減し、有意性も消えました(p > 0.2)。外れ値ラップは「入口速度が低く・制動Gが低く・T1タイムが長い」という3変数の同時異常です。これが相関係数を強く引き上げていたに過ぎません。

正常ラップでは入口速度はほぼ285〜289 km/hに集中(標準偏差わずか3.5 km/h)しており、速度のバラツキが小さすぎてラップ間の差異を説明できません。

② 最大横G:ノイズが取れ正の相関が現れた

r = +0.04 → +0.245 になりました(p = 0.017)。スピン時の異常な横G(最大8.3G)が取り除かれたことで、正常ラップ内での傾向が見えてきました。コーナリング中の横Gが高めのラップがやや遅い傾向があります。これは「突っ込みすぎて横Gが高くなったラップはライン修正が発生して遅くなる」という解釈が成立します。

③ スロットル全開距離:正常ラップ内での関係が見えてきた

r = -0.19(n.s.)→ -0.463(p < 0.0001)に強まりました。今回のデータでは、スロットル全開地点が後ろ寄りのラップほど T1 通過タイムが短い傾向がありました。

一見すると直感に反します。ただし、これは「遅く踏めばよい」という単純な意味ではありません。コーナー出口で車が安定した正しいポイントまで待ってからスロットルを踏めているラップほど速い、と解釈できます。逆に700〜720m付近で早々にスロットルを踏んでいるラップは、コーナリング中に無理に加速しようとして姿勢が乱れた結果タイムを失っている可能性があります(Supraはリアドライブのため、コーナリング中の早踏みはオーバーステアのリスクがあります)。重要なのは「遅く踏むこと」ではなく、「車両が安定した状態で踏めているか」です。

スロットル全開距離 r = -0.463 の意味

回帰直線の傾きで換算すると、スロットル全開になる位置が10 m遅いほど T1通過タイムが約0.06秒速くなります。

ブレーキング・コーナリングが似ているラップの中で差をつけているのは、出口でのスロットル全開のタイミングでした。


まとめ

指標 全100本 r 外れ値除外後 r 有意性(除外後)
入口速度 -0.773 -0.129 n.s.
最大制動G -0.761 -0.131 n.s.
最大横G +0.044 +0.245 * (p=0.017)
スロットル全開距離 -0.192 -0.463 ***

相関係数を計算して「強い相関が出た」と判断する前に、散布図で外れ値の影響を確認することが必須です。今回は外れ値5本(全体の5%)が r = -0.77 という見かけ上の強い相関を作り出していました。

外れ値を除いた正常ラップの中でT1通過タイムと最も強く関係していたのは スロットル全開距離(r = -0.463)でした。入口スピードやブレーキングではなく、出口でいかに車両が安定した状態でアクセルを踏み込めているかが、正常ラップ間のタイム差と強く関係していることをデータが示しています。

この構図はレーシングデータに限りません。業務データでも、少数の異常値(大口顧客・障害イベント・入力ミス)が相関係数を引き上げ、「重要な変数」に見えることがあります。相関係数を計算したら、数値を信じる前に必ず散布図を確認する——この習慣がデータ分析の精度を左右します。


参考

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?