Overview

This report replicates the Excel EDA pipeline for FIFA Men’s World Cup match attendance data spanning all 23 tournaments (1930–2026). The analysis mirrors three steps performed in Excel:

  1. EDADATA_ATTEND — Select 8 key columns from raw match data
  2. ATTENDENCE_WORK — Sort matches by attendance and build a per-tournament summary table
  3. Chart — Grouped bar chart showing the lowest, average, and highest attendance per tournament

Setup

library(tidyverse)
library(scales)

Step 1 — Load and Select Key Columns

The raw dataset covers all World Cup matches from 1930 through 2022. Eight columns relevant to the attendance analysis are selected, replicating the EDADATA_ATTEND sheet.

df_raw <- read_csv("matches_1930_2022.csv", show_col_types = FALSE)

df_eda <- df_raw |>
  select(Host, Year, Attendance, home_team, away_team, Score, Round, Notes)

The raw data contains 964 matches across 44 columns. After selecting the 8 key columns, the working dataset has 964 rows.

glimpse(df_eda)
## Rows: 964
## Columns: 8
## $ Host       <chr> "Qatar", "Qatar", "Qatar", "Qatar", "Qatar", "Qatar", "Qata…
## $ Year       <dbl> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022,…
## $ Attendance <dbl> 88966, 44137, 68294, 88966, 44198, 68895, 43893, 88235, 446…
## $ home_team  <chr> "Argentina", "Croatia", "France", "Argentina", "Morocco", "…
## $ away_team  <chr> "France", "Morocco", "Morocco", "Croatia", "Portugal", "Fra…
## $ Score      <chr> "(4) 3–3 (2)", "2–1", "2–0", "3–0", "1–0", "1–2", "(4) 1–1 …
## $ Round      <chr> "Final", "Third-place match", "Semi-finals", "Semi-finals",…
## $ Notes      <chr> "Argentina won on penalty kicks following extra time", NA, …

Step 2 — Sort by Attendance

Matches are sorted in ascending order by attendance, replicating the left-side sort in the ATTENDENCE_WORK sheet. This surfaces the five lowest and five highest attended individual matches in the dataset.

df_sorted <- df_eda |>
  arrange(Attendance)

Five Lowest-Attended Matches

head(df_sorted |> select(Year, Host, Round, home_team, away_team, Attendance), 5)
## # A tibble: 5 × 6
##    Year Host    Round                home_team   away_team Attendance
##   <dbl> <chr>   <chr>                <chr>       <chr>          <dbl>
## 1  1930 Uruguay Group stage          Chile       France          2000
## 2  1930 Uruguay Group stage          Romania     Peru            2549
## 3  1958 Sweden  Group stage play-off Wales       Hungary         2823
## 4  1934 Italy   Quarter-finals       Germany     Sweden          3000
## 5  1950 Brazil  Group stage          Switzerland Mexico          3580

Five Highest-Attended Matches

tail(df_sorted |> select(Year, Host, Round, home_team, away_team, Attendance), 5)
## # A tibble: 5 × 6
##    Year Host   Round       home_team away_team  Attendance
##   <dbl> <chr>  <chr>       <chr>     <chr>           <dbl>
## 1  1986 Mexico Group stage Mexico    Paraguay       114600
## 2  1950 Brazil Final stage Brazil    Sweden         138886
## 3  1950 Brazil Group stage Brazil    Yugoslavia     142429
## 4  1950 Brazil Final stage Brazil    Spain          152772
## 5  1950 Brazil Final stage Uruguay   Brazil         173850

The lowest-attended match on record was from the inaugural 1930 tournament in Uruguay, while the top five are all from the 1950 Brazil tournament — where the Maracanã stadium regularly drew crowds well over 100,000.


Step 3 — Per-Tournament Summary Table

For each of the 23 tournaments, the lowest, average, and highest single-match attendance values are computed. The 2026 North America tournament row is added manually using data from the updated source file (matches_1930_2022_JD).

