gr-analytics 0.3.2__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gr_analytics
3
- Version: 0.3.2
3
+ Version: 0.5.0
4
4
  Summary: Scoring and salary calculation for GridRival fantasy F1
5
5
  Author: nce8
6
6
  License-Expression: MIT
@@ -14,6 +14,8 @@ Description-Content-Type: text/markdown
14
14
  Requires-Dist: pandas
15
15
  Requires-Dist: scipy
16
16
  Requires-Dist: numpy
17
+ Provides-Extra: fastf1
18
+ Requires-Dist: fastf1; extra == "fastf1"
17
19
 
18
20
  # gr_analytics
19
21
 
@@ -39,7 +41,8 @@ scenario = pd.read_csv("my_race.csv")
39
41
  # Score the event (defaults to the latest round in driver_data)
40
42
  result = score_event(scenario)
41
43
 
42
- # Or score a specific round
44
+ # Or score a specific race: pass the round one less than the race
45
+ # (round=1 is the post-race-1 state, so this scores race 2). See "Driver Data".
43
46
  result = score_event(scenario, round=1)
44
47
 
45
48
  # Score your specific team selection
@@ -135,19 +138,31 @@ Bundled driver data (`gr_analytics/data/driver_data.csv`) contains starting sala
135
138
  - `round=0` — pre-season (before Australia 2026)
136
139
  - `round=1` — post-Australia 2026
137
140
 
141
+ Each `round=N` row holds the state **after** race N: the post-race salaries,
142
+ the finishing position from race N, and the 8-race average including race N.
143
+
144
+ **Round semantics for `score_event`:** the `round` argument is that
145
+ post-race state, so `score_event(scenario, round=N)` scores the race that
146
+ *follows* it. Pass the round **one less** than the race you're scoring:
147
+ `round=0` scores race 1 (off the pre-season seeds), `round=1` scores race 2,
148
+ and so on. Improvement points therefore compare each finish to the 8-race
149
+ average *going into* that race (`calculate_eight_race_averages(through_round=round)`).
150
+
138
151
  ## Eight-Race Average
139
152
 
140
153
  GridRival's "8 race average" can be computed instead of entered by hand.
141
154
  GridRival seeds the season with 8 slots holding a hard-coded initial
142
155
  average (the `round=0` values in driver_data); each race replaces one
143
156
  slot with the driver's classified finishing position, and the displayed
144
- value is the **ceiling** of the slot mean. Race finishing positions live
145
- in `gr_analytics/data/race_results.csv`.
157
+ value is the **ceiling** of the slot mean. Each driver's finishing
158
+ position per race is recorded in the `finishing_position` column of
159
+ `driver_data.csv` (the `round=N` row holds the finish from race N), so
160
+ the average is derived entirely from `driver_data`.
146
161
 
147
162
  ```python
148
163
  from gr_analytics import calculate_eight_race_averages, eight_race_average
149
164
 
150
- # All drivers, after the latest round in race_results.csv
165
+ # All drivers, after the latest round with recorded finishing positions
151
166
  calculate_eight_race_averages()
152
167
 
153
168
  # All drivers, after round 2
@@ -160,6 +175,12 @@ eight_race_average(1, [6, 16])
160
175
  This reproduces GridRival's displayed values exactly for all rounds so
161
176
  far (verified in `tests/test_eight_race_average.py`).
162
177
 
178
+ `score_event` uses this computed average for improvement points (the value
179
+ *going into* the race, i.e. `calculate_eight_race_averages(through_round=round)`)
180
+ rather than the stored `eight_race_average` column. That column is kept only as
181
+ GridRival's transcribed ground-truth/test oracle and may be blank for recent
182
+ rounds; scoring no longer depends on it.
183
+
163
184
  ## Lineup Optimisation
164
185
 
165
186
  `optimal_lineup` uses mixed-integer linear programming (via `scipy.optimize.milp`) to find the best 5-driver + 1-constructor lineup within a salary budget.
@@ -22,7 +22,8 @@ scenario = pd.read_csv("my_race.csv")
22
22
  # Score the event (defaults to the latest round in driver_data)
23
23
  result = score_event(scenario)
24
24
 
25
- # Or score a specific round
25
+ # Or score a specific race: pass the round one less than the race
26
+ # (round=1 is the post-race-1 state, so this scores race 2). See "Driver Data".
26
27
  result = score_event(scenario, round=1)
27
28
 
28
29
  # Score your specific team selection
@@ -118,19 +119,31 @@ Bundled driver data (`gr_analytics/data/driver_data.csv`) contains starting sala
118
119
  - `round=0` — pre-season (before Australia 2026)
119
120
  - `round=1` — post-Australia 2026
120
121
 
122
+ Each `round=N` row holds the state **after** race N: the post-race salaries,
123
+ the finishing position from race N, and the 8-race average including race N.
124
+
125
+ **Round semantics for `score_event`:** the `round` argument is that
126
+ post-race state, so `score_event(scenario, round=N)` scores the race that
127
+ *follows* it. Pass the round **one less** than the race you're scoring:
128
+ `round=0` scores race 1 (off the pre-season seeds), `round=1` scores race 2,
129
+ and so on. Improvement points therefore compare each finish to the 8-race
130
+ average *going into* that race (`calculate_eight_race_averages(through_round=round)`).
131
+
121
132
  ## Eight-Race Average
122
133
 
123
134
  GridRival's "8 race average" can be computed instead of entered by hand.
124
135
  GridRival seeds the season with 8 slots holding a hard-coded initial
125
136
  average (the `round=0` values in driver_data); each race replaces one
126
137
  slot with the driver's classified finishing position, and the displayed
127
- value is the **ceiling** of the slot mean. Race finishing positions live
128
- in `gr_analytics/data/race_results.csv`.
138
+ value is the **ceiling** of the slot mean. Each driver's finishing
139
+ position per race is recorded in the `finishing_position` column of
140
+ `driver_data.csv` (the `round=N` row holds the finish from race N), so
141
+ the average is derived entirely from `driver_data`.
129
142
 
130
143
  ```python
