cfbseedR simulates and evaluates college football seasons: standings with a documented tiebreaker cascade, conference champions, College Football Playoff (CFP) seeding, and week-by-week season simulation with a pluggable results generator.
Everything in this article runs offline using the toy season bundled with the package (9 teams: two 4-team conferences, “Alpha” and “Beta”, plus one independent).
library(cfbseedR)
games <- read.csv(system.file("extdata", "toy_games.csv", package = "cfbseedR"))
teams <- read.csv(system.file("extdata", "toy_teams.csv", package = "cfbseedR"))
head(games)
#> sim week game_type home_team away_team result neutral
#> 1 2024 1 REG A1 A2 7 0
#> 2 2024 1 REG A3 A4 3 0
#> 3 2024 1 REG B1 B2 4 0
#> 4 2024 1 REG B3 B4 6 0
#> 5 2024 2 REG A3 A1 3 0
#> 6 2024 2 REG A2 A4 14 0
teams
#> team conference
#> 1 A1 Alpha
#> 2 A2 Alpha
#> 3 A3 Alpha
#> 4 A4 Alpha
#> 5 B1 Beta
#> 6 B2 Beta
#> 7 B3 Beta
#> 8 B4 Beta
#> 9 I1 FBS IndependentsStandings from a games frame
cfb_standings() needs a games frame
(sim/season, game_type,
week, home_team, away_team,
result) and a teams frame (team,
conference). It returns one row per team with overall and
conference records, conference ranks (via the tiebreaker cascade), and
conference champions.
standings <- cfb_standings(games, teams, verbosity = "NONE")
standings[, c("team", "conference", "wins", "losses", "conf_pct",
"conf_rank", "conf_champ")]
#> # A tibble: 9 × 7
#> team conference wins losses conf_pct conf_rank conf_champ
#> <chr> <chr> <int> <int> <dbl> <int> <lgl>
#> 1 A1 Alpha 3 2 0.667 1 TRUE
#> 2 A2 Alpha 2 2 0.667 2 FALSE
#> 3 A3 Alpha 2 1 0.667 3 FALSE
#> 4 A4 Alpha 0 4 0 4 FALSE
#> 5 B1 Beta 5 0 1 1 TRUE
#> 6 B2 Beta 2 2 0.667 2 FALSE
#> 7 B3 Beta 1 2 0.333 3 FALSE
#> 8 B4 Beta 0 4 0 4 FALSE
#> 9 I1 FBS Independents 2 0 0 NA FALSETwo college-football-specific semantics to be aware of:
-
Conference championship games
(
game_type == "CONF_CHAMP") count toward the overall record and decide the conference champion, but not toward the conference record/rank. -
sov/sosare conference-scoped: strength of victory and strength of schedule are computed over regular-season conference games only (sovover conference victories,sosover conference opponents); independents get0.0.
If you have real data, cfb_games_from_schedule() maps a
cfbfastR::load_cfb_schedules() frame into this schema:
# Not run: requires network access via cfbfastR
sched <- cfbfastR::load_cfb_schedules(2024)
games_2024 <- cfb_games_from_schedule(sched)Playoff seeds with a rankings frame
cfb_playoff_seeds() implements CFP straight
seeding (2025 rule): the field is the best-ranked teams with
the 5 highest-ranked conference champions guaranteed inclusion, seeded
strictly in ranking order. Pass a committee-style rankings frame
(team, rank); without one, a documented
fallback ordering (win pct, SOV, SOS, point differential) is used.
rankings <- data.frame(team = c("B1", "I1", "A1", "A3"), rank = 1:4)
seeded <- cfb_playoff_seeds(standings, rankings = rankings, playoff_seeds = 4)
seeded[!is.na(seeded$seed), c("team", "conference", "conf_champ", "seed")]
#> # A tibble: 4 × 4
#> team conference conf_champ seed
#> <chr> <chr> <lgl> <int>
#> 1 A1 Alpha TRUE 3
#> 2 A3 Alpha FALSE 4
#> 3 B1 Beta TRUE 1
#> 4 I1 FBS Independents FALSE 2A small simulation
cfb_simulations() fills in the missing (NA)
results week by week using a compute_results function - by
default cfbseedR_compute_results(), an ELO-based generator
adapted from nflseedR - then computes standings, champions, seeds, and
(with sim_include = "POST") the playoff bracket.
games$result[games$week >= 3] <- NA
set.seed(42)
sim <- cfb_simulations(games, teams, simulations = 50, playoff_seeds = 4)
#> Start simulation of 50 seasons (4 weeks to simulate).
#> 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 3.26 0.88 0.9 0.02 0.34
#> 2 Alpha A2 1.4 0.12 0.12 0.02 0.02
#> 3 Alpha A3 2.72 0 0.88 0.7 0.06
#> 4 Alpha A4 0.6 0 0 0 0
#> 5 Beta B1 3.24 0.68 0.7 0.06 0.14
#> 6 Beta B2 1.8 0.32 0.34 0 0.04
#> 7 Beta B3 1.66 0 0.34 0 0.06
#> 8 Beta B4 0.68 0 0.04 0 0
#> 9 FBS Independents I1 1.64 0 0.68 0.2 0.34The returned cfbseedR_simulation list also carries
per-simulation standings, all simulated games,
team_wins win-total probabilities, per-matchup
game_summary, and the sim_params used.
For deeper topics - custom compute_results functions,
the tiebreaker_depth ladder, and the CFP-12 seeding rules -
see the Simulating
seasons article.
Acknowledgments
cfbseedR is an adaptation of nflseedR (MIT) by Lee
Sharpe and Sebastian Carl. The entire
architecture - the standings engine, the tiebreaking cascade design, the
week-loop simulator with a pluggable compute_results
contract, and the ELO-based default generator - is their design,
re-derived here with college football semantics. Thank you to Lee,
Sebastian, and the broader nflverse team whose open-source
work this package builds on.