attendance_summary <- df_eda |>
  group_by(Year, Host) |>
  summarise(
    High    = max(Attendance,  na.rm = TRUE),
    Average = mean(Attendance, na.rm = TRUE),
    Low     = min(Attendance,  na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(Year)

# Add 2026 (North America) row manually from matches_1930_2022_JD source
attendance_summary <- attendance_summary |>
  add_row(Year = 2026, Host = "North America",
          High = 80824, Average = 65483, Low = 43000)

attendance_summary |>
  mutate(Average = round(Average, 0)) |>
  knitr::kable(
    col.names = c("Year", "Host", "Highest", "Average", "Lowest"),
    format.args = list(big.mark = ","),
    align = "llrrr"
  )
Year Host Highest Average Lowest
1,930 Uruguay 79,867 32,808 2,000
1,934 Italy 55,000 21,353 3,000
1,938 France 58,455 20,872 7,000
1,950 Brazil 173,850 47,511 3,580
1,954 Switzerland 62,500 29,562 4,000
1,958 Sweden 50,928 23,423 2,823
1,962 Chile 76,594 27,912 5,700
1,966 England 98,270 48,848 13,792
1,970 Mexico 108,192 50,124 9,624
1,974 Germany 81,100 49,099 13,400
1,978 Argentina 71,712 40,679 7,938
1,982 Spain 95,000 40,572 11,000
1,986 Mexico 114,600 46,039 13,800
1,990 Italy 74,765 48,389 27,833
1,994 United States 94,194 68,991 44,132
1,998 France 80,000 45,367 27,650
2,002 Korea Republic, Japan 69,029 42,271 24,000
2,006 Germany 72,000 52,384 37,216
2,010 South Africa 84,490 49,670 23,871
2,014 Brazil 74,738 53,592 37,603
2,018 Russia 78,011 47,371 27,015
2,022 Qatar 88,966 53,191 39,089
2,026 North America 80,824 65,483 43,000

Chart — Grouped Bar Chart

The summary table is reshaped to long format and plotted as a grouped bar chart, with each tournament on the x-axis and bars for the lowest (red), average (green), and highest (blue) per-match attendance.

attendance_long <- attendance_summary |>
  mutate(label = paste0(Year, "\n(", Host, ")")) |>
  pivot_longer(
    cols      = c(Low, Average, High),
    names_to  = "Metric",
    values_to = "Attendance"
  ) |>
  mutate(Metric = factor(Metric, levels = c("Low", "Average", "High")))

levels(attendance_long$Metric) <- c("Lowest Attendance",
                                    "Average Attendance",
                                    "Highest Attendance")

x_order <- unique(attendance_long$label)

ggplot(attendance_long,
       aes(x    = factor(label, levels = x_order),
           y    = Attendance,
           fill = Metric)) +
  geom_col(position = position_dodge(width = 0.8),
           width     = 0.7,
           alpha     = 0.85) +
  scale_fill_manual(values = c(
    "Lowest Attendance"  = "#C00000",
    "Average Attendance" = "#70AD47",
    "Highest Attendance" = "#4472C4"
  )) +
  scale_y_continuous(
    labels = comma,
    limits = c(0, 185000),
    breaks = seq(0, 175000, by = 25000)
  ) +
  labs(
    title    = "Men's World Cup Match Attendance by Tournament (1930\u20132026)",
    subtitle = "Lowest, Average, and Highest per Tournament",
    x        = "World Cup Year (Host)",
    y        = "Attendance",
    fill     = NULL
  ) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title           = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle        = element_text(hjust = 0.5),
    axis.text.x          = element_text(angle = 45, hjust = 1, size = 7),
    legend.position      = c(0.98, 0.98),
    legend.justification = c(1, 1),
    legend.background    = element_rect(fill = "white", color = "grey80", linewidth = 0.4),
    panel.grid.major.x   = element_blank()
  )

Grouped bar chart of Men's World Cup match attendance by tournament from 1930 to 2026, showing lowest, average, and highest attendance per tournament.

Key Observations

  • The 1950 Brazil tournament produced the highest single-match attendance ever recorded (173,850 at the Maracanã), a record that still stands today.
  • 2026 North America is on track to be one of the highest-averaging tournaments in history (~65,483 per match), surpassing every tournament except 1994 USA (~68,991).
  • Average per-match attendance has grown substantially since the low point in the early tournaments, reflecting both larger stadiums and global growth of the sport.