131
144
  from gr_analytics import calculate_eight_race_averages, eight_race_average
132
145
 
133
- # All drivers, after the latest round in race_results.csv
146
+ # All drivers, after the latest round with recorded finishing positions
134
147
  calculate_eight_race_averages()
135
148
 
136
149
  # All drivers, after round 2
@@ -143,6 +156,12 @@ eight_race_average(1, [6, 16])
143
156
  This reproduces GridRival's displayed values exactly for all rounds so
144
157
  far (verified in `tests/test_eight_race_average.py`).
145
158
 
159
+ `score_event` uses this computed average for improvement points (the value
160
+ *going into* the race, i.e. `calculate_eight_race_averages(through_round=round)`)
161
+ rather than the stored `eight_race_average` column. That column is kept only as
162
+ GridRival's transcribed ground-truth/test oracle and may be blank for recent
163
+ rounds; scoring no longer depends on it.
164
+
146
165
  ## Lineup Optimisation
147
166
 
148
167
  `optimal_lineup` uses mixed-integer linear programming (via `scipy.optimize.milp`) to find the best 5-driver + 1-constructor lineup within a salary budget.
@@ -27,21 +27,33 @@ import pandas as pd
27
27
  from scipy.optimize import Bounds, LinearConstraint, milp
28
28
 
29
29
  _DATA_DIR = Path(__file__).parent / "data"
30
+ _DRIVER_DATA_PATH = _DATA_DIR / "driver_data.csv"
31
+
32
+ # Read driver_data.csv once at import time so repeated driver_data() calls
33
+ # don't re-read and re-parse the file from disk.
34
+ try:
35
+ _DRIVER_DATA = pd.read_csv(_DRIVER_DATA_PATH)
36
+ except FileNotFoundError as exc:
37
+ raise FileNotFoundError(
38
+ f"gr_analytics could not find its bundled driver data at "
39
+ f"{_DRIVER_DATA_PATH}. The packaged data file appears to be missing; "
40
+ f"reinstalling gr_analytics should restore it."
41
+ ) from exc
42
+ except (pd.errors.EmptyDataError, pd.errors.ParserError) as exc:
43
+ raise RuntimeError(
44
+ f"gr_analytics could not parse its bundled driver data at "
45
+ f"{_DRIVER_DATA_PATH}: {exc}"
46
+ ) from exc
30
47
 
31
48
 
32
49
  def driver_data() -> pd.DataFrame:
33
- """Load and return driver_data.csv as a DataFrame."""
34
- return pd.read_csv(_DATA_DIR / "driver_data.csv")
50
+ """Return driver_data.csv as a DataFrame.
35
51
 
36
-
37
- def race_results() -> pd.DataFrame:
38
- """
39
- Load and return race_results.csv as a DataFrame.
40
-
41
- One row per driver per round with the official classified finishing
42
- position (DNF/DNS drivers receive their classified position).
52
+ The CSV is read into memory once at module import; each call returns a
53
+ fresh copy so callers can safely mutate the result without corrupting
54
+ the shared in-memory copy.
43
55
  """
44
- return pd.read_csv(_DATA_DIR / "race_results.csv")
56
+ return _DRIVER_DATA.copy()
45
57
 
46
58
 
47
59
  # ---------------------------------------------------------------------------
@@ -372,16 +384,19 @@ def calculate_eight_race_averages(through_round: int = None) -> pd.Series:
372
384
  """
