Report sections

Introduction

1.1 Problem Statement

This project examines whether better-performing NFL teams have higher home attendance and how attendance varies across teams and seasons. Understanding these patterns can help ticketing, marketing and business operations staff assess attendance alongside team performance.

1.2 Data and Methodology

We use NFL attendance, standings and game records for 2000-2019. Repeated season attendance totals are reduced to one row per team-season and joined to standings. We calculate ties as 16 minus wins minus losses and the proportion of games won as wins/16; ties receive no win credit. Game records provide a check on tied games. For the next stage, we plan to use grouped summaries, tables and plots to compare attendance across years, performance groups and franchises.

1.3 Proposed Analytical Approach

We plan to use exploratory data analysis to describe associations, rather than estimate causal effects or forecast attendance. Our research questions are:

  1. How has NFL attendance changed over time?
  2. Do better-performing teams tend to have higher home attendance?
  3. Is attendance different for teams that make the playoffs or win the Super Bowl?
  4. How much does attendance vary across NFL teams, and do stronger seasons draw more than a team’s usual attendance?

The planned outcome is season home attendance. We will compare performance using the proportion of games won, win groups, point differential and postseason outcomes. We also plan to compare each season’s attendance and wins with that franchise’s historical averages. Weekly attendance is inspected during cleaning but is not an outcome in the analysis.

1.4 Value of the Analysis

The planned analysis could provide historical benchmarks for comparing attendance with performance and identifying weaker seasons for further investigation. Differences between franchises may also point to the importance of venue and market conditions, although those factors are not measured here. The intended use is to guide further business investigation, rather than predict demand or evaluate marketing campaigns.

Packages Required

tidyverse provides data import, cleaning, joins, reshaping, grouped summaries and plots. knitr::kable() formats tables. Package-loading messages and warnings are suppressed.

library(tidyverse)

Data Preparation

3.1 Data Source

The three files come from the TidyTuesday NFL Attendance release, originally sourced from Pro Football Reference. They cover the 2000-2019 seasons and were released on 4 February 2020. The documentation does not specify the original collection dates.

3.2 Description of the Source Data

File Rows Variables Unit and contents
attendance.csv 10,846 8 Team-week: identifiers, weekly attendance and repeated season total, home and away attendance
standings.csv 638 15 Team-season: record, scoring, rating measures and postseason outcomes
games.csv 5,324 19 Individual game: teams, date/time, scores, yards and turnovers, including postseason games

These levels must be considered before joining. Missing weekly attendance and tie labels are assessed according to their meaning in section 3.3.

3.3 Data Importing and Cleaning

Import and assess the source files

We imported the original CSV files with read_csv() and checked dimensions, types and missing values. Detailed inspection output is hidden in the report.

attendance <- read_csv("attendance.csv")
standings <- read_csv("standings.csv")
games <- read_csv("games.csv")

Attendance has 638 missing weekly_attendance values: one per team-season, consistent with the non-playing week described in the dictionary. We retain these as missing; the season totals used in the analysis are complete. Standings has no missing values. The 5,314 missing tie labels in games indicate that no second tied team is listed; scores are available, so these rows are retained.

Create one observation per team-season

Season totals repeat across 17 weekly attendance rows. select() and distinct() retain each team-season once, preventing those totals from being counted repeatedly.

# Keep the season totals once for each team and year
season_attendance <- attendance |>
  select(team, team_name, year, total, home, away) |>
  distinct()

nrow(season_attendance)
## [1] 638
# Check whether any team-season appears more than once
attendance_duplicates <- season_attendance |>
  count(team, team_name, year) |>
  filter(n > 1)

nrow(attendance_duplicates)
## [1] 0
standings_duplicates <- standings |>
  count(team, team_name, year) |>
  filter(n > 1)

nrow(standings_duplicates)
## [1] 0

The prepared attendance table has 638 rows, with 0 duplicated team-city, nickname and year keys. Standings has 0 duplicated keys. City is retained for the join; planned franchise summaries will group by nickname across city changes.

Join attendance and season performance

We check unmatched records with anti_join(), then add standings using left_join() on city, nickname and year. The left join retains all prepared attendance rows.

# Find attendance rows with no matching standings row
unmatched_standings <- season_attendance |>
  anti_join(standings, by = c("team", "team_name", "year"))

# Add performance variables to the attendance data
nfl_data <- season_attendance |>
  left_join(standings, by = c("team", "team_name", "year"))

All attendance records match (0 unmatched), and the join retains 638 rows.

Calculate ties and the proportion of games won

Standings has no ties column. Each team-season in these files has 16 regular-season games, so we calculate ties as 16 minus wins minus losses and win_pct as wins/16. This formula applies to the seasons covered here.

# Find the number of tied games in each team's season record
nfl_data <- nfl_data |>
  mutate(ties = 16 - wins - loss)

# Calculate the proportion of games actually won
nfl_data <- nfl_data |>
  mutate(win_pct = wins / 16)

Ties receive no win credit: an 8-7-1 record means 8/16 = 50% of games won. We check the tie total against equal-score games in regular-season weeks 1-17; each tied game involves two teams.

# Keep weeks 1-17 and exclude playoff games
regular_season_games <- games |>
  filter(week %in% as.character(1:17))

# Identify games where both teams scored the same number of points
tied_games <- regular_season_games |>
  filter(pts_win == pts_loss)

nrow(tied_games)
## [1] 10
sum(nfl_data$ties)
## [1] 20

The 10 tied games correspond to 20 team-season ties. Individual game rows remain separate to avoid repeating season totals.

Validate and retain unusual observations

