Skip to main content
A lap time heatmap provides a comprehensive view of race pace across all drivers and laps. By color-coding lap times, you can quickly identify consistent performers, tire degradation patterns, pit stop strategies, and incidents. Lap time heatmap showing race pace patterns

Understanding the visualization

In a lap time heatmap:
  • Rows represent drivers (sorted by average pace)
  • Columns represent lap numbers
  • Colors indicate lap time performance:
    • Green/cooler colors = faster laps
    • Red/warmer colors = slower laps
    • White/missing = pit stops or deleted laps
This makes it easy to spot patterns like tire degradation (gradual color shift), pit stop windows (gaps), and incidents (sudden color changes).

Loading the session

We’ll analyze the 2023 Monaco Grand Prix race to see how drivers performed throughout the event.
import tif1
import matplotlib.pyplot as plt
import seaborn as sns

# Setup plotting
tif1.plotting.setup_mpl(color_scheme='fastf1')

# Load race session
session = tif1.get_session(2023, 'Monaco Grand Prix', 'R')
laps = session.laps

Preparing the data

Convert lap times to seconds and filter out invalid laps for cleaner visualization.
# Convert lap times to seconds
laps_clean = laps.copy()
laps_clean['LapTimeSeconds'] = laps_clean['LapTime'].dt.total_seconds()

# Filter out invalid laps
laps_clean = laps_clean[~laps_clean['Deleted']]
laps_clean = laps_clean[laps_clean['PitInTime'].isna()]
laps_clean = laps_clean[laps_clean['PitOutTime'].isna()]

# Remove outliers (laps slower than 107% of fastest)
fastest_lap = laps_clean['LapTimeSeconds'].min()
laps_clean = laps_clean[laps_clean['LapTimeSeconds'] < fastest_lap * 1.07]

Creating the pivot table

Reshape the data so drivers are rows and lap numbers are columns.
# Create pivot table
heatmap_data = laps_clean.pivot_table(
    index='Driver',
    columns='LapNumber',
    values='LapTimeSeconds',
    aggfunc='first'
)

# Sort drivers by average lap time (fastest at top)
driver_avg = heatmap_data.mean(axis=1).sort_values()
heatmap_data = heatmap_data.loc[driver_avg.index]

Visualizing with seaborn

Use seaborn’s heatmap function for a clean, professional visualization.
fig, ax = plt.subplots(figsize=(16, 10))

sns.heatmap(
    heatmap_data,
    cmap='RdYlGn_r',  # Red (slow) to Green (fast)
    vmin=fastest_lap,
    vmax=fastest_lap * 1.07,
    cbar_kws={'label': 'Lap Time (seconds)', 'aspect': 40},
    linewidths=0.5,
    linecolor='#1a1a1a',
    xticklabels=5,  # Show every 5th lap number
    ax=ax
)

# Styling
ax.set_xlabel('Lap Number', fontsize=12, fontweight='bold')
ax.set_ylabel('Driver', fontsize=12, fontweight='bold')
ax.set_title('Monaco Grand Prix - Lap Time Heatmap',
             fontsize=14, fontweight='bold', pad=20)

plt.yticks(rotation=0, fontsize=10)
plt.xticks(fontsize=10)

plt.tight_layout()
plt.show()

Interpreting the heatmap

When analyzing a lap time heatmap, look for:
  • Vertical patterns: Consistent colors down a column indicate similar pace across all drivers (e.g., safety car periods)
  • Horizontal patterns: Gradual color shifts across a row show tire degradation or fuel load effects
  • Gaps: White spaces indicate pit stops
  • Sudden changes: Abrupt color shifts may indicate incidents, traffic, or mistakes
  • Consistent greens: Drivers with predominantly green rows had strong, consistent pace

Adding fuel correction

To better compare true pace, you can apply fuel correction to account for decreasing fuel load:
# Simple fuel correction formula
# Assumes ~0.03s per lap improvement per 1kg of fuel burned
max_laps = laps_clean['LapNumber'].max()
laps_clean['FuelCorrected'] = (
    laps_clean['LapTimeSeconds'] -
    (0.03 * (max_laps - laps_clean['LapNumber']))
)

# Create heatmap with fuel-corrected times
heatmap_fuel = laps_clean.pivot_table(
    index='Driver',
    columns='LapNumber',
    values='FuelCorrected',
    aggfunc='first'
)