373
385
  Compute every driver's eight-race average after ``through_round``.
374
386
 
375
- Uses the round-0 seeds in driver_data.csv and the finishing positions
376
- in race_results.csv. Matches the convention of driver_data, where the
377
- round-N row holds the state *after* race N (so the returned values are
378
- what GridRival displays going into race N+1).
387
+ Reads everything from driver_data.csv: the round-0 ``eight_race_average``
388
+ seeds and the per-race ``finishing_position`` recorded on each driver's
389
+ round row (the round-N row holds the finishing position from race N,
390
+ parallel to ``points_from_last_race``). Matches the convention of
391
+ driver_data, where the round-N row holds the state *after* race N (so
392
+ the returned values are what GridRival displays going into race N+1).
379
393
 
380
394
  Parameters
381
395
  ----------
382
396
  through_round : int, optional
383
397
  Include races 1 through this round. Defaults to the latest round
384
- in race_results. Pass 0 to get the season-start seeds.
398
+ whose finishing positions are recorded in driver_data. Pass 0 to
399
+ get the season-start seeds.
385
400
 
386
401
  Returns
387
402
  -------
@@ -389,20 +404,20 @@ def calculate_eight_race_averages(through_round: int = None) -> pd.Series:
389
404
  Eight-race average indexed by driver_abbr.
