Skip to contents

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.

Usage

cfb_standings(
  games,
  teams,
  ...,
  tiebreaker_depth = c("SOS", "PRE-SOV", "POINTS", "RANDOM"),
  playoff_seeds = NULL,
  rankings = NULL,
  tiebreaker_data = NULL,
  verbosity = c("MIN", "MAX", "NONE")
)

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_points columns (per-game scores) feed the official SEC capped_scoring_margin tiebreaker 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 team and conference. Teams with conference "FBS Independents" or NA are treated as independents: they appear in overall standings but receive no conference rank. An optional division column (e.g. "FBS"/"FCS") feeds the Big 12 total_wins FCS cap; absent -> that cap degrades to uncapped win totals (noted, see tiebreak_notes below). teams need not list every team that appears in games - 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 12 total_wins FCS 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, a seed column is added via cfb_playoff_seeds() with this number of playoff spots.

rankings

Optional committee-style rankings data frame with columns team and rank, passed to cfb_playoff_seeds(). Ignored when playoff_seeds is NULL.

tiebreaker_data

Optional named list of external inputs for the official registry rungs. Currently supported: analytics_ratings, a data frame with columns team and rating (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.

ColumnTypeDescription
sim / seasonintegerSeason or simulation ID (name follows the input).
teamcharacterTeam name.
conferencecharacterConference name ("FBS Independents" / NA = independent).
gamesintegerGames played (REG + CONF_CHAMP).
winsintegerTrue win count (ties not counted).
lossesintegerTrue loss count.
tiesintegerTie count.
win_pctnumericOverall win percentage; a tie counts as half a win.
pdintegerOverall point differential.
conf_gamesnumericRegular-season conference games played (0 for independents).
conf_winsnumericWins over regular-season conference games.
conf_lossesnumericLosses over regular-season conference games.
conf_tiesnumericTies over regular-season conference games.
conf_pctnumericConference win percentage (CONF_CHAMP games excluded).
conf_pdnumericPoint differential over regular-season conference games.
sovnumericStrength of victory, conference-REG-scoped: beaten conference opponents' conference wins divided by their conference games. Independents: 0.0.
sosnumericStrength of schedule, conference-REG-scoped: all conference opponents' conference wins divided by their conference games. Independents: 0.0.
conf_rankintegerRank within the conference via the tiebreaker cascade (NA for independents).
conf_champlogicalConference champion flag (decided by the CONF_CHAMP game).
seedintegerCFP 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

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)