# Sort and plot
driver_avg_fuel = heatmap_fuel.mean(axis=1).sort_values()
heatmap_fuel = heatmap_fuel.loc[driver_avg_fuel.index]

fig, ax = plt.subplots(figsize=(16, 10))
sns.heatmap(
    heatmap_fuel,
    cmap='RdYlGn_r',
    vmin=heatmap_fuel.min().min(),
    vmax=heatmap_fuel.min().min() * 1.07,
    cbar_kws={'label': 'Fuel-Corrected Lap Time (s)', 'aspect': 40},
    linewidths=0.5,
    linecolor='#1a1a1a',
    xticklabels=5,
    ax=ax
)

ax.set_xlabel('Lap Number', fontsize=12, fontweight='bold')
ax.set_ylabel('Driver', fontsize=12, fontweight='bold')
ax.set_title('Monaco GP - Fuel-Corrected Lap Times',
             fontsize=14, fontweight='bold', pad=20)

plt.tight_layout()
plt.show()

Complete example

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

# Setup
tif1.plotting.setup_mpl(color_scheme='fastf1')
session = tif1.get_session(2023, 'Monaco Grand Prix', 'R')
laps = session.laps

# Prepare data
laps_clean = laps.copy()
laps_clean['LapTimeSeconds'] = laps_clean['LapTime'].dt.total_seconds()
laps_clean = laps_clean[~laps_clean['Deleted']]
laps_clean = laps_clean[laps_clean['PitInTime'].isna()]
laps_clean = laps_clean[laps_clean['PitOutTime'].isna()]

fastest_lap = laps_clean['LapTimeSeconds'].min()
laps_clean = laps_clean[laps_clean['LapTimeSeconds'] < fastest_lap * 1.07]

# Create pivot table
heatmap_data = laps_clean.pivot_table(
    index='Driver',
    columns='LapNumber',
    values='LapTimeSeconds',
    aggfunc='first'
)

driver_avg = heatmap_data.mean(axis=1).sort_values()
heatmap_data = heatmap_data.loc[driver_avg.index]

# Plot
fig, ax = plt.subplots(figsize=(16, 10))

sns.heatmap(
    heatmap_data,
    cmap='RdYlGn_r',
    vmin=fastest_lap,
    vmax=fastest_lap * 1.07,
    cbar_kws={'label': 'Lap Time (seconds)', 'aspect': 40},
    linewidths=0.5,
    linecolor='#1a1a1a',
    xticklabels=5,
    ax=ax
)

ax.set_xlabel('Lap Number', fontsize=12, fontweight='bold')
ax.set_ylabel('Driver', fontsize=12, fontweight='bold')
ax.set_title('Monaco Grand Prix - Lap Time Heatmap',
             fontsize=14, fontweight='bold', pad=20)

plt.yticks(rotation=0, fontsize=10)
plt.xticks(fontsize=10)

plt.tight_layout()
plt.show()

Advanced variations

Compare specific drivers

Focus on a subset of drivers for detailed comparison:
# Select top 5 finishers
top_drivers = session.drivers[:5]
laps_top = laps_clean[laps_clean['Driver'].isin(top_drivers)]

heatmap_top = laps_top.pivot_table(
    index='Driver',
    columns='LapNumber',
    values='LapTimeSeconds',
    aggfunc='first'
)

# Plot with larger cells for better visibility
fig, ax = plt.subplots(figsize=(16, 6))
sns.heatmap(heatmap_top, cmap='RdYlGn_r', linewidths=1, ax=ax)
ax.set_title('Top 5 Finishers - Lap Time Comparison')
plt.tight_layout()
plt.show()

Annotate with lap times

Add actual lap time values to cells for precise analysis:
fig, ax = plt.subplots(figsize=(20, 10))

sns.heatmap(
    heatmap_data,
    cmap='RdYlGn_r',
    vmin=fastest_lap,
    vmax=fastest_lap * 1.07,
    annot=True,  # Show values
    fmt='.1f',   # Format to 1 decimal place
    cbar_kws={'label': 'Lap Time (seconds)'},
    linewidths=0.5,
    ax=ax
)

plt.tight_layout()
plt.show()

Next steps

  • Combine with tire strategy visualization to correlate pace with compounds
  • Analyze specific stint performance by filtering lap ranges
  • Compare heatmaps across different races to identify track-specific patterns
  • Use clustering algorithms to group drivers with similar pace profiles
Last modified on March 6, 2026