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?

相関係数27位 → Random Forest 1位。その原因は「95本中たった1本のラップ」だった

0
Posted at

レーシングシミュレータの実データを使って、データサイエンスの概念を実装とともに解説しています。
この記事では 特徴量スクリーニング(相関係数とRandom Forestの比較)を扱います。


こんな人に役立ちます

  • 相関係数とRandom Forest の特徴量重要度がなぜ異なる順位を返すかを理解したい
  • 数十〜数百のカラムから「効いている変数」を絞り込む手順を実例で見たい
  • 非線形な変数間関係を散布図で発見・検証する方法を知りたい

結論

  • 相関係数27位の変数が、Random Forest の重要度では1位(53.7%)
  • 原因は「非線形な閾値パターン」——散布図を見れば一目瞭然
  • 相関係数だけでは見落とす変数が存在する

2手法の本質的な違い:相関係数は「全体の傾向」を測り、Random Forest は「データを分ける境界」を見つける。この違いが、同じデータから全く異なるランキングを生み出します。


問い:相関係数とRandom Forest、どちらが「効く変数」を正確に見つけられるか

前の記事では104個の特徴量に対して相関係数を計算し、T1区間の通過タイムと最も強く相関する変数を探しました。しかし記事の最後でこう書きました。

「相関係数は線形な関係しか捉えられない。Random Forestを使うと非線形な関係も含めて評価できる」

今回はその続きです。同じデータで相関係数とRandom Forestの両方を計算し、2手法の結果を比べます。

相関係数では27位だった変数が、Random Forestでは1位(53.7%)になりました。どちらが間違っているのでしょうか?

00_concept_overview.png


2つの手法でスクリーニングする

「効いている変数」を探す代表的な2手法を組み合わせます。

手法 何を測るか 見落とすもの
相関係数 線形な単調関係の強さ 非線形関係・閾値効果・交互作用
Random Forest 予測に使われた分割の貢献度 変数の「方向性」(正負)・解釈のしやすさ

相関係数は「一方が増えると他方も増える/減る」という単調な関係しか捉えられません。Random Forest は決定木の分割を繰り返すため、「閾値を超えると急に変化する」「U字型の関係」など非線形なパターンも検出できます。

2手法の順位を比べることで、線形スクリーニングでは見落とされた変数を発見できます。


データ準備

富士スピードウェイT1区間を対象とし、IQR法による外れ値除外後の95本を使います(閾値 13.519秒、5本のスピン・コースオフラップを除外)。使用ライブラリ:numpy、pandas、scipy、scikit-learn、matplotlib。

各チャンネルについて T1 区間内の4統計量(mean / max / min / std)を特徴量として抽出します。

import os
import pandas as pd
import numpy as np
from scipy import stats
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler

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

# メタデータ・フラグ・セッション持ち越しカラムを除外
EXCLUDE_COLS = {
    'packet_id', 'lap_count', 'best_lap_ms', 'last_lap_ms',
    'tyre_temp_fl', 'tyre_temp_fr', 'tyre_temp_rl', 'tyre_temp_rr',  # セッション持ち越し
    'water_temp', 'oil_temp', 'fuel_remaining',
    'pos_x', 'pos_y', 'pos_z',
    'is_on_track', 'is_paused', 'car_code',
    # ... (定数列・ギア比など)
}

def calc_dist(df):
    dx = df['pos_x'].diff().fillna(0)
    dy = df['pos_y'].diff().fillna(0)
    dz = df['pos_z'].diff().fillna(0)
    return np.sqrt(dx**2 + dy**2 + dz**2).cumsum()

def extract_features(filepath):
    df = pd.read_csv(filepath).sort_values("packet_id").reset_index(drop=True)
    df['dist'] = calc_dist(df)

    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

    seg = df[(df['dist'] >= T1_START_M) & (df['dist'] <= T1_END_M)].copy()
    analysis_cols = [c for c in df.columns if c not in EXCLUDE_COLS and c != 'dist']

    row = {'t1_time': t1_time}
    for col in analysis_cols:
        vals = seg[col].dropna()
        if vals.nunique() <= 1:  # 定数列はスキップ
            continue
        row[f'{col}__mean'] = vals.mean()
        row[f'{col}__max']  = vals.max()
        row[f'{col}__min']  = vals.min()
        row[f'{col}__std']  = vals.std()
    return row

抽出後の特徴量数は 144(36チャンネル × 4統計量)。IQR外れ値除外後 n=95。


まず全チャンネルで相関係数を計算する

