Conference Standings

KyleSJohnston KyleSJohnston

Any regular-season analysis of the NFL would be incomplete without some awareness of the conference standings. Standings have implications for both the playoffs and the draft, and understanding those implications can help explain counter-intuitive phenomena—especially near the end of the regular season.

In this tutorial, we're going to explore whether we can reproduce the final 2024 conference standings. The main focus will be sourcing from NFLData.jl and performing basic dataframe operations rather than an exact result that reflects the NFL rulebook. The code could be adapted to handle other points in time or simulated results.

Some familiarity with Julia, DataFrames.jl, and with the NFL is helpful. Everything here should be accessible for users familiar with the nflverse but new to Julia.

Preparing the Environment

For this tutorial, we need to import a few packages.

using Chain
using DataFrames
using NFLData

Depending on your environment, you may need to add these packages to the environment first. If this errors, you have two options:

  • pkg> add Chain, DataFrames, NFLData or

  • import Pkg; Pkg.add(["Chain", "DataFrames", "NFLData"])

Either will modify your environment to make these packages available.[1]

Why Chain?

I started using Julia after several years of Python. Python syntax supports building up expressions by chaining methods to the end of previous results.

For example:

input_df.groupby([
    "key1", "key2",
]).agg(
    value_mean=pd.NamedAgg(column="value", aggfunc=np.mean),
    value_std=pd.NamedAgg(column="value", aggfunc=np.std),
).plot()

This syntax arises somewhat naturally when iterating in a Jupyter notebook.

Julia supports a similar piping syntax.[2]

input_df |>
    (x -> groupby(x, [:key1, :key2])) |>
    (x -> combine(x, :value => mean => :value_mean, :value => std => :value_std))

I find that syntax to be clunky. With Chain, the same example reads a lot cleaner.

@chain input_df begin
    groupby([:key1, :key2])
    combine(
        :value => mean => :value_mean,
        :value => std => :value_std,
    )
end

The Chain documentation has a number of (better) examples.

Loading Data

We source data with NFLData.load_teams() and NFLData.load_schedules().

Teams

first(load_teams(), 3)
3×16 DataFrame
 Row │ team_abbr  team_name          team_id  team_nick  team_conf  team_division  team_color  team_color2  team_color3  team_color4  team_logo_wikipedia                team_logo_espn                     team_wordmark                      team_conference_logo               team_league_logo                   team_logo_squared
     │ String     String             Int64    String     String     String         String      String       String?      String?      String                             String                             String                             String                             String                             String
─────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ ARI        Arizona Cardinals     3800  Cardinals  NFC        NFC West       #97233F     #000000      #ffb612      #a5acaf      https://upload.wikimedia.org/wik…  https://a.espncdn.com/i/teamlogo…  https://github.com/nflverse/nflv…  https://github.com/nflverse/nflv…  https://raw.githubusercontent.co…  https://github.com/nflverse/nflv…
   2 │ ATL        Atlanta Falcons        200  Falcons    NFC        NFC South      #A71930     #000000      #a5acaf      #a30d2d      https://upload.wikimedia.org/wik…  https://a.espncdn.com/i/teamlogo…  https://github.com/nflverse/nflv…  https://github.com/nflverse/nflv…  https://raw.githubusercontent.co…  https://github.com/nflverse/nflv…
   3 │ BAL        Baltimore Ravens       325  Ravens     AFC        AFC North      #241773     #9E7C0C      #9e7c0c      #c60c30      https://upload.wikimedia.org/wik…  https://a.espncdn.com/i/teamlogo…  https://github.com/nflverse/nflv…  https://github.com/nflverse/nflv…  https://raw.githubusercontent.co…  https://github.com/nflverse/nflv…

To make joins slightly easier, we source the team data and rename with select.

team_df = @chain load_teams() begin
    select(
        :team_abbr => :team,
        :team_name => :name,
        :team_conf => :conf,
        :team_division => :division,
    )
end

first(team_df, 10)
10×4 DataFrame
 Row │ team    name                conf    division
     │ String  String              String  String
─────┼───────────────────────────────────────────────
   1 │ ARI     Arizona Cardinals   NFC     NFC West
   2 │ ATL     Atlanta Falcons     NFC     NFC South
   3 │ BAL     Baltimore Ravens    AFC     AFC North
   4 │ BUF     Buffalo Bills       AFC     AFC East
   5 │ CAR     Carolina Panthers   NFC     NFC South
   6 │ CHI     Chicago Bears       NFC     NFC North
   7 │ CIN     Cincinnati Bengals  AFC     AFC North
   8 │ CLE     Cleveland Browns    AFC     AFC North
   9 │ DAL     Dallas Cowboys      NFC     NFC East
  10 │ DEN     Denver Broncos      AFC     AFC West

