
Get started with nflseedR
Sebastian Carl and Lee Sharpe
Source:vignettes/articles/nflseedR.Rmd
nflseedR.Rmd
Preface
nflseedR essentially performs two tasks:
- Calculation of NFL standings with
nfl_standings()
based on game results of one or more seasons, especially taking into account the comprehensive and sometimes complicated tie-breaking procedures for division ranks, conference seeds and the draft order. Read this article for further information on the implemented tie-breakers. - Running thousands of simulations (Monte Carlo style) of an NFL
season with
nfl_simulations()
. The standings from point 1 and especially the conference seeds are needed to determine playoff participants. Basically, the first point only exists because we need it to carry out the simulations.
The actual core of a simulation is the generation of game results based on any information that the user deems important. This is why nflseedR is virtually extensible. By default, a simple ELO model is implemented that works with initial starting ELO values and updates them from week to week based on game results. However, the user can write their own function for calculating game results and pass it to nflseedR together with any additional data that may be required.
Usage
Standings
We need real or simulated match data to determine standings. The
required variables are specified in the documentation of the function
nfl_standings()
.
Here are games data from the 2023 and 2024 seasons.
games <- nflreadr::load_schedules(2023:2024)
We can pass this data directly to nflseedR and calculate standings. It defaults to compute division ranks as well as conference ranks for all teams and it applies tiebreakers through strength of schedule.
standings <- nflseedR::nfl_standings(games, ranks = "DRAFT")
#> ℹ 16:31:14 | Initiate Standings & Tiebreaking Data
#> ℹ 16:31:14 | Compute Division Ranks
#> ℹ 16:31:14 | Compute Conference Ranks
#> ℹ 16:31:14 | Compute Draft Order
# Let's view the structure of the output
str(standings, max.level = 1, width = 50, strict.width = "cut")
#> Classes 'data.table' and 'data.frame': 64 obs. of 24 variables:
#> $ season : int 2023 2023 2023 2023..
#> $ team : chr "BUF" "MIA" "NYJ" "..
#> $ conf : chr "AFC" "AFC" "AFC" "..
#> $ division : chr "AFC East" "AFC Ea"..
#> $ games : int 17 17 17 17 17 17 1..
#> $ wins : num 11 11 7 4 13 11 10 ..
#> $ true_wins : int 11 11 7 4 13 11 10 ..
#> $ losses : int 6 6 10 13 4 6 7 8 7..
#> $ ties : int 0 0 0 0 0 0 0 0 0 0..
#> $ pf : int 451 496 268 236 483..
#> $ pa : int 311 391 355 366 280..
#> $ pd : int 140 105 -87 -130 20..
#> $ win_pct : num 0.647 0.647 0.412 0..
#> $ div_pct : num 0.667 0.667 0.333 0..
#> $ conf_pct : num 0.583 0.583 0.333 0..
#> $ sov : num 0.471 0.358 0.454 0..
#> $ sos : num 0.471 0.45 0.502 0...
#> $ div_rank : int 1 2 3 4 1 2 3 4 1 2..
#> $ div_tie_broken_by : chr "Head-To-Head Win "..
#> $ conf_rank : int 2 6 13 16 1 5 7 8 4..
#> $ conf_tie_broken_by : chr "Head-To-Head Swee"..
#> $ exit : int 19 18 0 0 20 18 18 ..
#> $ draft_rank : int 28 21 10 3 30 23 20..
#> $ draft_tie_broken_by: chr NA NA NA NA ...
#> - attr(*, ".internal.selfref")=<externalptr>
nflseedR also provides functionality to create a “pretty” html table
using the gt package. Use
nfl_standings_prettify()
with the output of
nfl_standings()
to create the table. It allows grouping by
division, conference or overall and it can sort by division rank,
conference rank (seed), and draft rank.
The default groups by division and sorts by division rank.
# It doesn't allow more than one season
s <- standings[season == 2024]
nflseedR::nfl_standings_prettify(s)
2024 NFL Standings | ||||||||||||||||||
Div Rank |
team |
G |
W |
L |
T |
pf |
pa |
pd |
pct |
Div PCT |
Conf PCT |
sov |
sos |
Division Tie broken by |
Conf Rank |
Conference Tie broken by |
Draft Rank |
Draft Tie broken by |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
AFC East | ||||||||||||||||||
1 | 17 | 13 | 4 | 0 | 525 | 368 | 157 | 0.765 | 0.833 | 0.750 | 0.448 | 0.467 | — | 2 | — | 30 | — | |
2 | 17 | 8 | 9 | 0 | 345 | 364 | -19 | 0.471 | 0.500 | 0.500 | 0.294 | 0.419 | — | 10 | — | 13 | — | |
3 | 17 | 5 | 12 | 0 | 338 | 404 | -66 | 0.294 | 0.333 | 0.417 | 0.341 | 0.495 | — | 11 | — | 7 | — | |
4 | 17 | 4 | 13 | 0 | 289 | 417 | -128 | 0.235 | 0.333 | 0.250 | 0.471 | 0.471 | — | 13 | SOV (2) | 4 | — | |
AFC North | ||||||||||||||||||
1 | 17 | 12 | 5 | 0 | 518 | 361 | 157 | 0.706 | 0.667 | 0.667 | 0.525 | 0.529 | — | 3 | — | 27 | — | |
2 | 17 | 10 | 7 | 0 | 380 | 347 | 33 | 0.588 | 0.500 | 0.583 | 0.453 | 0.502 | — | 6 | Head-To-Head Sweep (2) | 21 | Conference Tiebreaker | |
3 | 17 | 9 | 8 | 0 | 472 | 434 | 38 | 0.529 | 0.500 | 0.500 | 0.314 | 0.478 | — | 8 | — | 17 | — | |
4 | 17 | 3 | 14 | 0 | 258 | 435 | -177 | 0.176 | 0.333 | 0.250 | 0.510 | 0.536 | — | 15 | SOV (2) | 2 | — | |
AFC South | ||||||||||||||||||
1 | 17 | 10 | 7 | 0 | 372 | 372 | 0 | 0.588 | 0.833 | 0.667 | 0.376 | 0.481 | — | 4 | — | 25 | — | |
2 | 17 | 8 | 9 | 0 | 377 | 427 | -50 | 0.471 | 0.500 | 0.583 | 0.309 | 0.457 | — | 9 | Head-To-Head Sweep (2) | 14 | — | |
3 | 17 | 4 | 13 | 0 | 320 | 435 | -115 | 0.235 | 0.500 | 0.333 | 0.265 | 0.478 | — | 12 | Conference Win PCT (3) | 5 | — | |
4 | 17 | 3 | 14 | 0 | 311 | 460 | -149 | 0.176 | 0.167 | 0.250 | 0.431 | 0.522 | — | 16 | — | 1 | — | |
AFC West | ||||||||||||||||||
1 | 17 | 15 | 2 | 0 | 385 | 326 | 59 | 0.882 | 0.833 | 0.833 | 0.463 | 0.488 | — | 1 | — | 31 | — | |
2 | 17 | 11 | 6 | 0 | 402 | 301 | 101 | 0.647 | 0.667 | 0.667 | 0.348 | 0.467 | — | 5 | — | 22 | — | |
3 | 17 | 10 | 7 | 0 | 425 | 311 | 114 | 0.588 | 0.500 | 0.500 | 0.394 | 0.502 | — | 7 | — | 20 | Conference Tiebreaker | |
4 | 17 | 4 | 13 | 0 | 309 | 434 | -125 | 0.235 | 0.000 | 0.250 | 0.353 | 0.540 | — | 14 | — | 6 | — | |
NFC East | ||||||||||||||||||
1 | 17 | 14 | 3 | 0 | 463 | 303 | 160 | 0.824 | 0.833 | 0.750 | 0.424 | 0.453 | — | 2 | — | 32 | — | |
2 | 17 | 12 | 5 | 0 | 485 | 391 | 94 | 0.706 | 0.667 | 0.750 | 0.358 | 0.436 | — | 6 | — | 29 | — | |
3 | 17 | 7 | 10 | 0 | 350 | 468 | -118 | 0.412 | 0.500 | 0.417 | 0.387 | 0.522 | — | 11 | — | 12 | — | |
4 | 17 | 3 | 14 | 0 | 273 | 415 | -142 | 0.176 | 0.000 | 0.083 | 0.412 | 0.554 | — | 16 | — | 3 | — | |
NFC North | ||||||||||||||||||
1 | 17 | 15 | 2 | 0 | 564 | 342 | 222 | 0.882 | 1.000 | 0.917 | 0.494 | 0.516 | — | 1 | — | 28 | — | |
2 | 17 | 14 | 3 | 0 | 432 | 332 | 100 | 0.824 | 0.667 | 0.750 | 0.408 | 0.474 | — | 5 | — | 24 | — | |
3 | 17 | 11 | 6 | 0 | 460 | 338 | 122 | 0.647 | 0.167 | 0.500 | 0.412 | 0.533 | — | 7 | — | 23 | — | |
4 | 17 | 5 | 12 | 0 | 310 | 370 | -60 | 0.294 | 0.167 | 0.250 | 0.388 | 0.554 | — | 13 | Head-To-Head Sweep (2) | 10 | — | |
NFC South | ||||||||||||||||||
1 | 17 | 10 | 7 | 0 | 502 | 385 | 117 | 0.588 | 0.667 | 0.667 | 0.465 | 0.502 | — | 3 | Conference Win PCT (2) | 19 | Head-To-Head (2) | |
2 | 17 | 8 | 9 | 0 | 389 | 423 | -34 | 0.471 | 0.667 | 0.583 | 0.426 | 0.519 | — | 9 | Conference Win PCT (2) | 15 | — | |
3 | 17 | 5 | 12 | 0 | 341 | 534 | -193 | 0.294 | 0.333 | 0.333 | 0.329 | 0.498 | SOV (2) | 14 | Division Tiebreaker | 8 | — | |
4 | 17 | 5 | 12 | 0 | 338 | 398 | -60 | 0.294 | 0.333 | 0.333 | 0.306 | 0.505 | SOV (2) | 15 | Division Tiebreaker | 9 | — | |
NFC West | ||||||||||||||||||
1 | 17 | 10 | 7 | 0 | 367 | 386 | -19 | 0.588 | 0.667 | 0.500 | 0.441 | 0.505 | SOV (2) | 4 | — | 26 | — | |
2 | 17 | 10 | 7 | 0 | 375 | 368 | 7 | 0.588 | 0.667 | 0.500 | 0.424 | 0.498 | SOV (2) | 8 | — | 18 | — | |
3 | 17 | 8 | 9 | 0 | 400 | 379 | 21 | 0.471 | 0.500 | 0.333 | 0.404 | 0.536 | — | 10 | — | 16 | — | |
4 | 17 | 6 | 11 | 0 | 389 | 436 | -47 | 0.353 | 0.167 | 0.333 | 0.402 | 0.564 | — | 12 | — | 11 | — | |
nflseedR |
But we can also do things like ordering the complete league by draft rank.
nflseedR::nfl_standings_prettify(s, grp_by = "nfl", order_by = "draft_rank")
2024 NFL Standings | ||||||||||||||||||
Draft Rank |
team |
G |
W |
L |
T |
pf |
pa |
pd |
pct |
Div PCT |
Conf PCT |
sov |
sos |
Div Rank |
Division Tie broken by |
Conf Rank |
Conference Tie broken by |
Draft Tie broken by |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 17 | 3 | 14 | 0 | 311 | 460 | -149 | 0.176 | 0.167 | 0.250 | 0.431 | 0.522 | 4 | — | 16 | — | — | |
2 | 17 | 3 | 14 | 0 | 258 | 435 | -177 | 0.176 | 0.333 | 0.250 | 0.510 | 0.536 | 4 | — | 15 | SOV (2) | — | |
3 | 17 | 3 | 14 | 0 | 273 | 415 | -142 | 0.176 | 0.000 | 0.083 | 0.412 | 0.554 | 4 | — | 16 | — | — | |
4 | 17 | 4 | 13 | 0 | 289 | 417 | -128 | 0.235 | 0.333 | 0.250 | 0.471 | 0.471 | 4 | — | 13 | SOV (2) | — | |
5 | 17 | 4 | 13 | 0 | 320 | 435 | -115 | 0.235 | 0.500 | 0.333 | 0.265 | 0.478 | 3 | — | 12 | Conference Win PCT (3) | — | |
6 | 17 | 4 | 13 | 0 | 309 | 434 | -125 | 0.235 | 0.000 | 0.250 | 0.353 | 0.540 | 4 | — | 14 | — | — | |
7 | 17 | 5 | 12 | 0 | 338 | 404 | -66 | 0.294 | 0.333 | 0.417 | 0.341 | 0.495 | 3 | — | 11 | — | — | |
8 | 17 | 5 | 12 | 0 | 341 | 534 | -193 | 0.294 | 0.333 | 0.333 | 0.329 | 0.498 | 3 | SOV (2) | 14 | Division Tiebreaker | — | |
9 | 17 | 5 | 12 | 0 | 338 | 398 | -60 | 0.294 | 0.333 | 0.333 | 0.306 | 0.505 | 4 | SOV (2) | 15 | Division Tiebreaker | — | |
10 | 17 | 5 | 12 | 0 | 310 | 370 | -60 | 0.294 | 0.167 | 0.250 | 0.388 | 0.554 | 4 | — | 13 | Head-To-Head Sweep (2) | — | |
11 | 17 | 6 | 11 | 0 | 389 | 436 | -47 | 0.353 | 0.167 | 0.333 | 0.402 | 0.564 | 4 | — | 12 | — | — | |
12 | 17 | 7 | 10 | 0 | 350 | 468 | -118 | 0.412 | 0.500 | 0.417 | 0.387 | 0.522 | 3 | — | 11 | — | — | |
13 | 17 | 8 | 9 | 0 | 345 | 364 | -19 | 0.471 | 0.500 | 0.500 | 0.294 | 0.419 | 2 | — | 10 | — | — | |
14 | 17 | 8 | 9 | 0 | 377 | 427 | -50 | 0.471 | 0.500 | 0.583 | 0.309 | 0.457 | 2 | — | 9 | Head-To-Head Sweep (2) | — | |
15 | 17 | 8 | 9 | 0 | 389 | 423 | -34 | 0.471 | 0.667 | 0.583 | 0.426 | 0.519 | 2 | — | 9 | Conference Win PCT (2) | — | |
16 | 17 | 8 | 9 | 0 | 400 | 379 | 21 | 0.471 | 0.500 | 0.333 | 0.404 | 0.536 | 3 | — | 10 | — | — | |
17 | 17 | 9 | 8 | 0 | 472 | 434 | 38 | 0.529 | 0.500 | 0.500 | 0.314 | 0.478 | 3 | — | 8 | — | — | |
18 | 17 | 10 | 7 | 0 | 375 | 368 | 7 | 0.588 | 0.667 | 0.500 | 0.424 | 0.498 | 2 | SOV (2) | 8 | — | — | |
19 | 17 | 10 | 7 | 0 | 502 | 385 | 117 | 0.588 | 0.667 | 0.667 | 0.465 | 0.502 | 1 | — | 3 | Conference Win PCT (2) | Head-To-Head (2) | |
20 | 17 | 10 | 7 | 0 | 425 | 311 | 114 | 0.588 | 0.500 | 0.500 | 0.394 | 0.502 | 3 | — | 7 | — | Conference Tiebreaker | |
21 | 17 | 10 | 7 | 0 | 380 | 347 | 33 | 0.588 | 0.500 | 0.583 | 0.453 | 0.502 | 2 | — | 6 | Head-To-Head Sweep (2) | Conference Tiebreaker | |
22 | 17 | 11 | 6 | 0 | 402 | 301 | 101 | 0.647 | 0.667 | 0.667 | 0.348 | 0.467 | 2 | — | 5 | — | — | |
23 | 17 | 11 | 6 | 0 | 460 | 338 | 122 | 0.647 | 0.167 | 0.500 | 0.412 | 0.533 | 3 | — | 7 | — | — | |
24 | 17 | 14 | 3 | 0 | 432 | 332 | 100 | 0.824 | 0.667 | 0.750 | 0.408 | 0.474 | 2 | — | 5 | — | — | |
25 | 17 | 10 | 7 | 0 | 372 | 372 | 0 | 0.588 | 0.833 | 0.667 | 0.376 | 0.481 | 1 | — | 4 | — | — | |
26 | 17 | 10 | 7 | 0 | 367 | 386 | -19 | 0.588 | 0.667 | 0.500 | 0.441 | 0.505 | 1 | SOV (2) | 4 | — | — | |
27 | 17 | 12 | 5 | 0 | 518 | 361 | 157 | 0.706 | 0.667 | 0.667 | 0.525 | 0.529 | 1 | — | 3 | — | — | |
28 | 17 | 15 | 2 | 0 | 564 | 342 | 222 | 0.882 | 1.000 | 0.917 | 0.494 | 0.516 | 1 | — | 1 | — | — | |
29 | 17 | 12 | 5 | 0 | 485 | 391 | 94 | 0.706 | 0.667 | 0.750 | 0.358 | 0.436 | 2 | — | 6 | — | — | |
30 | 17 | 13 | 4 | 0 | 525 | 368 | 157 | 0.765 | 0.833 | 0.750 | 0.448 | 0.467 | 1 | — | 2 | — | — | |
31 | 17 | 15 | 2 | 0 | 385 | 326 | 59 | 0.882 | 0.833 | 0.833 | 0.463 | 0.488 | 1 | — | 1 | — | — | |
32 | 17 | 14 | 3 | 0 | 463 | 303 | 160 | 0.824 | 0.833 | 0.750 | 0.424 | 0.453 | 1 | — | 2 | — | — | |
nflseedR |
Please note that nfl_standings_prettify()
returns a
gt::gt()
table so you can change it according to your own
preferences.
Simulations
With nflseedR 2.0, we have rethought and implemented the execution of
simulations from scratch. Particular attention was paid to flexibility
and performance. As the usage of the new function
nfl_simulations()
differs from the old function
simulate_nfl()
, we will keep both variants for the time
being and maintain two separate articles explaining how to use them.
It is strongly recommended to switch to
nfl_simulations()
because it is far superior to the old
implementation in practically every respect, especially in terms of
performance.
- Go to this
article for a detailed explanation of how to use
nfl_simulations()
- Go to this
article for a detailed explanation of how to use
simulate_nfl()