Skip to content
B

Loading experience

Now PlayingReading Ambient
← Back to writing
[DATA + F1]March 15, 202514 MIN READ

Why F1 Data Is the Best Playground for Learning ML

Formula 1 telemetry generates over 1.5 million data points per second. That's not just a sport — it's a real-time distributed sensing system, and the perfect ML laboratory.

Why F1 Data Is the Best Playground for Learning ML

There's a moment in every data science student's journey where textbook datasets start feeling hollow. The Titanic. Iris flowers. MNIST digits. They serve their purpose — but they don't breathe.

Formula 1 breathes.

Every lap is a new inference problem. Every tyre compound is a hyperparameter with a degradation curve. Every pit stop is a sequential decision made under real-time uncertainty with a cost measured in tenths of a second. No synthetic dataset manufactures this kind of stakes, and no classroom exercise teaches you what F1 data teaches you: that the gap between a model that works and a model that wins is almost always in the feature engineering.

The Numbers First

A modern F1 car is fitted with over 300 sensors, transmitting data at roughly 1.5 million samples per second across a race weekend. That's approximately 500GB of telemetry data per car, per race.

Here's what gets captured:

  • Throttle position (sampled at 1kHz)
  • Brake pressure at all four corners independently
  • Steering angle and steering torque
  • G-forces in three axes: lateral, longitudinal, vertical
  • Tyre surface temperatures, measured in zones across each tyre face
  • Engine RPM, torque output, fuel flow rate
  • GPS positioning accurate to centimetre resolution
  • Aerodynamic downforce estimates derived from suspension load cells
  • DRS status and ERS battery state-of-charge

For a machine learning student, this is paradise. But paradise needs a pipeline — and building that pipeline is where most of the actual learning happens.

The ML System Architecture

Before you write a single line of model code, there is a system that has to work. F1 ML is not just about algorithms. It is about ingesting live telemetry, engineering features that do not leak future information, and returning predictions fast enough to inform a strategy call before the window closes. Here is what that looks like end to end:

FASTF1 APItelemetry · laps · car data · sessionTIMING STREAMsector splits · pit delta · track positionTRACK + WEATHERsurface temp · grip evolution · windFEATURE ENGINEERING PIPELINETyre compound x age · Fuel-adjusted lap delta · Rolling sector variance · ERS deployment fingerprintTrack evolution index · Stint degradation slope · Gap-to-leader delta · Lag features (t-1, t-3, t-5)3-sigma outlier clamping · min-max normalization · TimeSeriesSplit — never shuffle sequential lap dataREGRESSIONLap time predictionGBT · LSTM (rolling window)Physics-informed loss (Pacejka)Metric: RMSE · MAPECLASSIFICATIONDriver ID from telemetryTCN · Transformer encoderBraking fingerprint sequencesMetric: Macro-F1 · ROC-AUC OvRRL AGENTPit stop timing (MDP)State: pos · tyre · gap · weatherAction: stay out / pit (compound)Reward: delta-t to optimal strategyEVALUATION + EXPLAINABILITYTimeSeriesSplit CV · SHAP feature attribution · Residual analysis by track type · Concept drift monitoringOOD detection on weather conditions unseen during training300+ sensors · 1.5M samples/sec~500GB telemetry · per car · per race3 ML paradigms in one race weekendRegression · Classification · RL

Problem One: Lap Time Prediction as Regression

Given sector-by-sector telemetry — throttle traces, braking points, corner entry speeds — can you predict final lap time? This is a multivariate regression problem with rich time-series inputs, and it teaches three hard lessons quickly.

Lesson one: features need domain knowledge. Raw throttle position is less useful than the fuel-adjusted lap delta — the difference between a lap time and what you'd expect given the car's current fuel load. Fuel burns off at roughly 1.7kg per lap in modern F1, and lighter cars are faster. If you don't control for fuel load, your model learns a spurious correlation between lap number and lap time.

Lesson two: tyre compound × age is a nonlinear interaction. A Soft tyre on lap 1 behaves nothing like a Soft on lap 15. The compound encodes initial grip; tyre age encodes degradation. You need the interaction term, not either feature alone. Gradient Boosted Trees handle this naturally because tree splits can capture joint conditions; a linear model will systematically misfit the degradation curve.