Schedules & Results

games_df = @chain load_schedules() begin
    subset(
        :season => x -> x .== 2024,
        :game_type => x -> x .== "REG",
    )
end;

first(games_df, 5)
5×46 DataFrame
 Row │ game_id          season  game_type  week   gameday     weekday   gametime  away_team  away_score  home_team  home_score  location  result  total   overtime  old_game_id  gsis    nfl_detail_id  pfr           pff      espn       ftn     away_rest  home_rest  away_moneyline  home_moneyline  spread_line  away_spread_odds  home_spread_odds  total_line  under_odds  over_odds  div_game  roof      surface    temp     wind     away_qb_id  home_qb_id  away_qb_name   home_qb_name     away_coach       home_coach      referee        stadium_id  stadium
     │ String           Int64   String     Int64  Date        String    Time?     String     Int64?      String     Int64?      String    Int64?  Int64?  Int64?    Int64        Int64?  String?        String        Int64?   Int64      Int64?  Int64      Int64      Int64?          Int64?          Float64?     Int64?            Int64?            Float64?    Int64?      Int64?     Int64     String    String     Int64?   Int64?   String?     String?     String?        String?          String           String          String?        String      String
─────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ 2024_01_BAL_KC     2024  REG            1  2024-09-05  Thursday  20:20:00  BAL                20  KC                 27  Home           7      47         0   2024090500   59508  missing        202409050kan  missing  401671789    6449          7          7             124            -148          3.0              -118              -102        46.0        -110       -110         0  outdoors  grass           67        8  00-0034796  00-0033873  Lamar Jackson  Patrick Mahomes  John Harbaugh    Andy Reid       Shawn Hochuli  KAN00       GEHA Field at Arrowhead Stadium
   2 │ 2024_01_GB_PHI     2024  REG            1  2024-09-06  Friday    20:15:00  GB                 29  PHI                34  Neutral        5      63         0   2024090600   59509  missing        202409060phi  missing  401671805    6450          7          7             110            -130          2.0              -110              -110        49.5        -112       -108         0  outdoors             missing  missing  00-0036264  00-0036389  Jordan Love    Jalen Hurts      Matt LaFleur     Nick Sirianni   Ron Torbert    SAO00       Arena Corinthians
   3 │ 2024_01_PIT_ATL    2024  REG            1  2024-09-08  Sunday    13:00:00  PIT                18  ATL                10  Home          -8      28         0   2024090800   59510  missing        202409080atl  missing  401671744    6451          7          7             160            -192          4.0              -110              -110        43.0        -115       -105         0  closed    fieldturf  missing  missing  00-0036945  00-0029604  Justin Fields  Kirk Cousins     Mike Tomlin      Raheem Morris   Brad Rogers    ATL97       Mercedes-Benz Stadium
   4 │ 2024_01_ARI_BUF    2024  REG            1  2024-09-08  Sunday    13:00:00  ARI                28  BUF                34  Home           6      62         0   2024090801   59511  missing        202409080buf  missing  401671617    6452          7          7             250            -310          6.5              -105              -115        46.0        -112       -108         0  outdoors  a_turf          61       20  00-0035228  00-0034857  Kyler Murray   Josh Allen       Jonathan Gannon  Sean McDermott  Tra Blake      BUF00       New Era Field
   5 │ 2024_01_TEN_CHI    2024  REG            1  2024-09-08  Sunday    13:00:00  TEN                17  CHI                24  Home           7      41         0   2024090802   59512  missing        202409080chi  missing  401671719    6453          7          7             164            -198          4.0              -108              -112        43.0        -110       -110         0  outdoors  grass           67        8  00-0039152  00-0039918  Will Levis     Caleb Williams   Brian Callahan   Matt Eberflus   Shawn Smith    CHI98       Soldier Field
nrow(games_df)
272
extrema(games_df[!, :week])
(1, 18)

272 games are included over 18 weeks of football.

ismissing.(games_df[!, :result]) |> sum
0

Results are available for every game.

Cleaning Data

