Comprehensive API for discovering, querying, and working with Formula 1 events, Grand Prix schedules, and session metadata across multiple seasons
The events module is your gateway to discovering and navigating Formula 1 calendar data. It provides a rich, type-safe API for querying event schedules, session timings, and comprehensive metadata across multiple seasons, serving as the foundation for all data discovery operations in tif1.
The events module provides a comprehensive, production-ready set of functions and classes for discovering what Formula 1 data is available in the TracingInsights CDN for specific years and events. This module serves as the primary entry point for event discovery and session navigation in tif1, enabling sophisticated workflows for data analysis, visualization, and automation.
Query season schedules: Retrieve complete event calendars for any supported F1 season (2018-present) with full metadata including event names, locations, countries, dates, and formats
Discover available sessions: Programmatically determine which sessions (Practice 1-3, Qualifying, Sprint Qualifying, Sprint, Race) have data available for each event
Access rich event metadata: Get detailed information about each Grand Prix including official event names (with sponsors), circuit locations, round numbers, event dates, and weekend formats
Navigate flexibly: Use intelligent fuzzy matching to find events by partial names, abbreviations, circuit names, or round numbers without requiring exact string matches
Work with timezone-aware data: Access session start times in both local circuit timezone and UTC, enabling accurate time-based analysis and scheduling
Handle different event formats: Seamlessly work with conventional race weekends (3 practice sessions), sprint weekends (varied formats by year), and pre-season testing events
Leverage pandas integration: Work with familiar DataFrame and Series interfaces enhanced with domain-specific methods for F1 data navigation
Benefit from intelligent caching: All schedule data is cached in-memory after first fetch, making subsequent queries instant with zero network overhead
The events module is built on several key design principles:
Performance-First: Aggressive caching strategies ensure that schedule queries complete in <10ms after initial load. The module uses vendored JSON files for recent seasons and CDN fallback for historical data.
Pandas Integration: All data structures extend pandas DataFrame and Series, providing familiar interfaces while adding F1-specific methods. This enables seamless integration with the broader pandas ecosystem.
Flexible Querying: Multiple lookup methods (by round number, by name, by fuzzy match) ensure you can query data in the most natural way for your use case, whether programmatic or interactive.
Type Safety: Full type hints throughout the API enable excellent IDE autocomplete and static type checking with mypy or pyright.
Graceful Degradation: The module handles missing data, network failures, and edge cases gracefully, providing clear error messages and fallback behaviors.
All event discovery functions use intelligent caching to minimize network requests. Schedule data is fetched once per Python session and reused across multiple queries, making repeated lookups essentially free.
The events module tells you what data should be available based on the official F1 calendar. However, actual data availability in the CDN may vary. Always handle DataNotFoundError exceptions when loading session data, as some sessions may have incomplete or missing data.
Retrieves the complete EventSchedule DataFrame containing all Grand Prix events for the specified Formula 1 season. This is the primary entry point for discovering what events occurred (or are scheduled) in a given year.The function returns an EventSchedule object, which is a specialized pandas DataFrame subclass that includes additional methods for event lookup and filtering. Each row in the DataFrame represents a single Grand Prix event with comprehensive metadata including event names, locations, dates, session schedules, and format information.Parameters:
The Formula 1 season year to query. Must be between 2018 and the current year (2026). Years outside this range will raise a DataNotFoundError as schedule data is not available.Supported years: 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026
A pandas DataFrame subclass containing all events for the specified year. The DataFrame includes the following columns:
EventName: Official short name (e.g., “Belgian Grand Prix”)
Location: Circuit or venue name (e.g., “Spa-Francorchamps”)
OfficialEventName: Full official event title with sponsors
RoundNumber: Championship round number (1-indexed)
Country: Country where the event takes place
EventDate: Main event date (typically race day)
EventFormat: Format type (“conventional”, “sprint”, or “testing”)
Session1, Session2, …: Names of available sessions
Session1Date, Session2Date, …: Local session start times with timezone
Session1DateUtc, Session2DateUtc, …: UTC session start times
The EventSchedule object also provides specialized methods like get_event_by_round() and get_event_by_name() for convenient event lookup.
Raises:
DataNotFoundError: If schedule data is not available for the specified year
NetworkError: If the CDN request fails and no cached data is available
InvalidDataError: If the schedule data is malformed or cannot be parsed
Behavior & Implementation Details:
Caching: Schedule data is cached in-memory after the first request. Subsequent calls for the same year return cached data instantly without network requests.
Data Source: Schedule data is fetched from the TracingInsights CDN (via jsdelivr) or loaded from vendored JSON files included with the package for recent seasons.
Testing Events: By default, get_events() includes all events including pre-season testing. Use get_event_schedule(year, include_testing=False) if you want to exclude testing events.
Performance: This function is highly optimized and typically completes in <10ms for cached data or <200ms for initial CDN fetch.
Example Usage:
import tif1# Get all events for the 2021 seasonschedule = tif1.get_events(2021)print(f"Found {len(schedule)} events in 2021")# Output: Found 22 events in 2021# Access event namesprint("\n2021 F1 Calendar:")for event_name in schedule['EventName']: print(f" - {event_name}")# Get specific event by round numberbelgian_gp = schedule.get_event_by_round(12)print(f"\nRound 12: {belgian_gp['EventName']}")print(f"Location: {belgian_gp['Location']}")print(f"Country: {belgian_gp['Country']}")# Access the underlying DataFrameprint(f"\nDataFrame shape: {schedule.shape}")print(f"Columns: {list(schedule.columns)}")# Filter events by countryuk_events = schedule[schedule['Country'] == 'United Kingdom']print(f"\nUK events: {len(uk_events)}")
Advanced Example - Analyzing Event Distribution:
import tif1import pandas as pd# Get multiple yearsyears = [2021, 2022, 2023, 2024]all_schedules = []for year in years: schedule = tif1.get_events(year) schedule['Year'] = year # Add year column all_schedules.append(schedule)# Combine into single DataFramecombined = pd.concat(all_schedules, ignore_index=True)# Analyze event distribution by countrycountry_counts = combined['Country'].value_counts()print("Events by country (2021-2024):")print(country_counts.head(10))# Find events that appear in all yearsevent_names = combined.groupby('EventName')['Year'].nunique()consistent_events = event_names[event_names == len(years)]print(f"\nEvents in all {len(years)} years: {len(consistent_events)}")
Use get_events() when you need to work with the complete season schedule as a DataFrame. If you only need a single event, use get_event() or get_event_by_round() for better performance.
Returns a list of all available session names for a specific Grand Prix event. This function is essential for discovering which sessions (Practice, Qualifying, Sprint, Race, etc.) have data available in the CDN for a particular event.Session availability varies by event format:
Conventional weekends: Typically include Practice 1, Practice 2, Practice 3, Qualifying, and Race
Sprint weekends: May include Practice 1, Sprint Qualifying, Sprint, Qualifying, and Race (format varies by year)
Testing events: Usually include multiple test sessions
The Grand Prix event name. This should match the official event name (e.g., “Belgian Grand Prix”), but fuzzy matching is applied internally so partial names like “Belgian” will also work.Accepted formats:
Full official name: “Belgian Grand Prix”
Partial name: “Belgian”
Case-insensitive: “belgian grand prix”, “BELGIAN GP”
An ordered list of session names available for the specified event. Sessions are returned in chronological order (Practice 1 → Practice 2 → … → Race).Common session names:
DataNotFoundError: If the event is not found in the specified year’s schedule
NetworkError: If schedule data cannot be fetched and no cache is available
InvalidDataError: If the schedule data is malformed
Behavior & Implementation Details:
Fuzzy Matching: The function uses intelligent fuzzy matching to find events even with partial or misspelled names. For example, “Silverstone”, “British”, and “British Grand Prix” will all match the British Grand Prix.
Caching: Session lists are cached after the first query, making subsequent calls instant.
Data Availability: The returned list indicates which sessions have data available in the CDN. Just because a session is listed doesn’t guarantee all data types (laps, telemetry, etc.) are available for that session.
Empty Lists: If an event has no sessions (rare edge case), an empty list is returned rather than raising an exception.
Example Usage:
import tif1# Get sessions for a specific eventsessions = tif1.get_sessions(2021, "Belgian Grand Prix")print(f"Belgian GP 2021 sessions: {sessions}")# Output: ['Practice 1', 'Practice 2', 'Practice 3', 'Qualifying', 'Race']# Iterate through sessionsfor session in sessions: print(f" - {session}")# Check if a specific session existsif "Sprint" in sessions: print("This is a sprint weekend!")else: print("This is a conventional weekend")# Fuzzy matching works with partial namessessions_fuzzy = tif1.get_sessions(2021, "Belgian")assert sessions == sessions_fuzzy # Same result
Advanced Example - Session Availability Analysis:
import tif1year = 2023schedule = tif1.get_events(year)# Analyze session availability across all eventssession_counts = {}for event_name in schedule['EventName']: sessions = tif1.get_sessions(year, event_name) session_count = len(sessions) if session_count not in session_counts: session_counts[session_count] = [] session_counts[session_count].append(event_name)# Print distributionprint(f"Session count distribution for {year}:")for count, events in sorted(session_counts.items()): print(f" {count} sessions: {len(events)} events") for event in events: print(f" - {event}")# Find sprint weekendsprint(f"\nSprint weekends in {year}:")for event_name in schedule['EventName']: sessions = tif1.get_sessions(year, event_name) if "Sprint" in sessions: print(f" - {event_name}") print(f" Sessions: {sessions}")
Practical Example - Loading All Sessions for an Event:
import tif1year = 2021event_name = "Belgian Grand Prix"# Get all available sessionssessions = tif1.get_sessions(year, event_name)# Load data for each sessionevent = tif1.get_event(year, event_name)for session_name in sessions: print(f"\nLoading {session_name}...") session = event.get_session(session_name) try: session.load(laps=True, telemetry=False) print(f" ✓ Loaded {len(session.laps)} laps") print(f" ✓ Drivers: {', '.join(session.drivers)}") except Exception as e: print(f" ✗ Failed to load: {e}")
The presence of a session in the returned list does not guarantee that all data types are available. Some sessions may have lap data but no telemetry, or vice versa. Always handle potential DataNotFoundError exceptions when loading session data.
Retrieves an Event object for a specific Grand Prix by either name or round number. This is the most flexible event lookup function, accepting multiple identifier formats and providing intelligent fuzzy matching for event names.The returned Event object is a pandas Series subclass that contains all event metadata and provides methods for accessing sessions, session timings, and other event-specific information.Parameters:
An Event object (pandas Series subclass) containing event metadata and methods for session access. Returns None if the event is not found and exact_match=False.When exact_match=True, raises DataNotFoundError instead of returning None.
Raises:
DataNotFoundError: If the event is not found and exact_match=True, or if the round number is invalid
ValueError: If the round number is out of range for the season
NetworkError: If schedule data cannot be fetched
InvalidDataError: If the schedule data is malformed
Behavior & Implementation Details:
Fuzzy Matching Algorithm: Uses Levenshtein distance and token-based matching to find the closest event name. Handles common abbreviations, typos, and variations.
Round Number Lookup: When gp is an integer, performs direct index lookup in the schedule (O(1) operation).
Caching: Event objects are cached after creation, making repeated lookups instant.
None vs Exception: With exact_match=False, returns None for not found. With exact_match=True, raises DataNotFoundError. Choose based on your error handling preference.
Example Usage:
import tif1# Lookup by round number (most reliable)event = tif1.get_event(2021, 12)print(f"Round 12: {event['EventName']}")# Output: Round 12: Belgian Grand Prix# Lookup by full nameevent = tif1.get_event(2021, "Belgian Grand Prix")# Lookup by partial name (fuzzy matching)event = tif1.get_event(2021, "Belgian")event = tif1.get_event(2021, "Spa")# Case-insensitive matchingevent = tif1.get_event(2021, "belgian grand prix")event = tif1.get_event(2021, "BELGIAN GP")# Exact match (strict)event = tif1.get_event(2021, "Belgian Grand Prix", exact_match=True)# Access event dataif event: print(f"Event: {event['EventName']}") print(f"Location: {event['Location']}") print(f"Round: {event['RoundNumber']}") print(f"Country: {event['Country']}") print(f"Year: {event.year}") print(f"Format: {event['EventFormat']}")# Handle not foundevent = tif1.get_event(2021, "NonexistentGP")if event is None: print("Event not found")
Advanced Example - Event Comparison:
import tif1# Compare the same event across multiple yearsevent_name = "Monaco Grand Prix"years = [2021, 2022, 2023, 2024]print(f"Comparing {event_name} across years:\n")for year in years: event = tif1.get_event(year, event_name) if event: print(f"{year}:") print(f" Round: {event['RoundNumber']}") print(f" Date: {event['EventDate']}") print(f" Format: {event['EventFormat']}") # Get sessions sessions = tif1.get_sessions(year, event_name) print(f" Sessions: {', '.join(sessions)}") print()
Practical Example - Flexible Event Lookup:
import tif1def find_and_load_race(year: int, event_identifier: int | str): """ Flexible function to find and load race data by any identifier. """ # Try to get the event event = tif1.get_event(year, event_identifier) if event is None: print(f"Could not find event: {event_identifier}") return None print(f"Found: {event['EventName']}") print(f"Location: {event['Location']}") print(f"Round: {event['RoundNumber']}") # Load the race session race = event.get_race() race.load() print(f"Loaded {len(race.laps)} laps from {len(race.drivers)} drivers") return race# All of these work:race1 = find_and_load_race(2021, 12) # By roundrace2 = find_and_load_race(2021, "Belgian") # By partial namerace3 = find_and_load_race(2021, "Belgian Grand Prix") # By full name
For programmatic access where you know the exact round number, use the integer form for best performance and reliability. For user-facing applications or interactive use, the fuzzy string matching provides excellent user experience.
Retrieves an Event object by its championship round number. This is the most reliable and performant way to look up events when you know the round number, as it performs a direct index lookup without any string matching or fuzzy logic.Round numbers are 1-indexed and correspond to the official FIA Formula 1 World Championship round numbering. For example, the first race of the season is round 1, the second is round 2, and so on. Round numbers are consistent within a season but may vary between seasons for the same Grand Prix (e.g., Monaco might be round 5 in one year and round 7 in another).Parameters:
An Event object (pandas Series subclass) containing all event metadata and methods for session access. The Event object provides dictionary-style access to fields like EventName, Location, Country, RoundNumber, EventDate, EventFormat, and session information.
Raises:
ValueError: If the round number is out of range for the specified season (e.g., requesting round 25 when the season only has 22 rounds)
DataNotFoundError: If schedule data is not available for the specified year
NetworkError: If schedule data cannot be fetched from the CDN and no cached data is available
Behavior & Implementation Details:
Performance: This is the fastest event lookup method, performing a direct index lookup in O(1) time. Use this when you know the round number.
Reliability: Round numbers are unambiguous and don’t require any fuzzy matching or string comparison, making this the most reliable lookup method.
Caching: Event objects are cached after creation, so repeated calls with the same parameters return instantly.
Testing Events: Pre-season testing events typically don’t have round numbers and cannot be accessed via this function. Use get_event_by_name() for testing events.
Example Usage:
import tif1# Get the 12th race of 2021 (Belgian Grand Prix)event = tif1.get_event_by_round(2021, 12)print(f"Round 12: {event['EventName']}")# Output: Round 12: Belgian Grand Prixprint(f"Location: {event['Location']}")# Output: Location: Spa-Francorchampsprint(f"Country: {event['Country']}")# Output: Country: Belgium# Access all event metadataprint(f"Official Name: {event['OfficialEventName']}")print(f"Event Date: {event['EventDate']}")print(f"Format: {event['EventFormat']}")print(f"Year: {event.year}")
Advanced Example - Analyzing Round Progression:
import tif1year = 2021# Get the complete scheduleschedule = tif1.get_events(year)total_rounds = len(schedule)print(f"{year} F1 Season - {total_rounds} rounds\n")# Iterate through all roundsfor round_num in range(1, total_rounds + 1): event = tif1.get_event_by_round(year, round_num) print(f"Round {round_num:2d}: {event['EventName']:30s} | {event['Location']:25s} | {event['Country']}") # Check if it's a sprint weekend if event['EventFormat'] == 'sprint': print(f" ^ Sprint Weekend")
Practical Example - Loading Specific Round Data:
import tif1def load_round_data(year: int, round_number: int): """ Load complete race weekend data for a specific championship round. """ try: # Get the event event = tif1.get_event_by_round(year, round_number) print(f"Loading Round {round_number}: {event['EventName']}") print(f"Location: {event['Location']}, {event['Country']}") print(f"Date: {event['EventDate']}") print(f"Format: {event['EventFormat']}") print() # Get all sessions sessions = tif1.get_sessions(year, event['EventName']) print(f"Available sessions: {', '.join(sessions)}") print() # Load race data race = event.get_race() race.load() print(f"Race loaded successfully:") print(f" - {len(race.laps)} total laps") print(f" - {len(race.drivers)} drivers") print(f" - Winner: {race.results.iloc[0]['Abbreviation']}") return race except ValueError as e: print(f"Error: Invalid round number - {e}") return None except Exception as e: print(f"Error loading round data: {e}") return None# Load specific roundsrace = load_round_data(2021, 12) # Belgian GPrace = load_round_data(2021, 1) # Season opener
When building applications that iterate through a season chronologically, using get_event_by_round() with a simple range loop is more efficient and clearer than iterating through the EventSchedule DataFrame.
Retrieves an Event object by its name with optional fuzzy matching. This function provides flexible event lookup using event names, partial names, circuit names, or common abbreviations. It’s ideal for interactive use, user-facing applications, or when you don’t know the exact event name format.The fuzzy matching algorithm uses Levenshtein distance and token-based matching to find the closest event name, handling typos, case variations, and partial matches intelligently. For example, “Silverstone”, “British”, “british grand prix”, and “BRITISH GP” will all successfully match the British Grand Prix.Parameters:
An Event object (pandas Series subclass) containing all event metadata and methods for session access.
Raises:
DataNotFoundError: If the event is not found in the specified year’s schedule
NetworkError: If schedule data cannot be fetched from the CDN and no cached data is available
InvalidDataError: If the schedule data is malformed or cannot be parsed
Behavior & Implementation Details:
Fuzzy Matching Algorithm: Uses a combination of Levenshtein distance (edit distance) and token-based matching to find the closest event name. The algorithm tokenizes both the search query and event names, compares tokens, and calculates similarity scores.
Match Scoring: The fuzzy matcher assigns a similarity score to each event name. The event with the highest score above a threshold is returned. If no event scores above the threshold, DataNotFoundError is raised.
Performance: Fuzzy matching is more expensive than round number lookup but still completes in <5ms for cached schedules. The algorithm iterates through all events in the season (typically 20-23 events).
Caching: Event objects are cached after creation. The fuzzy matching itself is not cached, so each call performs the matching algorithm, but the resulting Event object is reused.
Example Usage:
import tif1# Fuzzy match with partial nameevent = tif1.get_event_by_name(2021, "Belgian")print(f"Event: {event['EventName']}")# Output: Event: Belgian Grand Prix# Fuzzy match with circuit nameevent = tif1.get_event_by_name(2021, "Spa")print(f"Event: {event['EventName']}")# Output: Event: Belgian Grand Prix# Case-insensitive fuzzy matchevent = tif1.get_event_by_name(2021, "belgian grand prix")event = tif1.get_event_by_name(2021, "BELGIAN GP")# Both work and return the same event# Exact match (strict)event = tif1.get_event_by_name(2021, "Belgian Grand Prix", exact_match=True)print(f"Event: {event['EventName']}")# Output: Event: Belgian Grand Prix# Exact match failure (raises DataNotFoundError)try: event = tif1.get_event_by_name(2021, "Belgian", exact_match=True)except tif1.DataNotFoundError as e: print(f"Not found: {e}")# Access event metadataevent = tif1.get_event_by_name(2021, "Monaco")print(f"Event: {event['EventName']}")print(f"Location: {event['Location']}")print(f"Round: {event['RoundNumber']}")
Advanced Example - User Input Handling:
import tif1def find_event_interactive(year: int): """ Interactive event finder with fuzzy matching. """ user_input = input(f"Enter event name for {year}: ") try: event = tif1.get_event_by_name(year, user_input) print(f"\nFound: {event['EventName']}") print(f"Location: {event['Location']}, {event['Country']}") print(f"Round: {event['RoundNumber']}") print(f"Date: {event['EventDate']}") print(f"Format: {event['EventFormat']}") return event except tif1.DataNotFoundError: print(f"\nNo event found matching '{user_input}' in {year}") print("\nAvailable events:") schedule = tif1.get_events(year) for event_name in schedule['EventName']: print(f" - {event_name}") return None# User can type: "spa", "Belgian", "belgium", "belgian gp", etc.event = find_event_interactive(2021)
For user-facing applications, fuzzy matching provides excellent UX by accepting partial names and typos. For programmatic access with known event names, use get_event_by_round() for better performance.
Retrieves the complete event schedule for a Formula 1 season as an EventSchedule DataFrame. This function provides the most comprehensive view of a season’s calendar, including all Grand Prix events, their sessions, timing information, and metadata. It’s the foundation for season-wide analysis and iteration.The returned EventSchedule object is a specialized pandas DataFrame subclass that includes all the standard DataFrame functionality plus additional F1-specific methods for event lookup and filtering. Each row represents a single Grand Prix event with comprehensive metadata.Parameters:
An EventSchedule object (pandas DataFrame subclass) containing all events for the specified year. The DataFrame includes the following columns:Core Event Information:
EventName: Official short name (e.g., “Belgian Grand Prix”)
Location: Circuit or venue name (e.g., “Spa-Francorchamps”)
OfficialEventName: Full official event title with sponsors (e.g., “FORMULA 1 ROLEX BELGIAN GRAND PRIX 2021”)
RoundNumber: Championship round number (1-indexed integer)
Country: Country where the event takes place (e.g., “Belgium”)
EventDate: Main event date as pandas Timestamp (typically race day)
EventFormat: Format type - one of “conventional”, “sprint”, or “testing”
Session Information:
Session1, Session2, Session3, Session4, Session5: Names of available sessions (e.g., “Practice 1”, “Qualifying”, “Race”)
Session1Date, Session2Date, …: Local session start times with timezone information
Session1DateUtc, Session2DateUtc, …: UTC session start times as pandas Timestamps
The EventSchedule object also provides specialized methods:
get_event_by_round(round_number): Get a specific event by round number
get_event_by_name(name, strict_search=False): Get a specific event by name with optional fuzzy matching
get_event(identifier, strict_search=False): Get an event by either round number or name
All standard pandas DataFrame operations are available (filtering, sorting, grouping, etc.).
Raises:
DataNotFoundError: If schedule data is not available for the specified year
NetworkError: If the CDN request fails and no cached data is available
InvalidDataError: If the schedule data is malformed or cannot be parsed
Behavior & Implementation Details:
Data Source: Schedule data is fetched from vendored JSON files (for recent seasons) or the TracingInsights CDN (for historical seasons). The data source is transparent to the user.
Caching: Schedule data is cached in-memory after the first request. Subsequent calls for the same year and include_testing value return cached data instantly.
Testing Events: Testing events typically have EventFormat="testing" and may not have a RoundNumber. They usually appear at the beginning of the season (pre-season testing).
Session Columns: The number of session columns varies by event format. Conventional weekends typically have 5 sessions (FP1, FP2, FP3, Q, R), while sprint weekends may have different configurations. Unused session columns contain NaN or empty strings.
Timezone Handling: Session dates include timezone information. Local times use the circuit’s timezone (e.g., “Europe/Brussels” for Spa), while UTC times are timezone-aware UTC timestamps.
Performance: Initial load takes <200ms for CDN fetch or <10ms for vendored data. Cached access is <1ms.
Example Usage:
import tif1# Get complete schedule including testingschedule = tif1.get_event_schedule(2021)print(f"Total events: {len(schedule)}")# Output: Total events: 23 (including testing)# Get schedule without testingschedule = tif1.get_event_schedule(2021, include_testing=False)print(f"Championship events: {len(schedule)}")# Output: Championship events: 22# Access DataFrame columnsprint(f"Columns: {list(schedule.columns)}")# Iterate through eventsfor idx, row in schedule.iterrows(): print(f"Round {row['RoundNumber']}: {row['EventName']}")# Get specific event using EventSchedule methodsbelgian_gp = schedule.get_event_by_round(12)print(f"\nRound 12: {belgian_gp['EventName']}")# Filter events by countryuk_events = schedule[schedule['Country'] == 'United Kingdom']print(f"\nUK events: {len(uk_events)}")for event_name in uk_events['EventName']: print(f" - {event_name}")# Filter sprint weekendssprint_events = schedule[schedule['EventFormat'] == 'sprint']print(f"\nSprint weekends: {len(sprint_events)}")
Advanced Example - Season Analysis:
import tif1import pandas as pdyear = 2021# Get complete scheduleschedule = tif1.get_event_schedule(year, include_testing=False)print(f"=== {year} F1 Season Analysis ===\n")# Basic statisticsprint(f"Total championship events: {len(schedule)}")print(f"Sprint weekends: {len(schedule[schedule['EventFormat'] == 'sprint'])}")print(f"Conventional weekends: {len(schedule[schedule['EventFormat'] == 'conventional'])}")# Events by countrycountry_counts = schedule['Country'].value_counts()print(f"\nEvents by country:")for country, count in country_counts.items(): print(f" {country}: {count}")# Events by monthschedule['Month'] = pd.to_datetime(schedule['EventDate']).dt.month_name()month_counts = schedule['Month'].value_counts()print(f"\nEvents by month:")for month, count in month_counts.items(): print(f" {month}: {count}")# Find back-to-back racesschedule_sorted = schedule.sort_values('EventDate')schedule_sorted['DateDiff'] = schedule_sorted['EventDate'].diff().dt.daysback_to_back = schedule_sorted[schedule_sorted['DateDiff'] <= 7]print(f"\nBack-to-back race weekends:")for idx, row in back_to_back.iterrows(): print(f" {row['EventName']} (Round {row['RoundNumber']})")
Practical Example - Export to CSV:
import tif1year = 2021# Get scheduleschedule = tif1.get_event_schedule(year, include_testing=False)# Select relevant columns for exportexport_columns = [ 'RoundNumber', 'EventName', 'Location', 'Country', 'EventDate', 'EventFormat', 'Session1', 'Session2', 'Session3', 'Session4', 'Session5']# Export to CSVschedule[export_columns].to_csv(f'f1_schedule_{year}.csv', index=False)print(f"Schedule exported to f1_schedule_{year}.csv")# Or export to Excelschedule[export_columns].to_excel(f'f1_schedule_{year}.xlsx', index=False)
Practical Example - Calendar Visualization:
import tif1import matplotlib.pyplot as pltimport pandas as pdyear = 2023# Get scheduleschedule = tif1.get_event_schedule(year, include_testing=False)# Convert EventDate to datetimeschedule['EventDate'] = pd.to_datetime(schedule['EventDate'])# Create visualizationfig, ax = plt.subplots(figsize=(14, 8))# Plot events on timelinefor idx, row in schedule.iterrows(): color = 'red' if row['EventFormat'] == 'sprint' else 'blue' ax.scatter(row['EventDate'], row['RoundNumber'], c=color, s=100, alpha=0.6) ax.text(row['EventDate'], row['RoundNumber'], f" {row['EventName']}", fontsize=8, va='center')ax.set_xlabel('Date')ax.set_ylabel('Round Number')ax.set_title(f'{year} F1 Calendar')ax.grid(True, alpha=0.3)# Add legendax.scatter([], [], c='blue', s=100, alpha=0.6, label='Conventional')ax.scatter([], [], c='red', s=100, alpha=0.6, label='Sprint')ax.legend()plt.tight_layout()plt.savefig(f'f1_calendar_{year}.png', dpi=300)print(f"Calendar visualization saved to f1_calendar_{year}.png")
Use get_event_schedule() when you need to work with the complete season as a DataFrame for analysis, filtering, or iteration. For single event lookup, use get_event() or get_event_by_round() for better performance.
The get_events(year) function is an alias for get_event_schedule(year, include_testing=True). Both return the same EventSchedule object. Use whichever name is more intuitive for your use case.
def get_session(session_name: int | str) -> Session
Get a Session object for a specific session within this event.Parameters:
session_name: Session identifier - can be:
Session name (e.g., “Qualifying”, “Race”)
Session abbreviation (e.g., “Q”, “R”, “FP1”)
Session number (e.g., 1, 2, 3)
Returns:
Session object ready to load data
Raises:
ValueError: If the session identifier is invalid or doesn’t exist for this event
Example:
import tif1event = tif1.get_event(2021, "Belgian Grand Prix")# Get by namequalifying = event.get_session("Qualifying")race = event.get_session("Race")# Get by abbreviationfp1 = event.get_session("FP1")# Get by numberpractice_1 = event.get_session(1)# Load datarace.load()print(f"Drivers: {race.drivers}")
Return an Event by round number or name.Parameters:
identifier: Round number (int) or event name (str)
strict_search: If True, requires exact name match
Returns:
Event object or None if not found
Example:
import tif1schedule = tif1.get_event_schedule(2021)# Get by roundbelgian_gp = schedule.get_event_by_round(12)# Get by namebelgian_gp = schedule.get_event_by_name("Belgian Grand Prix")# Get by eitherbelgian_gp = schedule.get_event(12)belgian_gp = schedule.get_event("Belgian")
While tif1 is flexible with session names, use these standard formats for consistency:
Session Type
Standard Name
Practice
Practice 1, Practice 2, Practice 3
Qualifying
Qualifying
Sprint Qualifying
Sprint Qualifying, Sprint Shootout
Sprint Race
Sprint
Main Race
Race
Testing
Pre-Season Testing
tif1 uses fuzzy matching internally, so “P1”, “FP1”, and “Practice 1” all resolve to the same session. However, using standard names improves code clarity.
import tif1year = 2021# Get all events (returns EventSchedule DataFrame)schedule = tif1.get_events(year)print(f"Season {year} has {len(schedule)} events\n")# Get sessions for each eventfor event_name in schedule['EventName']: sessions = tif1.get_sessions(year, event_name) print(f"{event_name}:") for session in sessions: print(f" - {session}") print()
import tif1# Get event by round (Belgian GP was round 12 in 2021)event = tif1.get_event_by_round(2021, 12)print(f"Round 12: {event['EventName']}")print(f"Location: {event['Location']}")# Get all sessions for this eventsessions = tif1.get_sessions(event.year, event['EventName']})print(f"Sessions: {sessions}")# Load a specific sessionrace = event.get_session("Race")race.load()print(f"Drivers: {race.drivers}")
import tif1# All of these work with fuzzy matchingevent1 = tif1.get_event(2021, "Belgian")event2 = tif1.get_event(2021, "belgian grand prix")event3 = tif1.get_event(2021, "BELGIAN GP")# All resolve to the same eventassert event1['EventName'] == event2['EventName'] == event3['EventName']print(event1['EventName']) # "Belgian Grand Prix"
1. Use round numbers when possibleRound number lookups are O(1) operations and don’t require fuzzy matching:
# Fast - direct index lookupevent = tif1.get_event_by_round(2021, 12)# Slower - requires fuzzy matchingevent = tif1.get_event_by_name(2021, "Belgian")
2. Cache schedule data for repeated queriesIf you’re making multiple queries for the same year, fetch the schedule once:
# Good - fetch once, query many timesschedule = tif1.get_events(2021)for round_num in range(1, len(schedule) + 1): event = schedule.get_event_by_round(round_num) # Process event...# Less efficient - fetches schedule on every callfor round_num in range(1, 23): event = tif1.get_event_by_round(2021, round_num) # Refetches schedule each time # Process event...
3. Use include_testing=False when appropriateIf you don’t need testing events, exclude them to reduce data size:
# Only championship eventsschedule = tif1.get_event_schedule(2021, include_testing=False)
4. Leverage pandas operations for filteringUse pandas DataFrame operations for efficient filtering:
schedule = tif1.get_events(2021)# Efficient - uses pandas vectorized operationssprint_events = schedule[schedule['EventFormat'] == 'sprint']# Less efficient - Python loopsprint_events = []for idx, row in schedule.iterrows(): if row['EventFormat'] == 'sprint': sprint_events.append(row)
import tif1def load_race_for_event(year: int, event_identifier: int | str): """ Helper to load race data for any event identifier. """ event = tif1.get_event(year, event_identifier) if event is None: raise ValueError(f"Event not found: {event_identifier}") race = event.get_race() race.load() return racedef get_sprint_weekends(year: int) -> list[str]: """ Get list of sprint weekend event names for a year. """ schedule = tif1.get_event_schedule(year, include_testing=False) sprint_events = schedule[schedule['EventFormat'] == 'sprint'] return sprint_events['EventName'].tolist()# Usagerace = load_race_for_event(2021, 12)sprint_weekends = get_sprint_weekends(2023)
2. Use type hints for clarity
from typing import Optionalimport tif1def find_event_by_country( year: int, country: str) -> Optional[tif1.Event]: """ Find the first event in a specific country. """ schedule = tif1.get_events(year) events = schedule[schedule['Country'] == country] if len(events) == 0: return None return events.iloc[0]
3. Document expected data availability
def analyze_qualifying_performance(year: int, event_name: str): """ Analyze qualifying performance for an event. Note: Requires qualifying session data to be available. May raise DataNotFoundError if qualifying data is missing. """ event = tif1.get_event(year, event_name) qualifying = event.get_qualifying() qualifying.load() # Analysis code...
year = 2021event_name = "Belgian Grand Prix"# Check what sessions are availableavailable_sessions = tif1.get_sessions(year, event_name)if "Sprint" in available_sessions: event = tif1.get_event(year, event_name) sprint = event.get_sprint() sprint.load()else: print("No sprint session available for this event")
2. Validate event format expectations
event = tif1.get_event(2023, "Monaco")if event['EventFormat'] == 'sprint': print("This is a sprint weekend") # Load sprint-specific dataelif event['EventFormat'] == 'conventional': print("This is a conventional weekend") # Load conventional weekend data
3. Check data completeness
event = tif1.get_event(2021, 12)sessions = tif1.get_sessions(2021, event['EventName'])print(f"Event: {event['EventName']}")print(f"Available sessions: {len(sessions)}")# Conventional weekend should have 5 sessionsif event['EventFormat'] == 'conventional' and len(sessions) < 5: print("Warning: Some sessions may be missing data")
# Reliable test casesdef test_event_lookup(): # Monaco is always in the calendar event = tif1.get_event_by_name(2021, "Monaco") assert event is not None assert event['Country'] == 'Monaco' # Round 1 always exists event = tif1.get_event_by_round(2021, 1) assert event is not None
2. Mock network calls in tests
import pytestfrom unittest.mock import patchdef test_event_loading_with_network_error(): with patch('tif1.events._load_f1schedule_year_from_cdn') as mock_cdn: mock_cdn.side_effect = tif1.NetworkError("CDN unavailable") # Should fall back to vendored data or raise error with pytest.raises(tif1.NetworkError): schedule = tif1.get_events(2015) # Year without vendored data
3. Test fuzzy matching edge cases
def test_fuzzy_matching(): # Test various input formats test_cases = [ ("Belgian", "Belgian Grand Prix"), ("belgian grand prix", "Belgian Grand Prix"), ("BELGIAN GP", "Belgian Grand Prix"), ("Spa", "Belgian Grand Prix"), ] for input_name, expected_name in test_cases: event = tif1.get_event_by_name(2021, input_name) assert event['EventName'] == expected_name
Problem: get_event_by_name() raises DataNotFoundError even though the event exists.Solution: The event name might be too generic or ambiguous. Try:
# 1. List all available eventsschedule = tif1.get_events(2021)print(schedule['EventName'].tolist())# 2. Use more specific nameevent = tif1.get_event_by_name(2021, "Belgian Grand Prix") # Full name# 3. Use round number insteadevent = tif1.get_event_by_round(2021, 12) # More reliable
Problem: get_session() raises ValueError for a session that should exist.Solution: Check actual session availability:
# Check what sessions are actually availablesessions = tif1.get_sessions(2021, "Belgian Grand Prix")print(f"Available sessions: {sessions}")# Use exact session name from the listevent = tif1.get_event(2021, "Belgian Grand Prix")race = event.get_session(sessions[-1]) # Last session is usually the race
Problem: NetworkError when fetching schedule data.Solution: The CDN might be unavailable. Recent years use vendored data:
try: schedule = tif1.get_events(2021)except tif1.NetworkError as e: print(f"Network error: {e}") print("Try a more recent year with vendored data") # Recent years (2023+) typically have vendored data schedule = tif1.get_events(2024)
Problem: Round numbers don’t match expectations.Solution: Round numbers can change between years:
# Don't assume round numbers are consistent across years# Monaco might be round 5 in one year, round 7 in another# Instead, use event namesfor year in [2021, 2022, 2023]: event = tif1.get_event_by_name(year, "Monaco") print(f"{year}: Monaco is round {event['RoundNumber']}")
Problem: get_session_date() raises ValueError.Solution: Not all events have complete timing data:
event = tif1.get_event(2021, "Belgian Grand Prix")try: race_time = event.get_session_date("Race", utc=True) print(f"Race time: {race_time}")except ValueError: print("Session timing data not available") # Fall back to event date print(f"Event date: {event['EventDate']}")
None. The events API is stable and fully supported.
Need help? If you encounter issues with the events API, check the Troubleshooting section above or open an issue on GitHub with details about your use case.