Growing up, I have always been interested in baseball. I played my entire childhood, and when I wasn’t on the field playing or practicing, I was consuming it in another way. Whether it was reading about it, watching it, or even playing various baseball video games, baseball was always a big part of my life. Although my playing days are over, it is still a huge passion of mine and was an easy choice as the basis for this project. I knew I wanted to find a dataset that was baseball-related and would give me the opportunity to look at some interesting things. Baseball is a sport filled with data, and in recent years, analytics have taken a more prominent role in team structure and overall success. The Lahman dataset is a huge collection of historical baseball statistics that provides information on everything from players and team statistics to salaries and stadium attendance. I thought this would be a great data set to use for this project. I knew I wanted to be able to look at a variety of different areas, so the flexibility that Lahman’s data set provided made it an easy selection.
Everyone loves the home run. There is nothing better than seeing the ball fly over the fence and watching your favorite player jog around the bases. Fans also love seeing their team win, especially when they are winning consistently. This was the thought process behind starting my project. I wanted to see to what extent home runs and wins truly drive stadium attendance and whether teams that hit more home runs and win more games actually bring more fans to the ballpark. I specifically chose to look at the decade from 1995 to 2004 for a few reasons. Home runs have become a major part of the modern game, so I wanted to look back at a different period of baseball and see how these trends compared. This decade also includes the height of the steroid era, when players like Mark McGwire and Sammy Sosa were putting up some of the most memorable home run numbers in baseball history. Looking at this period gave me an interesting opportunity to explore whether the excitement around home runs and team success translated into more fans coming to the stadium.
The results of the descriptive statistics show pretty much what I expected, a wide range of values across all three areas. A standard deviation of 35 for home runs is quite large, but when you think about it in the context of MLB, it makes more sense. There are teams that are built around hitting for power, with some of the league’s best sluggers throughout their lineup. At the same time, there are plenty of teams that can have just as much success without hitting as many home runs. We can see this difference when looking at wins as well, with the range going from 43 all the way to 116 wins. This also makes sense because there are 30 MLB teams, meaning the teams at the top and bottom of the standings can have a pretty large difference in wins. Attendance also showed a wide range, although it was the smallest of the three. This shows that while there are differences in how many fans teams bring to the stadium, the gap in attendance was not quite as large as the differences we saw in home runs and wins. Overall, the descriptive statistics show that there was a lot of variation between teams during this decade, which makes these variables interesting to explore further.
library(Lahman)
library(dplyr)
library(ggplot2)
library(scales)
library(ggrepel)
library(RColorBrewer)
library(plotly)
teams_9504 <- Teams |>
filter(yearID >= 1995, yearID <= 2004, !is.na(attendance), Ghome > 0) |>
mutate(avg_att = attendance / Ghome / 1000) |>
as.data.frame()
dim(teams_9504)
## [1] 294 49
summary(teams_9504[, c("HR", "W", "avg_att")])
## HR W avg_att
## Min. : 94.0 Min. : 43.00 Min. : 7.935
## 1st Qu.:147.2 1st Qu.: 70.00 1st Qu.:20.904
## Median :169.0 Median : 79.00 Median :28.018
## Mean :174.0 Mean : 80.08 Mean :28.297
## 3rd Qu.:199.0 3rd Qu.: 89.75 3rd Qu.:35.952
## Max. :264.0 Max. :116.00 Max. :48.037
sapply(teams_9504[, c("HR", "W", "avg_att")], sd)
## HR W avg_att
## 35.04363 12.32595 9.30427
The first chart I created is a basic line chart to see the overall trend in stadium attendance over the decade in question. I used this chart to give me a little bit of context as I dig deeper into the data set and construct more telling visualizations. Overall, this graph shows a clear steady incline over the first 4 years, before a decline from 2001-2003. This can be explained for a variety of different reasons, there was fear over a player strike in 2002 that was halted just hours before the deadline. In addition, rising geopolitical tensions following the 2001 attacks must have a role. This chart provided a good introduction to encourage further discovery.
library(ggplot2)
library(dplyr)
library(ggrepel)
library(Lahman)
library(plotly)
league_att <- Teams |>
filter(yearID >= 1995, yearID <= 2004, !is.na(attendance), Ghome > 0) |>
group_by(yearID) |>
summarise(avg_att = sum(attendance) / sum(Ghome) / 1000, .groups = "drop") |>
as.data.frame()
hi_lo <- league_att |> filter(avg_att == max(avg_att) | avg_att == min(avg_att))
att_trend <- ggplot(league_att, aes(x = yearID, y = avg_att,
text = paste0("Year: ", yearID, "<br>",
"Attendance: ", round(avg_att, 1), "K"))) +
geom_line(color = "black", linewidth = 1, aes(group = 1)) +
geom_point(shape = 21, size = 4, color = "red", fill = "white") +
geom_point(data = hi_lo, shape = 21, size = 4, fill = "red", color = "red") +
geom_label(data = hi_lo, aes(label = round(avg_att, 1)),
vjust = -0.8, color = "grey50") +
scale_x_continuous(breaks = 1995:2004, minor_breaks = NULL) +
labs(title = "Average MLB Home Attendance Per Game, 1995-2004",
x = "Season", y = "Average Attendance (Thousands)") +
theme_light() +
theme(plot.title = element_text(hjust = 0.5))
ggplotly(att_trend, tooltip = "text")
This horizontal bar chart displays the top 10 teams that hit the most home runs throughout the decade I am studying, along with how many games each of those teams won. I made this chart to give me an idea of what I should expect to see in my further visualizations. These are the 10 teams I would expect to see near the top in attendance numbers when I look into that later on. I wanted to use this chart as a kind of check to see if my results later on match what I would expect based on team performance. If some of these teams also have high attendance numbers, it could support the idea that winning and hitting home runs are connected to how many fans come out to the stadium. On the other hand, if some of these teams have lower attendance despite their success, that could show that there are other factors affecting attendance. Overall, this chart gives me a good starting point for comparing team performance with attendance throughout the rest of my project.
library(ggplot2)
library(dplyr)
library(ggrepel)
library(Lahman)
library(plotly)
top_10_hr_wins <- Teams |>
dplyr::filter(yearID >= 1995 & yearID <= 2004) |>
dplyr::group_by(name) |>
dplyr::summarise(
total_HR = sum(HR, na.rm = TRUE),
total_W = sum(W, na.rm = TRUE)
) |>
dplyr::slice_max(order_by = total_HR, n = 10)
top10 <- ggplot(top_10_hr_wins, aes(x = total_HR, y = reorder(name, total_HR),
fill = total_W,
text = paste0("Team: ", name, "<br>",
"Home Runs: ", total_HR, "<br>",
"Wins: ", total_W))) +
geom_col(width = 0.5) +
geom_text(aes(label = paste0(total_HR, " HRs (", total_W, " Wins)")),
hjust = -0.1, size = 2.8, color = "black") +
scale_fill_gradient(low = "gray80", high = "#0A2540", name = "Total Wins") +
labs(
title = "Top 10 Home Run Teams and Their Total Wins, 1995-2004",
x = "Total Team Home Runs",
y = "Team Name"
) +
theme_minimal() +
expand_limits(x = max(top_10_hr_wins$total_HR) * 1.2) +
theme(
plot.margin = margin(t = 15, r = 25, b = 15, l = 30),
axis.text.y = element_text(size = 7, color = "black"),
axis.text.x = element_text(size = 7, color = "black"),
axis.title = element_text(size = 11, face = "bold"),
plot.title = element_text(size = 15, vjust = 0.5, face = "bold"),
legend.title = element_text(size = 15, face = "bold"),
legend.text = element_text(size = 12)
)
ggplotly(top10, tooltip = "text")
This chart takes the top 10 home run teams from the previous visualization and breaks down their total attendance over the entire decade. Instead of just looking at rankings, I wanted to see how much attendance each team actually accumulated and whether there were any teams that stood out. The stacked bars also show how each team’s attendance was spread across the 10 seasons, which gives a better picture of their consistency over time. From the results it is easy to see there are other factors that drive stadium attendance besides simply just if your offense hits for power and scores a lot. This is a conclusion that is reasonable to expect, however certainly interesting.
library(ggplot2)
library(dplyr)
library(ggrepel)
library(Lahman)
library(scales)
library(plotly)
att_df <- Teams |>
filter(yearID >= 1995, yearID <= 2004,
name %in% top_10_hr_wins$name, !is.na(attendance)) |>
select(name, yearID, attendance) |>
mutate(att_m = attendance / 1e6) |>
as.data.frame()
att_df$yearID <- as.factor(att_df$yearID)
agg_tot <- att_df |>
group_by(name) |>
summarise(tot = sum(att_m), .groups = 'keep') |>
data.frame()
stack_att <- ggplot(att_df, aes(x = reorder(name, att_m, sum), y = att_m, fill = yearID)) +
geom_bar(aes(text = paste0("Team: ", name, "<br>",
"Season: ", yearID, "<br>",
"Attendance: ", round(att_m, 2), "M")),
stat = "identity", position = position_stack(reverse = TRUE)) +
coord_flip() +
labs(title = "Total Attendance for the Top 10 Home Run Teams",
x = "", y = "Total Attendance (Millions)", fill = "Season") +
theme_light() +
theme(plot.title = element_text(hjust = 0.5)) +
scale_fill_manual(
values = c(
"1995" = "#D9D9D9",
"1996" = "#A6A6A6",
"1997" = "#B3CDE3",
"1998" = "#6497B1",
"1999" = "#005B96",
"2000" = "#03396C",
"2001" = "#011F4B",
"2002" = "#7B8FA1",
"2003" = "#4F6D7A",
"2004" = "#0A2540"
),
guide = guide_legend(reverse = TRUE)
) +
geom_text(data = agg_tot, aes(x = name, y = tot, label = round(tot, 1), fill = NULL),
hjust = -0.1, size = 4) +
scale_y_continuous(labels = comma, limits = c(0, max(agg_tot$tot) * 1.1))
ggplotly(stack_att, tooltip = "text")
I use this chart to make a simple comparison between the top 10 home run teams, the top 10 winning teams, and the rest of the league. I like this chart because it is easy to read and quickly shows the differences in attendance between the three groups. The biggest thing that stood out to me was that the winning teams had higher attendance than the home run teams, while the rest of the league was lower. This suggests that winning may have a stronger relationship with attendance than home runs during this period. It is not at all surprising to see the “Other” category had the smallest attendance.
library(ggplot2)
library(dplyr)
library(ggrepel)
library(Lahman)
library(scales)
library(plotly)
team_att <- Teams |>
filter(yearID >= 1995, yearID <= 2004, !is.na(attendance), Ghome > 0) |>
group_by(name) |>
summarise(total_HR = sum(HR),
total_W = sum(W),
att = sum(attendance) / sum(Ghome) / 1000,
.groups = 'keep') |>
as.data.frame()
top_hr <- team_att$name[order(-team_att$total_HR)][1:10]
top_w <- team_att$name[order(-team_att$total_W)][1:10]
others <- team_att[!(team_att$name %in% c(top_hr, top_w)), ]
group_att <- data.frame(
group = c("Top 10 Home Run Teams", "Top 10 Win Teams", "All Other Teams"),
avg_att = round(c(mean(team_att$att[team_att$name %in% top_hr]),
mean(team_att$att[team_att$name %in% top_w]),
mean(others$att)), 1)
)
group_att$group <- factor(group_att$group,
levels = c("Top 10 Home Run Teams", "Top 10 Win Teams", "All Other Teams"))
group_bars <- ggplot(group_att, aes(x = group, y = avg_att, fill = group)) +
geom_bar(aes(text = paste0("Group: ", group, "<br>",
"Avg Attendance: ", avg_att, "K")),
colour = "black", stat = "identity") +
geom_text(aes(label = avg_att), vjust = -0.5, size = 5) +
scale_fill_manual(values = c("Top 10 Home Run Teams" = "#0A2540",
"Top 10 Win Teams" = "#6497B1",
"All Other Teams" = "#D9D9D9")) +
scale_y_continuous(labels = comma, limits = c(0, max(group_att$avg_att) * 1.15)) +
labs(title = "Average Home Attendance: Power Teams vs. Winning Teams vs. Everyone Else",
x = "", y = "Average Attendance Per Game (Thousands)") +
theme_light() +
theme(plot.title = element_text(hjust = 0.5),
legend.position = "none")
ggplotly(group_bars, tooltip = "text")
For this chart, I divided teams into high-win and low-win groups based on the median number of wins for each season. Teams that were at or above the median were considered high-win teams, while teams below the median were considered low-win teams. I use this chart to compare the average attendance between these two groups from 1995 to 2004. The biggest thing that stood out to me was that the high-win teams had higher average attendance than the low-win teams throughout the entire decade. There were some years where the gap was larger than others, but the high-win group never dipped below the low win group for even one year. This supports the idea that winning has a noticeable relationship with how many fans come to the stadium.
library(ggplot2)
library(dplyr)
library(ggrepel)
library(Lahman)
library(scales)
library(plotly)
win_tiers <- Teams |>
filter(yearID >= 1995, yearID <= 2004, !is.na(attendance), Ghome > 0) |>
group_by(yearID) |>
mutate(W_group = ifelse(W >= median(W), "High Wins", "Low Wins")) |>
group_by(yearID, W_group) |>
summarise(
avg_att = sum(attendance) / sum(Ghome) / 1000,
.groups = "drop"
) |>
as.data.frame()
tier_lines <- ggplot(
win_tiers,
aes(x = yearID, y = avg_att, group = W_group)
) +
geom_line(
aes(color = W_group),
linewidth = 3
) +
geom_point(
aes(text = paste0(
"Season: ", yearID, "<br>",
"Win Group: ", W_group, "<br>",
"Average Attendance: ", round(avg_att, 1), "K"
)),
shape = 21,
size = 5,
color = "black",
fill = "white"
) +
scale_x_continuous(
breaks = sort(unique(win_tiers$yearID)),
minor_breaks = NULL
) +
scale_color_manual(
values = c(
"Low Wins" = "#bcccdc",
"High Wins" = "#0A2540"
),
name = "Win Group"
) +
scale_y_continuous(
labels = comma
) +
labs(
title = "Average Home Attendance By Win Group, 1995-2004",
x = "Season",
y = "Average Attendance (Thousands)"
) +
theme_light() +
theme(
plot.title = element_text(hjust = 0.5)
)
ggplotly(tier_lines, tooltip = "text")
Before doing a deep dive into the Lahman dataset, I set out to decide whether home runs and wins drive stadium attendance or whether other factors matter more. There are many reasons a fan might buy a ticket. Avid fans might go to see a new stadium or to watch their favorite player on a different team, while casual fans might go simply because they are on vacation or have a free afternoon. I wanted to see to what extent home runs and wins influenced that decision. After looking at the decade from 1995 to 2004, I found a relationship between both factors, and I was not surprised that winning was the stronger one. The top 10 winning teams had a higher average attendance than the top 10 home run teams, and both groups drew bigger crowds than the rest of the league. The final chart backed this up, since the high-win teams had a higher average attendance than the low-win teams in every season of the decade, even though the size of the gap changed from year to year. Home runs did not tell the same story. Even in the steroid era, with power hitters like Barry Bonds and José Canseco putting up memorable numbers, my chart of the top home run teams showed that hitting for power alone did not guarantee big crowds. This suggests that fans may love watching the ball fly over the fence but show up more consistently for teams that win. My first chart also showed that attendance was affected by things outside of baseball, like the fear of a player strike and the events following the 2001 attacks. Other things matter too, and because of that, I can’t say winning is the only reason fans come to games. What I can say is that during this decade, winning had a clearer relationship with attendance than home runs did.