lapbox.pace

API reference

lapbox.pace

Lap cleaning, a circuit’s representative pace, fuel-corrected pace, clean air, consistency and the ideal lap.

Takes FastF1’s laps as they come (session.laps). A lap is timed when it has a lap time and a pit lap when it has a pit-in or pit-out time. Drivers keep the order they first appear in, and rankings are stable sorts over it.

These are the calculations the LapBox Race Analysis page shows. When they ran in the browser, the Python and the site’s TypeScript agreed on all 7,226 values compared on two races (Monza 2025, Silverstone 2026).

Summary

LapCleaningPipeline
Drops laps unsuitable for analysis, step by step, and reports how many each step removed.
LapCleaningConfig
Thresholds and switches for LapCleaningPipeline. Frozen: build a new one to change a value.
CleaningResult
What LapCleaningPipeline.clean returns: the cleaned laps and an audit.
representative_base_time
A circuit’s normal lap time, ignoring laps that weren’t run at it.
BASE_TIME_CLEAN_PCT
Laps slower than this multiple of the best weren’t run at representative pace: rain, a safety car, traffic, a damaged car.
MIN_CLEAN_LAPS
Below this many representative laps, representative_base_time falls back to the plain median.
fuel_correct
Every timed, non-pit lap normalised to end-of-race fuel.
true_pace_ranking
The field ranked by fuel-corrected pace, with the raw rank alongside.
REPRESENTATIVE
Laps slower than this multiple of a driver’s best are compromised — safety car, traffic, mistakes — and are no measure of pace.
clean_air_split
One driver’s race split into clean-air and traffic laps, with the pace of each.
clean_air_ranking
Every driver with enough laps on both sides, biggest traffic penalty first.
CleanAirSplit
One driver’s race split by the gap to the car ahead.
DIRTY_AIR_S
The default traffic threshold (s): a rule of thumb, not measured by lapbox.
driver_consistency
The spread of one driver’s representative laps.
consistency_ranking
Consistency for every driver (or only drivers), most consistent first (lowest CV).
DriverConsistency
The spread of a driver’s representative laps.
ideal_lap
A driver’s ideal lap: the sum of their best S1, S2 and S3, against their fastest actual lap.
biggest_loss
The sector where the fastest lap left the most time.
IdealLap
The sum of a driver’s best sectors against their fastest actual lap.
BestSector
A driver’s best time in one sector, and what their fastest lap lost there.

Cleaning

LapCleaningPipelineclass

from lapbox.pace import LapCleaningPipeline
LapCleaningPipeline(config=None)

Drops laps unsuitable for analysis, step by step, and reports how many each step removed.

Parameters

config: LapCleaningConfig | None = None
Thresholds and switches; defaults to LapCleaningConfig().

The steps, in order: duplicates (on dedupe_keys), missing_laptime, pit_laps (a pit-in or pit-out time), inaccurate (FastF1’s IsAccurate is false), deleted (FastF1’s Deleted: removed by the stewards, e.g. for track limits),implausible_time (outside min_lap_time_s–max_lap_time_s) and outliers (per-driver robust z-score above outlier_mad_threshold; off by default). A step whose columns are missing is skipped, and FastF1’s LapTime is read as seconds when LapTimeSeconds isn’t there.

LapCleaningPipeline.cleanmethod

method
LapCleaningPipeline.clean(laps)

Clean laps and return the frame with a per-step audit.

Parameters

laps: pd.DataFrame
FastF1’s laps, or the same after normalize_laps.

Returns

CleaningResult

LapCleaningPipeline.runmethod

method
LapCleaningPipeline.run(laps)

Clean laps and return only the cleaned frame (clean(laps).frame).

Parameters

laps: pd.DataFrame
As for clean.

Returns

pd.DataFrame

LapCleaningConfigdataclass

from lapbox.pace import LapCleaningConfig
LapCleaningConfig(dedupe_keys=('Driver', 'LapNumber'), require_laptime=True, drop_pit_laps=True, use_is_accurate=True, drop_deleted=True, min_lap_time_s=40.0, max_lap_time_s=300.0, outlier_mad_threshold=None)

Thresholds and switches for LapCleaningPipeline. Frozen: build a new one to change a value.

Fields

dedupe_keys: tuple[str, ...] = ('Driver', 'LapNumber')
Rows sharing these are duplicates.
require_laptime: bool = True
Drop untimed laps.
drop_pit_laps: bool = True
Drop in-laps and out-laps. Turn off for studies that need them.
use_is_accurate: bool = True
Drop laps FastF1 marks inaccurate.
drop_deleted: bool = True
Drop laps the stewards deleted (FastF1’s Deleted).
min_lap_time_s: float = 40.0
Faster than this is implausible.
max_lap_time_s: float = 300.0
Slower than this is implausible.
outlier_mad_threshold: float | None = None
Per-driver robust z-score (median absolute deviation) above which a lap is an outlier. None keeps the step off.

CleaningResultdataclass

from lapbox.pace import CleaningResult
class CleaningResult

What LapCleaningPipeline.clean returns: the cleaned laps and an audit.

Fields

