Newer
Older
from fastf1.core import Session, Lap, Laps
from DataHandler import DataHandler
class DataAnalyser(DataHandler):
# ===== Overtakes =====
def analyseRacesForOvertakes(self, races: List[Session]):
overtakesInRaces: List[List[int]] = [[]]
for race in races:
overtakesInRaces.append(self.analyseRaceForOvertakes(race))
return overtakesInRaces
def analyseRaceForOvertakes(self, race: Session):
Lennard Geese
committed
overtakesInLaps: List[int] = self.countOvertakesPerLap(race)
return overtakesInLaps
Lennard Geese
committed
def countOvertakesPerLap(self, race: Session):
overtakes: list[int] = []
for lapNumber in range(1, race.total_laps + 1): # in this context, index 1 = lap 1
overtakes.append(self.countOvertakesInLap(lapNumber, race))
return overtakes
Lennard Geese
committed
def prepareWeightedOrderFromLap(self, lapNumber: int, race: Session):
'''Prepare a list from specific lap & race, that can be sorted via bubble sort to determine the number of
overtakes that occured in that lap.
:param lapNumber: Which lap to prepare from the given race. Note that value of 1 will return a list ordered by
starting grid, as there is no previous lap.
:param race: Race from which to pull the given lap from.
:return: list[(Lap, int)]: A list with pairs of every driver's lap and their position at the end of the lap. Entries are
sorted by the driver's positions at the start of the lap. If an invalid lap number (below 1 or above the number
of laps the race had), all laps in the list will be either None objects or empty Panda Dataframes.
'''
Lennard Geese
committed
# TODO: Implement returning starting grid for lapNumber = 0
Lennard Geese
committed
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
previousLaps: Laps = race.laps.pick_laps(lapNumber - 1)
currentLaps: Laps = race.laps.pick_laps(lapNumber)
out: int = self.numberOfDrivers # weighting for drivers that did not complete the lap (they are "out" of the lap/race)
weightedOrder: list[(Lap, int)] = [(None, out)] * 20 # data from start of lap + weighting for end of lap; initialize to add laps in lap's starting order
# Put every driver's laps in a sortable array & apply weighting based on position at end of lap
for driver in race.drivers:
driverCurrentLap: Laps = previousLaps.pick_drivers(
driver) # should only get 1 lap, but data type shenanigans
driverNextLap: Laps = currentLaps.pick_drivers(driver)
startPosition: int = out
endPosition: int = out
try:
startPosition = int(driverCurrentLap['Position'].iloc[0])
endPosition = int(driverNextLap['Position'].iloc[0])
except ValueError:
if self.activateDebugOvertakeAnalysis: print(f"Could not fetch positions from lap; driver %d likely didn't finish lap %d or %d",
driver, lapNumber, (lapNumber + 1))
except IndexError:
if self.activateDebugOvertakeAnalysis: print("['Position'].iloc[0] was out of bounds; lap was likely empty because driver previously left the race")
weightedOrder[startPosition - 1] = (driverCurrentLap, endPosition)
return weightedOrder
def countOvertakesInLap(self, lapNumber: int, race: Session):
orderToSort: list[(Lap, int)] = self.prepareWeightedOrderFromLap(lapNumber, race)
overtakes: int = 0
i: int = 0
while i < len(orderToSort) - 1: # do not change to for-loop, seems to not like resetting the index
weightedDriverAhead: list[(Lap, int)] = orderToSort[i]
weightedDriverBehind: list[(Lap, int)] = orderToSort[i + 1]
if weightedDriverAhead[1] > weightedDriverBehind[1]:
temp: int = orderToSort[i]
orderToSort[i] = orderToSort[i + 1]
orderToSort[i + 1] = temp
i = -1 # reset to first index; -1 because loop will set it to 0
if not ( # don't count overtake if driver nonexistent or if one of them is on an in-lap
weightedDriverAhead[0] is None
or weightedDriverBehind[0] is None
or self.isAnInLap(weightedDriverAhead[0])
or self.isAnInLap(weightedDriverBehind[0])
Lennard Geese
committed
): overtakes += 1
i += 1
return overtakes
Lennard Geese
committed
# TODO: Implement :)
return False
def getGridPositions(self, race: Session):
sessionResults = race.results
gridPositions: dict[str, int] = {} # driverId & lapNumber
for i in range(self.numberOfDrivers):
driverPosition = sessionResults['GridPosition'].iloc[i]
driverAbbreviation = sessionResults['Abbreviation'].iloc[i]
gridPositions[driverAbbreviation] = driverPosition
if self.activateDebugOvertakeAnalysis:
print(f"\nLap: 0")
for i in range(len(gridPositions)):
position: int = i + 1
driverAtPosition = self.getDriverByPositionFromMap(gridPositions, position)
print(f"P{position}: {driverAtPosition}")
return gridPositions
def getRunningLapsPositions(self, race: Session):
runningLapsPositions: List[dict[str, int]] = []
allRaceLaps = race.laps
for raceLapIndex in range(race.total_laps):
runningLapPositions: dict[str, int] = {} # driverId & lapNumber
for driver in race.drivers:
raceLap = allRaceLaps.pick_laps(raceLapIndex + 1) # Lap 0 doesn't exist
raceLap = raceLap.pick_drivers(driver)
try :
driverAbbreviation = raceLap['Driver'].iloc[0]
driverPosition = raceLap['Position'].iloc[0]
runningLapPositions[driverAbbreviation] = driverPosition
Lennard Geese
committed
except: # triggered when not all drivers that took part reached lap end (i.e. when value is NaN), probably by crashing or being behind
x = 0 # do nothing
runningLapsPositions.append(runningLapPositions)
# debug
if self.activateDebugOvertakeAnalysis:
for raceLapIndex in range(race.total_laps):
print(f"\nLap: {raceLapIndex + 1}")
runningLapPositions = runningLapsPositions[raceLapIndex]
for i in range(len(runningLapPositions)):
position: int = i + 1
driverAtPosition = self.getDriverByPositionFromMap(runningLapPositions, position)
print(f"P{position}: {driverAtPosition}")
def getDriverByPositionFromMap(self, positions: dict[str, int], position: int):
driver = list(positions.keys())[list(positions.values()).index(position)]
return driver
# ===== Weather =====
def analyseRacesForWeather(self, races: List[Session]):
weatherInRaces: List[List[bool]] = [[]] # isRaining per lap per race
for race in races:
weatherInRace = self.analyseRaceForWeather(race)
weatherInRaces.append(weatherInRace)
return weatherInRaces
def analyseRaceForWeather(self, race: Session):
x = 0
return 0
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
# ===== Tire Changes =====
def getEarliestTireChanges(self, races: List[Session]):
earliestTireChanges: List[int] = [[]] # isRaining per lap per race
for race in races:
earliestTireChange = self.getEarliestTireChange(race)
earliestTireChanges.append(earliestTireChange)
return earliestTireChanges
# Returns -1 if no tire change occured
def getEarliestTireChange(self, race: Session):
earliestTireChangeLap: int = -1
compoundsPerLap: List[List[str]] = self.getCompoundsForRace(race)
compoundsPerLap[0] = compoundsPerLap[1] # presume grid tires same as 1st lap; races are only picked if weather change after first 10 laps anyways, so its ok
startingCompound: str = self.getPredominantCompound(compoundsPerLap[0])
earliestTireChangeLap = self.getFirstLapWithOppositeCompound(compoundsPerLap, startingCompound)
return earliestTireChangeLap
def getLatestTireChanges(self, races: List[Session]):
latestTireChanges: List[int] = [[]] # isRaining per lap per race
for race in races:
latestTireChange = self.getLatestTireChange(race)
latestTireChanges.append(latestTireChange)
return latestTireChanges
# Returns -1 if no tire change occured
def getLatestTireChange(self, race: Session):
latestTireChangeLap: int = -1
compoundsPerLap: List[List[str]] = self.getCompoundsForRace(race)
compoundsPerLap[0] = compoundsPerLap[1] # presume grid tires same as 1st lap; races are only picked if weather change after first 10 laps anyways, so its ok
startingCompound: str = self.getPredominantCompound(compoundsPerLap[0])
latestTireChangeLap = self.getFirstLapWithoutCompound(compoundsPerLap, startingCompound)
return latestTireChangeLap
def getFirstLapWithoutCompound(self, compoundsPerLap: List[List[str]], startingCompound: str):
currentLap = 0
filter = self.setFilter(startingCompound)
for compoundsThisLap in compoundsPerLap:
noStartingCompoundsLeft = True
for compound in compoundsThisLap:
if compound in filter:
noStartingCompoundsLeft = False
if noStartingCompoundsLeft: return currentLap
currentLap += 1
return -1 # no lap without compound found; all laps use same compound type
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
def getCompoundsForRace(self, race: Session):
compoundsPerLap: List[List[str]] = [[]]
allRaceLaps = race.laps
for raceLapIndex in range(race.total_laps):
compoundsThisLap: List[str] = []
for driver in race.drivers:
raceLap = allRaceLaps.pick_laps(raceLapIndex + 1) # Lap 0 doesn't exist
raceLap = raceLap.pick_drivers(driver)
try:
driverAbbreviation = raceLap['Driver'].iloc[0]
compound = raceLap['Compound'].iloc[0]
compoundsThisLap.append(compound)
except: # triggered when not all drivers that took part reached lap, probably by crashing or being behind
x = 0 # do nothing
compoundsPerLap.append(compoundsThisLap)
return compoundsPerLap
def getPredominantCompound(self, compoundsThisLap: List[str]):
slickCounter = 0
interCounter = 0
wetCounter = 0
for compound in compoundsThisLap:
if compound in self.slickCompounds: slickCounter += 1
if compound == 'INTERMEDIATE': interCounter += 1
if compound == 'WET': wetCounter += 1
mostUsed = max(slickCounter, interCounter, wetCounter)
if slickCounter == mostUsed: return 'SLICK'
if interCounter == mostUsed: return 'INTERMEDIATE'
if wetCounter == mostUsed: return 'WET'
return 'error'
def getFirstLapWithOppositeCompound(self, compoundsPerLap: List[List[str]], startingCompound: str):
filter = self.setFilter(startingCompound)
currentLap = 0
for compoundsThisLap in compoundsPerLap:
for compound in compoundsThisLap:
if compound not in filter:
return currentLap
currentLap += 1
return -1 # no lap with opposite compound found; all laps use same compound type
def setFilter(self, startingCompound: str):
if startingCompound == 'SLICK': return self.slickCompounds
return startingCompound
# ===== Crashes =====
def analyseRacesForCrashes(self, races):
x = 0
# ===== Events =====
def analyseRacesForSafetyCars(self, races):