Project 2 — Data Tidying: NFL Touchdown Scoring by Type

Author

Kevin Villa

Published

October 5, 2026

4. Data Source

This dataset is the official NFL.com team scoring table for the 2026 regular season, pulled from https://www.nfl.com/stats/team-stats/offense/scoring/2026/reg/all. It’s the same kind of dataset Daanish picked for the Week 5 Discussion 5A post: a team by team breakdown of how each team’s touchdowns were scored (rushing vs. receiving vs. two-point conversions), with the touchdown type spread across separate columns instead of stored as a variable.

The raw file, exactly as scraped from the site with no tidying applied, is committed here:

https://raw.githubusercontent.com/nowhyporque/data607DataAcquisitionAndManagement/refs/heads/main/project%202/nfl_scoring_raw.csv

5. Data Structure Before Tidying

Code
nfl_raw <- read_csv("https://raw.githubusercontent.com/nowhyporque/data607DataAcquisitionAndManagement/refs/heads/main/project%202/nfl_scoring_raw.csv")

glimpse(nfl_raw)
Rows: 32
Columns: 5
$ Team   <chr> "Bills", "49ers", "Bears", "Ravens", "Seahawks", "Lions", "Chie…
$ Rsh_TD <dbl> 10, 5, 9, 8, 3, 5, 5, 6, 7, 4, 3, 4, 4, 1, 4, 2, 2, 1, 2, 3, 2,…
$ Rec_TD <dbl> 6, 11, 4, 6, 10, 9, 9, 8, 3, 9, 6, 5, 8, 11, 6, 8, 7, 8, 7, 5, …
$ Tot_TD <dbl> 16, 16, 13, 14, 13, 14, 14, 14, 10, 14, 9, 9, 12, 12, 10, 10, 1…
$ Two_PT <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, …
Code
kable(head(nfl_raw, 8))
Team Rsh_TD Rec_TD Tot_TD Two_PT
Bills 10 6 16 0
49ers 5 11 16 0
Bears 9 4 13 0
Ravens 8 6 14 0
Seahawks 3 10 13 0
Lions 5 9 14 0
Chiefs 5 9 14 0
Cowboys 6 8 14 1

This is wide and untidy for the same reason as the example in the assignment: touchdown type is a variable, but instead of living in its own column, it’s spread across the column headers Rsh_TD, Rec_TD, and Two_PT. There are 32 rows (one per team) and 5 columns. Tot_TD is a pre-computed total rather than a raw observation, so it isn’t itself a touchdown type, it gets handled separately in the transformation step below.

6. Transformation Steps

Code
nfl_long <- nfl_raw %>%
  select(-Tot_TD) %>%
  pivot_longer(
    cols = c(Rsh_TD, Rec_TD, Two_PT),
    names_to = "td_type",
    values_to = "count"
  ) %>%

  mutate(
    td_type = case_when(
      td_type == "Rsh_TD" ~ "rushing",
      td_type == "Rec_TD" ~ "receiving",
      td_type == "Two_PT" ~ "two_point_conversion"
    ),
    team = str_to_lower(Team)
  ) %>%
  rename(team_name = Team) %>%
  select(team_name, team, td_type, count)

nfl_long <- nfl_long %>%
  mutate(count = replace_na(count, 0))

kable(head(nfl_long, 9))
team_name team td_type count
Bills bills rushing 10
Bills bills receiving 6
Bills bills two_point_conversion 0
49ers 49ers rushing 5
49ers 49ers receiving 11
49ers 49ers two_point_conversion 0
Bears bears rushing 9
Bears bears receiving 4
Bears bears two_point_conversion 0

7. Analytical Methods

The business question from the discussion post was to compare each team’s scoring style, the ratio of touchdowns by type, not just raw counts. I calculate each team’s rushing and receiving touchdowns as a percentage of total touchdowns (excluding two point conversions, which aren’t full touchdowns), then rank teams by how rush heavy or pass-heavy their scoring is.

Code
scoring_style <- nfl_long %>%
  filter(td_type %in% c("rushing", "receiving")) %>%
  group_by(team_name) %>%
  mutate(total_td = sum(count)) %>%
  ungroup() %>%
  filter(total_td > 0) %>%
  mutate(pct_of_td = round(count / total_td * 100, 1))

rushing_share <- scoring_style %>%
  filter(td_type == "rushing") %>%
  select(team_name, total_td, pct_rushing = pct_of_td) %>%
  arrange(desc(pct_rushing))

kable(head(rushing_share, 5), caption = "Most run-heavy scoring teams")
Most run-heavy scoring teams
team_name total_td pct_rushing
Dolphins 4 75.0
Colts 10 70.0
Bears 13 69.2
Bills 16 62.5
Falcons 5 60.0
Code
kable(tail(rushing_share, 5), caption = "Most pass-heavy scoring teams")
Most pass-heavy scoring teams
team_name total_td pct_rushing
Giants 7 14.3
Commanders 9 11.1
Raiders 12 8.3
Packers 8 0.0
Steelers 7 0.0
Code
ggplot(scoring_style, aes(x = reorder(team_name, pct_of_td), y = pct_of_td, fill = td_type)) +
  geom_bar(stat = "identity", position = "stack") +
  coord_flip() +
  labs(title = "Share of Touchdowns by Type, All 32 Teams",
       x = "Team", y = "Percent of Touchdowns", fill = "TD Type") +
  theme_minimal(base_size = 8)

The league as a whole leans pass heavy: 62.2% of all touchdowns this season came through the air versus 35.0% on the ground (the remaining share is two point conversions). But individual teams vary a lot. The Dolphins are the most run heavy scoring team, with 75% of their touchdowns coming on the ground, while the Packers are the opposite extreme, 100% of their touchdowns this season have been through the air, with zero rushing touchdowns.

8. Conclusions

Reshaping this table from wide to long made it possible to calculate a real rate (percent of touchdowns by type) instead of just comparing raw counts, which would have been misleading since teams have scored different total numbers of touchdowns. The spread between the most run heavy team (Dolphins, 75%) and the most pass heavy team (Packers, 100% through the air) shows real differences in offensive identity across the league, not just differences in how many points teams have scored overall. A natural next step would be to track this breakdown week by week to see whether a team’s scoring style shifts over the course of the season, for example if a run heavy team becomes more pass reliant once trailing in the standings.