For conference standing purposes, I would consider games_df to be a "wide" format.[3] We have one row per game; we want one row per team. We can obtain a "long" format by concatenating a "home" dataframe and an "away" dataframe.

Conference & Division Indicators

To compute conference (division) records, we need to know whether both teams are in the same conference (division). We join with team_df to add the conference and division fields, and compute the indicators with transform.

games_df = @chain games_df begin
    leftjoin(
        team_df,
        on = :away_team => :team,
        renamecols = "" => "_away",
    )
    leftjoin(
        team_df,
        on = :home_team => :team,
        renamecols = "" => "_home",
    )
    transform(
        [:conf_home, :conf_away] => ((h,a) -> h .== a) => :is_conf,
        [:division_home, :division_away] => ((h,a) -> h .== a) => :is_division,
    )
end;

Home & Away Interpretations

We have to do two things to interpret the data correctly:

  • home_score and away_score need to be converted into points_for and points_against.

  • result—which is home_score - away_score—requires some logic to become W/L/T indicators.

Most of the other fields are simply passed through the select.

away_df = @chain games_df begin
    select(
        :away_team => :team,
        :away_team => (x -> false) => :is_home,
        :away_score => :points_for,
        :home_score => :points_against,
        :result => (x -> x .< 0) => :is_win,
        :result => (x -> x .> 0) => :is_loss,
        :result => (x -> x .== 0) => :is_tie,
        :is_conf,
        :is_division,
    )
end;

home_df = @chain games_df begin
    select(
        :home_team => :team,
        :home_team => (x -> true) => :is_home,
        :home_score => :points_for,
        :away_score => :points_against,
        :result => (x -> x .> 0) => :is_win,
        :result => (x -> x .< 0) => :is_loss,
        :result => (x -> x .== 0) => :is_tie,
        :is_conf,
        :is_division,
    )
end;

df = vcat(home_df, away_df)

first(df, 5)
5×9 DataFrame
 Row │ team    is_home  points_for  points_against  is_win  is_loss  is_tie  is_conf  is_division
     │ String  Bool     Int64?      Int64?          Bool    Bool     Bool    Bool     Bool
─────┼────────────────────────────────────────────────────────────────────────────────────────────
   1 │ KC         true          27              20    true    false   false     true        false
   2 │ PHI        true          34              29    true    false   false     true        false
   3 │ ATL        true          10              18   false     true   false    false        false
   4 │ BUF        true          34              28    true    false   false    false        false
   5 │ CHI        true          24              17    true    false   false    false        false

Just to make sure we've done this correctly, we verify some totals.

sum(df[!, :is_division]) == 192  # 4 teams * 3 opponents * 2 meetings * 8 divisions
true
sum(df[!, :points_for]) == sum(df[!, :points_against])
true
sum(df[!, :is_win]) == sum(df[!, :is_loss])
true
sum(df[!, :is_tie]) % 2 == 0
true

Aggregation

We know from the tiebreaking procedures that ties count as a half-win for each team. Since we will be repeating this calculation a number of times, it is a good candidate for a function.

function winning_percentage(wins, losses, ties)
    total_wins = wins + 0.5 * ties
    total_games = wins + losses + ties
    return total_wins / total_games
end
winning_percentage (generic function with 1 method)

For a dense representation of the record, we can combine it into a string.

recordstr(wins, losses, ties) = "$wins - $losses - $ties"
recordstr (generic function with 1 method)

We start by computing team totals.

total_df = @chain df begin
    groupby(:team)
    combine(
        :is_win => sum => :wins,
        :is_loss => sum => :losses,
        :is_tie => sum => :ties,
        :points_for => sum => :points_for,
        :points_against => sum => :points_against,
    )
    transform(
        [:wins, :losses, :ties] => ((w,l,t) -> winning_percentage.(w,l,t)) => :pct,
        [:points_for, :points_against] => ((pf,pa) -> pf .- pa) => :net_pts,
    )
end

first(sort(total_df, :pct, rev=true), 5)
5×8 DataFrame
 Row │ team    wins   losses  ties   points_for  points_against  pct       net_pts
     │ String  Int64  Int64   Int64  Int64       Int64           Float64   Int64
─────┼─────────────────────────────────────────────────────────────────────────────
   1 │ KC         15       2      0         385             326  0.882353       59
   2 │ DET        15       2      0         564             342  0.882353      222
   3 │ PHI        14       3      0         463             303  0.823529      160
   4 │ MIN        14       3      0         432             332  0.823529      100
   5 │ BUF        13       4      0         525             368  0.764706      157