final_duplicates <- nfl_data |>
  count(team, team_name, year) |>
  filter(n > 1)

# Look for impossible ties or winning percentages
record_errors <- nfl_data |>
  filter(ties < 0 | ties > 16 | win_pct < 0 | win_pct > 1)

attendance_errors <- nfl_data |>
  filter(home < 0 | away < 0 | total != home + away)

bye_check <- attendance |>
  group_by(team, team_name, year) |>
  summarise(
    missing_weeks = sum(is.na(weekly_attendance)),
    .groups = "drop"
  )

# Identify team-seasons without exactly one missing week
bye_errors <- bye_check |>
  filter(missing_weeks !=1)
Final check Result
Team-season observations 638
Duplicate team-season keys 0
Missing values in the final table 0
Invalid ties or win proportions 0
Negative attendance or total unequal to home plus away 0
Team-seasons without exactly one missing week 0

The validation checks found no invalid attendance or performance values. Although the 2017 Los Angeles Chargers recorded an unusually low home attendance of 202,687, we retained this observation because there was no evidence of a data error. Stadium capacity data was unavailable, so we could not fully explain the difference.

3.4 Final Clean Dataset

The final table has 638 team-seasons and 20 variables for 2000-2019. It retains season attendance and standings variables and adds ties and the proportion of games won. A six-row preview follows.

nfl_data |>
  arrange(year, team, team_name) |>
  select(team, team_name, year, home, wins, loss, ties, win_pct) |>
  head(6) |>
  knitr::kable(
    digits = 2,
    caption = "A condensed preview of the cleaned team-season data."
  )
A condensed preview of the cleaned team-season data.
team team_name year home wins loss ties win_pct
Arizona Cardinals 2000 387475 3 13 0 0.19
Atlanta Falcons 2000 422814 4 12 0 0.25
Baltimore Ravens 2000 551695 12 4 0 0.75
Buffalo Bills 2000 560695 8 8 0 0.50
Carolina Panthers 2000 583489 7 9 0 0.44
Chicago Bears 2000 535552 5 11 0 0.31

3.5 Variables of Interest

Variable Meaning and role
home Primary outcome: a team’s season home attendance; counts visits rather than unique people
total, away Season attendance across all games and at away games; retained for context
wins, loss, ties Regular-season record; ties are calculated as 16 minus wins minus losses
win_pct Wins/16: the proportion of games actually won; ties receive no win credit
points_differential Points scored minus points allowed; a second performance measure
playoffs, sb_winner End-of-season playoff and championship categories
team, team_name, year Historical city, team nickname and season identifiers
# Put the selected variables into a long table, as in Week 6
summary_long <- nfl_data |>
  select(home, wins, win_pct, points_differential)

summary_long <- summary_long |>
  pivot_longer(home:points_differential,
               names_to = "Variable", values_to = "value")

# Calculate a few descriptive statistics for each variable
variable_summary <- summary_long |>
  group_by(Variable) |>
  summarise(
    Minimum = min(value),
    Median = median(value),
    Mean = mean(value),
    Maximum = max(value)
  ) |>
  ungroup()

knitr::kable(variable_summary, digits = 3,
             caption = "Selected numeric variables across the 638 team-seasons.")
Selected numeric variables across the 638 team-seasons.
Variable Minimum Median Mean Maximum
home 202687 543185.0 540455.013 741775
points_differential -261 1.5 0.000 315
win_pct 0 0.5 0.499 1
wins 0 8.0 7.984 16

Mean season home attendance is 540,455 visits; the median is 543,185. There are 240 playoff team-seasons, including 20 champions. Postseason outcomes describe end-of-season results.

Proposed Exploratory Data Analysis

4.1 How We Plan to Uncover New Information

We plan to examine the prepared data from several angles rather than only report overall attendance. First, we will group by year and calculate mean home attendance per team-season. Using a mean will help compare seasons with different numbers of teams. Next, we will compare attendance across teams with 0-5, 6-9 and 10-16 wins, and across three postseason groups: teams missing the playoffs, other playoff teams and Super Bowl winners. Each group will be summarised using its size, mean and median attendance. We have already joined attendance to standings and created wins/16. For the within-franchise comparison, we will calculate each franchise’s average attendance and wins, join those averages to its season records, and compare each season with its averages.

4.2 Planned Plots and Tables

A line plot of mean home attendance by year will show changes over time. Scatterplots will compare attendance with the proportion of games won and point differential, showing whether teams with similar performance have different crowds. A bar chart will compare mean attendance across the three postseason groups. An ordered point plot will compare franchise averages, while a further bar chart will show attendance differences from each franchise’s own average in stronger and weaker seasons.

Small summary tables will accompany the comparisons, reporting group sizes and attendance averages. Medians will help show whether unusually large or small crowds affect the means. Plot titles, axis labels and units will explain what each comparison represents.

4.3 What We Still Need to Learn

We do not yet know how to separate the influence of performance from stadium capacity, ticket prices and market differences. Our grouped comparisons can describe associations, but they cannot show how much attendance changes because of performance alone. Some of those questions would also require data we do not currently have.

We need more practice interpreting seasons relative to a franchise’s own average, particularly when a team has changed cities or stadiums. Before drawing conclusions, we will check those comparisons carefully and explain the remaining limitations.

4.4 Plans for Machine Learning

We do not currently plan to use machine learning, regression or clustering. Our questions focus on describing attendance patterns, so the grouped summaries, joins and plots covered in class are appropriate for the planned analysis. We will not claim that these comparisons predict attendance or prove that winning causes larger crowds. Any later decision to add a model would require further learning and a clear reason for using it.