The models module provides a comprehensive, object-oriented interface for working with Formula 1 data. These classes extend pandas DataFrame and Series objects with domain-specific methods tailored for F1 analysis, making it intuitive to filter, analyze, and visualize racing data with minimal code.
All model classes in tif1 are built on top of pandas data structures, providing familiar DataFrame and Series interfaces while adding specialized methods for F1 data manipulation. This design philosophy allows you to leverage the full power of pandas operations alongside tif1-specific functionality, creating a seamless development experience.
The Laps class represents a collection of lap timing data and is the primary interface for filtering and analyzing multiple laps. It inherits from pandas.DataFrame, providing all standard DataFrame operations plus specialized F1-specific filtering methods. This is the most commonly used class in tif1, serving as the entry point for most lap-based analysis.
Inheritance:pandas.DataFrame → LapsModule:tif1.models (re-exported from tif1.core)Thread Safety: Read operations are thread-safe; write operations should be synchronized externally
The Laps class can be accessed through multiple pathways depending on your analysis needs:
# All laps from all drivers in a sessionall_laps = session.laps# All laps for a specific driverdriver_laps = driver.laps# Filter session laps to specific driver(s)verstappen_laps = session.laps.pick_driver("VER")# Chain multiple filtersfast_soft_laps = session.laps.pick_driver("VER").pick_tyre("SOFT").pick_quicklaps()
Reference to the parent Session object. This provides access to session-level data and methods, enabling operations like telemetry loading and driver information lookup. The session reference is automatically propagated when filtering or slicing Laps objects, ensuring that derived objects maintain full context.Usage:
laps = session.lapsfiltered = laps.pick_driver("VER")# filtered.session still references the original sessiontelemetry = filtered.telemetry # Can access session data
The Laps class provides a comprehensive set of filtering methods that can be chained together for complex queries. All filtering methods return new Laps objects, preserving the original data (immutable operations). These methods are optimized for performance using pandas’ vectorized operations.
Filter laps to a single driver using flexible identifier matching. This is one of the most commonly used methods in tif1.
def pick_driver(identifier: str | int | dict) -> Laps
Parameters:
identifier: Can be one of several formats for maximum flexibility:
str: 3-letter driver code (e.g., “VER”, “HAM”, “LEC”) - most common usage
int: Driver racing number (e.g., 1, 44, 16)
dict: Dictionary containing driver info with keys like “driver”, “dn”, “RacingNumber”, “Abbreviation”
Object: Any object with a driver or Abbreviation attribute
Returns:
Laps object containing only laps from the specified driver. Returns empty Laps if driver not found.
Behavior Details:
Case-sensitive for driver codes (must use uppercase: “VER” not “ver”)
Automatically normalizes various identifier formats to internal representation
Returns empty Laps if driver not found (no exception raised) - allows graceful handling
Preserves session reference in returned object for continued data access
Uses vectorized pandas operations for O(n) performance even with large datasets
Performance Characteristics:
Time Complexity: O(n) where n is the number of laps
Space Complexity: O(m) where m is the number of matching laps
Optimized with pandas boolean indexing (no Python loops)
Example Usage:
import tif1session = tif1.get_session(2021, "Belgian Grand Prix", "Race")laps = session.laps# Using driver code (most common and recommended)ver_laps = laps.pick_driver("VER")print(f"Verstappen completed {len(ver_laps)} laps")# Using driver numberver_laps = laps.pick_driver(1)# Using driver info dict (useful when iterating over driver data)driver_info = {"driver": "VER", "dn": 1, "team": "Red Bull Racing"}ver_laps = laps.pick_driver(driver_info)# Chain with other filters for complex queriesver_soft_laps = laps.pick_driver("VER").pick_tyre("SOFT")ver_fast_laps = laps.pick_driver("VER").pick_quicklaps(threshold=1.05)# Graceful handling of missing driversmissing_laps = laps.pick_driver("XXX") # Returns empty Laps, no exceptionif not missing_laps.empty: print("Driver found")else: print("Driver not in session")# Access telemetry after filteringver_laps = laps.pick_driver("VER")if not ver_laps.empty: fastest = ver_laps.pick_fastest() if fastest is not None: tel = fastest.telemetry # Session context preserved
Common Patterns:
# Compare lap times between driversfor driver_code in ["VER", "HAM", "LEC"]: driver_laps = laps.pick_driver(driver_code) if not driver_laps.empty: fastest = driver_laps.pick_fastest() if fastest is not None: print(f"{driver_code}: {fastest['LapTime']}")# Analyze tire degradation for a driverver_laps = laps.pick_driver("VER").pick_tyre("SOFT")if not ver_laps.empty: # Group by stint and analyze lap time progression for stint in ver_laps["Stint"].unique(): stint_laps = ver_laps[ver_laps["Stint"] == stint] print(f"Stint {stint}: {len(stint_laps)} laps")
Error Handling:
# The method never raises exceptions for missing driverstry: laps_subset = laps.pick_driver("INVALID") # laps_subset will be empty, but no exceptionexcept Exception: # This block will never execute pass# Proper way to check for driver existencedriver_laps = laps.pick_driver("VER")if driver_laps.empty: print("Driver not found or has no laps")else: print(f"Found {len(driver_laps)} laps")
Filter laps to multiple drivers simultaneously. Useful for comparing performance between specific drivers or analyzing battles.
def pick_drivers(identifiers: list[str | int | dict]) -> Laps
Parameters:
identifiers: List of driver identifiers (same formats as pick_driver). Can mix different identifier types in the same list.
Returns:
Laps object containing laps from all specified drivers. Returns empty Laps if no matching drivers found.
Behavior Details:
Accepts mixed identifier types in the same list (e.g., ["VER", 44, {"driver": "LEC"}])
Order of drivers in result matches original lap chronological order, not identifier order
Duplicate identifiers are automatically deduplicated
Returns empty Laps if no matching drivers found
Preserves all lap metadata and session context
Performance Characteristics:
Time Complexity: O(n) where n is the number of laps
More efficient than multiple pick_driver calls combined with concatenation
Uses pandas’ isin() method for vectorized filtering
Example Usage:
# Top 3 drivers comparisontop_three = laps.pick_drivers(["VER", "HAM", "LEC"])print(f"Total laps from top 3: {len(top_three)}")# Mixed identifier typesmixed = laps.pick_drivers(["VER", 44, {"driver": "LEC"}])# Analyze multiple drivers' performancedrivers_to_compare = ["VER", "HAM", "LEC", "SAI"]comparison_laps = laps.pick_drivers(drivers_to_compare)for driver in drivers_to_compare: driver_laps = comparison_laps.pick_driver(driver) if not driver_laps.empty: fastest = driver_laps.pick_fastest() if fastest is not None: print(f"{driver}: {fastest['LapTime']} ({len(driver_laps)} laps)")# Team comparison (get all drivers from specific teams)rbr_drivers = ["VER", "PER"]merc_drivers = ["HAM", "RUS"]rbr_laps = laps.pick_drivers(rbr_drivers)merc_laps = laps.pick_drivers(merc_drivers)print(f"Red Bull: {len(rbr_laps)} laps")print(f"Mercedes: {len(merc_laps)} laps")# Visualize multiple driversimport matplotlib.pyplot as pltdrivers = ["VER", "HAM", "LEC"]multi_driver_laps = laps.pick_drivers(drivers)for driver in drivers: driver_laps = multi_driver_laps.pick_driver(driver) if not driver_laps.empty: plt.plot(driver_laps["LapNumber"], driver_laps["LapTimeSeconds"], label=driver, marker='o')plt.xlabel("Lap Number")plt.ylabel("Lap Time (seconds)")plt.legend()plt.title("Lap Time Comparison")plt.show()
Use Cases:
Comparing performance between specific drivers
Analyzing battles between teammates or rivals
Creating visualizations with selected drivers
Team-level analysis (all drivers from specific teams)
Qualifying session analysis (top 10 drivers)
Common Patterns:
# Get fastest lap from a group of driverscontenders = laps.pick_drivers(["VER", "HAM", "LEC"])fastest_overall = contenders.pick_fastest()if fastest_overall is not None: print(f"Fastest: {fastest_overall['Driver']} - {fastest_overall['LapTime']}")# Analyze tire strategy across multiple driversstrategy_drivers = ["VER", "HAM", "LEC", "SAI"]strategy_laps = laps.pick_drivers(strategy_drivers)for compound in ["SOFT", "MEDIUM", "HARD"]: compound_laps = strategy_laps.pick_tyre(compound) if not compound_laps.empty: print(f"\n{compound} tire usage:") for driver in strategy_drivers: driver_compound = compound_laps.pick_driver(driver) print(f" {driver}: {len(driver_compound)} laps")
Get the single fastest lap from the collection. This is a convenience method that returns a Lap object (pandas Series) rather than a Laps DataFrame.
def pick_fastest(only_by_time: bool = False) -> Lap | None
Parameters:
only_by_time (bool, default=False): Reserved for future use. Currently has no effect. In FastF1, this parameter controls whether to consider only lap time or also track status. In tif1, only lap time is considered.
Returns:
Lap object representing the fastest lap, or None if no valid laps found
Behavior Details:
Only considers laps with valid lap times (non-null, non-NaN)
Returns the lap with the minimum LapTime value
Returns None if the collection is empty or has no valid lap times
Preserves session context in the returned Lap object
If multiple laps have identical fastest times, returns the first occurrence
Performance Characteristics:
Time Complexity: O(n) for finding minimum
Efficient pandas vectorized operation
Example Usage:
# Get fastest lap from all driversfastest = session.laps.pick_fastest()if fastest is not None: print(f"Fastest lap: {fastest['LapTime']} by {fastest['Driver']}") print(f"On lap {fastest['LapNumber']} with {fastest['Compound']} tires") # Access telemetry of fastest lap fastest_tel = fastest.telemetry if not fastest_tel.empty: print(f"Max speed: {fastest_tel['Speed'].max()} km/h")else: print("No valid laps found")# Get fastest lap for a specific driverver_fastest = session.laps.pick_driver("VER").pick_fastest()if ver_fastest is not None: print(f"Verstappen's fastest: {ver_fastest['LapTime']}")# Compare fastest laps between driversdrivers = ["VER", "HAM", "LEC"]for driver in drivers: fastest = session.laps.pick_driver(driver).pick_fastest() if fastest is not None: print(f"{driver}: {fastest['LapTime']} (Lap {fastest['LapNumber']})")# Get fastest lap on specific tire compoundsoft_fastest = session.laps.pick_tyre("SOFT").pick_fastest()if soft_fastest is not None: print(f"Fastest on SOFT: {soft_fastest['LapTime']} by {soft_fastest['Driver']}")# Analyze fastest lap conditionsfastest = session.laps.pick_fastest()if fastest is not None: print(f"Driver: {fastest['Driver']}") print(f"Lap: {fastest['LapNumber']}") print(f"Time: {fastest['LapTime']}") print(f"Compound: {fastest['Compound']}") print(f"Tire Life: {fastest['TyreLife']} laps") print(f"Track Status: {fastest['TrackStatus']}") print(f"Personal Best: {fastest['IsPersonalBest']}")
Common Patterns:
# Find fastest lap and analyze its telemetryfastest = session.laps.pick_fastest()if fastest is not None: tel = fastest.telemetry if not tel.empty: # Analyze speed trace max_speed = tel["Speed"].max() avg_speed = tel["Speed"].mean() # Find braking zones braking = tel[tel["Brake"] == True] num_braking_zones = len(braking) print(f"Max speed: {max_speed:.1f} km/h") print(f"Avg speed: {avg_speed:.1f} km/h") print(f"Braking zones: {num_braking_zones}")# Compare fastest laps across sessionsfor session_name in ["Practice 1", "Practice 2", "Qualifying"]: sess = tif1.get_session(2021, "Belgian Grand Prix", session_name) fastest = sess.laps.pick_fastest() if fastest is not None: print(f"{session_name}: {fastest['LapTime']} by {fastest['Driver']}")
Error Handling:
# Always check for None before accessing lap datafastest = laps.pick_fastest()if fastest is None: print("No valid laps in collection")else: # Safe to access lap attributes lap_time = fastest["LapTime"] driver = fastest["Driver"]# Chaining with other filterssoft_laps = laps.pick_tyre("SOFT")if not soft_laps.empty: fastest_soft = soft_laps.pick_fastest() if fastest_soft is not None: print(f"Fastest on SOFT: {fastest_soft['LapTime']}")
Filter laps within a percentage of the fastest lap time. This is useful for analyzing competitive laps and excluding outliers or slow laps affected by traffic, pit stops, or incidents.
def pick_quicklaps(threshold: float = 1.07) -> Laps
Parameters:
threshold (float, default=1.07): Percentage multiplier for the fastest lap time. Default is 1.07 (107%), which is the F1 qualifying 107% rule. Must be >= 1.0.
Returns:
Laps object containing only laps within the threshold. Returns empty Laps if no laps meet the criteria.
Behavior Details:
Calculates the fastest lap time in the collection
Returns all laps where LapTime <= fastest_time * threshold
Only considers laps with valid lap times (non-null, non-NaN)
Returns empty Laps if no valid lap times exist
Preserves session context and all lap metadata
Performance Characteristics:
Time Complexity: O(n) - one pass to find minimum, one pass to filter
Efficient pandas vectorized operations
Example Usage:
# Get all laps within 107% of fastest (F1 qualifying rule)quick_laps = laps.pick_quicklaps(threshold=1.07)print(f"Laps within 107%: {len(quick_laps)}")# More restrictive: laps within 102% (very competitive laps)very_quick = laps.pick_quicklaps(threshold=1.02)print(f"Laps within 102%: {len(very_quick)}")# Analyze only competitive laps for a driverver_competitive = laps.pick_driver("VER").pick_quicklaps(threshold=1.05)if not ver_competitive.empty: avg_time = ver_competitive["LapTimeSeconds"].mean() print(f"Average competitive lap time: {avg_time:.3f}s")# Compare competitive pace between driversthreshold = 1.03drivers = ["VER", "HAM", "LEC"]for driver in drivers: driver_quick = laps.pick_driver(driver).pick_quicklaps(threshold=threshold) if not driver_quick.empty: avg = driver_quick["LapTimeSeconds"].mean() count = len(driver_quick) print(f"{driver}: {count} quick laps, avg {avg:.3f}s")# Analyze tire degradation using only quick lapsver_soft = laps.pick_driver("VER").pick_tyre("SOFT").pick_quicklaps()if not ver_soft.empty: # Plot lap time vs tire life import matplotlib.pyplot as plt plt.scatter(ver_soft["TyreLife"], ver_soft["LapTimeSeconds"]) plt.xlabel("Tire Life (laps)") plt.ylabel("Lap Time (seconds)") plt.title("Tire Degradation (Quick Laps Only)") plt.show()# Find drivers who couldn't meet 107% rule in qualifyingq1_laps = session.laps # Assuming qualifying sessionquick_laps = q1_laps.pick_quicklaps(threshold=1.07)all_drivers = set(q1_laps["Driver"].unique())quick_drivers = set(quick_laps["Driver"].unique())slow_drivers = all_drivers - quick_driversif slow_drivers: print(f"Drivers outside 107%: {', '.join(slow_drivers)}")
Use Cases:
Filtering out slow laps affected by traffic or incidents
Analyzing competitive pace without outliers
Implementing F1’s 107% qualifying rule
Tire degradation analysis with consistent pace
Race pace analysis excluding pit laps and safety car periods
Common Patterns:
# Calculate race pace excluding outliersrace_laps = session.laps.pick_wo_box() # Exclude pit lapsquick_race_laps = race_laps.pick_quicklaps(threshold=1.10)for driver in ["VER", "HAM", "LEC"]: driver_pace = quick_race_laps.pick_driver(driver) if not driver_pace.empty: median_time = driver_pace["LapTimeSeconds"].median() print(f"{driver} race pace: {median_time:.3f}s")# Analyze consistency (standard deviation of quick laps)for driver in ["VER", "HAM"]: quick = laps.pick_driver(driver).pick_quicklaps(threshold=1.02) if not quick.empty and len(quick) > 1: std = quick["LapTimeSeconds"].std() print(f"{driver} consistency (std dev): {std:.3f}s")
compounds (list[str]): List of tire compound names for pick_compounds
Returns:
Laps object containing only laps on the specified compound(s)
Behavior Details:
Compound names are case-sensitive (use uppercase)
Returns empty Laps if no laps match the compound
pick_tyre is a convenience wrapper for pick_compounds with a single compound
Preserves all lap metadata and session context
Example Usage:
# Single compoundsoft_laps = laps.pick_tyre("SOFT")print(f"Laps on SOFT tires: {len(soft_laps)}")# Multiple compounds (all slick tires)slick_laps = laps.pick_compounds(["SOFT", "MEDIUM", "HARD"])print(f"Laps on slick tires: {len(slick_laps)}")# Wet weather compoundswet_laps = laps.pick_compounds(["INTERMEDIATE", "WET"])# Analyze tire performance for a driverver_laps = laps.pick_driver("VER")for compound in ["SOFT", "MEDIUM", "HARD"]: compound_laps = ver_laps.pick_tyre(compound) if not compound_laps.empty: fastest = compound_laps.pick_fastest() if fastest is not None: print(f"{compound}: {fastest['LapTime']} ({len(compound_laps)} laps)")# Compare tire degradationimport matplotlib.pyplot as pltfor compound in ["SOFT", "MEDIUM", "HARD"]: compound_laps = laps.pick_driver("VER").pick_tyre(compound) if not compound_laps.empty: plt.scatter(compound_laps["TyreLife"], compound_laps["LapTimeSeconds"], label=compound, alpha=0.6)plt.xlabel("Tire Life (laps)")plt.ylabel("Lap Time (seconds)")plt.legend()plt.title("Tire Degradation by Compound")plt.show()# Analyze stint length by compoundfor compound in ["SOFT", "MEDIUM", "HARD"]: compound_laps = laps.pick_tyre(compound) if not compound_laps.empty: max_life = compound_laps["TyreLife"].max() print(f"{compound}: Max stint length {max_life} laps")
Filter by specific lap number(s). Useful for analyzing specific moments in a race or comparing performance at the same point in different stints.
def pick_lap(lap_number: int) -> Lapsdef pick_laps(laps: int | list | slice) -> Laps
Parameters:
lap_number (int): Single lap number to filter
laps: Can be:
int: Single lap number
list[int]: Multiple specific lap numbers
slice: Range of laps (e.g., slice(10, 20) for laps 10-20 inclusive)
Returns:
Laps object containing only the specified lap(s)
Behavior Details:
Lap numbers are 1-indexed (first lap is lap 1)
Returns empty Laps if lap number doesn’t exist
pick_lap is a convenience wrapper for pick_laps with a single lap
Slice ranges are inclusive on both ends
Preserves all driver data (returns laps from all drivers at that lap number)
Example Usage:
# Single lap (all drivers)lap_1 = laps.pick_lap(1)print(f"Drivers on lap 1: {lap_1['Driver'].nunique()}")# Range of laps using slicelaps_10_to_20 = laps.pick_laps(slice(10, 20))print(f"Laps 10-20: {len(laps_10_to_20)} total laps")# Specific laps using listkey_laps = laps.pick_laps([1, 10, 20, 30, 40])# Open-ended slice (from lap 50 to end)final_laps = laps.pick_laps(slice(50, None))# Analyze first lap incidentslap_1 = laps.pick_lap(1)for driver in lap_1["Driver"].unique(): driver_lap_1 = lap_1[lap_1["Driver"] == driver] if not driver_lap_1.empty: lap_time = driver_lap_1.iloc[0]["LapTime"] position = driver_lap_1.iloc[0]["Position"] print(f"{driver}: P{position}, {lap_time}")# Compare specific lap across driverslap_19 = laps.pick_lap(19)for driver in ["VER", "HAM", "LEC"]: driver_lap = lap_19[lap_19["Driver"] == driver] if not driver_lap.empty: print(f"{driver} lap 19: {driver_lap.iloc[0]['LapTime']}")# Analyze race start (first 3 laps)start_laps = laps.pick_laps(slice(1, 3))for driver in ["VER", "HAM"]: driver_start = start_laps[start_laps["Driver"] == driver] if not driver_start.empty: avg_time = driver_start["LapTimeSeconds"].mean() print(f"{driver} start pace: {avg_time:.3f}s")# Find fastest lap at specific race stagemid_race = laps.pick_laps(slice(20, 30))fastest_mid = mid_race.pick_fastest()if fastest_mid is not None: print(f"Fastest in laps 20-30: {fastest_mid['Driver']} - {fastest_mid['LapTime']}")
Use Cases:
Analyzing race starts (lap 1)
Comparing performance at specific race stages
Identifying key moments (safety car laps, pit stop windows)
Filter by track status (green flag, yellow flag, safety car, etc.). Essential for analyzing race conditions and their impact on lap times.
def pick_track_status(status: str, how: str = "equals") -> Laps
Parameters:
status (str): Track status code:
“1” = Green flag (normal racing)
“2” = Yellow flag
“4” = Safety Car
“5” = Red Flag
“6” = Virtual Safety Car (VSC)
“7” = VSC Ending
how (str): Matching mode:
“equals” (default): Exact match
“contains”: Partial match (useful for complex status codes)
Returns:
Laps object containing only laps with the specified track status
Example Usage:
# Green flag laps onlygreen_laps = laps.pick_track_status("1", how="equals")print(f"Green flag laps: {len(green_laps)}")# Yellow flag laps (partial match for any yellow)yellow_laps = laps.pick_track_status("2", how="contains")# Safety car lapssc_laps = laps.pick_track_status("4")# Analyze pace under different conditionsfor status, name in [("1", "Green"), ("4", "Safety Car"), ("6", "VSC")]: status_laps = laps.pick_track_status(status) if not status_laps.empty: avg_time = status_laps["LapTimeSeconds"].mean() print(f"{name}: {len(status_laps)} laps, avg {avg_time:.3f}s")# Find fastest lap under green flag conditionsgreen_fastest = laps.pick_track_status("1").pick_fastest()if green_fastest is not None: print(f"Fastest under green: {green_fastest['Driver']} - {green_fastest['LapTime']}")
Filter for valid and accurate laps. Essential for ensuring data quality in analysis.
def pick_not_deleted() -> Lapsdef pick_accurate() -> Laps
Returns:
pick_not_deleted(): Laps that weren’t deleted by race control
pick_accurate(): Laps with accurate timing data
Behavior Details:
pick_not_deleted() filters out laps where Deleted == True
pick_accurate() filters for laps where IsAccurate == True
Often used together for high-quality data analysis
Returns all laps if the respective column doesn’t exist
Example Usage:
# Valid laps onlyvalid_laps = laps.pick_not_deleted()# Accurate laps onlyaccurate_laps = laps.pick_accurate()# Both valid and accurate (recommended for analysis)clean_laps = laps.pick_not_deleted().pick_accurate()print(f"Clean laps: {len(clean_laps)}")# Analyze only high-quality dataquality_laps = (laps .pick_not_deleted() .pick_accurate() .pick_wo_box() .pick_track_status("1"))fastest = quality_laps.pick_fastest()if fastest is not None: print(f"Fastest clean lap: {fastest['Driver']} - {fastest['LapTime']}")# Find deleted laps (track limits violations, etc.)if "Deleted" in laps.columns: deleted = laps[laps["Deleted"] == True] if not deleted.empty: print(f"Deleted laps: {len(deleted)}") for _, lap in deleted.iterrows(): reason = lap.get("DeletedReason", "Unknown") print(f" {lap['Driver']} lap {lap['LapNumber']}: {reason}")
Access high-frequency telemetry data for the laps in the collection. This is one of the most powerful features of tif1, enabling detailed analysis of driver inputs and car behavior.
FastF1 compatibility aliases for telemetry access. These methods provide the same functionality as telemetry property but with FastF1-compatible naming.
get_pos_data() returns position data (same as car data in tif1)
Both methods accept **kwargs for FastF1 compatibility (ignored in tif1)
For multi-driver laps, concatenates telemetry from all laps
Example Usage:
# FastF1 compatibilityver_laps = laps.pick_driver("VER")car_data = ver_laps.get_car_data()pos_data = ver_laps.get_pos_data()# Both return the same telemetry dataprint(f"Car data samples: {len(car_data)}")print(f"Pos data samples: {len(pos_data)}")
require (list[str] | None): List of column names that must have non-null values. Laps with null values in these columns are skipped. Default is ["LapTime", "Driver"].
Yields:
_IterLapResult: Tuple-like object with:
index: DataFrame index of the lap
lap: Lap object (pandas Series) with lap data
Behavior Details:
Skips laps with null values in required columns
Removes null columns from each lap before yielding
Preserves session context in yielded Lap objects
Raises KeyError if a required column doesn’t exist in the DataFrame
Example Usage:
# Basic iterationfor lap_result in laps.iterlaps(): lap = lap_result.lap print(f"Lap {lap['LapNumber']}: {lap['Driver']} - {lap['LapTime']}")# Access via tuple unpackingfor index, lap in laps.iterlaps(): print(f"Index {index}: {lap['Driver']} lap {lap['LapNumber']}")# Require specific columnsfor lap_result in laps.iterlaps(require=["LapTime", "Driver", "Compound"]): lap = lap_result.lap print(f"{lap['Driver']} on {lap['Compound']}: {lap['LapTime']}")# Process each lap's telemetryver_laps = laps.pick_driver("VER")for lap_result in ver_laps.iterlaps(require=["LapTime"]): lap = lap_result.lap tel = lap.telemetry if not tel.empty: max_speed = tel["Speed"].max() print(f"Lap {lap['LapNumber']}: Max speed {max_speed:.1f} km/h")# Calculate statistics across lapslap_times = []for lap_result in laps.pick_driver("VER").iterlaps(): lap = lap_result.lap lap_times.append(lap["LapTimeSeconds"])if lap_times: import statistics print(f"Mean: {statistics.mean(lap_times):.3f}s") print(f"Std Dev: {statistics.stdev(lap_times):.3f}s")# Filter and processfor lap_result in laps.iterlaps(require=["LapTime", "Compound"]): lap = lap_result.lap if lap["Compound"] == "SOFT" and lap["TyreLife"] < 5: print(f"{lap['Driver']} lap {lap['LapNumber']}: Fresh SOFT tires")
The Laps class inherits all pandas DataFrame operations, so you can use standard pandas methods:
# Standard pandas operationsprint(laps.shape) # (rows, columns)print(laps.columns.tolist()) # List of column namesprint(laps.head()) # First 5 rowsprint(laps.describe()) # Statistical summary# Filtering with pandasfast_laps = laps[laps["LapTimeSeconds"] < 90]soft_tire_laps = laps[laps["Compound"] == "SOFT"]# Grouping and aggregationby_driver = laps.groupby("Driver")["LapTimeSeconds"].mean()print(by_driver)# Sortingsorted_laps = laps.sort_values("LapTime")fastest_10 = sorted_laps.head(10)# Column operationslaps["LapTimeMinutes"] = laps["LapTimeSeconds"] / 60# Merging with other data# (Note: use join() or merge() methods to preserve Laps type)
Important: When using pandas operations that return DataFrames, the result may be a plain pandas DataFrame rather than a Laps object. Use the tif1-specific methods (pick_*) to maintain the Laps type and session context.
The Lap class represents a single lap with comprehensive timing data and access to high-frequency telemetry. It inherits from pandas.Series, making it a row-like object with named fields that can be accessed like a dictionary or via attribute access.
Inheritance:pandas.Series → LapModule:tif1.models (re-exported from tif1.core)Common Access Patterns:
# From Laps collectionfastest_lap = laps.pick_fastest() # Returns Lap object# From Driver objectlap_19 = driver.get_lap(19) # Returns Lap object# From iterationfor lap_result in laps.iterlaps(): lap = lap_result.lap # Lap object
A Lap object is a pandas Series containing all the lap data columns described in the Laps section. You can access any column value using dictionary-style or attribute-style access:
lap = driver.get_lap(19)# Dictionary-style access (recommended)lap_time = lap["LapTime"]driver_code = lap["Driver"]compound = lap["Compound"]# Check if column existsif "SpeedI1" in lap: speed_i1 = lap["SpeedI1"]# Get with defaulttyre_life = lap.get("TyreLife", 0)
High-frequency telemetry data for this lap. This property provides lazy-loaded access to detailed sensor data including speed, throttle, brake, gear, DRS, and position information.Behavior:
Loaded lazily on first access (not fetched until you access this property)
Cached after first load for performance
Returns empty Telemetry DataFrame if data not found or unavailable
Automatically handles network requests and caching
Preserves session context for continued analysis
Performance:
First access may trigger network request (~50-200ms depending on connection)
Subsequent accesses are instant (cached in memory)
Telemetry data is typically 1000-3000 samples per lap
Usage:
lap = driver.get_lap(19)tel = lap.telemetryif not tel.empty: print(f"Telemetry samples: {len(tel)}") print(f"Max speed: {tel['Speed'].max()} km/h") print(f"Avg throttle: {tel['Throttle'].mean():.1f}%") # Analyze braking braking_points = tel[tel["Brake"] == True] print(f"Braking zones: {len(braking_points)}")else: print("No telemetry available for this lap")
Error Handling:
try: tel = lap.telemetry if tel.empty: print("Telemetry not available") else: # Process telemetry passexcept Exception as e: print(f"Error loading telemetry: {e}")
Reference to the parent Session object. This maintains the connection to the session that created this lap, enabling access to session-level data and methods.Usage:
lap = driver.get_lap(19)# Access session informationprint(f"Year: {lap.session.year}")print(f"GP: {lap.session.gp}")print(f"Session: {lap.session.session}")# Access other session dataall_laps = lap.session.lapsweather = lap.session.weather_data
Explicitly load telemetry data with driver-ahead channels. This method is similar to accessing the telemetry property but includes additional channels for analyzing car-to-car interactions.
def get_telemetry() -> Telemetry
Returns:
Telemetry DataFrame with all telemetry channels including:
Standard channels: Time, Speed, RPM, nGear, Throttle, Brake, DRS, Distance, X, Y, Z
FastF1 compatibility aliases for telemetry access. These methods provide the same functionality as the telemetry property but with FastF1-compatible naming conventions.
**kwargs: Accepts any keyword arguments for FastF1 compatibility (ignored in tif1)
Returns:
Telemetry DataFrame with telemetry data
Behavior Details:
get_car_data() returns full telemetry data (same as telemetry property)
get_pos_data() returns position data (in tif1, same as car data)
Both methods are provided for FastF1 compatibility
No functional difference between the two in tif1
Example Usage:
lap = driver.get_lap(19)# FastF1-style accesscar_data = lap.get_car_data()pos_data = lap.get_pos_data()# Both return the same data in tif1print(f"Car data samples: {len(car_data)}")print(f"Pos data samples: {len(pos_data)}")# Use like regular telemetryif not car_data.empty: max_speed = car_data["Speed"].max() print(f"Max speed: {max_speed} km/h")
Migration from FastF1:
# FastF1 codecar_data = lap.get_car_data()pos_data = lap.get_pos_data()# Works identically in tif1car_data = lap.get_car_data()pos_data = lap.get_pos_data()# Or use tif1's preferred approachtelemetry = lap.telemetry
Get weather data for this lap. Currently returns an empty Series as lap-specific weather data is included in the lap’s columns.
def get_weather_data() -> Series
Returns:
Series: Empty pandas Series (weather data is in lap columns)
Behavior Details:
Provided for FastF1 compatibility
Weather data is already available in lap columns (AirTemp, TrackTemp, Humidity, etc.)
Returns empty Series in current implementation
Example Usage:
lap = driver.get_lap(19)# Weather data is in the lap itselfif "AirTemp" in lap: print(f"Air temp: {lap['AirTemp']}°C")if "TrackTemp" in lap: print(f"Track temp: {lap['TrackTemp']}°C")if "Humidity" in lap: print(f"Humidity: {lap['Humidity']}%")if "Rainfall" in lap: print(f"Raining: {lap['Rainfall']}")# get_weather_data() returns empty Seriesweather = lap.get_weather_data() # Empty Series
Accessing Weather Data:
# Recommended approach: access weather columns directlylap = driver.get_lap(19)weather_info = { "Air Temperature": lap.get("AirTemp"), "Track Temperature": lap.get("TrackTemp"), "Humidity": lap.get("Humidity"), "Pressure": lap.get("Pressure"), "Wind Speed": lap.get("WindSpeed"), "Wind Direction": lap.get("WindDirection"), "Rainfall": lap.get("Rainfall", False)}for key, value in weather_info.items(): if value is not None: print(f"{key}: {value}")
# Get all laps for a driverdriver_laps = laps.pick_driver("VER")# Find personal bestfor lap_result in driver_laps.iterlaps(): lap = lap_result.lap if lap.get("IsPersonalBest", False): print(f"Personal best: Lap {lap.lap_number}") print(f"Time: {lap['LapTime']}") print(f"Compound: {lap['Compound']}") # Analyze the personal best lap tel = lap.telemetry if not tel.empty: print(f"Max speed: {tel['Speed'].max():.1f} km/h")
The Driver class represents a driver’s participation in a session, providing access to their metadata, all their laps, and convenience methods for analyzing their performance. It inherits from pandas.Series, making it a dictionary-like object with driver information.
Inheritance:pandas.Series → DriverModule:tif1.models (re-exported from tif1.core)Common Access Patterns:
# From Session objectdriver = session.get_driver("VER")# Iterate over all driversfor driver_code in session.drivers: driver = session.get_driver(driver_code)
A Driver object is a pandas Series containing driver metadata. The data is stored in the Series itself and can be accessed using dictionary-style or attribute-style access:Available Fields (in Series data):
DriverNumber (str): Racing number (e.g., “1”, “44”, “16”)
The 3-letter driver code (e.g., “VER”, “HAM”, “LEC”). This is stored as an attribute (not in the Series data) and provides quick access to the driver identifier.Usage:
driver = session.get_driver("VER")print(f"Driver code: {driver.driver}") # "VER"# Use in filteringall_laps = session.lapsdriver_laps = all_laps[all_laps["Driver"] == driver.driver]
Note: This is the primary identifier for the driver and is used throughout tif1 for filtering and lookups.
All laps completed by this driver in the session. This property provides lazy-loaded access to the driver’s lap data.Type: Returns Laps (pandas backend) or DataFrame (polars backend)Behavior:
Loaded lazily on first access (not fetched until you access this property)
Cached after first load for performance
Returns empty DataFrame if no laps found
Automatically filters session laps to this driver
Preserves session context in returned Laps object
Performance:
First access may trigger network request if not cached (~50-200ms)
Subsequent accesses are instant (cached in memory)
Efficient filtering using pandas/polars vectorized operations
Usage:
driver = session.get_driver("VER")laps = driver.lapsif not laps.empty: print(f"Total laps: {len(laps)}") print(f"Fastest lap: {laps['LapTime'].min()}") # Use all Laps methods fastest = laps.pick_fastest() soft_laps = laps.pick_tyre("SOFT") quick_laps = laps.pick_quicklaps() # Analyze lap times avg_time = laps["LapTimeSeconds"].mean() print(f"Average lap time: {avg_time:.3f}s")else: print("No laps found for this driver")
Common Patterns:
# Tire strategy analysisfor compound in ["SOFT", "MEDIUM", "HARD"]: compound_laps = driver.laps.pick_tyre(compound) if not compound_laps.empty: print(f"{compound}: {len(compound_laps)} laps")# Pace analysisrace_pace = driver.laps.pick_wo_box().pick_quicklaps()if not race_pace.empty: median_time = race_pace["LapTimeSeconds"].median() print(f"Race pace: {median_time:.3f}s")# Lap time progressionimport matplotlib.pyplot as pltplt.plot(driver.laps["LapNumber"], driver.laps["LapTimeSeconds"])plt.xlabel("Lap Number")plt.ylabel("Lap Time (s)")plt.title(f"{driver.driver} Lap Times")plt.show()
Get telemetry from this driver’s fastest lap. This is a convenience method that combines finding the fastest lap and loading its telemetry in one call.
def get_fastest_lap_tel() -> DataFrame
Returns:
DataFrame: Telemetry DataFrame for the fastest lap. Returns empty DataFrame if not found.
Behavior Details:
Automatically identifies the fastest lap
Loads telemetry for that lap
Returns empty DataFrame if no fastest lap or no telemetry available
More efficient than calling get_fastest_lap() then loading telemetry separately
Uses internal optimization to avoid redundant lookups
Performance Characteristics:
Optimized to minimize data fetching
May use cached telemetry if available
Single method call for convenience
Example Usage:
driver = session.get_driver("VER")# Get fastest lap telemetry directlyfastest_tel = driver.get_fastest_lap_tel()if not fastest_tel.empty: print(f"Telemetry samples: {len(fastest_tel)}") # Analyze speed max_speed = fastest_tel["Speed"].max() avg_speed = fastest_tel["Speed"].mean() print(f"Max speed: {max_speed:.1f} km/h") print(f"Avg speed: {avg_speed:.1f} km/h") # Analyze braking braking = fastest_tel[fastest_tel["Brake"] == True] print(f"Braking zones: {len(braking)} samples") # Analyze DRS drs_active = fastest_tel[fastest_tel["DRS"] >= 10] print(f"DRS active: {len(drs_active)} samples") # Gear usage for gear in range(1, 9): gear_samples = fastest_tel[fastest_tel["nGear"] == gear] if not gear_samples.empty: print(f"Gear {gear}: {len(gear_samples)} samples")else: print("No telemetry available for fastest lap")# Compare fastest lap telemetry between driversimport matplotlib.pyplot as pltfig, ax = plt.subplots(figsize=(12, 6))for driver_code in ["VER", "HAM", "LEC"]: driver = session.get_driver(driver_code) tel = driver.get_fastest_lap_tel() if not tel.empty: ax.plot(tel["Distance"], tel["Speed"], label=driver_code)ax.set_xlabel("Distance (m)")ax.set_ylabel("Speed (km/h)")ax.set_title("Fastest Lap Speed Comparison")ax.legend()ax.grid(True)plt.show()# Analyze throttle applicationfastest_tel = driver.get_fastest_lap_tel()if not fastest_tel.empty: full_throttle = fastest_tel[fastest_tel["Throttle"] >= 99] partial_throttle = fastest_tel[(fastest_tel["Throttle"] > 0) & (fastest_tel["Throttle"] < 99)] print(f"Full throttle: {len(full_throttle)} samples " f"({len(full_throttle)/len(fastest_tel)*100:.1f}%)") print(f"Partial throttle: {len(partial_throttle)} samples " f"({len(partial_throttle)/len(fastest_tel)*100:.1f}%)")
The Telemetry class represents high-frequency data recorded throughout a lap or multiple laps. It inherits from pandas.DataFrame with additional slicing and analysis methods specifically designed for telemetry data. This is one of the most powerful classes in tif1, enabling detailed analysis of driver inputs, car behavior, and racing lines.
Inheritance:pandas.DataFrame → TelemetryModule:tif1.models (re-exported from tif1.core)Sampling Rate: Typically 10-20 Hz (10-20 samples per second), resulting in 1000-3000 samples per lapCommon Access Patterns:
# From Lap objectlap = driver.get_lap(19)tel = lap.telemetry# From Laps collection (single driver only)ver_laps = laps.pick_driver("VER")tel = ver_laps.telemetry# From Driver object (fastest lap)tel = driver.get_fastest_lap_tel()
pad (int, default=0): Number of samples to include before/after
pad_side (str, default=“both”): Where to apply padding
interpolate_edges (bool, default=False): Whether to interpolate at boundaries
Returns:
Telemetry DataFrame for the specified lap(s)
Behavior Details:
Extracts telemetry based on lap start/end times or lap numbers
Raises ValueError if ref_laps contains multiple drivers
Returns empty Telemetry if lap not found
Example Usage:
# Get telemetry for specific laplap = driver.get_lap(19)session_tel = session.car_data # All telemetrylap_tel = session_tel.slice_by_lap(lap)# Get telemetry for multiple lapslaps_10_to_15 = driver.laps.pick_laps(slice(10, 15))multi_lap_tel = session_tel.slice_by_lap(laps_10_to_15)
Calculate distance from speed and time. Essential when Distance column is missing.
def add_distance(drop_existing: bool = True) -> Telemetrydef integrate_distance() -> Series
Returns:
add_distance(): Telemetry with Distance column added
integrate_distance(): Series with calculated distances
Behavior Details:
Integrates speed over time to calculate distance
Converts speed from km/h to m/s internally
Returns original telemetry if Distance already exists
integrate_distance() returns Series without modifying telemetry
Example Usage:
tel = lap.telemetry# Add distance if missingif "Distance" not in tel.columns: tel_with_dist = tel.add_distance() print(f"Distance added: {tel_with_dist['Distance'].max():.0f}m")# Get distance as Seriesdistance_series = tel.integrate_distance()print(f"Total distance: {distance_series.max():.0f}m")
add_driver_ahead(): Telemetry with DriverAhead and DistanceToDriverAhead columns
calculate_driver_ahead(): Tuple of (driver_ahead_array, distance_array) or (driver_ahead, distance, reference_tel)
Behavior Details:
Currently returns placeholder data (not fully implemented in tif1)
Provided for FastF1 compatibility
Future versions will include actual driver-ahead calculations
Example Usage:
tel = lap.get_telemetry() # Use get_telemetry() for driver-ahead dataif "DriverAhead" in tel.columns: ahead_data = tel[tel["DriverAhead"].notna()] if not ahead_data.empty: print(f"Car ahead detected: {ahead_data['DriverAhead'].iloc[0]}") print(f"Min distance: {ahead_data['DistanceToDriverAhead'].min():.1f}m")
Useful for reducing data size or aligning different telemetry sources
Example Usage:
tel = lap.telemetry# Resample to 1 second intervalsresampled_1s = tel.resample_channels(rule="1S")print(f"Original: {len(tel)} samples")print(f"Resampled: {len(resampled_1s)} samples")# Resample to 100ms intervalsresampled_100ms = tel.resample_channels(rule="100ms")# Resample to 5 second intervals for overviewresampled_5s = tel.resample_channels(rule="5S")
The SessionResults class contains race results and final standings for a session. It inherits from pandas.DataFrame, providing a tabular view of how drivers finished the session.
The DriverResult class represents an individual driver’s race result. It inherits from pandas.Series, making it a single row from the SessionResults DataFrame.
Inheritance:pandas.Series → DriverResultModule:tif1.models (re-exported from tif1.core)Common Access Patterns:
# From SessionResultsresults = session.resultsif results is not None and not results.empty: winner = results.iloc[0] # DriverResult object # Iterate over results for idx, driver_result in results.iterrows(): # driver_result is a DriverResult object print(f"{driver_result['Driver']}: P{driver_result['Position']}")
Whether the driver did not finish (computed property). Returns True if status indicates DNF, retirement, or disqualification.Behavior:
Returns True if status is NOT one of: “Finished”, “+1 Lap”, “+2 Laps”, “Not classified”
Returns False otherwise
Case-insensitive comparison
Usage:
result = results.iloc[0]if isinstance(result, DriverResult): if result.dnf: print(f"{result['Driver']} did not finish") else: print(f"{result['Driver']} finished the race")
results = session.resultsif results is not None and not results.empty: # Analyze each result for idx, result in results.iterrows(): if isinstance(result, DriverResult): driver = result['Driver'] position = result['Position'] points = result.get('Points', 0) if result.dnf: print(f"{driver}: DNF ({result['Status']})") else: print(f"{driver}: P{position} ({points} points)")
The CircuitInfo class holds information about the circuit layout, including corner locations and track markers. This is a dataclass (not a pandas object) that provides circuit-specific data.
Location of corners on the circuit. DataFrame with columns: X, Y, Number, Letter, Angle, Distance.Columns:
X (float): X coordinate of corner
Y (float): Y coordinate of corner
Number (int): Corner number
Letter (str): Corner letter designation (if applicable)
Angle (float): Corner angle in degrees
Distance (float): Distance along track (populated after add_marker_distance())
Usage:
circuit = session.get_circuit_info()if circuit is not None and not circuit.corners.empty: print(f"Circuit has {len(circuit.corners)} corners") print(circuit.corners[["Number", "X", "Y"]])
Location of marshal lights (always empty in tif1 - not available in source data). Provided for FastF1 compatibility.Note: This data is not available through the tif1 data source. The DataFrame will always be empty but maintains the correct column schema.
Location of marshal sectors (always empty in tif1 - not available in source data). Provided for FastF1 compatibility.Note: This data is not available through the tif1 data source. The DataFrame will always be empty but maintains the correct column schema.
Compute the Distance value for each track marker using telemetry from a reference lap. This method populates the Distance column in the corners DataFrame.
reference_lap (Lap): A Lap object whose telemetry contains X, Y, and Distance columns
Behavior Details:
Uses best-fit approach to match marker positions with telemetry samples
For each marker, finds the telemetry sample with minimum squared XY error
Assigns that sample’s Distance value to the marker
Modifies the corners DataFrame in-place
Logs warnings if telemetry is unavailable or missing required columns
Requires valid X, Y, and Distance data in the reference lap’s telemetry
Algorithm:
Loads telemetry from reference lap
Filters to samples with valid X, Y, Distance values
For each marker, calculates squared Euclidean distance to all telemetry samples
Assigns the Distance from the closest telemetry sample
Example Usage:
# Get circuit infocircuit = session.get_circuit_info()if circuit is not None: # Get a reference lap (typically the fastest lap) fastest_lap = session.laps.pick_fastest() if fastest_lap is not None: # Add distance markers circuit.add_marker_distance(fastest_lap) # Now corners have distance information if not circuit.corners.empty: print("Corner distances:") for _, corner in circuit.corners.iterrows(): print(f" Corner {corner['Number']}: {corner['Distance']:.0f}m") # Use corner distances for analysis if not circuit.corners.empty and "Distance" in circuit.corners.columns: # Find telemetry at each corner lap = driver.get_lap(19) tel = lap.telemetry if not tel.empty: for _, corner in circuit.corners.iterrows(): corner_dist = corner["Distance"] # Find closest telemetry sample closest_idx = (tel["Distance"] - corner_dist).abs().idxmin() corner_tel = tel.loc[closest_idx] print(f"Corner {corner['Number']}:") print(f" Speed: {corner_tel['Speed']:.1f} km/h") print(f" Gear: {corner_tel['nGear']}")
Use Cases:
Mapping corner locations to lap distance
Analyzing speed through specific corners
Comparing corner performance between drivers
Visualizing racing lines with corner markers
Sector analysis based on corner positions
Error Handling:
circuit = session.get_circuit_info()fastest_lap = session.laps.pick_fastest()if circuit is not None and fastest_lap is not None: try: circuit.add_marker_distance(fastest_lap) print("Marker distances added successfully") except Exception as e: print(f"Failed to add marker distances: {e}") # Circuit info is still usable, just without distances
import tif1import matplotlib.pyplot as plt# Load sessionsession = tif1.get_session(2021, "Belgian Grand Prix", "Race")# Get all lapslaps = session.lapsprint(f"Total laps in session: {len(laps)}")# Analyze top 3 driverstop_drivers = ["VER", "HAM", "LEC"]for driver_code in top_drivers: print(f"\n{'='*50}") print(f"Analysis for {driver_code}") print(f"{'='*50}") # Get driver object driver = session.get_driver(driver_code) print(f"Driver: {driver['FullName']}") print(f"Team: {driver['TeamName']}") print(f"Number: {driver['DriverNumber']}") # Get driver's laps driver_laps = driver.laps if driver_laps.empty: print("No laps found") continue print(f"\nLap Statistics:") print(f" Total laps: {len(driver_laps)}") # Fastest lap fastest = driver_laps.pick_fastest() if fastest is not None: print(f" Fastest lap: {fastest['LapTime']} (Lap {fastest['LapNumber']})") print(f" Compound: {fastest['Compound']}") # Race pace (excluding pit laps, within 110% of fastest) race_pace = driver_laps.pick_wo_box().pick_quicklaps(threshold=1.10) if not race_pace.empty: median_time = race_pace["LapTimeSeconds"].median() std_dev = race_pace["LapTimeSeconds"].std() print(f" Race pace (median): {median_time:.3f}s") print(f" Consistency (std dev): {std_dev:.3f}s") # Tire strategy print(f"\nTire Strategy:") for compound in ["SOFT", "MEDIUM", "HARD"]: compound_laps = driver_laps.pick_tyre(compound) if not compound_laps.empty: max_life = compound_laps["TyreLife"].max() avg_time = compound_laps["LapTimeSeconds"].mean() print(f" {compound}: {len(compound_laps)} laps " f"(max life: {max_life}, avg: {avg_time:.3f}s)") # Telemetry analysis of fastest lap if fastest is not None: tel = fastest.telemetry if not tel.empty: print(f"\nFastest Lap Telemetry:") print(f" Max speed: {tel['Speed'].max():.1f} km/h") print(f" Avg speed: {tel['Speed'].mean():.1f} km/h") print(f" Max RPM: {tel['RPM'].max()}") # DRS usage drs_active = len(tel[tel['DRS'] >= 10]) print(f" DRS usage: {drs_active/len(tel)*100:.1f}% of lap") # Full throttle full_throttle = len(tel[tel['Throttle'] >= 99]) print(f" Full throttle: {full_throttle/len(tel)*100:.1f}% of lap")# Race resultsresults = session.resultsif results is not None and not results.empty: print(f"\n{'='*50}") print("Race Results") print(f"{'='*50}") print(results[["Position", "Driver", "Team", "Points", "Status"]].head(10))
All models support comprehensive type hints for better IDE integration, autocomplete, and type checking. This makes development faster and reduces errors.
Always check if data is available before processing:
# ✓ Good: Check before accessinglaps = driver.lapsif not laps.empty: fastest = laps.pick_fastest() if fastest is not None: print(f"Fastest: {fastest['LapTime']}")# ✗ Bad: Assume data existsfastest = driver.laps.pick_fastest()print(f"Fastest: {fastest['LapTime']}") # May raise AttributeError
Use specific exception types for better error handling:
from tif1.exceptions import LapNotFoundError, DriverNotFoundError# ✓ Good: Specific exception handlingtry: lap = driver.get_lap(99)except LapNotFoundError as e: print(f"Lap not found: {e.lap_number}")