Simulating NFL seasons using nflseedR (2024)

Running a Simulation

Loading the package is obligatory, so it is done first (along withdplyr for data wrangling and the pipe):

library(nflseedR)library(dplyr, warn.conflicts = FALSE)options(digits = 3)

Note: For this guide, we’ll set an initial random seed of4 at the beginning and simulations = 100 forthe purposes of this document so you can follow along by entering thesame code and get the same results shown here. We’ll also setfresh_season = TRUE to blank out the existing results fromthe 2020 season, but normally when simulating an incomplete season, youwouldn’t do these things.

set.seed(4)sims <- simulate_nfl( nfl_season = 2020, fresh_season = TRUE, simulations = 100)#>  19:16:11 | Loading games data#>  19:16:12 | Beginning simulation of 100 seasons in 1 round#>  19:16:26 | Combining simulation data#>  19:16:26 | Aggregating across simulations#>  19:16:26 | DONE!

The output contains a lot of pre-aggregated information, as well asthe individual results from each game of each simulation. For example,let’s look at the overall results for the Bears:

sims$overall %>% dplyr::filter(team == "CHI") %>% knitr::kable()
confdivisionteamwinsplayoffdiv1seed1won_confwon_sbdraft1draft5
NFCNFC NorthCHI10.80.870.390.10.150.0400.01

We can see the Bears got 10.8 wins on average. They made the playoffs87% of simulations, won the division in 39%, won the Super Bowl in 4%,and only in 1% did they receive a top five draft pick. Theteams section of the output will show how a team did ineach simulated season.

sims$teams %>% dplyr::filter(team == "CHI") %>% dplyr::select(sim, team, wins, seed, draft_order) %>%  utils::head(6) %>% knitr::kable()
simteamwinsseeddraft_order
1CHI10632
2CHI11321
3CHI9620
4CHI10626
5CHI10320
6CHI6NA11

Let’s check out the playoff games from the first simulation, wherethe Bears went 10-6 and got the 6th seed.

sims$games %>% dplyr::filter(sim == 1, game_type != "REG") %>% knitr::kable()
simgame_typeweekaway_teamhome_teamaway_resthome_restlocationresult
1WC18MIACLE77Home-8
1WC18TENDEN77Home24
1WC18NYJIND77Home14
1WC18PHITB77Home-6
1WC18CHILA77Home-26
1WC18DETMIN77Home2
1DIV19DENIND77Home3
1DIV19MIANE714Home3
1DIV19PHIMIN77Home-12
1DIV19CHIDAL714Home-11
1CON20INDNE77Home10
1CON20CHIPHI77Home-3
1SB21CHINE1414Neutral-6

In this simulation, the Bears beat the Rams in a wildcard game by 26points, then beat the Cowboys in the divisional round by 11 points, tookthe Eagles by a field goal in the NFC Championship Game, and finallydefeated the Patriots by 6 in the Super Bowl.

As you may have gathered at this point, the default simulation codepicks a random Elo for every team, and uses those as the starting Eloratings for all 32 teams. However, the default code Elo will adjustindependently within each simulation as each week is simulated. (The Elomodel used is loosely based off of that of FiveThirtyEight.)

Use Your Own Model

But of course the real value of nflseedR is putting in your own modelinto the simulator. To accomplish this, you can write your own functionwhich will determine the output of games instead. As an example, here’sa very stupid model that makes the team earlier alphabetically win by 3points 90% of the time, and lose by 3 points the other 10% of thetime.