390
405
  """
391
406
  dd = driver_data()
392
- seeds = (
393
- dd[(dd["type"] == "driver") & (dd["round"] == 0)]
394
- .set_index("driver_abbr")["eight_race_average"]
395
- )
407
+ drivers = dd[dd["type"] == "driver"]
408
+ seeds = drivers[drivers["round"] == 0].set_index("driver_abbr")[
409
+ "eight_race_average"
410
+ ]
396
411
 
397
- results = race_results()
412
+ finishes = drivers[(drivers["round"] >= 1) & drivers["finishing_position"].notna()]
398
413
  if through_round is None:
399
- through_round = results["round"].max()
400
- results = results[results["round"] <= through_round].sort_values("round")
414
+ through_round = int(finishes["round"].max())
415
+ finishes = finishes[finishes["round"] <= through_round].sort_values("round")
401
416
 
402
417
  averages = {
403
418
  abbr: eight_race_average(
404
419
  seed,
405
- results.loc[results["driver_abbr"] == abbr, "finishing_position"],
420
+ finishes.loc[finishes["driver_abbr"] == abbr, "finishing_position"],
406
421
  )
407
422
  for abbr, seed in seeds.items()
408
423
  }
@@ -531,6 +546,11 @@ def score_event(scenario: pd.DataFrame, round: int = None) -> pd.DataFrame:
531
546
  round = dd["round"].max()
532
547
  dd = dd[dd["round"] == round].copy()
533
548
 
549
+ # `finishing_position` stored in driver_data is the historical race result
550
+ # (used only for the eight-race average); the race being scored is supplied
551
+ # by `scenario`, so drop the stored column to avoid a merge collision.
552
+ dd = dd.drop(columns=["finishing_position"], errors="ignore")
553
+
534
554
  drivers_dd = dd[dd["type"] == "driver"].copy()
535
555
  teams_dd = dd[dd["type"] == "team"].copy()
536
556
 
@@ -562,6 +582,15 @@ def score_event(scenario: pd.DataFrame, round: int = None) -> pd.DataFrame:
562
582
  how="left",
563
583
  ).rename(columns={"race_position": "finishing_position"})
564
584
 
585
+ # Improvement points compare each driver's finish to GridRival's eight-race
586
+ # average *going into* this race — i.e. the average as of the end of `round`,
587
+ # the state the scenario is scored against (round=0 scores race 1 off the
588
+ # season-start seeds). Compute it rather than trust the stored
589
+ # eight_race_average column, which is left blank for rounds whose GridRival
590
+ # value hasn't been transcribed.
591
+ avgs = calculate_eight_race_averages(through_round=round)
592
+ drivers_merged["eight_race_average"] = drivers_merged["driver_abbr"].map(avgs)
593
+
565
594
  df = pd.concat([drivers_merged, teams_dd], ignore_index=True)
566
595
 
567
596
  drivers = df[df["type"] == "driver"].copy()
@@ -654,8 +683,9 @@ def optimal_lineup(
654
683
  The star driver (whose points component is doubled) is chosen
655
684
  optimally whenever balance > 0.
656
685
  budget : float
657
- Salary budget in £M available for non-locked-in picks (default 100).
658
- Locked-in drivers are treated as free (already under contract).
686
+ Total salary budget in £M (default 100). The current salary
687
+ (``starting_salary``) of every locked-in pick is subtracted from
688
+ this to determine the budget remaining for non-locked-in picks.
659
689
  star_salary_cap : float
660
690
  Maximum starting_salary allowed for the star driver (default 19.0).
661
691
  Drivers above this threshold are excluded from star consideration.
@@ -704,7 +734,7 @@ def optimal_lineup(
704
734
  drivers_needed = 5 - n_locked_drivers
705
735
  teams_needed = 1 - n_locked_teams
706
736
 
707
- remaining_budget = budget
737
+ remaining_budget = budget - locked["starting_salary"].sum()
708
738
 
709
739
  # Restrict free pool to only the slot types still needed
710
740
  if teams_needed == 0:
@@ -0,0 +1,298 @@
1
+ type,driver_abbr,driver_name,driver_team,round,starting_salary,eight_race_average,points_from_last_race,finishing_position
2
+ driver,VER,M. Verstappen,RBR,0,30,1,,
3
+ driver,RUS,G. Russell,MER,0,28.7,2,,
4
+ driver,NOR,L. Norris,MCL,0,27.4,3,,
5
+ driver,PIA,O. Piastri,MCL,0,26.1,4,,
6
+ driver,ANT,K. Antonelli,MER,0,24.8,5,,
7
+ driver,LEC,C. Leclerc,FER,0,23.5,6,,
8
+ driver,ALO,F. Alonso,AMR,0,22.2,7,,
9
+ driver,HAM,L. Hamilton,FER,0,20.9,8,,
10
+ driver,HAD,I. Hadjar,RBR,0,19.6,9,,
11
+ driver,GAS,P. Gasly,ALP,0,18.3,10,,
12
+ driver,STR,L. Stroll,AMR,0,17,11,,
13
+ driver,SAI,C. Sainz Jr,WIL,0,15.7,12,,
14
+ driver,LAW,L. Lawson,RBS,0,14.4,13,,
15
+ driver,ALB,A. Albon,WIL,0,13.1,14,,
16
+ driver,HUL,N. Hulkenberg,AUD,0,11.8,15,,
17
+ driver,BOR,G. Bortoleto,AUD,0,10.5,16,,
18
+ driver,BEA,O. Bearman,HAS,0,9.2,17,,
19
+ driver,OCO,E. Ocon,HAS,0,7.9,18,,
20
+ driver,BOT,V. Bottas,CAD,0,4.7,19,,
21
+ driver,PER,S. Perez,CAD,0,4.7,19,,
22
+ driver,COL,F. Colapinto,ALP,0,4.7,19,,
23
+ driver,LIN,A. Lindblad,RBS,0,4.6,19,,
24
+ team,MER,MER,,0,28.5,1,,
25
+ team,MCL,MCL,,0,28.5,2,,
26
+ team,RBR,RBR,,0,25,3,,
27
+ team,FER,FER,,0,22.5,4,,
28
+ team,AMR,AMR,,0,20,5,,
29
+ team,WIL,WIL,,0,17.5,6,,
30
+ team,RBS,RBS,,0,15,7,,
31
+ team,ALP,ALP,,0,12.5,8,,
32
+ team,AUD,AUD,,0,10,9,,
33
+ team,CAD,CAD,,0,7.5,10,,
34
+ team,HAS,HAS,,0,5,11,,
35
+ driver,VER,M. Verstappen,RBR,1,28.2,2,,6
36
+ driver,RUS,G. Russell,MER,1,29.6,2,,1
37
+ driver,NOR,L. Norris,MCL,1,26.7,4,,5
38
+ driver,PIA,O. Piastri,MCL,1,24.1,7,,21
39
+ driver,ANT,K. Antonelli,MER,1,25.9,5,,2
40
+ driver,LEC,C. Leclerc,FER,1,24.5,6,,3
41
+ driver,ALO,F. Alonso,AMR,1,20.2,9,,18
42
+ driver,HAM,L. Hamilton,FER,1,22.1,8,,4
43
+ driver,HAD,I. Hadjar,RBR,1,17.6,11,,20
44
+ driver,GAS,P. Gasly,ALP,1,18.3,10,,10
45
+ driver,STR,L. Stroll,AMR,1,15,12,,17
46
+ driver,SAI,C. Sainz Jr,WIL,1,13.9,13,,15
47
+ driver,LAW,L. Lawson,RBS,1,14.5,13,,13
48
+ driver,ALB,A. Albon,WIL,1,13.9,14,,12
49
+ driver,HUL,N. Hulkenberg,AUD,1,9.8,16,,22
50
+ driver,BOR,G. Bortoleto,AUD,1,12.5,16,,9
51
+ driver,BEA,O. Bearman,HAS,1,11.2,16,,7
52
+ driver,OCO,E. Ocon,HAS,1,9.9,18,,11
53
+ driver,BOT,V. Bottas,CAD,1,4.5,19,,19
54
+ driver,PER,S. Perez,CAD,1,6.4,19,,16
55
+ driver,COL,F. Colapinto,ALP,1,6.7,19,,14
56
+ driver,LIN,A. Lindblad,RBS,1,6.6,18,,8
57
+ team,MER,MER,,1,28.8,,,
58
+ team,MCL,MCL,,1,25.7,,,
59
+ team,RBR,RBR,,1,22.4,,,
60
+ team,FER,FER,,1,23.7,,,
61
+ team,AMR,AMR,,1,17,,,
62
+ team,WIL,WIL,,1,16.1,,,
63
+ team,RBS,RBS,,1,17.4,,,
64
+ team,ALP,ALP,,1,14.2,,,
65
+ team,AUD,AUD,,1,9.8,,,
66
+ team,CAD,CAD,,1,7.3,,,
67
+ team,HAS,HAS,,1,8,,,
68
+ driver,RUS,G. Russell,MER,2,29.5,2,171,2
69
+ driver,ANT,K. Antonelli,MER,2,27.5,5,174,1
70
+ driver,VER,M. Verstappen,RBR,2,26.2,4,132,16
71
+ driver,LEC,C. Leclerc,FER,2,24.8,6,164,4
72
+ driver,NOR,L. Norris,MCL,2,24.7,6,107,20
73
+ driver,HAM,L. Hamilton,FER,2,24.1,7,169,3
74
+ driver,PIA,O. Piastri,MCL,2,22.1,8,50,19
75
+ driver,GAS,P. Gasly,ALP,2,19.8,10,143,6
76
+ driver,ALO,F. Alonso,AMR,2,18.2,10,78,17
77
+ driver,HAD,I. Hadjar,RBR,2,18.1,11,118,8
78
+ driver,LAW,L. Lawson,RBS,2,16.5,13,142,7
79
+ driver,SAI,C. Sainz Jr,WIL,2,15.9,12,123,9
80
+ driver,BEA,O. Bearman,HAS,2,13.2,15,186,5
81
+ driver,STR,L. Stroll,AMR,2,13,13,75,18
82
+ driver,ALB,A. Albon,WIL,2,11.9,15,68,22
83
+ driver,HUL,N. Hulkenberg,AUD,2,11.8,16,81,11
84
+ driver,OCO,E. Ocon,HAS,2,10.7,17,124,14
85
+ driver,BOR,G. Bortoleto,AUD,2,10.5,16,90,21
86
+ driver,COL,F. Colapinto,ALP,2,8.7,18,130,10
87
+ driver,LIN,A. Lindblad,RBS,2,8.6,17,143,12
88
+ driver,PER,S. Perez,CAD,2,7.3,19,102,15
89
+ driver,BOT,V. Bottas,CAD,2,6.5,19,94,13
90
+ team,MER,MER,,2,29.1,,177,
91
+ team,FER,FER,,2,24.6,,163,
92
+ team,MCL,MCL,,2,22.7,,77,
93
+ team,RBR,RBR,,2,21.7,,110,
94
+ team,RBS,RBS,,2,17.3,,123,
95
+ team,ALP,ALP,,2,16.8,,121,
96
+ team,AMR,AMR,,2,15.7,,72,
97
+ team,WIL,WIL,,2,13.8,,78,
98
+ team,HAS,HAS,,2,11,,125,
99
+ team,AUD,AUD,,2,9.7,,80,
100
+ team,CAD,CAD,,2,9,,83,
101
+ driver,RUS,G. Russell,MER,3,29.5,3,164,4
102
+ driver,ANT,K. Antonelli,MER,3,28.7,4,173,1
103
+ driver,LEC,C. Leclerc,FER,3,26.3,5,162,3
104
+ driver,NOR,L. Norris,MCL,3,24.7,6,118,5
105
+ driver,VER,M. Verstappen,RBR,3,24.6,5,133,8
106
+ driver,PIA,O. Piastri,MCL,3,24.1,8,91,2
107
+ driver,HAM,L. Hamilton,FER,3,23.4,7,158,6
108
+ driver,GAS,P. Gasly,ALP,3,21.7,10,143,7
109
+ driver,LAW,L. Lawson,RBS,3,18,12,140,9
110
+ driver,HAD,I. Hadjar,RBR,3,17.7,11,117,12
111
+ driver,ALO,F. Alonso,AMR,3,16.2,11,79,18
112
+ driver,SAI,C. Sainz Jr,WIL,3,14.9,13,115,15
113
+ driver,HUL,N. Hulkenberg,AUD,3,13.3,15,95,11
114
+ driver,OCO,E. Ocon,HAS,3,12.7,16,130,10
115
+ driver,BOR,G. Bortoleto,AUD,3,11.5,16,98,13
116
+ driver,BEA,O. Bearman,HAS,3,11.2,15,142,22
117
+ driver,STR,L. Stroll,AMR,3,11,14,69,21
118
+ driver,ALB,A. Albon,WIL,3,9.9,16,70,20
119
+ driver,LIN,A. Lindblad,RBS,3,9.7,17,131,14
120
+ driver,COL,F. Colapinto,ALP,3,9,17,117,16
121
+ driver,PER,S. Perez,CAD,3,7.5,18,97,17
122
+ driver,BOT,V. Bottas,CAD,3,6.2,19,87,19
123
+ team,MER,MER,,3,29.3,,175,
124
+ team,FER,FER,,3,24.6,,161,
125
+ team,MCL,MCL,,3,23.8,,106,
126
+ team,RBR,RBR,,3,21.8,,116,
127
+ team,ALP,ALP,,3,17.5,,120,
128
+ team,RBS,RBS,,3,17.3,,120,
129
+ team,AMR,AMR,,3,12.8,,69,
130
+ team,WIL,WIL,,3,12.7,,80,
131
+ team,HAS,HAS,,3,11.2,,114,
132
+ team,AUD,AUD,,3,10.8,,92,
133
+ team,CAD,CAD,,3,8.4,,80,
134
+ driver,ANT,K. Antonelli,MER,4,28.8,4,176,1
135
+ driver,RUS,G. Russell,MER,4,28.7,3,165,4
136
+ driver,NOR,L. Norris,MCL,4,26.7,6,136,2
137
+ driver,LEC,C. Leclerc,FER,4,25.9,6,161,8
138
+ driver,PIA,O. Piastri,MCL,4,25.7,8,114,3
139
+ driver,VER,M. Verstappen,RBR,4,25.3,5,144,5
140
+ driver,HAM,L. Hamilton,FER,4,23.3,7,157,6
141
+ driver,GAS,P. Gasly,ALP,4,19.7,11,130,21
142
+ driver,SAI,C. Sainz Jr,WIL,4,16.4,12,122,9
143
+ driver,LAW,L. Lawson,RBS,4,16,13,126,20
144
+ driver,HAD,I. Hadjar,RBR,4,15.7,13,100,22
145
+ driver,ALO,F. Alonso,AMR,4,15.1,12,85,15
146
+ driver,OCO,E. Ocon,HAS,4,13.2,15,127,13
147
+ driver,BEA,O. Bearman,HAS,4,12.9,15,140,11
148
+ driver,BOR,G. Bortoleto,AUD,4,12.7,15,105,12
149
+ driver,ALB,A. Albon,WIL,4,11.9,15,87,10
150
+ driver,HUL,N. Hulkenberg,AUD,4,11.3,16,91,19
151
+ driver,COL,F. Colapinto,ALP,4,11,16,135,7
152
+ driver,LIN,A. Lindblad,RBS,4,10.5,16,125,14
153
+ driver,STR,L. Stroll,AMR,4,10.4,15,74,17
154
+ driver,PER,S. Perez,CAD,4,8.1,18,98,16
155
+ driver,BOT,V. Bottas,CAD,4,6,19,85,18
156
+ team,MER,MER,,4,29.4,,174,
157
+ team,MCL,MCL,,4,24.7,,120,
158
+ team,FER,FER,,4,24.6,,158,
159
+ team,RBR,RBR,,4,20,,111,
160
+ team,ALP,ALP,,4,18,,118,
161
+ team,RBS,RBS,,4,15.3,,113,
162
+ team,WIL,WIL,,4,15,,90,
163
+ team,HAS,HAS,,4,12.6,,113,
164
+ team,AMR,AMR,,4,11.3,,74,
165
+ team,AUD,AUD,,4,11,,92,
166
+ team,CAD,CAD,,4,7.3,,80,
167
+ driver,ANT,K. Antonelli,MER,5,30.1,,180,1
168
+ driver,RUS,G. Russell,MER,5,26.7,,156,19
169
+ driver,VER,M. Verstappen,RBR,5,26.2,,150,3
170
+ driver,LEC,C. Leclerc,FER,5,25.9,,163,4
171
+ driver,HAM,L. Hamilton,FER,5,25.3,,163,2
172
+ driver,NOR,L. Norris,MCL,5,24.7,,133,18
173
+ driver,PIA,O. Piastri,MCL,5,24.6,,121,11
174
+ driver,GAS,P. Gasly,ALP,5,19.3,,132,8
175
+ driver,SAI,C. Sainz Jr,WIL,5,18,,128,9
176
+ driver,LAW,L. Lawson,RBS,5,18,,135,7
177
+ driver,HAD,I. Hadjar,RBR,5,17.7,,113,5
178
+ driver,BEA,O. Bearman,HAS,5,14.5,,140,10
179
+ driver,ALO,F. Alonso,AMR,5,13.1,,80,20
180
+ driver,COL,F. Colapinto,ALP,5,13,,145,6
181
+ driver,HUL,N. Hulkenberg,AUD,5,12.5,,98,12
182
+ driver,BOR,G. Bortoleto,AUD,5,12.5,,107,13
183
+ driver,OCO,E. Ocon,HAS,5,12.4,,124,14
184
+ driver,STR,L. Stroll,AMR,5,9.9,,81,15
185
+ driver,ALB,A. Albon,WIL,5,9.9,,81,21
186
+ driver,LIN,A. Lindblad,RBS,5,8.5,,110,22
187
+ driver,PER,S. Perez,CAD,5,7.4,,96,17
188
+ driver,BOT,V. Bottas,CAD,5,6.2,,89,16
189
+ team,MER,MER,,5,28.3,,168,
190
+ team,FER,FER,,5,25.9,,158,
191
+ team,MCL,MCL,,5,23.5,,120,
192
+ team,RBR,RBR,,5,21.8,,120,
193
+ team,ALP,ALP,,5,19,,121,
194
+ team,WIL,WIL,,5,14.2,,90,
195
+ team,RBS,RBS,,5,13.8,,108,
196
+ team,HAS,HAS,,5,13,,111,
197
+ team,AUD,AUD,,5,12.5,,96,
198
+ team,AMR,AMR,,5,9.5,,74,
199
+ team,CAD,CAD,,5,7.2,,79,
200
+ driver,ANT,K. Antonelli,MER,6,29.9,,179,1
201
+ driver,HAM,L. Hamilton,FER,6,27,,165,2
202
+ driver,PIA,O. Piastri,MCL,6,25.3,,130,5
203
+ driver,RUS,G. Russell,MER,6,24.7,,150,12
204
+ driver,VER,M. Verstappen,RBR,6,24.2,,139,22
205
+ driver,LEC,C. Leclerc,FER,6,23.9,,153,17
206
+ driver,NOR,L. Norris,MCL,6,22.7,,125,19
207
+ driver,LAW,L. Lawson,RBS,6,20,,140,6
208
+ driver,GAS,P. Gasly,ALP,6,19.7,,134,3
209
+ driver,HAD,I. Hadjar,RBR,6,19.7,,126,4
210
+ driver,SAI,C. Sainz Jr,WIL,6,16,,122,16
211
+ driver,ALO,F. Alonso,AMR,6,14.7,,90,10
212
+ driver,OCO,E. Ocon,HAS,6,14.4,,128,9
213
+ driver,BOR,G. Bortoleto,AUD,6,13.8,,110,11
214
+ driver,HUL,N. Hulkenberg,AUD,6,12.6,,99,13
215
+ driver,BEA,O. Bearman,HAS,6,12.5,,127,20
216
+ driver,COL,F. Colapinto,ALP,6,12.3,,137,14
217
+ driver,ALB,A. Albon,WIL,6,11.9,,94,8
218
+ driver,LIN,A. Lindblad,RBS,6,10.5,,121,7
219
+ driver,PER,S. Perez,CAD,6,8.4,,97,15
220
+ driver,STR,L. Stroll,AMR,6,8.4,,80,18
221
+ driver,BOT,V. Bottas,CAD,6,4.8,,83,21
222
+ team,MER,MER,,6,28.7,,165,
223
+ team,FER,FER,,6,26.2,,155,
224
+ team,MCL,MCL,,6,22.6,,121,
225
+ team,RBR,RBR,,6,21.9,,122,
226
+ team,ALP,ALP,,6,18.5,,121,
227
+ team,RBS,RBS,,6,16.5,,113,
228
+ team,WIL,WIL,,6,14.2,,94,
229
+ team,AUD,AUD,,6,12.4,,98,
230
+ team,HAS,HAS,,6,12.1,,108,
231
+ team,AMR,AMR,,6,8.8,,76,
232
+ team,CAD,CAD,,6,6.4,,79,
233
+ driver,HAM,L. Hamilton,FER,7,28.7,,168,1
234
+ driver,ANT,K. Antonelli,MER,7,27.9,,169,16
235
+ driver,RUS,G. Russell,MER,7,26.6,,154,2
236
+ driver,PIA,O. Piastri,MCL,7,25.8,,132,5
237
+ driver,VER,M. Verstappen,RBR,7,25.4,,142,4
238
+ driver,NOR,L. Norris,MCL,7,24.7,,132,3
239
+ driver,LEC,C. Leclerc,FER,7,21.9,,146,15
240
+ driver,HAD,I. Hadjar,RBR,7,21.2,,129,6
241
+ driver,GAS,P. Gasly,ALP,7,20.8,,135,7
242
+ driver,LAW,L. Lawson,RBS,7,20.3,,139,8
243
+ driver,SAI,C. Sainz Jr,WIL,7,16.1,,121,12
244
+ driver,BOR,G. Bortoleto,AUD,7,14.8,,112,11
245
+ driver,OCO,E. Ocon,HAS,7,14.1,,125,13
246
+ driver,COL,F. Colapinto,ALP,7,14.1,,135,10
247
+ driver,ALO,F. Alonso,AMR,7,12.7,,88,19
248
+ driver,LIN,A. Lindblad,RBS,7,12.5,,123,9
249
+ driver,BEA,O. Bearman,HAS,7,11.5,,121,17
250
+ driver,HUL,N. Hulkenberg,AUD,7,11.2,,96,20
251
+ driver,ALB,A. Albon,WIL,7,10.3,,91,18
252
+ driver,PER,S. Perez,CAD,7,9.2,,99,14
253
+ driver,STR,L. Stroll,AMR,7,6.4,,75,22
254
+ driver,BOT,V. Bottas,CAD,7,4.1,,78,21
255
+ team,MER,MER,,7,27.8,,162,
256
+ team,FER,FER,,7,25.2,,153,
257
+ team,MCL,MCL,,7,24.4,,126,
258
+ team,RBR,RBR,,7,23.2,,126,
259
+ team,ALP,ALP,,7,18.2,,122,
260
+ team,RBS,RBS,,7,17.2,,116,
261
+ team,WIL,WIL,,7,13,,94,
262
+ team,AUD,AUD,,7,12.9,,99,
263
+ team,HAS,HAS,,7,12.1,,106,
264
+ team,AMR,AMR,,7,7.6,,74,
265
+ team,CAD,CAD,,7,6.4,,78,
266
+ driver,HAM,L. Hamilton,FER,8,28.5,,165,5
267
+ driver,ANT,K. Antonelli,MER,8,28.2,,167,3
268
+ driver,RUS,G. Russell,MER,8,28,,156,1
269
+ driver,VER,M. Verstappen,RBR,8,27.4,,146,2
270
+ driver,PIA,O. Piastri,MCL,8,27,,136,4
271
+ driver,NOR,L. Norris,MCL,8,24.3,,133,7
272
+ driver,LEC,C. Leclerc,FER,8,22.5,,145,8
273
+ driver,HAD,I. Hadjar,RBR,8,22.4,,131,6
274
+ driver,LAW,L. Lawson,RBS,8,20.5,,137,9
275
+ driver,GAS,P. Gasly,ALP,8,19.3,,132,13
276
+ driver,BOR,G. Bortoleto,AUD,8,15.6,,113,11
277
+ driver,LIN,A. Lindblad,RBS,8,14.2,,123,10
278
+ driver,SAI,C. Sainz Jr,WIL,8,14.1,,114,20
279
+ driver,COL,F. Colapinto,ALP,8,13.5,,130,15
280
+ driver,OCO,E. Ocon,HAS,8,13.1,,121,16
281
+ driver,HUL,N. Hulkenberg,AUD,8,12.5,,98,12
282
+ driver,BEA,O. Bearman,HAS,8,11.9,,118,14
283
+ driver,ALO,F. Alonso,AMR,8,11.3,,87,18
284
+ driver,ALB,A. Albon,WIL,8,9.9,,90,17
285
+ driver,PER,S. Perez,CAD,8,7.4,,94,21
286
+ driver,STR,L. Stroll,AMR,8,6.1,,74,19
287
+ driver,BOT,V. Bottas,CAD,8,3.2,,75,22
288
+ team,MER,MER,,8,28.3,,164,
289
+ team,FER,FER,,8,25.1,,154,
290
+ team,RBR,RBR,,8,24.2,,130,
291
+ team,MCL,MCL,,8,23.9,,129,
292
+ team,RBS,RBS,,8,17.8,,117,
293
+ team,ALP,ALP,,8,17.3,,119,
294
+ team,AUD,AUD,,8,13.9,,100,
295
+ team,HAS,HAS,,8,12.1,,105,
296
+ team,WIL,WIL,,8,12.1,,92,
297
+ team,AMR,AMR,,8,7.4,,73,
298
+ team,CAD,CAD,,8,5.8,,76,