Lesson three: the Pacejka tyre model is a free physics prior. Real teams use physics-informed loss functions that penalize predictions that violate known tyre dynamics. The Pacejka Magic Formula describes lateral and longitudinal force as a function of slip angle and slip ratio. You do not need to implement it fully — but adding a regularization term that penalizes violations of its shape constraints often improves generalization on tracks the model has never seen.

import fastf1
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor

session = fastf1.get_session(2024, 'Bahrain', 'R')
session.load()

laps = session.laps.copy()

# Engineer fuel-adjusted lap delta (approx 0.03s per kg of fuel burn)
laps['fuel_kg_est'] = 100 - (laps['LapNumber'] * 1.7)
laps['fuel_adj_lap'] = laps['LapTime'].dt.total_seconds() - (laps['fuel_kg_est'] * 0.03)

# Compound encoding + tyre age interaction
compound_map = {'SOFT': 0, 'MEDIUM': 1, 'HARD': 2}
laps['compound_enc'] = laps['Compound'].map(compound_map)
laps['tyre_interaction'] = laps['compound_enc'] * laps['TyreLife']

features = ['TyreLife', 'compound_enc', 'tyre_interaction',
            'fuel_kg_est', 'Sector1Time', 'Sector2Time', 'Sector3Time']

X = laps[features].dropna()
y = laps.loc[X.index, 'fuel_adj_lap']

Why You Cannot Use k-Fold Cross-Validation Here

This is the mistake that kills most F1 ML projects before they start. Standard k-fold randomly shuffles the dataset and creates folds. On lap data, that means a model trained on lap 40 will be evaluated on lap 5 — the model has seen the future and learned that lap 40 conditions imply fast times. Your validation score will be optimistic and your race-day performance will be wrong.

The fix is TimeSeriesSplit. It respects temporal order: fold one trains on the first race, tests on the second; fold two trains on the first two races, tests on the third. Information only flows forward in time, exactly as it would in production.

from sklearn.model_selection import TimeSeriesSplit, cross_val_score

tscv = TimeSeriesSplit(n_splits=5)

model = GradientBoostingRegressor(
    n_estimators=400,
    max_depth=4,
    learning_rate=0.05,
    subsample=0.8
)

# Note: data must be sorted by race + lap number before splitting
scores = cross_val_score(model, X, y, cv=tscv, scoring='neg_root_mean_squared_error')
print(f"CV RMSE: {-scores.mean():.3f}s ± {scores.std():.3f}s")

SHAP: What the Model Actually Learned

After training, SHAP (SHapley Additive exPlanations) values let you decompose each prediction into per-feature contributions. In F1 lap time models, two patterns consistently emerge: tyre age dominates in the middle of a stint, and track temperature interaction with compound dominates in the opening laps. If your SHAP plot shows sector times as the top features, your model has learned a tautology — sector times sum to lap time. Remove them.

import shap

model.fit(X, y)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X)

shap.summary_plot(shap_values, X, plot_type='bar')

Problem Two: Pit Stop Timing as a Markov Decision Process

When should you pit? The na answer is "when the tyres are slow." The real answer involves your current track position, your gap to the car ahead, whether a competitor is about to pit, what compound you will switch to, and what the weather might do in the next 12 laps.

This is a sequential decision problem under uncertainty — which is the textbook definition of a Markov Decision Process, the mathematical foundation of reinforcement learning.

The state space in a simplified pit stop MDP looks something like this:

  • Current track position (integer, 1–20)
  • Laps remaining in stint (integer)
  • Current tyre compound (categorical: SOFT, MEDIUM, HARD)
  • Gap to the car directly ahead in seconds (continuous)
  • Gap to the car directly behind in seconds (continuous)
  • Estimated tyre performance delta vs fresh rubber (continuous)
  • Probability of rain in the next 10 laps (continuous, 0–1)

The action space at each lap boundary: stay out, or pit for each available compound. In practice that is 3–4 discrete actions per decision point.

The reward function is where it gets interesting. The most natural reward is the delta between your actual finishing position and the optimal theoretical finishing position — but that signal is sparse (it only arrives at the end of the race) and delayed by 30–60 decision steps. A shaped reward that gives intermediate feedback based on track position relative to competitors helps the agent learn faster without changing the optimal policy.

