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.
'''
# TODO: Implement returning starting grid for lapNumber = 1
# TODO: Sidestep getting start position from lap, get from session results instead, for lap 1 SOL (start of lap)
Lennard Geese
committed
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:
driversPreviousLap: Laps = previousLaps.pick_drivers(
Lennard Geese
committed
driver) # should only get 1 lap, but data type shenanigans
driversCurrentLap: Laps = currentLaps.pick_drivers(driver)
Lennard Geese
committed
startPosition: int = out
endPosition: int = out
try:
if lapNumber == 1:
startPosition = self.getGridPositionFor(driver, race)
else: startPosition = int(driversPreviousLap['Position'].iloc[0])
endPosition = int(driversCurrentLap['Position'].iloc[0])
Lennard Geese
committed
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] = (driversCurrentLap, endPosition)
Lennard Geese
committed
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 getGridPositionFor(self, driverNumber: int, race: Session):
sessionResults = race.results
gridPosition: int = int(sessionResults['GridPosition'].loc[driverNumber])
return gridPosition
Lennard Geese
committed
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
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
# ===== 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
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
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):