feat_cols = [c for c in df.columns if c != 't1_time']
X = df[feat_cols].values
y = df['t1_time'].values

corr_rows = []
for i, col in enumerate(feat_cols):
    r, p = stats.pearsonr(X[:, i], y)
    corr_rows.append({'feature': col, 'r': r, 'abs_r': abs(r)})

corr_df = pd.DataFrame(corr_rows).sort_values('abs_r', ascending=False)

01_correlation_ranking.png

1位は speed_kmh__meanr = -0.992。T1区間の通過タイムは実質的に「距離 ÷ 平均速度」で決まるので、数学的に必然の結果です。これは 同義反復(トートロジー)であり、「なぜ速いのか」の説明にはなりません。

これは特徴量選択における 「やってはいけない例」 の典型です。結果(速い)をそのまま原因(速度が高い)で説明しており、ドライビングの"何が違うのか"という問いには答えられません。

同様に、タイヤ回転数(wheel_rad_s_*)、ミッション回転数(trans_rpm)、速度成分(vx/vy/vz)も速度から直接導出できる量です。これらをランキングに並べても「速いラップは速い」という循環論法になります。


速度派生チャンネルを除外して再分析する

SPEED_DERIVED_PREFIXES = (
    'speed_kmh__', 'wheel_rad_s_', 'trans_rpm__',
    'vx__', 'vy__', 'vz__', 'wheel_rotation_radians__',
)
non_speed_cols = [
    c for c in feat_cols
    if not any(c.startswith(p) for p in SPEED_DERIVED_PREFIXES)
]
# 144 → 104 特徴量

相関係数ランキング(速度派生除外後)

X2 = df[non_speed_cols].values
corr2 = [{'feature': c, 'r': r, 'abs_r': abs(r)}
         for c, (r, _) in zip(non_speed_cols, [stats.pearsonr(X2[:, i], y) for i in range(X2.shape[1])])]
corr2_df = pd.DataFrame(corr2).sort_values('abs_r', ascending=False)

03_correlation_ranking_no_speed.png

速度派生チャンネルを除くと、サスペンション変位と車体高さ が上位に浮かびます。

1位  sus_height_rl__mean   r = -0.919  左後サスペンション変位(平均)
2位  body_height__mean     r = +0.871  車体高さ(平均)
3位  sus_height_fl__mean   r = -0.854  左前サスペンション変位(平均)
4位  sway__mean            r = -0.809  横揺れ(平均)
5位  boost_pressure__mean  r = -0.804  ブースト圧(平均)
6位  throttle_pct__mean    r = -0.795  スロットル開度(平均)

サスペンションが沈んでいる(変位が小さい)ラップほど速い——コーナリング中の空力ダウンフォースや荷重移動が大きいラップが速い、という解釈と整合します。

Random Forest 特徴量重要度

Random Forest の特徴量重要度は、「その変数でデータを分割したときに、どれだけ予測誤差を減らせたか」を表す指標です。つまり、「どの変数が予測にどれだけ役立ったか」を示しています。

scaler = StandardScaler()
X2_scaled = scaler.fit_transform(X2)

rf = RandomForestRegressor(n_estimators=300, random_state=42, n_jobs=-1)
rf.fit(X2_scaled, y)