stupid_games_model <- function(teams, games, week_num, ...) { # make the earlier alphabetical team win 90% of the time games <- games %>% dplyr::mutate( result = dplyr::case_when( !is.na(result) | week != week_num ~ result, away_team < home_team ~ sample(c(-3, 3), n(), prob = c(0.9, 0.1), replace = TRUE), away_team > home_team ~ sample(c(-3, 3), n(), prob = c(0.1, 0.9), replace = TRUE), TRUE ~ 0 ) )  # return values return(list(teams = teams, games = games))}

When you create this function, the first two inputs are data on theteams (one row per team per sim), and data on the games (one row pergame per sim). The third argument is the week number currently beingsimulated, as only one week is processed at a time.

Your function’s job - by whatever means you choose - is to update theresult column for that week’s games in each of the simswith the number of points the home team won by (or lost by if negative,or 0 if the game ended in a tie).

It returns both the teams and the gamesdata. It does both because this way you can store information in newcolumns by team or by game to use in the next call. Make sure your codeboth accepts and returns the appropriate information, or the simulatorwill break!

For example, the default function updates a team’s Elo after thegame, and stores it in the teams data. When the simulatorprocesses the next week, it uses the updated Elo rating to inform theteam’s next game.

!! Also, make sure you aren’t overriding completed games or gamesthat aren’t in the current week of w. The simulator willnot stop you from setting past, present, or future gameresults in your function, whether you meant to do so or not. !!

Let’s run a simulation with stupid_games_model and seewhat happens:

sims2 <- simulate_nfl( nfl_season = 2020, process_games = stupid_games_model, fresh_season = TRUE, simulations = 100)#>  19:16:26 | Loading games data#>  19:16:26 | Beginning simulation of 100 seasons in 1 round#>  19:16:38 | Combining simulation data#>  19:16:38 | Aggregating across simulations#>  19:16:38 | DONE!sims2$overall %>% dplyr::arrange(team) %>% utils::head() %>% knitr::kable()
confdivisionteamwinsplayoffdiv1seed1won_confwon_sbdraft1draft5
NFCNFC WestARI14.31.000.970.400.640.5900
NFCNFC SouthATL14.41.000.910.470.290.2600
AFCAFC NorthBAL14.41.000.820.600.790.0900
AFCAFC EastBUF13.71.001.000.290.160.0400
NFCNFC SouthCAR12.00.960.090.030.030.0100
NFCNFC NorthCHI12.90.980.880.100.020.0000
sims2$overall %>% dplyr::arrange(team) %>% utils::tail() %>% knitr::kable()
confdivisionteamwinsplayoffdiv1seed1won_confwon_sbdraft1draft5
AFCAFC NorthPIT3.09000000.010.40
NFCNFC WestSEA3.94000000.020.33
NFCNFC WestSF2.39000000.100.86
NFCNFC SouthTB1.63000000.240.85
AFCAFC SouthTEN1.60000000.210.82
NFCNFC EastWAS1.68000000.390.85

As you might expect, the earliest alphabetical teams win a lot. TheCardinals won the Super Bowl in 59% of seasons! Meanwhile, the teams atthe bottom alphabetically are virtually certain to be at the top of thedraft order.

Adding In Your Own Data

This is all well and good, you might be thinking, but your modelworks off of other data not in the simulator! How can that work? This iswhere we utilize R’s ability to have generic arguments.

The ... at the end of the function definition means thatthe function can be called with any number of additional arguments. Youcan name these whatever you want, as long as they’re not already thename of other defined arguments.

When you call the simulate_nfl() function, it too usesthe ... syntax, which allows you to pass in any number ofadditional arguments to the function. The simulator will in turn passthese on to your function that processes games.

For example, let’s slightly modify our last example:

biased_games_model <- function(teams, games, week_num, ...) {  # arguments args <- list(...) best <- "" worst <- ""  # best team? if ("best" %in% names(args)) { best <- args$best }  # worst team? if ("worst" %in% names(args)) { worst <- args$worst } # make the best team always win and the worst team always lose # otherwise, make the earlier alphabetical team win 90% of the time games <- games %>% dplyr::mutate( result = dplyr::case_when( !is.na(result) | week != week_num ~ result, away_team == best | home_team == worst ~ -3, away_team == worst | home_team == best ~ 3, away_team < home_team ~ sample(c(-3, 3), n(), prob = c(0.9, 0.1), replace = TRUE), away_team > home_team ~ sample(c(-3, 3), n(), prob = c(0.1, 0.9), replace = TRUE), TRUE ~ 0 ) )  # return values return(list(teams = teams, games = games))}

This allows us to define best and worst,and use that information to determine a result (in this case, have thebest team always win and the worst team always lose). Whilebest and worst are in this examplesingle-length character vectors, they can be data frames or any other Rdata type.

Let’s simulate using this:

sims3 <- simulate_nfl( nfl_season = 2020, process_games = biased_games_model,  fresh_season = TRUE,  simulations = 100, best = "CHI",  worst = "GB")#>  19:16:38 | Loading games data#>  19:16:38 | Beginning simulation of 100 seasons in 1 round#>  19:16:50 | Combining simulation data#>  19:16:50 | Aggregating across simulations#>  19:16:50 | DONE!

Now let nflseedR summarize the simulation for you by usingsummary() with the nflseedR simulation object. This willprint a gt table.

summary(sims3)#> Warning: Since gt v0.9.0, the `colors` argument has been deprecated.#>  Please use the `fn` argument instead.#> This warning is displayed once every 8 hours.
Simulating the 2020 NFL Season
Summary of 100 Simulations using nflseedR
Simulating NFL seasons using nflseedR (1) Simulating NFL seasons using nflseedR (2)
AVG.
WINS
Make
POST
Win
DIV
No.1
Seed
Win
Conf
Win
SB
No.1
Pick
Top-5
Pick
AVG.
WINS
Make
POST
Win
DIV
No.1
Seed
Win
Conf
Win
SB
No.1
Pick
Top-5
Pick
E A S T
Simulating NFL seasons using nflseedR (3)13.4100%100%22%22%0%0%0%Simulating NFL seasons using nflseedR (4)10.595%94%0%0%0%0%0%
Simulating NFL seasons using nflseedR (5)6.53%0%0%0%0%0%1%Simulating NFL seasons using nflseedR (6)7.915%4%0%0%0%0%0%
Simulating NFL seasons using nflseedR (7)4.80%0%0%0%0%0%9%Simulating NFL seasons using nflseedR (8)6.74%2%0%0%0%0%1%
Simulating NFL seasons using nflseedR (9)3.40%0%0%0%0%0%46%Simulating NFL seasons using nflseedR (10)1.50%0%0%0%0%0%86%
N O R T H
Simulating NFL seasons using nflseedR (11)14.399%83%63%74%0%0%0%Simulating NFL seasons using nflseedR (12)16.0100%100%99%100%100%0%0%
Simulating NFL seasons using nflseedR (13)12.699%15%6%2%0%0%0%Simulating NFL seasons using nflseedR (14)10.495%0%0%0%0%0%0%
Simulating NFL seasons using nflseedR (15)11.194%2%1%2%0%0%0%Simulating NFL seasons using nflseedR (16)6.54%0%0%0%0%0%0%
Simulating NFL seasons using nflseedR (17)3.30%0%0%0%0%0%33%Simulating NFL seasons using nflseedR (18)0.00%0%0%0%0%100%100%
S O U T H
Simulating NFL seasons using nflseedR (19)10.593%86%0%0%0%0%0%Simulating NFL seasons using nflseedR (20)13.6100%94%0%0%0%0%0%
Simulating NFL seasons using nflseedR (21)8.740%11%0%0%0%0%0%Simulating NFL seasons using nflseedR (22)11.196%6%0%0%0%0%0%
Simulating NFL seasons using nflseedR (23)7.26%3%0%0%0%0%0%Simulating NFL seasons using nflseedR (24)5.60%0%0%0%0%0%2%
Simulating NFL seasons using nflseedR (25)2.40%0%0%0%0%0%63%Simulating NFL seasons using nflseedR (26)2.50%0%0%0%0%0%66%
W E S T
Simulating NFL seasons using nflseedR (27)11.997%92%8%0%0%0%0%Simulating NFL seasons using nflseedR (28)14.6100%98%1%0%0%0%0%
Simulating NFL seasons using nflseedR (29)8.960%7%0%0%0%0%0%Simulating NFL seasons using nflseedR (30)10.191%2%0%0%0%0%0%
Simulating NFL seasons using nflseedR (31)7.28%1%0%0%0%0%0%Simulating NFL seasons using nflseedR (32)3.80%0%0%0%0%0%35%
Simulating NFL seasons using nflseedR (33)5.61%0%0%0%0%0%1%Simulating NFL seasons using nflseedR (34)3.40%0%0%0%0%0%57%
nflseedR

And this shows exactly what we expect. By defining the Bears as thebest team, they always go 16-0, win the division, and win the SuperBowl. Interestingly, they do not always get the #1 seed. This makessense, however, as in games without the Bears or the Packers, thealphabetically earlier teams still wins 90% of the time. The Cardinalswould therefore be expected to go 16-0 in some of the simulations, andin some of those have thee tiebreakers over the Bears. However, even inthese simulations, they’ll still lose to Bears in the end when they meetin the playoffs.

Similarly, the Packers always go 0-16, and never make the playoffs.While in these simulated seasons they got the #1 draft pick every time,they aren’t guaranteed to do so. Using the same logic as above,sometimes the Washington Commanders will go 0-16 too, and may beat thePackers out for the #1 pick through tiebreakers.

Passing Data in from One Week to the Next

Sometimes though you want your data to keep updating as thesimulation progresses. For example, an Elo-based model that updates eachteam’s Elo after each game. You can pass in the starting Elo values perteam, and as games are simulated, update the Elo values for each teamand store them in the teams data. This column will be partof the teams data passed into your function when thefollowing week is simulated and your function is called.

Read the comments in the code below for specific tips on doing thisbut here are good ones:

  • You can add columns to teams and/or gamesif you want.
  • When doing joins to do the above, do left joins to make sure no rowsare removed.
  • Remove any “helper” columns you generate along the way you don’tactually need before returning.
  • Make sure any column doesn’t get blindly joined on so it has.x and .y versions in Week 2 and R throws anerror because an expected column name doesn’t exist.
  • Make sure you only update games withis.na(result) & week == week_num! You don’t want tooverride completed games, or games from a week other than the currentweek being simulated.
elo_model <- function(teams, games, week_num, ...) { # round out (away from zero) # this way the simulator never simulates a tie # the simulator will still allow ties to be simulated if you want # ... but not on playoff games round_out <- function(x) { x[!is.na(x) & x < 0] <- floor(x[!is.na(x) & x < 0]) x[!is.na(x) & x > 0] <- ceiling(x[!is.na(x) & x > 0]) return(x) } # we're going to store elo as a new columns in the teams data # it won't start off there of course, so we need to determine it # from our arguments if (!("elo" %in% colnames(teams))) { args <- list(...) if ("elo" %in% names(args)) { # pull the elo info from custom arguments teams <- teams %>% dplyr::inner_join(args$elo %>% dplyr::select(team, elo), by = c("team" = "team")) } else { # error with a friendly error message if no elo data is passed in stop("Pass in a tibble `elo` as an argument to `simulate_nfl()`") } } # isolate the ratings data by sim and by team only # we will want to join to the games data later and don't want excess columns ratings <- teams %>% dplyr::select(sim, team, elo) # simulate game outcomes games <- games %>% # add in the away team's elo to the game data # note we join on both `sim` and the team # always join on `sim` to make sure each sim cares about only its data dplyr::inner_join(ratings, by = c("sim" = "sim", "away_team" = "team")) %>% dplyr::rename(away_elo = elo) %>% # repeat for the home team as well dplyr::inner_join(ratings, by = c("sim" = "sim", "home_team" = "team")) %>% dplyr::rename(home_elo = elo) %>% dplyr::mutate( # calculate the elo difference elo_diff = home_elo - away_elo, # add in a small HFA amount if played at home elo_diff = elo_diff + ifelse(location == "Home", 20, 0), # make an adjustment for rest elo_diff = elo_diff + (home_rest - away_rest) / 7 * 25, # playoff games swing elo more elo_diff = elo_diff * ifelse(game_type == "REG", 1, 1.2), # from elo, we calculate the home team's win percentage wp = 1 / (10^(-elo_diff / 400) + 1), # we also can calculate the estimate (mean points home team wins by) estimate = elo_diff / 25, result = dplyr::case_when( # !!! ALWAYS DO THIS NEXT LINE IN YOUR `result` CHANGES !!! # you have to make sure you're only changing unfinished games in current week # if you don't do this, it will usually error out on a friendly error message is.na(result) & week == week_num ~  as.integer(round_out(rnorm(n(), estimate, 13))), # if not this week or known result, leave as-is TRUE ~ as.integer(result) ), # simplify to 1 = win, 0 = loss, 0.5 = tie to help calculate elo shift outcome = dplyr::case_when( is.na(result) ~ NA_real_, result > 0 ~ 1, result < 0 ~ 0, TRUE ~ 0.5 ), # calculate the amount to adjust home team's elo by elo_input = dplyr::case_when( is.na(result) ~ NA_real_, result > 0 ~ elo_diff * 0.001 + 2.2, result < 0 ~ -elo_diff * 0.001 + 2.2, TRUE ~ 1.0, ), elo_mult = log(pmax(abs(result), 1) + 1.0) * 2.2 / elo_input, elo_shift = 20 * elo_mult * (outcome - wp) ) %>% # we don't want these columns in `games` any more # remove any columns you don't need when you're done # otherwise the next week they'll get joined as `col.x` and `col.y` # which will almost certainly break your script dplyr::select( -away_elo, -home_elo, -elo_diff, -wp, -estimate, -outcome, -elo_input, -elo_mult ) # apply elo shifts teams <- teams %>% # join games results from this week to away teams (within same sim!) # note this is a LEFT join, we don't want to remove any teams rows dplyr::left_join(games %>% dplyr::filter(week == week_num) %>% dplyr::select(sim, away_team, elo_shift), by = c("sim" = "sim", "team" = "away_team") ) %>% # away team's elo gets subtracted by elo amount # if the team wasn't an away team, do nothing dplyr::mutate(elo = elo - ifelse(!is.na(elo_shift), elo_shift, 0)) %>% # we don't want to keep `elo_shift` in `teams` either, remove it dplyr::select(-elo_shift) %>% # repeat the above except now do it for the home team dplyr::left_join(games %>% dplyr::filter(week == week_num) %>% dplyr::select(sim, home_team, elo_shift), by = c("sim" = "sim", "team" = "home_team") ) %>% # note that a team on a bye will have `elo_shift` as NA for both joins # this means it won't change, which is what we want dplyr::mutate(elo = elo + ifelse(!is.na(elo_shift), elo_shift, 0)) %>% dplyr::select(-elo_shift) # we need to keep `elo_shift` out of `games` too and we're done with it games <- games %>% dplyr::select(-elo_shift) # return the updated teams and games information # note that `teams` will now have an updated `elo` column which will # be used for the next week's games # note that starting `elo` values are the same per-team...  # ... but after that will differ per sim depending on that sim's results return(list(teams = teams, games = games))}

Let’s generate initial random Elo values for each team. To see howthis works, we’ll supply an test_week = 3 as an argumentinto simulate_nfl() which will abort after simulating Week3, and instead return the result of our elo_model()function.

initial_elo <- tibble::tibble( team = unique(nflseedR::divisions$team), elo = rnorm(length(unique(nflseedR::divisions$team)), 1500, 150))test <- simulate_nfl( nfl_season = 2020, process_games = elo_model, elo = initial_elo, fresh_season = TRUE, test_week = 3)#>  19:16:51 | Loading games data#>  19:16:51 | Beginning simulation of 1000 seasons in 1 round#>  19:16:52 | Aborting and returning your `process_games` function's results#> from Week 3

Let’s look at the Bears’ Elo after Week 3 in the top handful ofsimulations:

test$teams %>% dplyr::filter(team == "CHI") %>% utils::head() %>% knitr::kable()
simteamconfdivisionsdivelo
1CHINFCNFC NorthNFCN1498
2CHINFCNFC NorthNFCN1498
3CHINFCNFC NorthNFCN1482
4CHINFCNFC NorthNFCN1464
5CHINFCNFC NorthNFCN1447
6CHINFCNFC NorthNFCN1496

You can see that different simulations have different Elo results forthe Bears, as the simulated seasons had different results for the games,and the Elos were adjusted accordingly.

Let’s examine the Bears’ games in that first simulation:

test$games %>% filter(sim == 1) %>% filter(away_team == "CHI" | home_team == "CHI")#> ── nflverse games and schedules ────────────────────────────────────────────────#>  Data updated: 2024-01-20 19:16:51 UTC#> # A tibble: 16 × 9#> sim game_type week away_team home_team away_rest home_rest location result#> <dbl> <chr> <int> <chr> <chr> <int> <int> <chr> <int>#>  1 1 REG 1 CHI DET 7 7 Home 13#>  2 1 REG 2 NYG CHI 6 7 Home 18#>  3 1 REG 3 CHI ATL 7 7 Home -1#>  4 1 REG 4 IND CHI 7 7 Home NA#>  5 1 REG 5 TB CHI 4 4 Home NA#>  6 1 REG 6 CHI CAR 10 7 Home NA#>  7 1 REG 7 CHI LA 8 8 Home NA#>  8 1 REG 8 NO CHI 7 6 Home NA#>  9 1 REG 9 CHI TEN 7 7 Home NA#> 10 1 REG 10 MIN CHI 8 8 Home NA#> 11 1 REG 12 CHI GB 13 7 Home NA#> 12 1 REG 13 DET CHI 10 7 Home NA#> 13 1 REG 14 HOU CHI 7 7 Home NA#> 14 1 REG 15 CHI MIN 7 7 Home NA#> 15 1 REG 16 CHI JAX 7 7 Home NA#> 16 1 REG 17 GB CHI 7 7 Home NA

Note that only the first three weeks have the result filled in, whilethe others are NA, indicating that game hasn’t yet occurredor been simulated. This is because the test_week = 3 inputaborted the simulation after Week 3, which was useful for seeing the Eloabove.

Simulation Configuration

There is a lot of flexibility in how you choose to run thesimulation. These are the parameters and how to configure them when yourun the simulate_nfl() function.

  • nfl_season - Which NFL season are you simulating?By default, it simulates the most recent season for which the regularseason schedule is available through Lee Sharpe’sNFL game data. The earliest season you can simulate is 2002.
  • Note: Before the schedule for a new season is released, nflseedR maysupport simulating using a fake schedule for the upcoming season. Itwill notify you if it is doing this. The opponents will be correct, butthe weeks in which games occur will not match the actual NFL schedule.The actual schedule will be utilized instead after it is released by theNFL.
  • process_games - This is where you supply a functionyou’ve written to encompass your model used to determine simulated gamesresults, like the examples above. By default, this will generate arandom Elo for every team per round of simulations, then use that todetermine game data.
  • playoff_seeds - How many playoff seeds perconference are used? By default, this is 7 for seasons 2020 and later,and 6 for earlier seasons.
  • if_ended_today - This should only be used whenrunning in the middle of the regular season. It will take all completedgames as done, but remove the rest of the regular season games from theschedule, and begin the playoffs as if everything was locked in based onthe regular season data that exists so far.
  • fresh_season - You’ll see this was set toTRUE in all of our examples above. This setting deletes anyplayoff games and clears out the results for all regular season games,so everything is generated fresh. The default is FALSEwhere all games that have been completed in real life are treated aslocked in, and instead remaining games are simulated.
  • fresh_playoffs - Similar tofresh_season, except instead when set to TRUE,regular season results remain and only playoff games are deleted andthen simulated. The default is FALSE in which case playoffgames that are completed are accepted as they occurred,
  • tiebreaker_depth - How far do you want tiebreakersto be utilized? Usually leaving it at the default below (3)is fine, but before the season starts, you may wish to have lesstie-breaking in order to
    • 1: All teams with the same record have any ties brokenrandomly.
    • 2: Instead of evaluating common games if that step isreached, break any ties randomly. But all earlier tiebreakers arehandled correctly.
    • 3: The default. All tiebreakers are handled throughstrength of schedule are processed (or through strength of victory fordraft pick order). In the unlikely event of a further tie, it will bebroken randomly.
  • test_week - This will abort after simulating thisweek number in the simulator. simulate_nfl() instead willreturn the output of your process_games() function. This isa useful input for debugging your code, but should be left asNULL (the default) for actual simulations. This also meansonly the first round will be simulated.
  • simulations - How many simulations should be run?Defaults to 1000.
  • sims_per_round - The simulator can break things upinto chunks of simulated seasons, process each chunk on its own (calleda round), and then aggregate everything together at the end. The defaultvalue determines the number of locally available cores and calculatesthe number of simulations per round to be equal to half of the availablecores (various benchmarks showed this results in optimal performance inparallel processes). If your computer is hanging and forces a restartwhile running this simulation, it is recommended that you lower thisnumber.

Simulation Output

The output of simulate_nfl(), assuming you don’t put ina test_week to debug your function, is a list of class"nflseedR_simulation" that holds four data frames withsimulation results as well as a list of parameters used in thesimulation. Here are the contents of each:

  • teams - One row per team per simulation.
    • sim - The ID number of the simulation. All rowswith the same value of sim in both teams andgames refer to the same simulated season.
    • team - The abbreviation representing the team
    • conf - The conference the team is in (such asAFC)
    • division - The division the team is in (such asNFC West)
    • games - How many regular season games the team hasplayed
    • wins - The number of games the team has won,counting ties as 0.5 wins
    • true_wins - The number of games the team has won,ignoring ties.
    • win_pct - The win rate of the team. Equal towins / games.
    • div_pct - The win rate of the teams in games playedagainst teams in the same division.
    • conf_pct - The win rate of the teams in gamesplayed against teams in the same conference.
    • sov - Strength of Victory. The combined win rate ofteams this team has beaten.
    • sos - Strength of Schedule. The combined win rateof teams this team has played.
    • div_rank - What place the team finished in itsdivision.
    • seed - What playoff seed number the team earned.NA if the team did not make the playoffs.
    • exit - The week of the team’s last game. The SuperBowl winner’s value will be one higher than the week of the SuperBowl.
    • draft_order - Which pick the team earned in thefollowing NFL Draft. Note that this value is before any trades,forfeits, or other modifications to draft picks.
  • games - One row per game per simulation.
    • sim - The ID number of the simulation. All rowswith the same value of sim in both teams andgames refer to the same simulated season.
    • game_type - What type of game this is
      • REG - A regular season game
      • POST - A playoff rounds earlier than a wildcard game(only used if simulating with lots of playoff teams)
      • WC - A wildcard playoff game
      • DIV - A divisional playoff game
      • CON - A conference championship game
      • SB - A Super Bowl
    • week - The numerical week the game takes place in.Continues incrementing after the regular season to each playoffround.
    • away_team - The abbreviation of the away team inthe game
    • home_team - The abbreviation of the home team inthe game
    • away_rest - The number of days since the awayteam’s last game. Is 7 for the team’s first game of theseason.
    • home_rest - The number of days since the hometeam’s last game. Is 7 for the team’s first game of theseason.
    • location - Either Home if played atthe home team’s stadium, or Neutral for a game playedelsewhere
    • result - The amount of points the home team won by(or lost by if negative). Is 0 for tied games. It isNA for games which aren’t yet complete or simulated, whichshould only ever be returned if you used test_week.
  • overall - One row per team, aggregated across allsimulations. Sorted by conf then division thenteam. This is a good reference for looking at futures bets,since this should represents your model’s chances of various thingshappening for each team.
    • conf - The conference the team is in (such asAFC)
    • division - The division the team is in (such asNFC West)
    • team - The abbreviation representing the team
    • wins - The mean (average) number of games wonacross the simulations, counting ties as 0.5 wins
    • playoff - The rate this team made the playoffsacross simulations
    • div1 - The rate this team won the division acrosssimulations
    • seed1 - The rate this team had the first playoffseed across simulations
    • won_conf - The rate this team won its conferenceacross simulations
    • won_sb - The rate this team won the Super Bowlacross simulations
    • draft1 - The rate this team received the first pickin the next draft across simulations
    • draft5 - The rate this team received a top fivepick in the next draft across simulations
  • team_wins - Each team has a row for 0 wins, 0.5wins, 1 win, 1.5 wins, … , 15.5 wins, 16 wins. These are aggregatedacross all simulations and are intending to represent your model’sprobability for teams being below or above a certain win total, makingthem an excellent reference for using your model to make win total bets.
    • team - The abbreviation representing the team
    • wins - A number of wins (either an integer halfwaybetween two integers)
    • over_prob - The rate this team had more wins thanthis number aggregated across simulations. Ties are ignored.
    • under_prob - The rate this team had fewer wins thanthis number aggregated across simulations. Ties are ignored. Note thatif wins is an integer, 1-over_prob-under_probrepresents the rate at which the team finished at exactly that manywins.
  • game_summary - One row per game, aggregated acrossall simulations. Sorted by game_type then weekthen away_team then home_team.
    • game_type - What type of game this is
      • REG - A regular season game
      • POST - A playoff rounds earlier than a wildcard game(only used if simulating with lots of playoff teams)
      • WC - A wildcard playoff game
      • DIV - A divisional playoff game
      • CON - A conference championship game
      • SB - A Super Bowl
    • week - The numerical week the game takes place in.Continues incrementing after the regular season to each playoffround.
    • away_team - The abbreviation of the away team inthe game
    • home_team - The abbreviation of the home team inthe game
    • away_wins - The number of times the away team haswon the game
    • home_wins - The number of times the home team haswon the game
    • ties - The number of times the game ended in atie
    • result - The amount of points the home team won by(or lost by if negative) on average across all simulations
    • games_played - The number of times the game wasplayed. For game_type == "REG" this will equal the numberof simulations. The number of playoff matchups will differ.
    • away_percentage - The rate the away team won thegame counting ties as half a win
    • home_percentage - The rate the home team won thegame counting ties as half a win
  • sim_params - a list of parameters used in thesimulation. The list contains the following parameters which aredescribed in ?simulate_nfl():
    • nfl_season
    • playoff_seeds
    • if_ended_today
    • fresh_season
    • fresh_playoffs
    • tiebreaker_depth
    • test_week
    • simulations
    • sims_per_round
    • .debug
    • print_summary
Simulating NFL seasons using nflseedR (2024)
Top Articles
Latest Posts
Article information

Author: Annamae Dooley

Last Updated:

Views: 5355

Rating: 4.4 / 5 (65 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Annamae Dooley

Birthday: 2001-07-26

Address: 9687 Tambra Meadow, Bradleyhaven, TN 53219

Phone: +9316045904039

Job: Future Coordinator

Hobby: Archery, Couponing, Poi, Kite flying, Knitting, Rappelling, Baseball

Introduction: My name is Annamae Dooley, I am a witty, quaint, lovely, clever, rich, sparkling, powerful person who loves writing and wants to share my knowledge and understanding with you.