Skip to content
Snippets Groups Projects
DataAnalyser.py 10.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Lennard Geese's avatar
    T
    Lennard Geese committed
    import math
    from typing import List
    
    from fastf1.core import Session, Lap
    
    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):
            # Collect grid positions
            allLapPositions: List[dict[str, int]] = []
    
            gridPositions: dict[str, int] = self.getGridPositions(race)
            allLapPositions.append(gridPositions)
    
            runningLapsPositions: List[dict[str, int]] = self.getRunningLapsPositions(race)
            for runningLapPositions in runningLapsPositions:
                allLapPositions.append(runningLapPositions)
    
            # Count position changes between all lap pairs
            overtakesInLaps: List[int] = self.countOvertakesInLaps(allLapPositions)
    
            return overtakesInLaps
    
        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
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
                    gridPositions.values()
    
                    driverAtPosition = self.getDriverByPositionFromMap(gridPositions, position)
                    print(f"P{position}: {driverAtPosition}")
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
    
            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
                    except: # triggered when not all drivers that took part reached lap, 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)):
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
                        runningLapPositions.values()
    
                        position: int = i + 1
                        driverAtPosition = self.getDriverByPositionFromMap(runningLapPositions, position)
                        print(f"P{position}: {driverAtPosition}")
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
    
            return runningLapsPositions
    
        def countOvertakesInLaps(self, allLapPositions: List[dict[str, int]]):
            overtakesInLaps: List[int] = []
            for i in range(len(allLapPositions) - 1):
                overtakesInLap: int = self.countOvertakesInLap(allLapPositions[i], allLapPositions[i + 1])
                overtakesInLaps.append(overtakesInLap)
                if self.activateDebugOvertakeAnalysis: print(f"Overtakes in lap {i+1}: {overtakesInLap}")
            return overtakesInLaps
    
        def countOvertakesInLap(self, startOrder: dict[str, int], endOrder: dict[str, int]):
            out: int = self.numberOfDrivers
            orderToSort: List[int] = [self.numberOfDrivers] * self.numberOfDrivers
            for driver in startOrder:
                if driver not in endOrder: # add missing drivers for sorting/counting purposes
                    endOrder[driver] = out # heaviest weighting to drop driver to bottom, as they do irl when crashing or similar
                if math.isnan(startOrder[driver]): startOrder[driver] = out
                if math.isnan(endOrder[driver]): endOrder[driver] = out
                driverStartPosition: int = int(startOrder[driver])
                driverEndPosition: int = int(endOrder[driver])
    
                orderToSort[driverStartPosition - 1] = driverEndPosition
    
            if self.activateDebugOvertakeAnalysis:
                #print("Weighting:")
                for i in range(len(orderToSort)):
                    x=0
                    #print(orderToSort[i])
    
    
            overtakes: int = 0
    
            i: int = 0
            while i < len(orderToSort) - 1:
                if self.countOutPitstops:
                    driverPittedThisLap: bool = False
                    currentDriversEndPosition = orderToSort[i]
                    driver = self.getDriverByPositionFromMap(endOrder, currentDriversEndPosition)
    
    
                if orderToSort[i] > orderToSort[i + 1]:
                    temp: int = orderToSort[i]
                    orderToSort[i] = orderToSort[i + 1]
                    orderToSort[i + 1] = temp
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
                    overtakes += 1
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
    
            return overtakes
    
    
        def getDriverByPositionFromMap(self, positions: dict[str, int], position: int):
            driver = list(positions.keys())[list(positions.values()).index(position)]
            return driver
    
    
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
        # ===== 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
    
        # ===== 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
    
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
    
        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
    
    Lennard Geese's avatar
    T
    Lennard Geese committed
    
        def setFilter(self, startingCompound: str):
            if startingCompound == 'SLICK': return self.slickCompounds
            return startingCompound
    
        # ===== Crashes =====
    
        def analyseRacesForCrashes(self, races):
            x = 0
    
        # ===== Events =====
    
        def analyseRacesForEvents(self, races):
            x = 0