Computes overall and conference standings from a table of game results, including conference ranks (via a documented tiebreaker cascade), conference champions, and - optionally - College Football Playoff seeds.
Adapted from nflseedR's nfl_standings() with
college football semantics: conferences instead of divisions, independents
excluded from conference ranks, and conference championship games counting
toward the overall record (and deciding the champion) but not the
conference record.
Arguments
- games
A data frame of games. Required columns:
- sim or season
A season or simulation ID.
- game_type
One of
"REG","CONF_CHAMP","POST".- week
Week number of the game.
- home_team, away_team
Team names matching
teams$team.- result
Home margin, i.e. home score minus away score. Must not be
NA(play or simulate the games first).
Optional
home_points/away_pointscolumns (per-game scores) feed the official SECcapped_scoring_margintiebreaker rung (see "Official per-conference tiebreakers" below);cfb_games_from_schedule()emits both. Absent -> that rung is skipped, not an error.- 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).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).- ...
Currently unused.
- 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 SEC/Big Ten/Big 12/ACC/MAC official procedures below always run in full.
- playoff_seeds
If not
NULL, aseedcolumn is added viacfb_playoff_seeds()with this number of playoff spots.- rankings
Optional committee-style rankings data frame with columns
teamandrank, passed tocfb_playoff_seeds(). Ignored whenplayoff_seedsisNULL.- tiebreaker_data
Optional named list of external inputs for the official registry rungs. Currently supported:
analytics_ratings, a data frame with columnsteamandrating(feeds the SportSource Analytics-style rating rung used by Big Ten/Big 12/ACC/MAC). Missing -> that rung is skipped for those conferences (noted).- verbosity
One of
"MIN"(default),"MAX", or"NONE"."MAX"logs every tied group as it's broken.
Value
A tibble of standings, one row per (sim, team) (the id column
is named season if the input used season), sorted by sim,
conference, conference rank, and team. Note that sov and sos are
conference-REG-scoped: they are computed over regular-season
conference games only, sov over conference victories and sos over
conference opponents; independents get 0.0 for both. The result also
carries a character vector attr(result, "tiebreak_notes") recording
any registry rungs skipped for lack of their optional input (see
"Official per-conference tiebreakers" above); empty when nothing was
skipped.
| Column | Type | Description |
sim / season | integer | Season or simulation ID (name follows the input). |
team | character | Team name. |
conference | character | Conference name ("FBS Independents" / NA = independent). |
games | integer | Games played (REG + CONF_CHAMP). |
wins | integer | True win count (ties not counted). |
losses | integer | True loss count. |
ties | integer | Tie count. |
win_pct | numeric | Overall win percentage; a tie counts as half a win. |
pd | integer | Overall point differential. |
conf_games | numeric | Regular-season conference games played (0 for independents). |
conf_wins | numeric | Wins over regular-season conference games. |
conf_losses | numeric | Losses over regular-season conference games. |
conf_ties | numeric | Ties over regular-season conference games. |
conf_pct | numeric | Conference win percentage (CONF_CHAMP games excluded). |
conf_pd | numeric | Point differential over regular-season conference games. |
sov | numeric | Strength of victory, conference-REG-scoped: beaten conference opponents' conference wins divided by their conference games. Independents: 0.0. |
sos | numeric | Strength of schedule, conference-REG-scoped: all conference opponents' conference wins divided by their conference games. Independents: 0.0. |
conf_rank | integer | Rank within the conference via the tiebreaker cascade (NA for independents). |
conf_champ | logical | Conference champion flag (decided by the CONF_CHAMP game). |
seed | integer | CFP seed, only when playoff_seeds is not NULL (NA outside the field). |
Details
Conference ranks are seeded by conference win percentage; ties within a
tier are broken by a documented cascade. Registered conferences (SEC,
Big Ten, Big 12, ACC, MAC) use their official 2024+ procedures (see
"Official per-conference tiebreakers" below); every other conference uses
the generic fallback: head-to-head record among the tied teams,
record vs. common conference opponents (minimum one), conference-scoped
strength of victory, conference-scoped strength of schedule, conference
point differential, and finally a coin flip, gated by tiebreaker_depth.
All cascade quantities are computed over regular-season conference games
so conference ranks depend only on conference play.
Official per-conference tiebreakers
CONFERENCE_TIEBREAKERS (internal) registers the SEC, Big Ten, Big 12,
ACC, and MAC 2024+ procedures as rung lists, ported verbatim from sdv-py's
cfb_standings.py so both engines produce identical output on the shared
cross-language parity fixture. Rung primitives: h2h (multi-team combined
head-to-head, applied only when every tied pair played; otherwise only
"defeated-all" elimination - the symmetric "lost-to-all" elimination is
intentionally not modeled, a documented simplification, see
R/tiebreakers.R), record_vs_common, record_vs_common_desc (descend
the standings from best to worst, comparing a tied GROUP of common
opponents collectively - the Big 12 rule, adopted for every registry
descent rung), opp_conf_win_pct (pooled opponents' conference win pct -
this reuses the existing sos column, which already computes the pooled
sum-of-wins/sum-of-games formula), capped_scoring_margin (SEC: points
scored capped at 42 / allowed capped at 48, per game, summed over
conference games; needs home_points/away_points), total_wins (Big
12: overall wins with at most one win vs an FCS-or-lower opponent
counted; needs teams$division), analytics_rating (external, via
tiebreaker_data$analytics_ratings), and coin_toss. After each team is
seeded/eliminated the procedure restarts from the first rung with the
remaining tied set; when a rung's required input is unavailable it is
skipped deterministically and the skip is recorded once (per conference)
in attr(result, "tiebreak_notes"). Under registry conferences,
conf_rank 1-2 are the two teams that reach the conference championship
game (the cascade only orders them - see the design brief).
See also
cfb_playoff_seeds(), cfb_simulations(),
cfb_games_from_schedule(),
the nflseedR original: https://nflseedr.com,
and cfbfastR for real schedules
Examples
games <- read.csv(system.file("extdata", "toy_games.csv", package = "cfbseedR"))
teams <- read.csv(system.file("extdata", "toy_teams.csv", package = "cfbseedR"))
standings <- cfb_standings(games, teams, tiebreaker_depth = "POINTS",
verbosity = "NONE")
standings[, c("team", "conference", "conf_rank", "conf_champ")]
#> # A tibble: 9 × 4
#> team conference conf_rank conf_champ
#> <chr> <chr> <int> <lgl>
#> 1 A1 Alpha 1 TRUE
#> 2 A2 Alpha 2 FALSE
#> 3 A3 Alpha 3 FALSE
#> 4 A4 Alpha 4 FALSE
#> 5 B1 Beta 1 TRUE
#> 6 B2 Beta 2 FALSE
#> 7 B3 Beta 3 FALSE
#> 8 B4 Beta 4 FALSE
#> 9 I1 FBS Independents NA FALSE
# An official-registry analytics rating input (used by Big Ten/Big
# 12/ACC/MAC when their cascade reaches the `analytics_rating` rung)
ratings <- data.frame(team = teams$team, rating = seq(90, 70, length.out = nrow(teams)))
standings2 <- cfb_standings(games, teams,
tiebreaker_data = list(analytics_ratings = ratings),
verbosity = "NONE")
attr(standings2, "tiebreak_notes")
#> character(0)