Then we compute the home, away, conference, and division records.

location_df = @chain df begin
    groupby([:team, :is_home])
    combine(
        :is_win => sum => :wins,
        :is_loss => sum => :losses,
        :is_tie => sum => :ties,
    )
    transform(
        [:wins, :losses, :ties] => ((w,l,t) -> recordstr.(w,l,t)) => :record,
    )
end;

division_df = @chain df begin
    groupby([:team, :is_division])
    combine(
        :is_win => sum => :wins,
        :is_loss => sum => :losses,
        :is_tie => sum => :ties,
    )
    transform(
        [:wins, :losses, :ties] => ((w,l,t) -> recordstr.(w, l ,t)) => :record,
        [:wins, :losses, :ties] => ((w,l,t) -> winning_percentage.(w, l ,t)) => :pct,
    )
end;

conf_df = @chain df begin
    groupby([:team, :is_conf])
    combine(
        :is_win => sum => :wins,
        :is_loss => sum => :losses,
        :is_tie => sum => :ties,
    )
    transform(
        [:wins, :losses, :ties] => ((w,l,t) -> recordstr.(w, l ,t)) => :record,
        [:wins, :losses, :ties] => ((w,l,t) -> winning_percentage.(w, l ,t)) => :pct,
    )
end;

Finally, we combine them into a single dataframe.

final_df = @chain total_df begin
    leftjoin(
        team_df,
        on = :team,
    )
    leftjoin(
        subset(location_df, :is_home),
        on = :team,
        renamecols = "" => "_home",
    )
    leftjoin(
        subset(location_df, :is_home => ByRow(!)),
        on = :team,
        renamecols = "" => "_away",
    )
    leftjoin(
        subset(division_df, :is_division),
        on = :team,
        renamecols = "" => "_division",
    )
    leftjoin(
        subset(conf_df, :is_conf),
        on = :team,
        renamecols = "" => "_conf",
    )
    leftjoin(
        subset(conf_df, :is_conf => ByRow(!)),
        on = :team,
        renamecols = "" => "_non_conf",
    )
end

names(final_df)
39-element Vector{String}:
 "team"
 "wins"
 "losses"
 "ties"
 "points_for"
 "points_against"
 "pct"
 "net_pts"
 "name"
 "conf"
 "division"
 "is_home_home"
 "wins_home"
 "losses_home"
 "ties_home"
 "record_home"
 "is_home_away"
 "wins_away"
 "losses_away"
 "ties_away"
 "record_away"
 "is_division_division"
 "wins_division"
 "losses_division"
 "ties_division"
 "record_division"
 "pct_division"
 "is_conf_conf"
 "wins_conf"
 "losses_conf"
 "ties_conf"
 "record_conf"
 "pct_conf"
 "is_conf_non_conf"
 "wins_non_conf"
 "losses_non_conf"
 "ties_non_conf"
 "record_non_conf"
 "pct_non_conf"

Using this dataframe, let's show the league standings and see how they compare.

@chain final_df begin
    select(
        :team,
        :name,
        :wins,
        :losses,
        :ties,
        :pct,
        :points_for,
        :points_against,
        :net_pts,
    )
    sort([
        order(:pct, rev=true),
        order(:name),
    ])
