Skip to main content
Qualifying is where drivers push to the absolute limit. This tutorial shows you how to analyze qualifying sessions, compare progression through Q1/Q2/Q3, and identify where time is gained or lost. Qualifying results showing top 10 grid positions

Loading qualifying data

import tif1
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Load qualifying session
session = tif1.get_session(2025, "Monaco Grand Prix", "Qualifying")
laps = session.laps

Understanding qualifying structure

Modern F1 qualifying has three segments:
  • Q1: All 20 drivers, bottom 5 eliminated
  • Q2: Top 15 drivers, bottom 5 eliminated
  • Q3: Top 10 drivers fight for pole
# Check which laps belong to which session
print(laps["Session"].value_counts())

Finding fastest laps per driver

Get each driver’s best lap time.
# Get fastest lap per driver
fastest_laps = session.get_fastest_laps(by_driver=True)

# Sort by lap time
fastest_laps_sorted = fastest_laps.sort_values("LapTime")

# Display top 10
print(fastest_laps_sorted[["Driver", "Team", "LapTime", "Compound"]].head(10))

Visualizing the Grid

Create a visual representation of the qualifying results.
# Get top 10 for pole position battle
top_10 = fastest_laps_sorted.head(10)

# Create bar chart
plt.figure(figsize=(12, 6))
colors = [f"#{row['TeamColor']}" if pd.notna(row['TeamColor']) else "#808080"
          for _, row in top_10.iterrows()]

plt.barh(top_10["Driver"], top_10["LapTime"], color=colors)
plt.xlabel("Lap Time (s)")
plt.ylabel("Driver")
plt.title("Qualifying Results - Top 10")
plt.gca().invert_yaxis()
plt.tight_layout()
plt.show()

Progression through sessions

Analyze how drivers improved from Q1 to Q3.
# Get fastest lap per driver per session segment
progression = laps.groupby(["Driver", "Session"])["LapTime"].min().reset_index()

# Pivot to show progression
progression_pivot = progression.pivot(
    index="Driver",
    columns="Session",
    values="LapTime"
)

# Calculate improvements
if "Q1" in progression_pivot.columns and "Q2" in progression_pivot.columns:
    progression_pivot["Q1_to_Q2"] = progression_pivot["Q1"] - progression_pivot["Q2"]

if "Q2" in progression_pivot.columns and "Q3" in progression_pivot.columns:
    progression_pivot["Q2_to_Q3"] = progression_pivot["Q2"] - progression_pivot["Q3"]

print(progression_pivot.head(10))

Visualizing Progression

Show how drivers improved through the sessions.
# Filter drivers who made it to Q3
q3_drivers = progression[progression["Session"] == "Q3"]["Driver"].unique()
q3_progression = progression[progression["Driver"].isin(q3_drivers)]

# Plot progression
plt.figure(figsize=(14, 8))
for driver in q3_drivers[:10]:  # Top 10 only
    driver_data = q3_progression[q3_progression["Driver"] == driver]
    plt.plot(driver_data["Session"], driver_data["LapTime"],
             marker="o", label=driver)

plt.xlabel("Session")
plt.ylabel("Lap Time (s)")
plt.title("Qualifying Progression - Top 10 Drivers")
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Tire compound analysis

See which compounds drivers used for their fastest laps.
# Compound usage in Q3
q3_laps = laps[laps["Session"] == "Q3"]
compound_usage = q3_laps.groupby(["Driver", "Compound"]).size().reset_index(name="Count")

print(compound_usage)

# Visualize
plt.figure(figsize=(12, 6))
sns.countplot(data=q3_laps, x="Driver", hue="Compound", palette="Set2")
plt.xticks(rotation=45)
plt.title("Tire Compound Usage in Q3")
plt.tight_layout()
plt.show()

Gap to Pole

Calculate and visualize the gap to pole position.
# Get pole time
pole_time = fastest_laps_sorted["LapTime"].iloc[0]
pole_driver = fastest_laps_sorted["Driver"].iloc[0]

# Calculate gap to pole
fastest_laps_sorted["GapToPole"] = fastest_laps_sorted["LapTime"] - pole_time

# Display
print(f"Pole Position: {pole_driver} - {pole_time:.3f}s")
print("\nGap to Pole:")
print(fastest_laps_sorted[["Driver", "LapTime", "GapToPole"]].head(10))

# Visualize
plt.figure(figsize=(12, 6))
top_10_gap = fastest_laps_sorted.head(10)
plt.bar(top_10_gap["Driver"], top_10_gap["GapToPole"])
plt.xlabel("Driver")
plt.ylabel("Gap to Pole (s)")
plt.title(f"Gap to Pole Position ({pole_driver})")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Sector Analysis