This maps so cleanly to RL that it is genuinely one of the best real-world examples for teaching why RL exists as a separate paradigm from supervised learning. No historical pit decision comes labelled "optimal" or "suboptimal" — the label only exists in counterfactual, and counterfactuals are not in your training data.


Problem Three: Driver Fingerprinting from Braking Signatures

Different drivers have strikingly different approaches to the same corner. Some drivers brake late and hard with a sharp peak (Schumacher, Hamilton). Others trail-brake — maintaining partial brake pressure through corner entry to rotate the car (Alonso, Verstappen). These are not just stylistic differences; they show up as reproducible statistical signatures in the telemetry.

The machine learning formulation: given a time-series of brake pressure readings at a specific corner over multiple laps, classify which driver is at the wheel. This is a multiclass classification problem on sequences, which naturally motivates architectures that can model temporal structure.

A Temporal Convolutional Network (TCN) works well here because brake pressure traces are local and causal — what matters is the shape of the pressure-time curve at a specific corner, not long-range dependencies across the whole lap. A Transformer encoder with positional embeddings works for the full-lap case, where the model needs to integrate context across all corners simultaneously.

import numpy as np
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import classification_report

# Load brake pressure traces for a specific corner, windowed per lap
# Shape: (n_laps, window_length) where window_length = samples around corner
brake_traces = []  # shape (n_laps, 200)
driver_labels = []

# For a production version: use fastf1 telemetry.get_pos_data()
# and slice around corner apex coordinates

le = LabelEncoder()
y_encoded = le.fit_transform(driver_labels)

# Train/test split respecting temporal order
split = int(0.8 * len(brake_traces))
X_train, X_test = brake_traces[:split], brake_traces[split:]
y_train, y_test = y_encoded[:split], y_encoded[split:]

The evaluation metric matters here. Accuracy is misleading if some drivers have more laps in your dataset than others. Use macro-F1, which computes F1 per class and averages, giving equal weight to each driver regardless of lap count imbalance.


The Data Infrastructure Lesson Nobody Talks About

Perhaps the most underrated lesson from working with F1 data: data engineering matters more than model choice.

Teams have real-time infrastructure delivering telemetry from a car travelling at 340km/h, validated and visualized at the pitwall within seconds. Every lap, the car's position, speed, tyre temperatures, and energy recovery state are transmitted over a radio link, timestamped, aligned, and fed into models that have already been trained and are waiting for inference.

Two specific challenges that will teach you more than any tutorial:

Track evolution as concept drift. A circuit that opens on Friday for practice is nothing like the same circuit on Sunday for the race. Rubber lays down from cars, the track "rubbers in," and grip increases. The feature distribution shifts continuously. A model trained on Friday practice will systematically underestimate Sunday race pace. This is concept drift — one of the central challenges in production ML — and F1 makes it literal and visceral.

Multivariate time-series alignment. Telemetry channels are sampled at different rates. GPS might be 50Hz, engine data at 100Hz, tyre temperatures at 10Hz. Before any ML work, these channels need to be aligned to a common timestamp grid, usually via forward-fill or linear interpolation. Getting this wrong introduces subtle timing errors that corrupt every feature downstream.

How to Start

pip install fastf1 pandas matplotlib scikit-learn shap
import fastf1
import fastf1.plotting

# Enable caching — sessions are large, you don't want to re-download
fastf1.Cache.enable_cache('./f1_cache')

session = fastf1.get_session(2024, 'Bahrain', 'R')
session.load()

# Get all laps from Max Verstappen
ver = session.laps.pick_driver('VER')
print(ver[['LapTime', 'Sector1Time', 'Sector2Time', 'Sector3Time',
           'TyreLife', 'Compound', 'TrackStatus']].head(20))

You now have real race telemetry. The problems are sitting right there in the columns. The best way to learn regression is to predict that LapTime column. The best way to learn feature engineering is to realize that TrackStatus — which encodes safety car and virtual safety car periods — is the single most confounding variable in the whole dataset and you will miss it until your model behaves strangely on laps 23–27.

That moment of debugging, of finding the variable you forgot, of understanding why the model was right and you were wrong — that is what makes F1 data the best playground for ML. It punishes sloppy thinking with a clean error signal, and it rewards good intuition with a model that would genuinely help win a race.


The best way to learn something is to apply it to something you love. If that thing happens to involve 20 cars going 340km/h while generating petabytes of sensor data — even better.