end
32×9 DataFrame
 Row │ team    name                   wins   losses  ties   pct       points_for  points_against  net_pts
     │ String  String?                Int64  Int64   Int64  Float64   Int64       Int64           Int64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ DET     Detroit Lions             15       2      0  0.882353         564             342      222
   2 │ KC      Kansas City Chiefs        15       2      0  0.882353         385             326       59
   3 │ MIN     Minnesota Vikings         14       3      0  0.823529         432             332      100
   4 │ PHI     Philadelphia Eagles       14       3      0  0.823529         463             303      160
   5 │ BUF     Buffalo Bills             13       4      0  0.764706         525             368      157
   6 │ BAL     Baltimore Ravens          12       5      0  0.705882         518             361      157
   7 │ WAS     Washington Commanders     12       5      0  0.705882         485             391       94
   8 │ GB      Green Bay Packers         11       6      0  0.647059         460             338      122
   9 │ LAC     Los Angeles Chargers      11       6      0  0.647059         402             301      101
  10 │ DEN     Denver Broncos            10       7      0  0.588235         425             311      114
  11 │ HOU     Houston Texans            10       7      0  0.588235         372             372        0
  12 │ LA      Los Angeles Rams          10       7      0  0.588235         367             386      -19
  13 │ PIT     Pittsburgh Steelers       10       7      0  0.588235         380             347       33
  14 │ SEA     Seattle Seahawks          10       7      0  0.588235         375             368        7
  15 │ TB      Tampa Bay Buccaneers      10       7      0  0.588235         502             385      117
  16 │ CIN     Cincinnati Bengals         9       8      0  0.529412         472             434       38
  17 │ ARI     Arizona Cardinals          8       9      0  0.470588         400             379       21
  18 │ ATL     Atlanta Falcons            8       9      0  0.470588         389             423      -34
  19 │ IND     Indianapolis Colts         8       9      0  0.470588         377             427      -50
  20 │ MIA     Miami Dolphins             8       9      0  0.470588         345             364      -19
  21 │ DAL     Dallas Cowboys             7      10      0  0.411765         350             468     -118
  22 │ SF      San Francisco 49ers        6      11      0  0.352941         389             436      -47
  23 │ CAR     Carolina Panthers          5      12      0  0.294118         341             534     -193
  24 │ CHI     Chicago Bears              5      12      0  0.294118         310             370      -60
  25 │ NO      New Orleans Saints         5      12      0  0.294118         338             398      -60
  26 │ NYJ     New York Jets              5      12      0  0.294118         338             404      -66
  27 │ JAX     Jacksonville Jaguars       4      13      0  0.235294         320             435     -115
  28 │ LV      Las Vegas Raiders          4      13      0  0.235294         309             434     -125
  29 │ NE      New England Patriots       4      13      0  0.235294         289             417     -128
  30 │ CLE     Cleveland Browns           3      14      0  0.176471         258             435     -177
  31 │ NYG     New York Giants            3      14      0  0.176471         273             415     -142
  32 │ TEN     Tennessee Titans           3      14      0  0.176471         311             460     -149

We really care about the conference standings.

rank(x; rev::Bool=false) = sortperm(sortperm(x; rev))
rank (generic function with 1 method)
rank_df = @chain final_df begin
    groupby(:division)
    transform(
        :pct => (x -> rank(x; rev=true)) => :division_rank,
    )
    transform(
        :division_rank => (x -> x .== 1) => :division_leader,
    )
    groupby([:conf, :division_leader])
    transform(
        :pct => (x -> rank(x; rev=true)) => :conference_rank,
    )
    transform(
        [:division_leader, :conference_rank] => ByRow((l,r) -> l ? r : r+4) => :conference_rank,
    )
    sort(:conference_rank)
end;

AFC Standings

