Simulates college football seasons based on a games/schedule table that
holds matchups with and without results. Missing results are computed
week by week using the pluggable compute_results function (default:
cfbseedR_compute_results(), an ELO-based generator adapted from
nflseedR). After the scheduled games, standings, conference champions,
and CFP seeds are computed, and - with sim_include = "POST" - the
playoff bracket is simulated round by round.
Usage
cfb_simulations(
games,
teams,
compute_results = cfbseedR_compute_results,
...,
simulations = 10000L,
playoff_seeds = 12L,
tiebreaker_depth = c("SOS", "PRE-SOV", "POINTS", "RANDOM"),
sim_include = c("POST", "REG"),
rankings = NULL,
autobid = c("2026", "2025"),
tiebreaker_data = NULL,
chunks = 8L,
verbosity = c("MIN", "MAX", "NONE")
)Arguments
- games
A data frame of games of a single season, in the schema of
cfb_standings()plus an optionalneutralcolumn (0/1). Games withresult = NAare simulated. Must not containgame_type == "POST"rows - the playoff bracket is generated from the computed seeds.- teams
A data frame with columns
teamandconference. Teams with conference"FBS Independents"orNAare treated as independents: they appear in overall standings but receive no conference rank. An optionaldivisioncolumn (e.g."FBS"/"FCS") feeds the Big 12total_winsFCS cap; absent -> that cap degrades to uncapped win totals (noted, seetiebreak_notesbelow). An optionalconf_divisioncolumn (e.g."East"/"West") turns on division-format ranking for a conference: each division is ranked separately (with that conference's registered procedure) and the division champions takeconf_rank1-2. Supplying divisions for a conference IS the opt-in - correct for the 2026 Sun Belt and equally for historical divisional seasons of any conference. An optional logicalpostseason_eligiblecolumn (explicitFALSE= ineligible) keeps a team out of the championship-game ranks while its games still count in every comparison (the ACC policy makes this explicit); with fewer than two eligible teams in a conference the remainingconf_rank1-2 slots are necessarily filled by ineligible teams.teamsneed not list every team that appears ingames- an unlisted opponent (e.g. an FCS-or-lower team) gets no standings row of its own, but its games still count toward its opponents' records and toward the Big 12total_winsFCS cap (an unknown opponent counts as FCS-or-lower).- compute_results
A function computing results of games, with the required arguments
teams,games, andweek_num. Seesimulations_verify_fct()for the contract andcfbseedR_compute_results()for the default.- ...
Additional parameters passed on to
compute_results.- simulations
The number of times the season shall be simulated.
- playoff_seeds
Number of CFP spots (default 12), passed to
cfb_playoff_seeds().- tiebreaker_depth
One of
"SOS"(default),"PRE-SOV","POINTS", or"RANDOM". Controls how deep the tiebreaker cascade goes before falling back to a coin flip:"RANDOM": coin flip immediately."PRE-SOV": head-to-head and common opponents only."SOS": adds strength of victory, then strength of schedule."POINTS": adds conference point differential.
This depth ladder gates ONLY the generic fallback cascade used by unregistered conferences; the registered official procedures below always run in full.
- sim_include
One of
"POST"(default) or"REG":"REG": simulate the remaining schedule and compute standings, conference champions, and playoff seeds."POST":"REG"+ simulate the playoff bracket.
- rankings
Optional committee rankings (
team,rank) used for CFP seeding, held static across simulations. WhenNULL, seeding falls back to the per-simulation standings ordering (seecfb_playoff_seeds()).- autobid
CFP automatic-qualifier policy passed to
cfb_playoff_seeds():"2026"(default, current rule) or"2025".- tiebreaker_data
Optional named list of external tiebreaker inputs (
analytics_ratings,cfp_rankings,apr), held static across simulations - seecfb_standings(). Missing inputs skip their rungs (noted).- chunks
The number of chunks the simulations are split into (default 8, capped at
simulations). Chunks are dispatched withfurrr::future_map(), so they run in parallel as soon as you set a parallel plan, e.g.future::plan("multisession"); with the default sequential plan they simply run one after another. There is no universally best number - too many chunks can be as slow as too few.- verbosity
One of
"MIN"(default),"MAX", or"NONE", as incfb_standings()."NONE"silences the progress messages entirely.
Value
A list of class cfbseedR_simulation with these elements:
| Element | Type | Description |
standings | tibble | Per-simulation standings in the cfb_standings() schema (see its column table; sov/sos are conference-REG-scoped) plus seed and - with sim_include = "POST" - exit (integer: 0 = missed playoff, r = eliminated in round r, max round + 1 = national champion). |
games | tibble | All games of all simulations (sim, game_type, week, home_team, away_team, result, neutral), incl. generated playoff games. |
overall | tibble | Per-team means across simulations: wins (average), and probabilities conf_champ, playoff, seed1, won_natty. |
team_wins | tibble | Per-team probability of clearing each half-win threshold (team, wins, over_prob, under_prob). |
game_summary | tibble | Per-matchup aggregates: away_wins, home_wins, ties, mean result, games_played, away_percentage, home_percentage. |
sim_params | list | The simulation parameters (number of simulations, seeds, depth, etc.). |
Details
The playoff bracket is a standard single-elimination bracket of size
2^ceiling(log2(playoff_seeds)) with byes for the top seeds - for 12
seeds this reproduces the CFP bracket (quarterfinals 1 vs 8/9 winner,
4 vs 5/12, 3 vs 6/11, 2 vs 7/10). First-round games are hosted by the
higher seed; later rounds are neutral-site. There is no reseeding
(fixed bracket, per the CFP format). Conference championship matchups
are simulated as scheduled, not re-derived from simulated standings.
Simulations are split into chunks and dispatched with furrr, so a
parallel future::plan() spreads them across cores. Progress can be
reported by turning on progressr::handlers() before the call, or by
piping the call into progressr::with_progress().
Set a seed with set.seed() for reproducibility: furrr generates
parallel-safe RNG streams per chunk, so a given seed reproduces exactly -
but the stream differs from a single sequential pass, so results for the
same seed change if you change chunks.
See also
cfb_standings(), cfb_playoff_seeds(),
cfbseedR_compute_results(), simulations_verify_fct(),
the nflseedR original: https://nflseedr.com
Examples
# \donttest{
games <- read.csv(system.file("extdata", "toy_games.csv", package = "cfbseedR"))
teams <- read.csv(system.file("extdata", "toy_teams.csv", package = "cfbseedR"))
games$result[games$week >= 3] <- NA
set.seed(4)
sim <- cfb_simulations(games, teams, simulations = 4, playoff_seeds = 4)
#> Start simulation of 4 seasons (4 weeks to simulate) in 4 chunks.
#> ℹ Chunks run sequentially. Set a parallel `future::plan()`, e.g.
#> `future::plan("multisession")`, to use more cores.
#> DONE!
sim$overall
#> # A tibble: 9 × 7
#> conference team wins conf_champ playoff seed1 won_natty
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Alpha A1 2.5 0.5 0.5 0 0.25
#> 2 Alpha A2 2 0.5 0.5 0 0
#> 3 Alpha A3 2.5 0 1 0.5 0.5
#> 4 Alpha A4 1.25 0 0.25 0 0
#> 5 Beta B1 3.5 0.75 0.75 0.5 0
#> 6 Beta B2 1.5 0.25 0.25 0 0
#> 7 Beta B3 1.75 0 0.75 0 0.25
#> 8 Beta B4 1.5 0 0 0 0
#> 9 FBS Independents I1 0.5 0 0 0 0
# }