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?

時系列データから特徴量を作る — 5,965行を1行に集約する特徴量エンジニアリング

0
Last updated at Posted at 2026-05-01

レーシングシミュレータの実データを使って、データサイエンスの概念を実装とともに解説しています。
この記事では特徴量エンジニアリングを扱います。


こんな人に役立ちます

  • 特徴量・特徴量エンジニアリングという言葉の意味を実例で理解したい
  • 時系列データを機械学習モデルに渡せる形に変換する手順を知りたい
  • 「良い特徴量」と「避けるべき特徴量」の違いを具体的に知りたい

問い:83列・5,965行のテレメトリを、どうやってモデルに渡すか

GT7テレメトリの1ラップには次のデータが記録されています。

  • 行数:5,965行(60fps × 約99秒)
  • 列数:83列(速度、操作量、姿勢角、サスペンション変位など)

100ラップ分を合わせると約60万行。これをそのまま機械学習モデルに渡すことはできません。なぜならラップごとに行数が違うからです(速いラップは少ない、遅いラップは多い)。

モデルが受け取るデータは「1ラップ = 1行、各列が変数」という形である必要があります。

特徴量とは

「このラップはなぜ速かったのか?」を説明しようとすると、次のような数値を思い浮かべるはずです。

  • ブレーキをどれだけ強く踏んだか
  • スロットルをどのタイミングで開けたか
  • コーナリング中の横Gはどれくらいだったか

これらのように、1つのデータ(ここでは1ラップ)を説明するための数値特徴量(feature)と呼びます。

機械学習モデルは「サンプル × 特徴量」の2次元テーブルを入力として受け取ります。今回の目標は「1ラップ = 1サンプル」として、各ラップの走り方を数値で並べることです。

             特徴量1    特徴量2    特徴量3  ...   目的変数
ラップ001   0.523      2.341     147.2        12.200
ラップ002   0.541      2.352     148.5        12.100
  :

「どんな数値を特徴量にするか」を設計することを特徴量エンジニアリング(feature engineering)と呼びます。


生の時系列をそのまま使えない理由

実際のテレメトリを見てみます。

df = pd.read_csv(filepath).sort_values("packet_id").reset_index(drop=True)
print(f"行数: {len(df)}行  列数: {len(df.columns)}")
# → 行数: 5,965行  列数: 83列

01_raw_timeseries.png

オレンジの帯が T1区間(500〜1000 m)です。速度は 280 km/h から 80 km/h 近くまで変化し、ブレーキが 100% まで踏まれ、前後Gが -2.4G まで落ちています。

問題はサンプル間で行数が揃わない点です。

  • ラップによって走行距離は同じでも通過時間が違う → 行数が変わる
  • 同じ行番号でも「ラップ開始からの経過時間」が異なる → 位置合わせが必要

さらに5,965行をそのまま特徴量にすると1サンプルが5,965次元になり、100ラップのデータは「100行 × 約49万列(5,965行 × 83列)」という高次元になります。これはモデルが扱えません。


手順①:対象区間を切り出す

最初にT1区間(累積走行距離 500〜1000 m)だけを切り出します。

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

df['dist'] = calc_dist(df)
seg = df[(df['dist'] >= 500) & (df['dist'] <= 1000)].copy()

print(f"T1区間の行数: {len(seg)}行 ({len(seg)/60:.1f}秒)")
# → T1区間の行数: 714行 (11.9秒)

5,965行から714行に絞り込めました。ただしこれでも「714行 × 83列」であり、ラップ間で行数が異なる問題は残っています。


手順②:統計量で集約する

714行の時系列を、各チャンネルの統計量(mean / max / min / std)で集約します。統計量は「単なる要約」ではなく、走り方の特徴を固定長の数値に圧縮する操作です。

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

T1区間のスロットルと前後Gに統計量を重ねると次のようになります。

02_t1_zone_stats.png

  • throttle_pct__mean = 58.1 :スロットルの平均開度。ブレーキ中は0%、全開時は100%が混在している平均
  • throttle_pct__max = 100.0 :区間内で一度でも全開にしたか
  • lon_g__min = -2.40 :最大制動G(最も強くブレーキした瞬間)
  • lon_g__std = ... :前後Gのばらつき。ブレーキ操作の滑らかさに対応

各センサー(速度・G・操作量・サスペンションなど)ごとに「平均・最大・最小・ばらつき」を計算することで、1つの走りを多角的に表現します。714行だったデータが1ラップ = 1行、40チャンネル × 4統計量 = 144列に集約されます。

# 100ラップ分を処理すると
print(df_feat.shape)
# → (100, 145)  ← 144特徴量 + t1_time

良い特徴量と避けるべき特徴量

すべてのチャンネルをそのまま特徴量にすると分析を歪めるものが混入します。

① 同義反復になる特徴量

speed_kmh__mean(区間平均速度)は T1通過タイムと r = -0.992 という強い相関を示します。しかしこれは「速い区間を速く走ったラップのタイムは短い」という当然の結果であり、なぜ速いのかは何も教えてくれません。

T1区間の通過タイムは実質的に「距離 ÷ 平均速度」で決まるので、平均速度は目的変数を言い換えているだけです。同様にタイヤ回転数(wheel_rad_s_*)やミッション回転数(trans_rpm)も速度から直接導出できる量です。

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 特徴量

② セッション持ち越しの特徴量

タイヤ温度(tyre_temp_fl/fr/rl/rr)、水温(water_temp)、油温(oil_temp)はラップ開始時点で前のセッションの状態を引き継ぎます。T1区間内の値がそのラップのコーナリングを反映しているとは限らないため除外します。

③ 定数列

ギア比(gear1gear8)、最大回転数(rev_limit)などは全ラップで同一の値になります。変化しない列は情報を持たないため除外します。

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',
    'gear1', 'gear2', 'gear3', 'gear4', 'gear5', 'gear6', 'gear7', 'gear8',
    'rev_limit', 'fuel_capacity',
    # ...
}

除外後に残るのは throttle_pctbrake_pctlon_glat_gengine_rpm、サスペンション変位、姿勢角などドライビング操作と車体挙動に直接関係するチャンネルです。


まとめ

ステップ 操作 変換結果
生データ 5,965行 × 83列(1ラップ)
区間を切り出す dist 500〜1000 m 714行 × 83列(T1区間)
統計量で集約する mean/max/min/std 1行 × 144列(1ラップ = 1サンプル)
除外後 同義反復・定数・持ち越し列を除く 1行 × 104列

今回の例では83チャンネル・5,965行の生データから、ラップ1本あたり1行・144列のテーブルへの変換を実現しました。このテーブルがあれば、相関係数や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?