subset(rank_df, :conf => (x -> x .== "AFC"))
16×42 DataFrame
 Row │ team    wins   losses  ties   points_for  points_against  pct       net_pts  name                  conf     division   is_home_home  wins_home  losses_home  ties_home  record_home  is_home_away  wins_away  losses_away  ties_away  record_away  is_division_division  wins_division  losses_division  ties_division  record_division  pct_division  is_conf_conf  wins_conf  losses_conf  ties_conf  record_conf  pct_conf  is_conf_non_conf  wins_non_conf  losses_non_conf  ties_non_conf  record_non_conf  pct_non_conf  division_rank  division_leader  conference_rank
     │ String  Int64  Int64   Int64  Int64       Int64           Float64   Int64    String?               String?  String?    Bool?         Int64?     Int64?       Int64?     String?      Bool?         Int64?     Int64?       Int64?     String?      Bool?                 Int64?         Int64?           Int64?         String?          Float64?      Bool?         Int64?     Int64?       Int64?     String?      Float64?  Bool?             Int64?         Int64?           Int64?         String?          Float64?      Int64          Bool             Int64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ KC         15       2      0         385             326  0.882353       59  Kansas City Chiefs    AFC      AFC West           true          8            0          0  8 - 0 - 0           false          7            2          0  7 - 2 - 0                    true              5                1              0  5 - 1 - 0            0.833333          true         10            2          0  10 - 2 - 0   0.833333             false              5                0              0  5 - 0 - 0                 1.0              1             true                1
   2 │ BUF        13       4      0         525             368  0.764706      157  Buffalo Bills         AFC      AFC East           true          8            0          0  8 - 0 - 0           false          5            4          0  5 - 4 - 0                    true              5                1              0  5 - 1 - 0            0.833333          true          9            3          0  9 - 3 - 0    0.75                 false              4                1              0  4 - 1 - 0                 0.8              1             true                2
   3 │ BAL        12       5      0         518             361  0.705882      157  Baltimore Ravens      AFC      AFC North          true          6            2          0  6 - 2 - 0           false          6            3          0  6 - 3 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          8            4          0  8 - 4 - 0    0.666667             false              4                1              0  4 - 1 - 0                 0.8              1             true                3
   4 │ HOU        10       7      0         372             372  0.588235        0  Houston Texans        AFC      AFC South          true          5            3          0  5 - 3 - 0           false          5            4          0  5 - 4 - 0                    true              5                1              0  5 - 1 - 0            0.833333          true          8            4          0  8 - 4 - 0    0.666667             false              2                3              0  2 - 3 - 0                 0.4              1             true                4
   5 │ LAC        11       6      0         402             301  0.647059      101  Los Angeles Chargers  AFC      AFC West           true          5            3          0  5 - 3 - 0           false          6            3          0  6 - 3 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          8            4          0  8 - 4 - 0    0.666667             false              3                2              0  3 - 2 - 0                 0.6              2            false                5
   6 │ PIT        10       7      0         380             347  0.588235       33  Pittsburgh Steelers   AFC      AFC North          true          5            3          0  5 - 3 - 0           false          5            4          0  5 - 4 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          7            5          0  7 - 5 - 0    0.583333             false              3                2              0  3 - 2 - 0                 0.6              2            false                6
   7 │ DEN        10       7      0         425             311  0.588235      114  Denver Broncos        AFC      AFC West           true          6            2          0  6 - 2 - 0           false          4            5          0  4 - 5 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          6            6          0  6 - 6 - 0    0.5                  false              4                1              0  4 - 1 - 0                 0.8              3            false                7
   8 │ CIN         9       8      0         472             434  0.529412       38  Cincinnati Bengals    AFC      AFC North          true          3            5          0  3 - 5 - 0           false          6            3          0  6 - 3 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          6            6          0  6 - 6 - 0    0.5                  false              3                2              0  3 - 2 - 0                 0.6              3            false                8
   9 │ IND         8       9      0         377             427  0.470588      -50  Indianapolis Colts    AFC      AFC South          true          5            3          0  5 - 3 - 0           false          3            6          0  3 - 6 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          7            5          0  7 - 5 - 0    0.583333             false              1                4              0  1 - 4 - 0                 0.2              2            false                9
  10 │ MIA         8       9      0         345             364  0.470588      -19  Miami Dolphins        AFC      AFC East           true          5            3          0  5 - 3 - 0           false          3            6          0  3 - 6 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          6            6          0  6 - 6 - 0    0.5                  false              2                3              0  2 - 3 - 0                 0.4              2            false               10
  11 │ NYJ         5      12      0         338             404  0.294118      -66  New York Jets         AFC      AFC East           true          3            5          0  3 - 5 - 0           false          2            7          0  2 - 7 - 0                    true              2                4              0  2 - 4 - 0            0.333333          true          5            7          0  5 - 7 - 0    0.416667             false              0                5              0  0 - 5 - 0                 0.0              3            false               11
  12 │ NE          4      13      0         289             417  0.235294     -128  New England Patriots  AFC      AFC East           true          2            6          0  2 - 6 - 0           false          2            7          0  2 - 7 - 0                    true              2                4              0  2 - 4 - 0            0.333333          true          3            9          0  3 - 9 - 0    0.25                 false              1                4              0  1 - 4 - 0                 0.2              4            false               12
  13 │ LV          4      13      0         309             434  0.235294     -125  Las Vegas Raiders     AFC      AFC West           true          2            6          0  2 - 6 - 0           false          2            7          0  2 - 7 - 0                    true              0                6              0  0 - 6 - 0            0.0               true          3            9          0  3 - 9 - 0    0.25                 false              1                4              0  1 - 4 - 0                 0.2              4            false               13
  14 │ JAX         4      13      0         320             435  0.235294     -115  Jacksonville Jaguars  AFC      AFC South          true          3            5          0  3 - 5 - 0           false          1            8          0  1 - 8 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          4            8          0  4 - 8 - 0    0.333333             false              0                5              0  0 - 5 - 0                 0.0              3            false               14
  15 │ CLE         3      14      0         258             435  0.176471     -177  Cleveland Browns      AFC      AFC North          true          2            6          0  2 - 6 - 0           false          1            8          0  1 - 8 - 0                    true              2                4              0  2 - 4 - 0            0.333333          true          3            9          0  3 - 9 - 0    0.25                 false              0                5              0  0 - 5 - 0                 0.0              4            false               15
  16 │ TEN         3      14      0         311             460  0.176471     -149  Tennessee Titans      AFC      AFC South          true          1            7          0  1 - 7 - 0           false          2            7          0  2 - 7 - 0                    true              1                5              0  1 - 5 - 0            0.166667          true          3            9          0  3 - 9 - 0    0.25                 false              0                5              0  0 - 5 - 0                 0.0              4            false               16