rf_df = pd.DataFrame({
    'feature':    non_speed_cols,
    'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)

02_rf_importance_no_speed.png

1位  yaw_rate__mean      53.7%  ← 相関ランキングは27位(r = +0.452)
2位  sus_height_rl__mean 22.3%
3位  sus_height_fl__mean  8.4%
4位  body_height__mean    5.4%
5位  throttle_pct__mean   3.0%

yaw_rate__mean(ヨーレート平均)が全体の 53.7% を占めています。一方で相関係数は r = +0.452(27位)——この乖離の原因を掘り下げます。


相関27位 → Random Forest の重要度1位の謎

相関順位と Random Forest の順位を並べると、逆転が際立ちます。

特徴量 相関係数の順位 Random Forest の重要度順位
yaw_rate__mean 27 1
sus_height_rl__mean 1 2
ang_vel_z__mean 8 84

yaw_rate__mean は相関係数では r = +0.452(27位)なのに、Random Forest では重要度1位(53.7%)です。散布図で確認します。

04_yaw_rate_scatter.png

左プロット(yaw_rate__mean)を見ると、94本の密集と1本の孤立点に分かれています。

  • 密集(yaw_rate ≈ -0.25〜-0.21 rad/s):94本。T1は12.0〜13.5秒に分布。
  • 孤立点(yaw_rate ≈ +0.14 rad/s):1本のみ。T1タイムが13.3秒(遅い側)。

T1は右コーナーのため、正常に旋回しているラップでは負のヨーレートになります。この1本は正のヨーレートを示しており、車が右にうまく曲がれていない——アンダーステアで外に膨らんだか、オーバーステアを修正しながら走ったラップです。Toyota Supraはリアドライブのため、T1区間でのアクセル操作ミスはオーバーステアに直結します。

決定木の分割は「yaw_rate__mean > -0.1 → 高T1タイム」というルールでこの1本を識別します。これが重要度53.7%の正体です。

一方、相関係数は全体に直線を引くため、94本が密集して1本だけ孤立する分布では傾きが緩くなり、r = 0.452 止まりになります。


外れラップの発見:t1_time の除外だけでは不十分だった

ここで立ち止まります。この1本は IQR法による外れ値除外 をくぐり抜けています。t1_time = 13.28秒で、閾値(13.519秒)以内に収まっていたためです。

目的変数(t1_time)の外れ値だけを除外しても、特徴量レベルの異常ラップは残る——これがデータクレンジングの盲点です。

この1本は「正常なコーナリングラップ」ではなく、「操作ミスで車が正しく向かなかったラップ」です。「速いラップと遅いラップで何が違うか」を分析したいなら、このようなラップは除外する方が正しいです。

# yaw_rate__mean が正(右コーナーで逆方向)のラップを除外
df_clean = df[df['yaw_rate__mean'] < 0].copy()
# 95本 → 94本(1本除外)

外れラップを除外して再分析する

除外後に相関係数と Random Forest の重要度を再計算します。

05_correlation_clean.png

06_rf_importance_clean.png

除外前後の yaw_rate の変化:

相関係数(r) Random Forest 重要度
除外前(n=95) +0.452(27位) 53.7%(1位)
除外後(n=94) +0.945(1位) 54.4%(1位)

07_yaw_rate_before_after.png

たった1本です。

95本中の、たった1本を除いただけで、相関係数が 0.452 から 0.945 に跳ね上がりました。

つまり、yaw_rate は最初から強力な予測変数でした。それが相関27位に見えていたのは、1本の異常ラップが直線の傾きを押し下げていたためです。除外後の散布図(右)では、94本の正常ラップの中で「ヨーレートが0に近いほど(旋回が弱いほど)タイムが遅い」という明確な線形傾向が現れています。

教訓:外れ値除外は目的変数だけでは不十分

  • 目的変数(t1_time)のIQR除外 → タイムが異常に遅い「スピン・コースオフ」ラップを除外
  • 特徴量(yaw_rate)の確認 → 「操作ミスで車が正しく向かなかった」ラップを発見・除外

IQR除外は「t1_timeの値が極端かどうか」しか見ていません。今回の異常ラップはt1_time = 13.28秒と閾値(13.519秒)より速く、タイム的には「普通のラップ」でした。挙動は異常でも結果が正常範囲に収まることはあります。

これは実務でも頻繁に起きます。製造では「部品寸法は規格内だが製造温度が異常」、金融では「取引金額は正常だが時刻・場所の組み合わせが異常」(クレジットカード不正の典型)など、目的変数だけ見ると正常、特徴量を見ると異常というパターンです。

「Random Forest の重要度が高いのに相関が低い」という乖離を見つけたら散布図で確認する——この習慣がデータ品質問題の発見につながります。


まとめ

特徴量 相関順位(除外後) Random Forest 重要度順位(除外後) 解釈
yaw_rate__mean 1(r=0.945) 1(54.4%) 両手法一致。ヨーレートが0に近いほど(旋回が弱いほど)遅い
sus_height_rl__mean 2(r=-0.915) 2(20.3%) 両手法一致:サスペンション沈み込み
throttle_pct__mean 7(r=-0.798) 5(3.9%) 両手法一致:スロットル開度

相関係数は「線形な関係の強さ」、Random Forest は「予測への貢献度」を測ります。今回は2手法の順位比較が「調査すべき変数」を浮かび上がらせ、散布図がその原因(1本の異常ラップ)を教えてくれました。散布図で確認し、除外して再分析することで、初めて正しい結果が得られます。

特徴量スクリーニングは手法を組み合わせ、散布図で必ず目視確認することが重要です。

特徴量スクリーニングのチェックリスト

  • 相関係数と Random Forest の順位を比較したか
  • 順位が大きく乖離する変数を散布図で確認したか
  • 外れ値を目的変数だけでなく特徴量レベルでも確認したか
  • 速度・回転数など「結果と同義の変数」を除外したか

関連記事

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?