#Install with:
# %pip install --upgrade fastf1
# Dev version:
# %pip install --upgrade https://github.com/theOehrly/Fast-F1/archive/refs/heads/master.zip
import fastf1 as ff1
2 Getting Started With FastF1
The theOehrly/Fast-F1
Python package [docs] provides a relatively straightforward way of downloading and accessing telemetry data from the Formula One website and the ergast historical motor racing results database.
In this chapter, we’ll have a quick overview of the package to get a feel for the sorts of things we can do with it, before diving in to more detail in other chapters.
Let’s start off by importing the package:
The package API may change rapidly, and may include breaking changes.
The code used in this chapter relates to version 2.2.9
.
# Check the package version
ff1.__version__
'2.2.9'
To minimise the number of calls made to the website, the package can be configured to cache data whenever possible.
from pathlib import Path
= ".cache"
cachedir =True, exist_ok=True)
Path(cachedir).mkdir(parents
ff1.Cache.enable_cache(cachedir)
If we request the same data again, the locally cached data will be used, rather than a call being made to the Formula One data service.
This has the advantage that if you have already cached the data, you can load it even if you are offline.
Data can be retrieved for specific sessions of specific races.
As the data is loaded, a diagnostic trace is displayed showing what data has been retrieved, and from where (for example, downloaded from the original API or retrieved from the cache).
# Select a wualifying session ("Q") or a race session("R")
= ff1.get_session(2019, 'Monza', 'Q')
monza_quali
monza_quali.load()
core INFO Loading data for Italian Grand Prix - Qualifying [v2.2.9]
api INFO Using cached data for driver_info
api INFO Using cached data for timing_data
api INFO Using cached data for timing_app_data
core INFO Processing timing data...
api INFO Using cached data for session_status_data
api INFO Using cached data for track_status_data
api INFO Using cached data for car_data
api INFO Using cached data for position_data
api INFO Using cached data for weather_data
api INFO Using cached data for race_control_messages
core INFO Finished loading data for 20 drivers: ['16', '44', '77', '5', '3', '27', '55', '23', '18', '7', '99', '20', '26', '4', '10', '8', '11', '63', '88', '33']
A good selection of metadata is available to describe the associated event:
= monza_quali.event
weekend weekend
RoundNumber 14
Country Italy
Location Monza
OfficialEventName FORMULA 1 GRAN PREMIO HEINEKEN D’ITALIA 2019
EventDate 2019-09-08 00:00:00
EventName Italian Grand Prix
EventFormat conventional
Session1 Practice 1
Session1Date 2019-09-06 00:00:00
Session2 Practice 2
Session2Date 2019-09-06 00:00:00
Session3 Practice 3
Session3Date 2019-09-07 00:00:00
Session4 Qualifying
Session4Date 2019-09-07 00:00:00
Session5 Race
Session5Date 2019-09-08 00:00:00
F1ApiSupport True
Name: Italian Grand Prix, dtype: object
2.1 Driver Details
A wide range of data is available, and can be can be explored in several ways.
For example, we can get the data for a particular driver, and identify their fastest lap, along with various summary statistics for it:
Here’s an example of the driver details:
'LEC') monza_quali.get_driver(
DriverNumber 16
BroadcastName C LECLERC
Abbreviation LEC
TeamName Ferrari
TeamColor dc0000
FirstName Charles
LastName Leclerc
FullName Charles Leclerc
Position 1.0
GridPosition 0.0
Q1 0 days 00:01:20.126000
Q2 0 days 00:01:19.553000
Q3 0 days 00:01:19.307000
Time NaT
Status
Points 0.0
Name: Charles, dtype: object
We can retrieve summary data about each lap:
monza_quali.laps.head()
Time | DriverNumber | LapTime | LapNumber | Stint | PitOutTime | PitInTime | Sector1Time | Sector2Time | Sector3Time | ... | IsPersonalBest | Compound | TyreLife | FreshTyre | LapStartTime | Team | Driver | TrackStatus | IsAccurate | LapStartDate | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 days 00:21:01.358000 | 16 | NaT | 1 | 1 | 0 days 00:19:26.847000 | NaT | NaT | 0 days 00:00:30.837000 | 0 days 00:00:33.016000 | ... | False | MEDIUM | 1.0 | True | 0 days 00:19:26.847000 | Ferrari | LEC | 1 | False | 2019-09-07 13:04:27.940 |
1 | 0 days 00:22:21.775000 | 16 | 0 days 00:01:20.417000 | 2 | 1 | NaT | NaT | 0 days 00:00:26.982000 | 0 days 00:00:26.734000 | 0 days 00:00:26.701000 | ... | False | MEDIUM | 2.0 | True | 0 days 00:21:01.358000 | Ferrari | LEC | 1 | True | 2019-09-07 13:06:02.451 |
2 | 0 days 00:24:03.991000 | 16 | 0 days 00:01:42.216000 | 3 | 1 | NaT | NaT | 0 days 00:00:33.988000 | 0 days 00:00:35.632000 | 0 days 00:00:32.596000 | ... | False | MEDIUM | 3.0 | True | 0 days 00:22:21.775000 | Ferrari | LEC | 1 | True | 2019-09-07 13:07:22.868 |
3 | 0 days 00:25:24.117000 | 16 | 0 days 00:01:20.126000 | 4 | 1 | NaT | NaT | 0 days 00:00:26.749000 | 0 days 00:00:26.777000 | 0 days 00:00:26.600000 | ... | False | MEDIUM | 4.0 | True | 0 days 00:24:03.991000 | Ferrari | LEC | 1 | True | 2019-09-07 13:09:05.084 |
4 | 0 days 00:27:09.461000 | 16 | 0 days 00:01:45.344000 | 5 | 1 | NaT | NaT | 0 days 00:00:33.884000 | 0 days 00:00:37.703000 | 0 days 00:00:33.757000 | ... | False | MEDIUM | 5.0 | True | 0 days 00:25:24.117000 | Ferrari | LEC | 1 | True | 2019-09-07 13:10:25.210 |
5 rows × 27 columns
2.2 Laps Data
A wide range of data is provided as part of the Laps table:
monza_quali.laps.columns
Index(['Time', 'DriverNumber', 'LapTime', 'LapNumber', 'Stint', 'PitOutTime',
'PitInTime', 'Sector1Time', 'Sector2Time', 'Sector3Time',
'Sector1SessionTime', 'Sector2SessionTime', 'Sector3SessionTime',
'SpeedI1', 'SpeedI2', 'SpeedFL', 'SpeedST', 'IsPersonalBest',
'Compound', 'TyreLife', 'FreshTyre', 'LapStartTime', 'Team', 'Driver',
'TrackStatus', 'IsAccurate', 'LapStartDate'],
dtype='object')
We can check the laps for a particular car:
= monza_quali.laps.pick_driver(16)
lec_laps 5] lec_laps[:
Time | DriverNumber | LapTime | LapNumber | Stint | PitOutTime | PitInTime | Sector1Time | Sector2Time | Sector3Time | ... | IsPersonalBest | Compound | TyreLife | FreshTyre | LapStartTime | Team | Driver | TrackStatus | IsAccurate | LapStartDate | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 days 00:21:01.358000 | 16 | NaT | 1 | 1 | 0 days 00:19:26.847000 | NaT | NaT | 0 days 00:00:30.837000 | 0 days 00:00:33.016000 | ... | False | MEDIUM | 1.0 | True | 0 days 00:19:26.847000 | Ferrari | LEC | 1 | False | 2019-09-07 13:04:27.940 |
1 | 0 days 00:22:21.775000 | 16 | 0 days 00:01:20.417000 | 2 | 1 | NaT | NaT | 0 days 00:00:26.982000 | 0 days 00:00:26.734000 | 0 days 00:00:26.701000 | ... | False | MEDIUM | 2.0 | True | 0 days 00:21:01.358000 | Ferrari | LEC | 1 | True | 2019-09-07 13:06:02.451 |
2 | 0 days 00:24:03.991000 | 16 | 0 days 00:01:42.216000 | 3 | 1 | NaT | NaT | 0 days 00:00:33.988000 | 0 days 00:00:35.632000 | 0 days 00:00:32.596000 | ... | False | MEDIUM | 3.0 | True | 0 days 00:22:21.775000 | Ferrari | LEC | 1 | True | 2019-09-07 13:07:22.868 |
3 | 0 days 00:25:24.117000 | 16 | 0 days 00:01:20.126000 | 4 | 1 | NaT | NaT | 0 days 00:00:26.749000 | 0 days 00:00:26.777000 | 0 days 00:00:26.600000 | ... | False | MEDIUM | 4.0 | True | 0 days 00:24:03.991000 | Ferrari | LEC | 1 | True | 2019-09-07 13:09:05.084 |
4 | 0 days 00:27:09.461000 | 16 | 0 days 00:01:45.344000 | 5 | 1 | NaT | NaT | 0 days 00:00:33.884000 | 0 days 00:00:37.703000 | 0 days 00:00:33.757000 | ... | False | MEDIUM | 5.0 | True | 0 days 00:25:24.117000 | Ferrari | LEC | 1 | True | 2019-09-07 13:10:25.210 |
5 rows × 27 columns
2.3 Car Telemetry Data
Perhaps more interestingly, we can look up regular samples of raw car data for a particular driver:
'16'][:5] monza_quali.car_data[
Date | RPM | Speed | nGear | Throttle | Brake | DRS | Source | Time | SessionTime | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 2019-09-07 12:45:02.230 | 0 | 0 | 0 | 0 | False | 0 | car | 0 days 00:00:01.137000 | 0 days 00:00:01.137000 |
1 | 2019-09-07 12:45:02.470 | 0 | 0 | 0 | 0 | False | 0 | car | 0 days 00:00:01.377000 | 0 days 00:00:01.377000 |
2 | 2019-09-07 12:45:02.710 | 0 | 0 | 0 | 0 | False | 0 | car | 0 days 00:00:01.617000 | 0 days 00:00:01.617000 |
3 | 2019-09-07 12:45:02.990 | 0 | 0 | 0 | 0 | False | 0 | car | 0 days 00:00:01.897000 | 0 days 00:00:01.897000 |
4 | 2019-09-07 12:45:03.230 | 0 | 0 | 0 | 0 | False | 0 | car | 0 days 00:00:02.137000 | 0 days 00:00:02.137000 |
An even more helpful telemetry data report includes a derived accumulated distance travelled round the track on each lap, as well as on-track contextual information, such as the distance to, and identity of, the driver ahead on-track.
We can retrieve the telemetry data associated with a particular lap by calling the get_telemetry()
method on a single lap object:
# We need to index to a particular lap record
1].get_telemetry() lec_laps.iloc[
Date | SessionTime | DriverAhead | DistanceToDriverAhead | Time | RPM | Speed | nGear | Throttle | Brake | DRS | Source | Distance | RelativeDistance | Status | X | Y | Z | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | 2019-09-07 13:06:02.451 | 0 days 00:21:01.358000 | 400.271389 | 0 days 00:00:00 | 11464 | 319 | 8 | 100 | False | 8 | interpolation | 0.031139 | 0.000005 | OnTrack | -1413 | -840 | 1871 | |
3 | 2019-09-07 13:06:02.521 | 0 days 00:21:01.428000 | 400.271389 | 0 days 00:00:00.070000 | 11479 | 319 | 8 | 100 | False | 8 | pos | 6.278267 | 0.001083 | OnTrack | -1396 | -769 | 1872 | |
4 | 2019-09-07 13:06:02.524 | 0 days 00:21:01.431000 | 400.271389 | 0 days 00:00:00.073000 | 11509 | 320 | 8 | 100 | False | 8 | car | 6.546389 | 0.001129 | OnTrack | -1395 | -766 | 1872 | |
5 | 2019-09-07 13:06:02.764 | 0 days 00:21:01.671000 | 88 | 400.271389 | 0 days 00:00:00.313000 | 11584 | 323 | 8 | 100 | False | 8 | car | 28.079722 | 0.004843 | OnTrack | -1375 | -566 | 1872 |
6 | 2019-09-07 13:06:02.821 | 0 days 00:21:01.728000 | 88 | 400.671389 | 0 days 00:00:00.370000 | 11578 | 323 | 8 | 100 | False | 8 | pos | 33.206760 | 0.005728 | OnTrack | -1371 | -520 | 1873 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
597 | 2019-09-07 13:07:22.522 | 0 days 00:22:21.429000 | 88 | 151.229167 | 0 days 00:01:20.071000 | 11512 | 319 | 8 | 100 | False | 8 | pos | 5754.345540 | 0.992564 | OnTrack | -1438 | -1212 | 1870 |
598 | 2019-09-07 13:07:22.525 | 0 days 00:22:21.432000 | 88 | 150.462500 | 0 days 00:01:20.074000 | 11539 | 320 | 8 | 100 | False | 8 | car | 5754.613056 | 0.992610 | OnTrack | -1437 | -1208 | 1870 |
599 | 2019-09-07 13:07:22.765 | 0 days 00:22:21.672000 | 88 | 148.795833 | 0 days 00:01:20.314000 | 11575 | 322 | 8 | 100 | False | 8 | car | 5776.079722 | 0.996313 | OnTrack | -1413 | -945 | 1870 |
600 | 2019-09-07 13:07:22.822 | 0 days 00:22:21.729000 | 88 | 148.795833 | 0 days 00:01:20.371000 | 11607 | 322 | 8 | 100 | False | 8 | pos | 5781.180410 | 0.997192 | OnTrack | -1408 | -889 | 1871 |
601 | 2019-09-07 13:07:22.868 | 0 days 00:22:21.775000 | 88 | 148.795833 | 0 days 00:01:20.417000 | 11623 | 322 | 8 | 100 | False | 8 | interpolation | 5785.295662 | 0.997902 | OnTrack | -1403 | -845 | 1871 |
600 rows × 18 columns
We can more explicitly return the record for a specific lap by filtering on the LapNumner
:
def onLap(laps, lap):
"""Get record for a particular lap."""
return laps[laps["LapNumber"]==lap].iloc[0]
2) onLap(lec_laps,
Time 0 days 00:22:21.775000
DriverNumber 16
LapTime 0 days 00:01:20.417000
LapNumber 2
Stint 1
PitOutTime NaT
PitInTime NaT
Sector1Time 0 days 00:00:26.982000
Sector2Time 0 days 00:00:26.734000
Sector3Time 0 days 00:00:26.701000
Sector1SessionTime 0 days 00:21:28.340000
Sector2SessionTime 0 days 00:21:55.074000
Sector3SessionTime 0 days 00:22:21.775000
SpeedI1 323.0
SpeedI2 342.0
SpeedFL 318.0
SpeedST 342.0
IsPersonalBest False
Compound MEDIUM
TyreLife 2.0
FreshTyre True
LapStartTime 0 days 00:21:01.358000
Team Ferrari
Driver LEC
TrackStatus 1
IsAccurate True
LapStartDate 2019-09-07 13:06:02.451000
Name: 1, dtype: object
We can get then telemetry for the lap by calling the .get_telemetry()
method on the returned lap object:
2).get_telemetry() onLap(lec_laps,
Date | SessionTime | DriverAhead | DistanceToDriverAhead | Time | RPM | Speed | nGear | Throttle | Brake | DRS | Source | Distance | RelativeDistance | Status | X | Y | Z | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | 2019-09-07 13:06:02.451 | 0 days 00:21:01.358000 | 400.271389 | 0 days 00:00:00 | 11464 | 319 | 8 | 100 | False | 8 | interpolation | 0.031139 | 0.000005 | OnTrack | -1413 | -840 | 1871 | |
3 | 2019-09-07 13:06:02.521 | 0 days 00:21:01.428000 | 400.271389 | 0 days 00:00:00.070000 | 11479 | 319 | 8 | 100 | False | 8 | pos | 6.278267 | 0.001083 | OnTrack | -1396 | -769 | 1872 | |
4 | 2019-09-07 13:06:02.524 | 0 days 00:21:01.431000 | 400.271389 | 0 days 00:00:00.073000 | 11509 | 320 | 8 | 100 | False | 8 | car | 6.546389 | 0.001129 | OnTrack | -1395 | -766 | 1872 | |
5 | 2019-09-07 13:06:02.764 | 0 days 00:21:01.671000 | 88 | 400.271389 | 0 days 00:00:00.313000 | 11584 | 323 | 8 | 100 | False | 8 | car | 28.079722 | 0.004843 | OnTrack | -1375 | -566 | 1872 |
6 | 2019-09-07 13:06:02.821 | 0 days 00:21:01.728000 | 88 | 400.671389 | 0 days 00:00:00.370000 | 11578 | 323 | 8 | 100 | False | 8 | pos | 33.206760 | 0.005728 | OnTrack | -1371 | -520 | 1873 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
597 | 2019-09-07 13:07:22.522 | 0 days 00:22:21.429000 | 88 | 151.229167 | 0 days 00:01:20.071000 | 11512 | 319 | 8 | 100 | False | 8 | pos | 5754.345540 | 0.992564 | OnTrack | -1438 | -1212 | 1870 |
598 | 2019-09-07 13:07:22.525 | 0 days 00:22:21.432000 | 88 | 150.462500 | 0 days 00:01:20.074000 | 11539 | 320 | 8 | 100 | False | 8 | car | 5754.613056 | 0.992610 | OnTrack | -1437 | -1208 | 1870 |
599 | 2019-09-07 13:07:22.765 | 0 days 00:22:21.672000 | 88 | 148.795833 | 0 days 00:01:20.314000 | 11575 | 322 | 8 | 100 | False | 8 | car | 5776.079722 | 0.996313 | OnTrack | -1413 | -945 | 1870 |
600 | 2019-09-07 13:07:22.822 | 0 days 00:22:21.729000 | 88 | 148.795833 | 0 days 00:01:20.371000 | 11607 | 322 | 8 | 100 | False | 8 | pos | 5781.180410 | 0.997192 | OnTrack | -1408 | -889 | 1871 |
601 | 2019-09-07 13:07:22.868 | 0 days 00:22:21.775000 | 88 | 148.795833 | 0 days 00:01:20.417000 | 11623 | 322 | 8 | 100 | False | 8 | interpolation | 5785.295662 | 0.997902 | OnTrack | -1403 | -845 | 1871 |
600 rows × 18 columns
Find the fastest lap from a set of laps:
= lec_laps.pick_fastest()
lec_fast_lap lec_fast_lap
Time 0 days 01:11:14.868000
DriverNumber 16
LapTime 0 days 00:01:19.307000
LapNumber 14
Stint 5
PitOutTime NaT
PitInTime NaT
Sector1Time 0 days 00:00:26.469000
Sector2Time 0 days 00:00:26.412000
Sector3Time 0 days 00:00:26.426000
Sector1SessionTime 0 days 01:10:22.030000
Sector2SessionTime 0 days 01:10:48.442000
Sector3SessionTime 0 days 01:11:14.868000
SpeedI1 329.0
SpeedI2 347.0
SpeedFL 321.0
SpeedST 349.0
IsPersonalBest True
Compound SOFT
TyreLife 3.0
FreshTyre True
LapStartTime 0 days 01:09:55.561000
Team Ferrari
Driver LEC
TrackStatus 2
IsAccurate True
LapStartDate 2019-09-07 13:54:56.654000
Name: 13, dtype: object
We can also iterate over the laps, for example as monza_quali.laps.pick_driver(16).iterlaps()
; each iteration returns a 2-tuple of the car number and the data for a particular lap.
We can get the telemetry for that driver on each lap by calling the .get_telemetry()
method on the second (lap) element in the 2-tuple.
2.4 Simple Plots
The fastf1
package also provides a range of tools to support the plotting of data:
from matplotlib import pyplot as plt
from fastf1 import plotting
# Configures timebase for axis ticks
plotting.setup_mpl()
For example, a range of labels and colour schemes are defined for enriching displays:
ff1.plotting.TEAM_TRANSLATE
{'MER': 'mercedes',
'FER': 'ferrari',
'RBR': 'red bull',
'MCL': 'mclaren',
'APN': 'alpine',
'AMR': 'aston martin',
'ARR': 'alfa romeo',
'APT': 'alphatauri',
'HAA': 'haas',
'WIL': 'williams'}
ff1.plotting.TEAM_COLORS
{'mercedes': '#00d2be',
'ferrari': '#dc0000',
'red bull': '#0600ef',
'mclaren': '#ff8700',
'alpine': '#0090ff',
'aston martin': '#006f62',
'alfa romeo': '#900000',
'alphatauri': '#2b4562',
'haas': '#ffffff',
'williams': '#005aff'}
ff1.plotting.DRIVER_COLORS
{'valtteri bottas': '#900000',
'zhou guanyu': '#500000',
'pierre gasly': '#2b4562',
'yuki tsunoda': '#356cac',
'fernando alonso': '#0090ff',
'esteban ocon': '#70c2ff',
'sebastian vettel': '#006f62',
'lance stroll': '#25a617',
'nico hulkenberg': '#2f9b90',
'charles leclerc': '#dc0000',
'carlos sainz': '#ff8181',
'kevin magnussen': '#ffffff',
'mick schumacher': '#cacaca',
'daniel ricciardo': '#ff8700',
'lando norris': '#eeb370',
'lewis hamilton': '#00d2be',
'george russell': '#24ffff',
'max verstappen': '#0600ef',
'sergio perez': '#716de2',
'alexander albon': '#005aff',
'nicholas latifi': '#012564'}
ff1.plotting.COLOR_PALETTE
['#FF79C6', '#50FA7B', '#8BE9FD', '#BD93F9', '#FFB86C', '#FF5555', '#F1FA8C']
We can trivially plot the speed against the distance round the track, for example, using telemetry data:
#Get the telemetry for a lap
= lec_fast_lap.get_telemetry()
lec_telem
= lec_telem['Distance']
t = lec_telem['Speed']
vCar
# Create a chart object
= plt.subplots()
fig, ax
# Plot the speed against distance round track
='Personal fastest lap')
ax.plot(t, vCar, label
# Add axis labels
'Distance round track')
ax.set_xlabel('Speed [Km/h]')
ax.set_ylabel(
# Add title
'Example speed track round track')
ax.set_title(
# Show legend
; ax.legend()
With access to lap data associated with a session, we can generate a wide range of charts that summarise different aspects of the session.
For example, let’s get the data from a particular race:
= ff1.get_session(2020, 'Turkish Grand Prix', 'R')
race race.load()
core INFO Loading data for Turkish Grand Prix - Race [v2.2.9]
api INFO Using cached data for driver_info
api INFO Using cached data for timing_data
api INFO Using cached data for timing_app_data
core INFO Processing timing data...
api INFO Using cached data for session_status_data
api INFO Using cached data for track_status_data
api INFO Using cached data for car_data
api INFO Using cached data for position_data
api INFO Using cached data for weather_data
api INFO Using cached data for race_control_messages
core INFO Finished loading data for 20 drivers: ['44', '11', '5', '16', '55', '33', '23', '4', '18', '3', '31', '26', '10', '77', '7', '63', '20', '8', '6', '99']
The following function can be used to plot a chart showing laptimes over the course of a session for one or more drivers.
from fastf1.plotting import DRIVER_COLORS, DRIVER_TRANSLATE
def plot_laptimes(race, drivers):
"""Plot laptimes over the course of a race."""
= [drivers] if isinstance(drivers, str) else drivers
drivers = plt.subplots()
fig, ax # Generate an appropriately coloured trace for each driver
for _driver in drivers:
= race.laps.pick_driver(_driver)
driver 'LapNumber'], driver['LapTime'],
ax.plot(driver[=DRIVER_COLORS[DRIVER_TRANSLATE[_driver]])
color# Annotate the chart with a title and axis labels
" vs ".join(drivers))
ax.set_title("Lap Number")
ax.set_xlabel("Lap Time")
ax.set_ylabel(return fig, ax
We can now compare laptimes over the course of the race in a graphical way:
"HAM", "LEC"]); plot_laptimes(race, [
2.5 Telemetry Visualisations via Track Maps
X and Y positions seems to be co-ordinate locations for an on-screen display ( https://github.com/theOehrly/Fast-F1/issues/64 ). This means we probably aren’t seeing location data at a resolution good enough to display racing lines on a map, which accurate GPS data would give us.
We can create a simple function to display a map of the track as generated from the X
and Y
co-ordinates of the sampled telemetry data.
def plot_track(lap, linewidth=16, color="white"):
"""Generate a track map from telemetry data co-ordinates."""
= plt.subplots(sharex=True, sharey=True,
fig, ax =(12, 6.75))
figsize'off')
ax.axis(
'X'], lap.telemetry['Y'],
ax.plot(lap.telemetry[=color, linestyle='-', linewidth=linewidth, zorder=0)
colorreturn fig, ax
= plot_track(lec_fast_lap)
fig, ax
# Add a title to the figure object
f'Track map', size=24, y=0.97); fig.suptitle(
The matplotlib
documentation provides an example for generating a multicoloured line from a list of co-ordinates. Consecutive pairs of co-ordinates define consecutive line segments. The line segments are then coloured according to a particular colour mapped value.
The following function will generate a coloured trace overlaying the track map that visualises one of the telemetry measures.
from matplotlib.collections import LineCollection
import matplotlib as mpl
import numpy as np
def get_multicoloured_line(lap, color='Speed', title='',
=mpl.cm.plasma, linewidth=5, ax=None):
colormap"""Generate a matplotlib plottable mutlicoloured line."""
if ax is None:
= plot_track(lap, linewidth=linewidth+2)
fig, ax else:
= ax.get_figure()
fig
##############################################################################
# Create a set of line segments so that we can color them
# individually. This creates the points as a N x 1 x 2 array so that we can
# stack points together easily to get the segments. The segments array for
# line collection needs to be (numlines) x (points per line) x 2 (for x and y)
= lap.telemetry['X']
X = lap.telemetry['Y']
Y
= lap.telemetry[color]
_color
= np.array([X, Y]).T.reshape(-1, 1, 2)
points = np.concatenate([points[:-1], points[1:]], axis=1)
segments
if color is not None:
# Create a continuous norm to map from data points to colors
= plt.Normalize(_color.min(), _color.max())
norm = LineCollection(segments, cmap=colormap, norm=norm, linestyle='-', linewidth=5)
lc # Set the values used for colormapping
lc.set_array(_color)else:
= LineCollection(segments, linestyle='-', linewidth=linewidth)
lc
= ax.add_collection(lc)
line # Add color bar legend
=ax, orientation="horizontal")
fig.colorbar(line, ax# Add title
if title:
f"{title} ({color})", size=24, y=0.97)
fig.suptitle(return lc
We can now plot telemtry data traces against the position on track at which they we recorded:
="Example track map"); get_multicoloured_line(lec_fast_lap, title
"Throttle", title="Example track map"); get_multicoloured_line(lec_fast_lap,
2.6 Retrieving Data from the ergast API
Basic support for retrieving data from the ergast historical motor racing results data API is provided.
For example, we can return summary results data for a particular race ("Race"
) or qualifying session ("Qualifying
“) in a particular season:
from fastf1 import ergast
import pandas as pd
= ergast.fetch_results(2022, 2, "Race")
erd_race
1] erd_race[:
[{'number': '1',
'position': '1',
'positionText': '1',
'points': '25',
'Driver': {'driverId': 'max_verstappen',
'permanentNumber': '33',
'code': 'VER',
'url': 'http://en.wikipedia.org/wiki/Max_Verstappen',
'givenName': 'Max',
'familyName': 'Verstappen',
'dateOfBirth': '1997-09-30',
'nationality': 'Dutch'},
'Constructor': {'constructorId': 'red_bull',
'url': 'http://en.wikipedia.org/wiki/Red_Bull_Racing',
'name': 'Red Bull',
'nationality': 'Austrian'},
'grid': '4',
'laps': '50',
'status': 'Finished',
'Time': {'millis': '5059293', 'time': '1:24:19.293'},
'FastestLap': {'rank': '2',
'lap': '50',
'Time': {'time': '1:31.772'},
'AverageSpeed': {'units': 'kph', 'speed': '242.191'}}}]
We can trivially cast the returned JSON data to a flattened pandas
dataframe:
pd.json_normalize(erd_race).head()
number | position | positionText | points | grid | laps | status | Driver.driverId | Driver.permanentNumber | Driver.code | ... | Constructor.url | Constructor.name | Constructor.nationality | Time.millis | Time.time | FastestLap.rank | FastestLap.lap | FastestLap.Time.time | FastestLap.AverageSpeed.units | FastestLap.AverageSpeed.speed | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 25 | 4 | 50 | Finished | max_verstappen | 33 | VER | ... | http://en.wikipedia.org/wiki/Red_Bull_Racing | Red Bull | Austrian | 5059293 | 1:24:19.293 | 2 | 50 | 1:31.772 | kph | 242.191 |
1 | 16 | 2 | 2 | 19 | 2 | 50 | Finished | leclerc | 16 | LEC | ... | http://en.wikipedia.org/wiki/Scuderia_Ferrari | Ferrari | Italian | 5059842 | +0.549 | 1 | 48 | 1:31.634 | kph | 242.556 |
2 | 55 | 3 | 3 | 15 | 3 | 50 | Finished | sainz | 55 | SAI | ... | http://en.wikipedia.org/wiki/Scuderia_Ferrari | Ferrari | Italian | 5067390 | +8.097 | 3 | 48 | 1:31.905 | kph | 241.841 |
3 | 11 | 4 | 4 | 12 | 1 | 50 | Finished | perez | 11 | PER | ... | http://en.wikipedia.org/wiki/Red_Bull_Racing | Red Bull | Austrian | 5070093 | +10.800 | 4 | 46 | 1:32.042 | kph | 241.481 |
4 | 63 | 5 | 5 | 10 | 6 | 50 | Finished | russell | 63 | RUS | ... | http://en.wikipedia.org/wiki/Mercedes-Benz_in_... | Mercedes | German | 5092025 | +32.732 | 7 | 43 | 1:32.821 | kph | 239.454 |
5 rows × 26 columns
The ergast API can also provide season summary information:
2022)).head(2).T pd.json_normalize(ergast.fetch_season(
0 | 1 | |
---|---|---|
season | 2022 | 2022 |
round | 1 | 2 |
url | http://en.wikipedia.org/wiki/2022_Bahrain_Gran... | http://en.wikipedia.org/wiki/2022_Saudi_Arabia... |
raceName | Bahrain Grand Prix | Saudi Arabian Grand Prix |
date | 2022-03-20 | 2022-03-27 |
time | 15:00:00Z | 17:00:00Z |
Circuit.circuitId | bahrain | jeddah |
Circuit.url | http://en.wikipedia.org/wiki/Bahrain_Internati... | http://en.wikipedia.org/wiki/Jeddah_Street_Cir... |
Circuit.circuitName | Bahrain International Circuit | Jeddah Corniche Circuit |
Circuit.Location.lat | 26.0325 | 21.6319 |
Circuit.Location.long | 50.5106 | 39.1044 |
Circuit.Location.locality | Sakhir | Jeddah |
Circuit.Location.country | Bahrain | Saudi Arabia |
FirstPractice.date | 2022-03-18 | 2022-03-25 |
FirstPractice.time | 12:00:00Z | 14:00:00Z |
SecondPractice.date | 2022-03-18 | 2022-03-25 |
SecondPractice.time | 15:00:00Z | 17:00:00Z |
ThirdPractice.date | 2022-03-19 | 2022-03-26 |
ThirdPractice.time | 12:00:00Z | 14:00:00Z |
Qualifying.date | 2022-03-19 | 2022-03-26 |
Qualifying.time | 15:00:00Z | 17:00:00Z |
Sprint.date | NaN | NaN |
Sprint.time | NaN | NaN |
We can also request the metadata describing an event more directly:
2022, 2)).T pd.json_normalize(ergast.fetch_weekend(
0 | |
---|---|
season | 2022 |
round | 2 |
url | http://en.wikipedia.org/wiki/2022_Saudi_Arabia... |
raceName | Saudi Arabian Grand Prix |
date | 2022-03-27 |
time | 17:00:00Z |
Circuit.circuitId | jeddah |
Circuit.url | http://en.wikipedia.org/wiki/Jeddah_Street_Cir... |
Circuit.circuitName | Jeddah Corniche Circuit |
Circuit.Location.lat | 21.6319 |
Circuit.Location.long | 39.1044 |
Circuit.Location.locality | Jeddah |
Circuit.Location.country | Saudi Arabia |
Circuit.Location.alt | 5 |
FirstPractice.date | 2022-03-25 |
FirstPractice.time | 14:00:00Z |
SecondPractice.date | 2022-03-25 |
SecondPractice.time | 17:00:00Z |
ThirdPractice.date | 2022-03-26 |
ThirdPractice.time | 14:00:00Z |
Qualifying.date | 2022-03-26 |
Qualifying.time | 17:00:00Z |
2.7 Summary
This chapter has provided a brief overview of some of the key features of the fastf1
API, demonstrating how we can look up event metadata as well as lap information and car telemetry data.
Some support is also provided for improving the quality of data visualisations by setting appropriate colour schemes or configuring matplotlib
axes, for example.