This correctly assigns the playoff teams, but it requires the tiebreaker to handle the three 4-13 teams.

NFC Standings

subset(rank_df, :conf => (x -> x .== "NFC"))
16×42 DataFrame
 Row │ team    wins   losses  ties   points_for  points_against  pct       net_pts  name                   conf     division   is_home_home  wins_home  losses_home  ties_home  record_home  is_home_away  wins_away  losses_away  ties_away  record_away  is_division_division  wins_division  losses_division  ties_division  record_division  pct_division  is_conf_conf  wins_conf  losses_conf  ties_conf  record_conf  pct_conf   is_conf_non_conf  wins_non_conf  losses_non_conf  ties_non_conf  record_non_conf  pct_non_conf  division_rank  division_leader  conference_rank
     │ String  Int64  Int64   Int64  Int64       Int64           Float64   Int64    String?                String?  String?    Bool?         Int64?     Int64?       Int64?     String?      Bool?         Int64?     Int64?       Int64?     String?      Bool?                 Int64?         Int64?           Int64?         String?          Float64?      Bool?         Int64?     Int64?       Int64?     String?      Float64?   Bool?             Int64?         Int64?           Int64?         String?          Float64?      Int64          Bool             Int64
─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ DET        15       2      0         564             342  0.882353      222  Detroit Lions          NFC      NFC North          true          7            2          0  7 - 2 - 0           false          8            0          0  8 - 0 - 0                    true              6                0              0  6 - 0 - 0            1.0               true         11            1          0  11 - 1 - 0   0.916667              false              4                1              0  4 - 1 - 0                 0.8              1             true                1
   2 │ PHI        14       3      0         463             303  0.823529      160  Philadelphia Eagles    NFC      NFC East           true          8            1          0  8 - 1 - 0           false          6            2          0  6 - 2 - 0                    true              5                1              0  5 - 1 - 0            0.833333          true          9            3          0  9 - 3 - 0    0.75                  false              5                0              0  5 - 0 - 0                 1.0              1             true                2
   3 │ SEA        10       7      0         375             368  0.588235        7  Seattle Seahawks       NFC      NFC West           true          3            6          0  3 - 6 - 0           false          7            1          0  7 - 1 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          6            6          0  6 - 6 - 0    0.5                   false              4                1              0  4 - 1 - 0                 0.8              1             true                3
   4 │ TB         10       7      0         502             385  0.588235      117  Tampa Bay Buccaneers   NFC      NFC South          true          5            4          0  5 - 4 - 0           false          5            3          0  5 - 3 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          8            4          0  8 - 4 - 0    0.666667              false              2                3              0  2 - 3 - 0                 0.4              1             true                4
   5 │ MIN        14       3      0         432             332  0.823529      100  Minnesota Vikings      NFC      NFC North          true          8            1          0  8 - 1 - 0           false          6            2          0  6 - 2 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          9            3          0  9 - 3 - 0    0.75                  false              5                0              0  5 - 0 - 0                 1.0              2            false                5
   6 │ WAS        12       5      0         485             391  0.705882       94  Washington Commanders  NFC      NFC East           true          7            2          0  7 - 2 - 0           false          5            3          0  5 - 3 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          9            3          0  9 - 3 - 0    0.75                  false              3                2              0  3 - 2 - 0                 0.6              2            false                6
   7 │ GB         11       6      0         460             338  0.647059      122  Green Bay Packers      NFC      NFC North          true          6            3          0  6 - 3 - 0           false          5            3          0  5 - 3 - 0                    true              1                5              0  1 - 5 - 0            0.166667          true          6            6          0  6 - 6 - 0    0.5                   false              5                0              0  5 - 0 - 0                 1.0              3            false                7
   8 │ LA         10       7      0         367             386  0.588235      -19  Los Angeles Rams       NFC      NFC West           true          5            4          0  5 - 4 - 0           false          5            3          0  5 - 3 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          6            6          0  6 - 6 - 0    0.5                   false              4                1              0  4 - 1 - 0                 0.8              2            false                8
   9 │ ATL         8       9      0         389             423  0.470588      -34  Atlanta Falcons        NFC      NFC South          true          4            5          0  4 - 5 - 0           false          4            4          0  4 - 4 - 0                    true              4                2              0  4 - 2 - 0            0.666667          true          7            5          0  7 - 5 - 0    0.583333              false              1                4              0  1 - 4 - 0                 0.2              2            false                9
  10 │ ARI         8       9      0         400             379  0.470588       21  Arizona Cardinals      NFC      NFC West           true          6            3          0  6 - 3 - 0           false          2            6          0  2 - 6 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          4            8          0  4 - 8 - 0    0.333333              false              4                1              0  4 - 1 - 0                 0.8              3            false               10
  11 │ DAL         7      10      0         350             468  0.411765     -118  Dallas Cowboys         NFC      NFC East           true          2            7          0  2 - 7 - 0           false          5            3          0  5 - 3 - 0                    true              3                3              0  3 - 3 - 0            0.5               true          5            7          0  5 - 7 - 0    0.416667              false              2                3              0  2 - 3 - 0                 0.4              3            false               11
  12 │ SF          6      11      0         389             436  0.352941      -47  San Francisco 49ers    NFC      NFC West           true          4            5          0  4 - 5 - 0           false          2            6          0  2 - 6 - 0                    true              1                5              0  1 - 5 - 0            0.166667          true          4            8          0  4 - 8 - 0    0.333333              false              2                3              0  2 - 3 - 0                 0.4              4            false               12
  13 │ CHI         5      12      0         310             370  0.294118      -60  Chicago Bears          NFC      NFC North          true          4            5          0  4 - 5 - 0           false          1            7          0  1 - 7 - 0                    true              1                5              0  1 - 5 - 0            0.166667          true          3            9          0  3 - 9 - 0    0.25                  false              2                3              0  2 - 3 - 0                 0.4              4            false               13
  14 │ CAR         5      12      0         341             534  0.294118     -193  Carolina Panthers      NFC      NFC South          true          3            6          0  3 - 6 - 0           false          2            6          0  2 - 6 - 0                    true              2                4              0  2 - 4 - 0            0.333333          true          4            8          0  4 - 8 - 0    0.333333              false              1                4              0  1 - 4 - 0                 0.2              3            false               14
  15 │ NO          5      12      0         338             398  0.294118      -60  New Orleans Saints     NFC      NFC South          true          3            6          0  3 - 6 - 0           false          2            6          0  2 - 6 - 0                    true              2                4              0  2 - 4 - 0            0.333333          true          4            8          0  4 - 8 - 0    0.333333              false              1                4              0  1 - 4 - 0                 0.2              4            false               15
  16 │ NYG         3      14      0         273             415  0.176471     -142  New York Giants        NFC      NFC East           true          1            8          0  1 - 8 - 0           false          2            6          0  2 - 6 - 0                    true              0                6              0  0 - 6 - 0            0.0               true          1           11          0  1 - 11 - 0   0.0833333             false              2                3              0  2 - 3 - 0                 0.4              4            false               16

Because we haven't coded the tiebreaker, our NFC standings lead to incorrect NFC West division winner, putting SEA in the playoffs instead of LA.

Tiebreakers

The tiebreaking algorithm requires a number of pair-wise calculations between the tied teams. It would be challenging to do an exhaustive calculation of these values and conduct a multi-column sort.[4]

As it turns out, LA and SEA have the same head-to-head record, division record, common record, and conference record. The tie is only broken with strength of victory. Implementing this correctly would require quite a bit more code than shown here. This may be addressed in a future tutorial.


[1] Depending on what you expect to get from this tutorial, this may be a good use case for a temporary environment.
[2] This example requires using Statistics.
[3] For a detailed explanation of "wide" and "long" formats, DataFrames.jl has a section about reshaping data.
[4] Up to the coin-toss, the tiebreaking algorithm could probably be structured as an alternate ordering. That is way beyond the scope of this tutorial.

© 2025 KyleSJohnston
Last modified: December 04, 2025.
Website built with Franklin.jl, the pure-sm template, and the Julia programming language.