frame: pd.DataFrame
The cleaned laps, index reset.
initial_rows: int
Rows before cleaning.
removed: dict[str, int]
Rows each step removed, by step name.

CleaningResult.final_rowsproperty

property
CleaningResult.final_rows -> int

Rows after cleaning.

CleaningResult.total_removedproperty

property
CleaningResult.total_removed -> int

initial_rows - final_rows.

A circuit’s normal pace

representative_base_timefunction

from lapbox.pace import representative_base_time
representative_base_time(lap_times)

A circuit’s normal lap time, ignoring laps that weren’t run at it.

Parameters

lap_times: pd.Series
Lap times in seconds; missing values are ignored.

Returns

float: the median of the laps within BASE_TIME_CLEAN_PCT (107 %) of the best, or the plain median when fewer than MIN_CLEAN_LAPS (20) survive — a handful of laps is a worse estimate than a contaminated many. NaN for no laps.

BASE_TIME_CLEAN_PCTconstant

from lapbox.pace import BASE_TIME_CLEAN_PCT
BASE_TIME_CLEAN_PCT = 1.07

Laps slower than this multiple of the best weren’t run at representative pace: rain, a safety car, traffic, a damaged car.

MIN_CLEAN_LAPSconstant

from lapbox.pace import MIN_CLEAN_LAPS
MIN_CLEAN_LAPS = 20

Below this many representative laps, representative_base_time falls back to the plain median.

Fuel-corrected pace

fuel_correctfunction

from lapbox.pace import fuel_correct
fuel_correct(laps, total_laps=None)

Every timed, non-pit lap normalised to end-of-race fuel.

Parameters

laps: pd.DataFrame
FastF1’s laps (session.laps) of one race.
total_laps: int | None = None
The race distance in laps; defaults to the highest lap number in laps.

Returns

pd.DataFrame, one row per timed, non-pit lap — lap 1 included.

Columns

Driver: str
The driver.
LapNumber: int
The lap.
Compound: str
The tyre.
raw: float
The lap time (s).
corrected: float
raw - (total_laps - lap) × FUEL_EFFECT_S_PER_LAP: the lap as if run on end-of-race fuel (s).

true_pace_rankingfunction

from lapbox.pace import true_pace_ranking
true_pace_ranking(laps, total_laps=None)

The field ranked by fuel-corrected pace, with the raw rank alongside.

Parameters

laps: pd.DataFrame
FastF1’s laps (session.laps) of one race.
total_laps: int | None = None
The race distance in laps; defaults to the highest lap number in laps.

Returns

pd.DataFrame, quickest corrected pace first. Drivers with fewer than three representative laps are left out.

Columns

driver: str
The driver.
team: str | None
From Team, when present.
laps: int
Representative laps: from lap 2, not pit laps, within 107 % of the driver’s best.
raw_median: float
Median of those laps’ raw times (s).
corrected_median: float
Median of their fuel-corrected times (s).
gap: float
To the quickest corrected median (s).
raw_rank: int
Rank on the raw median.
corrected_rank: int
Rank on the corrected median.
rank_change: int
raw_rank - corrected_rank: positive when a driver moves up once fuel is accounted for — they ran their quick laps on heavy fuel.

Lap 1 is decided by the grid and the start, so it is left out before the 107 % best is taken. Counted (up to 0.1.0.dev13), it moved the corrected median by a median 9.5 ms, up to 1.08 s, in 30 of 245 driver-races. A driver with few laps can move many places: read rank_change with laps.

REPRESENTATIVEconstant

from lapbox.pace import REPRESENTATIVE
REPRESENTATIVE = 1.07

Laps slower than this multiple of a driver’s best are compromised — safety car, traffic, mistakes — and are no measure of pace.

Clean air

clean_air_splitfunction

from lapbox.pace import clean_air_split
clean_air_split(laps, driver, total_laps=None, *, threshold=2.0, gaps=None)

One driver’s race split into clean-air and traffic laps, with the pace of each.

Parameters

laps: pd.DataFrame
FastF1’s laps (session.laps) of one race.
driver: str
The driver’s abbreviation.
total_laps: int | None = None
The race distance in laps; defaults to the highest lap number in laps.
threshold: float = 2.0
Seconds to the car ahead at or under which a lap counts as traffic (DIRTY_AIR_S).
gaps: Mapping[tuple[str, int], float] | None = None
Gap to the car ahead per (driver, lap); defaults to gaps_to_car_ahead(laps). Pass it to reuse one computation across calls.

Returns

CleanAirSplit | None: None when either side has fewer than three laps — common for a driver who led throughout or never got clear.

Racing laps (lap 2 on, timed, not pit laps, within 107 % of the driver’s best) are fuel-corrected and split by the gap to the car ahead at the line. A lap with nobody ahead counts as clean air. The default gaps only count cars on the same lap, so a lapped car in front is not traffic.

clean_air_rankingfunction

from lapbox.pace import clean_air_ranking
clean_air_ranking(laps, total_laps=None, *, threshold=2.0, gaps=None)

Every driver with enough laps on both sides, biggest traffic penalty first.

Parameters

