-
Notifications
You must be signed in to change notification settings - Fork 1
/
F1 minisectors.py
312 lines (175 loc) · 10.4 KB
/
F1 minisectors.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
" I am not very good at coding just an F1 fan who learnt some basic python at university "
" For calculating and graphing data "
" I have added some of my custom functions/dictionaries e.g. ollie_color_scheme "
" That takes into account the year of the session and changes the colour accordingly "
" It also gives drivers in the same team a slighlty different colour to differentiate "
" I have also used the a custom font (f1 font) but this should be an easy work around "
import fastf1
from fastf1 import plotting
from driver_year_color import *
from ollie_color_scheme import *
from matplotlib import pyplot as plt
from matplotlib.pyplot import figure
from matplotlib.collections import LineCollection
from matplotlib import cm
import numpy as np
import pandas as pd
from matplotlib.colors import ListedColormap
import matplotlib as mpl
import matplotlib.font_manager as font_manager
import os
from matplotlib import font_manager as fm, rcParams
import datetime
ollie_color_scheme()
# Enable cache
fastf1.Cache.enable_cache('/Users/....cache') #change to unique cache location
# Load the session
session = fastf1.get_session(2021, 'United States Grand Prix' , 'Q')
# Load all laps with telemetry
laps = session.load_laps(with_telemetry=True)
# Retrieve data from laps within 102.5% of best session time
# Allows for more reliable data collection
laps=laps.pick_quicklaps(threshold=1.03)
# Select only fast laps in Q3 # Not sure how to keep accurate if Q3 red flag
# Can hash out / delete for FP sessions
laps = laps.loc[laps['Time'] >= (laps['Time'].max() - datetime.timedelta(minutes = 14))]
# Get all unique driver number and TLAs
drv_num=laps['DriverNumber'].unique().astype(int)
# All drivers that completed fast laps in Q3
drivers=laps['Driver'].unique()
# Create list of wanted drivers e.g. head to head with two drivers ['HAM', 'VER']
driver_list = ['HAM', 'VER'] # Make = [] if want all drivers
# If you want all drivers laps from Q3 use driver_list = drivers.tolist()
#driver_list = drivers.tolist()
# Something like...
if len(driver_list) == 0:
driver_list = drivers.tolist()
telemetry = pd.DataFrame()
for driver in driver_list: #Gets telemetry for all drivers in list
driver_telemetry = laps.pick_driver(driver).pick_fastest().get_telemetry().add_distance() #Picks fastest lap in Q3
driver_telemetry['Driver'] = driver
driver_telemetry['Colour'] = driver_year_color(laps.pick_driver(driver).pick_fastest()['Driver'], session.weekend.year)
telemetry = telemetry.append(driver_telemetry)
# Only keep columns that we want/need for later
telemetry = telemetry[[ 'Distance', 'Speed', 'X', 'Y', 'Driver', 'Colour']]
" Credit @jasperhvat on medium for this snippet and inspiration"
#https://medium.com/towards-formula-1-analysis/formula-1-data-analysis-tutorial-2021-russian-gp-to-box-or-not-to-box-da6399bd4a39
# Choose how many minisectors we want e.g. 25
num_minisectors = 25
# What is the total distance of a lap?
total_distance = max(telemetry['Distance']) #Not the same for all, maybe find a better method
# Generate equally sized mini-sectors
minisector_length = total_distance / num_minisectors
#Assign distances to each minisector
minisectors = [0]
for i in range(0, (num_minisectors - 1)):
minisectors.append(minisector_length * (i + 1))
# Assign minisector to every row in the telemetry data
telemetry['Minisector'] = telemetry['Distance'].apply(
lambda z: (
minisectors.index(
min(minisectors, key=lambda x: abs(x-z)))+1
)
)
# Calculate fastest driver (highest average speed) per mini sector
average_speed = telemetry.groupby(['Minisector' , 'Driver'])['Speed'].mean().reset_index()
# Select the driver with the highest average speed
fastest_drivers= average_speed.loc[average_speed.groupby(['Minisector', 'Driver'])['Speed'].idxmax()]
#Perhaps for use later in interactive sector plot
b=fastest_drivers.sort_values(by=['Minisector', 'Speed']) #try to have speed descending order?
# Sort to find fastest driver for each minisector
fastest_minisector = fastest_drivers.sort_values('Speed', ascending = False).drop_duplicates(['Minisector'])
fastest_minisector = fastest_minisector.sort_values(by = ['Minisector'])
fastest_minisector = fastest_minisector[[ 'Minisector', 'Driver' , 'Speed']].rename(columns={'Speed': 'Sector_avg_speed', 'Driver' : 'Fastest_driver'})
# Get rid of the speed column and rename the driver column
fastest_drivers = fastest_drivers[[ 'Minisector', 'Driver']].rename(columns={'Driver': 'Fastest_driver'})
# Join the fastest minisector dataframe with the full telemetry
# to merge minisector, fastest driver in each and avg speed in that minisector
telemetry = telemetry.merge(fastest_minisector, on=['Minisector'])
# Order the data by distance
telemetry = telemetry.sort_values(by=['Distance'])
# Assign integer value to the fastest driver in each minisector (driver number)
for driver in driver_list:
telemetry.loc[telemetry['Fastest_driver'] == driver , 'Fastest_driver_int'] = int(drv_num[np.where(drivers==driver)])
# Get X, Y coordinates of the circuit
x = np.array(telemetry['X'].values)
y = np.array(telemetry['Y'].values)
# Divide up the circuit into 2D into segments
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
minisector_int = telemetry['Fastest_driver_int'].to_numpy().astype(float)
minisector_fastest_drivers = telemetry['Fastest_driver'].unique()
minisector_fastest_drivers_num = telemetry['Fastest_driver_int'].unique()
minisector_colours = []
for driver in minisector_fastest_drivers:
minisector_colours.append(driver_year_color(laps.pick_driver(driver).pick_fastest()['Driver'], session.weekend.year))
minisector_colours = np.array(minisector_colours)
minisector_details = pd.DataFrame({'Fastest_driver' : minisector_fastest_drivers, 'Fastest_driver_int' : minisector_fastest_drivers_num, 'Minisector_colours' : minisector_colours }, columns=['Fastest_driver', 'Fastest_driver_int', 'Minisector_colours'])
# Sort order by driver number min to max.
minisector_details = minisector_details.sort_values(by = ['Fastest_driver_int'])
# Get driver number, colour, name for plotting
num_list_ordered = minisector_details['Fastest_driver_int'].tolist()
colour_list_ordered = minisector_details['Minisector_colours'].tolist()
driver_name_ordered = minisector_details['Fastest_driver'].tolist()
############################################################################################################################################
"This seems to work despite but i dont really undertsand why"
#Quick fix to sort out colorbar error, add 1 to the last number
num_list_ordered.insert(len(num_list_ordered),(num_list_ordered[-1] +1))
##############################################################################################################################
# Create figure and size of the figure
fig, ax = plt.rcParams['figure.figsize'] = [16, 9]
# Define the colour map using the driver colours
cmap = mpl.colors.ListedColormap(colour_list_ordered)
cmap.set_over('0.25')
cmap.set_under('0.75')
bounds = num_list_ordered
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
# Create a linecollection of segments using the colour map
lc_comp = LineCollection(segments, norm=norm, cmap=cmap)
lc_comp.set_array(minisector_int)
lc_comp.set_linewidth(4)
# Plot the linecollection
plt.gca().add_collection(lc_comp)
plt.axis('equal')
plt.axis('off')
# For 1v1 driver vs driver
plt.title((driver_list[0] + ' vs ' + driver_list[1] + ' ' + str(session.weekend.year) + ' ' + str(session.weekend.name) + ' ' + str(session.name) + '\n' + 'Number of minisectors = ' + str(num_minisectors) ), fontproperties=prop, fontsize = 20)
# For many drivers
#plt.title((str(session.weekend.year) + ' ' + str(session.weekend.name) + ' ' + str(session.name) + '\n' + 'Number of minisectors = ' + str(num_minisectors) ), fontproperties=prop, fontsize = 20)
plt.show()
"An attempt to annotate map with driver numbers"
"There is probably a much cleaner way to do this"
"WARNING:- VERY MESSY"
"Effectively the same thing as before but with x,y coordinates"
############################################################################################################################################
# Calculate the average (mean) x,y positions of the fastest minisectors
average_xy = telemetry.groupby(['Minisector' , 'Driver'])['Speed' , 'X', 'Y'].mean().reset_index()
# Ideally the median (x,y) would work best but it doesnt select the driver with the highest mean speed through the sector
# Select the driver with the highest average speed
fastest_drivers_xy= average_xy.loc[average_xy.groupby(['Minisector', 'Driver', 'Y', 'X'])['Speed'].idxmax()]
# Sort to find fastest driver for each minisector
fastest_minisector_xy = fastest_drivers_xy.sort_values('Speed', ascending = False).drop_duplicates(['Minisector'])
fastest_minisector_xy = fastest_minisector_xy.sort_values(by = ['Minisector'])
fastest_minisector_xy = fastest_minisector_xy[[ 'Minisector', 'Driver' , 'Speed', 'X', 'Y']].rename(columns={'Speed': 'Sector_avg_speed', 'Driver' : 'Fastest_driver', 'X' : 'X_avg' , 'Y' : 'Y_avg'})
# Rename the driver column
fastest_drivers_xy = fastest_drivers_xy[[ 'Minisector', 'Driver', 'Speed', 'X', 'Y']].rename(columns={'Driver': 'Fastest_driver', 'X' : 'X_avg' , 'Y' : 'Y_avg'})
############################################################################################################################################
for driver in fastest_minisector_xy['Fastest_driver']:
fastest_minisector_xy.loc[fastest_minisector_xy['Fastest_driver'] == driver , 'Fastest_driver_int'] = int(drv_num[np.where(drivers==driver)])
# Take the mean x,y coordinates for each minisector
x_coords = fastest_minisector_xy['X_avg'].tolist()
y_coords = fastest_minisector_xy['Y_avg'].tolist()
driver_numbers = fastest_minisector_xy['Fastest_driver_int'].values.astype(int)
#for i in range(len(driver_numbers)):
# plt.text(x_coords[i], y_coords[i], str(driver_numbers[i]),
# fontsize=5, FontProperties = prop)
# My attempt to make the labelling on or off
def minisector_labels(x_coords, y_coords, driver_numbers):
for i in range(len(driver_numbers)):
plt.text(x_coords[i], y_coords[i], str(driver_numbers[i]),
fontsize=5, FontProperties = prop)
# Call the labelling function
# Hash out the following line to remove labels
minisector_labels(x_coords, y_coords, driver_numbers)