Identify which sectors drivers are strongest in.
# Get fastest sectors per driver
sector_analysis = fastest_laps_sorted[[
    "Driver", "Sector1Time", "Sector2Time", "Sector3Time"
]].copy()

# Find fastest sector time overall
fastest_s1 = laps["Sector1Time"].min()
fastest_s2 = laps["Sector2Time"].min()
fastest_s3 = laps["Sector3Time"].min()

# Calculate delta to fastest
sector_analysis["S1_Delta"] = sector_analysis["Sector1Time"] - fastest_s1
sector_analysis["S2_Delta"] = sector_analysis["Sector2Time"] - fastest_s2
sector_analysis["S3_Delta"] = sector_analysis["Sector3Time"] - fastest_s3

print(sector_analysis.head(10))

Telemetry Comparison

Compare telemetry between pole sitter and second place.
# Get top 2 drivers
p1_driver = fastest_laps_sorted["Driver"].iloc[0]
p2_driver = fastest_laps_sorted["Driver"].iloc[1]

# Get their fastest laps
p1_fastest = session.get_driver(p1_driver).get_fastest_lap()
p2_fastest = session.get_driver(p2_driver).get_fastest_lap()

# Get telemetry
p1_tel = session.get_driver(p1_driver).get_lap(
    p1_fastest["LapNumber"].iloc[0]
).telemetry
p2_tel = session.get_driver(p2_driver).get_lap(
    p2_fastest["LapNumber"].iloc[0]
).telemetry

# Plot speed comparison
plt.figure(figsize=(14, 6))
plt.plot(p1_tel["Distance"], p1_tel["Speed"], label=p1_driver, linewidth=2)
plt.plot(p2_tel["Distance"], p2_tel["Speed"], label=p2_driver, linewidth=2)
plt.xlabel("Distance (m)")
plt.ylabel("Speed (km/h)")
plt.title("Speed Comparison - Pole vs P2")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Identifying Deleted Laps

See which laps were deleted (track limits violations).
# Filter deleted laps
deleted_laps = laps[laps["Deleted"] == True]

# Count by driver
deleted_by_driver = deleted_laps.groupby("Driver").size().sort_values(ascending=False)

print("Deleted Laps by Driver:")
print(deleted_by_driver.head(10))

# Visualize
plt.figure(figsize=(12, 6))
deleted_by_driver.head(10).plot(kind="bar")
plt.xlabel("Driver")
plt.ylabel("Number of Deleted Laps")
plt.title("Track Limits Violations")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Consistency Analysis

Measure driver consistency across all qualifying laps.
# Calculate standard deviation of lap times per driver
consistency = laps.groupby("Driver")["LapTime"].agg(["mean", "std", "count"])
consistency = consistency[consistency["count"] >= 3]  # At least 3 laps
consistency = consistency.sort_values("std")

print("Most Consistent Drivers:")
print(consistency.head(10))

# Visualize
plt.figure(figsize=(12, 6))
consistency.head(10)["std"].plot(kind="bar")
plt.xlabel("Driver")
plt.ylabel("Lap Time Std Dev (s)")
plt.title("Driver Consistency (Lower is Better)")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Complete Analysis Function

Put it all together in a reusable function.
def analyze_qualifying(year, gp):
    """Complete qualifying analysis."""
    # Load session
    session = tif1.get_session(year, gp, "Qualifying")
    laps = session.laps

    # Get fastest laps
    fastest = session.get_fastest_laps(by_driver=True).sort_values("LapTime")

    # Results
    results = {
        "pole_position": fastest.iloc[0]["Driver"],
        "pole_time": fastest.iloc[0]["LapTime"],
        "top_10": fastest.head(10)[["Driver", "Team", "LapTime"]].to_dict("records"),
        "deleted_laps": len(laps[laps["Deleted"] == True]),
        "total_laps": len(laps)
    }

    # Gap to pole
    results["gaps"] = (fastest["LapTime"] - results["pole_time"]).head(10).tolist()

    return results

# Use it
results = analyze_qualifying(2025, "Monaco Grand Prix")
print(f"Pole: {results['pole_position']} - {results['pole_time']:.3f}s")
print(f"Total laps: {results['total_laps']}, Deleted: {results['deleted_laps']}")

Summary

Qualifying analysis with tif1 allows you to:
  • Identify pole position and grid order
  • Track progression through Q1/Q2/Q3
  • Analyze sector performance
  • Compare telemetry between drivers
  • Measure consistency
  • Identify track limits violations
Use these techniques to understand what separates pole position from the rest of the field.

Race Analysis

Analyze races

Advanced Telemetry

Telemetry deep dive

Examples

More examples
Last modified on March 6, 2026