laps: pd.DataFrame
FastF1’s laps (session.laps) of one race.
total_laps: int | None = None
The race distance in laps; defaults to the highest lap number in laps.
threshold: float = 2.0
Seconds to the car ahead at or under which a lap counts as traffic (DIRTY_AIR_S).
gaps: Mapping[tuple[str, int], float] | None = None
Gap to the car ahead per (driver, lap); defaults to gaps_to_car_ahead(laps). Pass it to reuse one computation across calls.

Returns

pd.DataFrame with one column per CleanAirSplit field.

CleanAirSplitdataclass

from lapbox.pace import CleanAirSplit
class CleanAirSplit

One driver’s race split by the gap to the car ahead.

Fields

driver: str
The driver.
team: str | None
The team.
clean_laps: int
Laps in clean air.
traffic_laps: int
Laps within threshold of the car ahead.
clean_pace: float
Fuel-corrected median of the clean-air laps (s).
traffic_pace: float
Fuel-corrected median of the traffic laps (s).
delta: float
traffic_pace - clean_pace: positive means traffic cost time.
traffic_share: float
Percentage of the counted laps spent in traffic.
led_laps: int
Laps with nobody ahead, counted as clean air.

DIRTY_AIR_Sconstant

from lapbox.pace import DIRTY_AIR_S
DIRTY_AIR_S = 2.0

The default traffic threshold (s): a rule of thumb, not measured by lapbox.

Wider than the 1 s at which a follower may use the overtaking aid, because the loss in the wake starts well before a driver is close enough to attack. Pass threshold= to change it.

Consistency

driver_consistencyfunction

from lapbox.pace import driver_consistency
driver_consistency(laps, driver)

The spread of one driver’s representative laps.

Parameters

laps: pd.DataFrame
FastF1’s laps (session.laps) of one race.
driver: str
The driver’s abbreviation.

Returns

DriverConsistency | None: None with fewer than three laps to measure.

Pit laps, lap 1 and laps over 107 % of the driver’s best are left out. Only the driver’s own rows are read, so it is cheap to call once per driver on a whole session.

consistency_rankingfunction

from lapbox.pace import consistency_ranking
consistency_ranking(laps, drivers=None)

Consistency for every driver (or only drivers), most consistent first (lowest CV).

Parameters

laps: pd.DataFrame
FastF1’s laps (session.laps) of one race.
drivers: list[str] | None = None
Limit the ranking to these drivers.

Returns

pd.DataFrame with driver, laps, best, mean, std and cv; the per-lap series is on driver_consistency.

DriverConsistencydataclass

from lapbox.pace import DriverConsistency
class DriverConsistency

The spread of a driver’s representative laps.

Fields

driver: str
The driver.
laps: int
Laps counted: timed, not pit laps, not lap 1, within 107 % of the best.
best: float
Best counted lap (s).
mean: float
Mean (s).
std: float
Population standard deviation (s).
cv: float
Coefficient of variation, % (std / mean).
series: tuple[tuple[int, float], ...]
(lap, seconds off the best) for every counted lap.

Ideal lap

ideal_lapfunction

from lapbox.pace import ideal_lap
ideal_lap(laps, driver)

A driver’s ideal lap: the sum of their best S1, S2 and S3, against their fastest actual lap.

Parameters

laps: pd.DataFrame
FastF1’s laps of any session.
driver: str
The driver’s abbreviation.

Returns

IdealLap | None: None when a sector was never timed on a clean lap, or when the ideal comes out slower than the fastest lap — which happens only when that lap is missing a split (FastF1 leaves lap 1’s S1 blank after a standing start), and there is no honest gain to state.

Only clean laps count (timed, not pit laps); each sector’s best is searched on its own, so a lap missing one split still contributes the others. The ideal is rarely achievable in one lap: the best sectors usually come from laps on different fuel and tyres.

biggest_lossfunction

from lapbox.pace import biggest_loss
biggest_loss(result)

The sector where the fastest lap left the most time.

Parameters

result: IdealLap
From ideal_lap.

Returns

BestSector | None: None when the fastest lap lost nothing anywhere (or its splits are missing).

IdealLapdataclass

from lapbox.pace import IdealLap
class IdealLap

The sum of a driver’s best sectors against their fastest actual lap.

Fields

driver: str
The driver.
sectors: tuple[BestSector, BestSector, BestSector]
The best S1, S2 and S3.
ideal: float
Sum of the three best sectors (s).
fastest: float
The fastest clean lap (s).
fastest_lap: int
Its lap number.
gain: float
fastest - ideal (s, ≥ 0): time left on the table.
fastest_sectors: tuple[float, float, float] | None
The fastest lap’s sector times, when all three were recorded.
laps: int
Clean laps considered.

BestSectordataclass

from lapbox.pace import BestSector
class BestSector

A driver’s best time in one sector, and what their fastest lap lost there.

Fields

sector: int
1, 2 or 3.
seconds: float
The best time (s).
lap: int
The lap it was set on.
loss_on_fastest: float | None
The fastest lap’s sector minus this best (s, ≥ 0); None if that split wasn’t timed.

These docs describe lapbox 0.1.0. Something wrong or missing? Open an issue.