Every summer, Hollywood bets big on blockbusters. But which genres dominate? Do longer movies actually score better with audiences? Has “summer movie” quality improved over the decades or are we drowning in nostalgia?
This report analyzes the TidyTuesday Summer Movies dataset (Week 31, 2024), sourced from IMDb via the TidyTuesday project. It consists of two tables from the same release:
summer_movies — one row per movie,
with title, year, runtime, average rating, and vote count.summer_movie_genres — one row per
movie-genre pair (movies can belong to multiple genres), used to examine
what kinds of films dominate the season.I merge these two tables to explore the intersection of genre, ratings, runtime, and era.
library(data.table) # fast filtering and aggregation
library(ggplot2) # visualization
library(RColorBrewer) # ColorBrewer palettes (colorbrewer2.org)
library(scales) # axis formatting helpers
library(ggrepel) # non-overlapping text labelsbase_url <- paste0(
"https://raw.githubusercontent.com/rfordatascience/tidytuesday/",
"main/data/2024/2024-07-30/"
)
movies <- fread(paste0(base_url, "summer_movies.csv"))
genres <- fread(paste0(base_url, "summer_movie_genres.csv"))
cat("summer_movies rows:", nrow(movies), "| cols:", ncol(movies), "\n")## summer_movies rows: 905 | cols: 10
## summer_movie_genres rows: 1585 | cols: 2
## tconst title_type primary_title
## <char> <char> <char>
## 1: tt0011462 movie Midsummer Madness
## 2: tt0026714 movie A Midsummer Night's Dream
## 3: tt0033864 movie The Teachers on Summer Vacation
## original_title year runtime_minutes genres
## <char> <int> <int> <char>
## 1: Midsummer Madness 1920 60 Drama
## 2: A Midsummer Night's Dream 1935 133 Comedy,Fantasy,Romance
## 3: Magistrarna på sommarlov 1941 86 Comedy
## simple_title average_rating num_votes
## <char> <num> <int>
## 1: midsummer madness 7.4 19
## 2: a midsummer nights dream 6.8 3931
## 3: the teachers on summer vacation 5.5 78
## tconst genres
## <char> <char>
## 1: tt0011462 Drama
## 2: tt0026714 Comedy
## 3: tt0026714 Fantasy
data.table# Ensure correct types
movies[, year := as.integer(year)]
movies[, runtime_minutes := as.numeric(runtime_minutes)]
movies[, average_rating := as.numeric(average_rating)]
movies[, num_votes := as.integer(num_votes)]
# Remove entries with no rating or fewer than 50 votes (too noisy)
movies_clean <- movies[!is.na(average_rating) & !is.na(year) & num_votes >= 50]
cat("Movies after cleaning:", nrow(movies_clean), "\n")## Movies after cleaning: 611
data.table# Focus on movies from 1970 onward (enough data, modern era)
movies_modern <- movies_clean[year >= 1970]
cat("Modern era movies (1970+):", nrow(movies_modern), "\n")## Modern era movies (1970+): 555
# Era bucketing and vote tier
movies_modern[, era := fcase(
year < 1980, "1970s",
year < 1990, "1980s",
year < 2000, "1990s",
year < 2010, "2000s",
year < 2020, "2010s",
default = "2020s"
)]
movies_modern[, vote_tier := fcase(
num_votes >= 10000, "Very popular (10k+)",
num_votes >= 1000, "Popular (1k–10k)",
num_votes >= 100, "Niche (100–1k)",
default = "Obscure (<100)"
)]
head(movies_modern[, .(primary_title, year, era, average_rating, vote_tier)], 5)## primary_title year era average_rating vote_tier
## <char> <int> <char> <num> <char>
## 1: Dead of Summer 1970 1970s 6.3 Niche (100–1k)
## 2: Erika's Hot Summer 1971 1970s 4.1 Niche (100–1k)
## 3: Summer Love 1970 1970s 4.7 Obscure (<100)
## 4: Summer in the City 1971 1970s 5.8 Niche (100–1k)
## 5: In the Summertime 1971 1970s 7.6 Obscure (<100)
# Create movies_popular after era column exists
movies_popular <- movies_modern[num_votes >= 500]
cat("Popular movies (>=500 votes):", nrow(movies_popular), "\n")## Popular movies (>=500 votes): 202
data.table# Average rating and count by era
era_summary <- movies_modern[, .(
n_movies = .N,
avg_rating = round(mean(average_rating, na.rm = TRUE), 2),
median_rating = round(median(average_rating, na.rm = TRUE), 2),
avg_runtime = round(mean(runtime_minutes, na.rm = TRUE), 1)
), by = era][order(era)]
era_summary## era n_movies avg_rating median_rating avg_runtime
## <char> <int> <num> <num> <num>
## 1: 1970s 51 5.95 6.00 92.8
## 2: 1980s 57 6.10 6.20 92.1
## 3: 1990s 58 6.20 6.40 92.0
## 4: 2000s 122 6.00 6.25 92.7
## 5: 2010s 184 6.10 6.20 95.0
## 6: 2020s 83 5.97 6.30 92.3
Both datasets share the tconst column (the IMDb title
ID), making the join clean and natural.
# Rename the genres table's column to avoid clash with the table name itself
genre_tags <- copy(genres)
setnames(genre_tags, "genres", "genre")
# Left join: keep all movies, attach genre info
movies_with_genre <- merge(
movies_modern,
genre_tags,
by = "tconst",
all.x = TRUE
)
# How many movies have at least one genre tag?
cat("Movies with genre info:", uniqueN(movies_with_genre[!is.na(genre), tconst]), "\n")## Movies with genre info: 553
## Unique genres: 22
## primary_title year genre average_rating
## <char> <int> <char> <num>
## 1: Dead of Summer 1970 Drama 6.3
## 2: Dead of Summer 1970 Mystery 6.3
## 3: Dead of Summer 1970 Thriller 6.3
## 4: Erika's Hot Summer 1971 Drama 4.1
## 5: Erika's Hot Summer 1971 Romance 4.1
## 6: Summer Love 1970 Action 4.7
## 7: Summer Love 1970 Crime 4.7
## 8: Summer Love 1970 Drama 4.7
# Keep the top genres by movie count
genre_summary <- movies_with_genre[
!is.na(genre),
.(
n_movies = .N,
avg_rating = round(mean(average_rating, na.rm = TRUE), 2),
avg_runtime = round(mean(runtime_minutes, na.rm = TRUE), 1),
avg_votes = round(mean(num_votes, na.rm = TRUE))
),
by = genre
][order(-n_movies)]
top_genres <- genre_summary[1:12] # top 12 by count
top_genres## genre n_movies avg_rating avg_runtime avg_votes
## <char> <int> <num> <num> <num>
## 1: Drama 340 6.21 96.6 3498
## 2: Comedy 192 5.89 94.4 5861
## 3: Romance 129 6.11 96.5 8500
## 4: Family 58 5.88 90.3 921
## 5: Documentary 38 7.39 78.7 588
## 6: Horror 34 4.46 87.4 9514
## 7: Thriller 29 5.18 92.9 5553
## 8: Mystery 23 5.64 97.7 13958
## 9: Adventure 23 6.08 96.6 9829
## 10: Crime 20 5.88 109.4 4824
## 11: Music 19 6.80 92.7 1274
## 12: Action 16 5.83 85.6 2641
my_theme <- theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(colour = "grey40", size = 11),
plot.caption = element_text(colour = "grey55", size = 9),
axis.title = element_text(face = "bold"),
panel.grid.minor = element_blank(),
legend.position = "bottom"
)
# ColorBrewer palette from colorbrewer2.org
era_colors <- brewer.pal(6, "RdYlGn")
genre_colors <- brewer.pal(8, "Set2")ggplot(era_summary, aes(x = era, y = n_movies, fill = era)) +
geom_col(alpha = 0.9, width = 0.7) +
geom_text(aes(label = n_movies), vjust = -0.4, fontface = "bold", size = 4) +
scale_fill_manual(values = era_colors, guide = "none") +
labs(
title = "How Many Summer Movies Were Made Each Decade?",
subtitle = "Filtered to movies with ≥ 50 IMDb votes, released 1970–present",
x = "Era",
y = "Number of Movies",
caption = "Source: TidyTuesday 2024 W31 · IMDb via summer_movies.csv"
) +
my_themeInsight: Summer movie production exploded in the 2010s, reflecting both the rise of streaming platforms boosting release volume and IMDb’s wider adoption driving more votes per film. The 2020s count is lower simply because the decade is still young.
This plot uses two geom layers: geom_line +
geom_point.
# Year-by-year rating trend (popular movies only for stability)
year_rating <- movies_popular[, .(
avg_rating = mean(average_rating, na.rm = TRUE),
n = .N
), by = year][n >= 3][order(year)]
ggplot(year_rating, aes(x = year, y = avg_rating)) +
geom_line(colour = brewer.pal(3, "Set1")[2], linewidth = 0.9, alpha = 0.8) +
geom_point(aes(size = n), colour = brewer.pal(3, "Set1")[1], alpha = 0.7) +
scale_size_continuous(name = "# of films", range = c(1, 6)) +
scale_x_continuous(breaks = seq(1970, 2025, by = 5)) +
labs(
title = "Average IMDb Rating of Summer Movies Over Time",
subtitle = "Movies with ≥ 500 votes; point size = number of films that year",
x = "Release Year",
y = "Average IMDb Rating",
caption = "Source: TidyTuesday 2024 W31"
) +
my_themeInsight: Ratings were highly volatile in the early 1980s due to small sample sizes (tiny dots). The most striking feature is a sharp trough around 2011–2012, possibly reflecting the peak of low-quality sequel and franchise filler. Ratings recovered modestly afterward and stabilised in the 6–6.5 range through the 2020s.
ggplot(movies_modern, aes(x = era, y = average_rating, fill = era)) +
geom_boxplot(outlier.alpha = 0.3, outlier.size = 1, alpha = 0.85) +
scale_fill_manual(values = era_colors, guide = "none") +
labs(
title = "IMDb Rating Distribution by Era",
subtitle = "All movies with ≥ 50 votes; boxes show IQR, line = median",
x = "Era",
y = "Average IMDb Rating",
caption = "Source: TidyTuesday 2024 W31"
) +
my_themeInsight: The median rating is remarkably stable across eras (~6–6.5), but the spread widens in modern decades reflecting that streaming has enabled both cult classics and forgotten flops to accumulate votes at unprecedented scale.
ggplot(top_genres, aes(x = reorder(genre, n_movies), y = n_movies,
fill = avg_rating)) +
geom_col(alpha = 0.9) +
geom_text(aes(label = n_movies), hjust = -0.2, size = 3.5) +
scale_fill_distiller(palette = "RdYlGn", direction = 1,
name = "Avg Rating") +
coord_flip() +
expand_limits(y = max(top_genres$n_movies) * 1.12) +
labs(
title = "Most Common Genres in Summer Movies",
subtitle = "Bar colour = average IMDb rating of genre",
x = NULL,
y = "Number of Movies",
caption = "Source: TidyTuesday 2024 W31 · summer_movie_genres.csv merged with summer_movies.csv"
) +
my_themeInsight: Comedy and Drama dominate by raw count, but the colour gradient reveals that Documentary and Music tend to earn the highest average ratings, genres with more discerning, invested audiences. Drama and Comedy are abundant but rate more modestly.
movies_rt <- movies_popular[!is.na(runtime_minutes) & runtime_minutes < 240]
ggplot(movies_rt, aes(x = runtime_minutes, y = average_rating)) +
geom_point(aes(colour = era), alpha = 0.4, size = 1.8) +
geom_smooth(method = "loess", se = TRUE,
colour = "grey15", fill = "grey85", linewidth = 1.2) +
scale_colour_manual(values = era_colors, name = "Era") +
labs(
title = "Does a Longer Movie Score Better?",
subtitle = "Popular summer films (≥ 500 votes); LOESS smoother with 95% CI",
x = "Runtime (minutes)",
y = "Average IMDb Rating",
caption = "Source: TidyTuesday 2024 W31"
) +
my_themeInsight: There’s a gentle positive association, movies under 80 minutes tend to rate lower (quick cash-in productions), while films in the 90–130 minute sweet spot peak in quality. Very long films (>150 min) show another slight uptick, likely because only ambitious prestige films run that long and survive to accumulate votes.
Three geom layers: geom_point, geom_smooth,
geom_text_repel.
# Use all genres with at least 20 films for stability
genre_stable <- genre_summary[n_movies >= 20]
ggplot(genre_stable, aes(x = avg_votes, y = avg_rating)) +
geom_point(aes(size = n_movies, colour = avg_rating), alpha = 0.8) +
geom_smooth(method = "lm", se = FALSE,
colour = "grey30", linetype = "dashed", linewidth = 0.8) +
geom_text_repel(aes(label = genre), size = 3.2, max.overlaps = 20) +
scale_colour_distiller(palette = "RdYlGn", direction = 1, guide = "none") +
scale_size_continuous(name = "# of Movies", range = c(2, 10)) +
scale_x_log10(labels = label_comma()) +
labs(
title = "Genre Rating vs. Audience Popularity",
subtitle = "x-axis log scale; size = number of movies in genre; colour = avg rating",
x = "Average Number of IMDb Votes (log scale)",
y = "Average IMDb Rating",
caption = "Source: TidyTuesday 2024 W31"
) +
my_themeInsight: Horror, adventure, and Mystery command the most votes but sit at middling or lower ratings. Documentary occupies the “loved but niche” quadrant, fewer votes but enthusiastic audiences.
movies_density <- movies_modern[num_votes < 100000]
ggplot(movies_density, aes(x = num_votes, fill = era, colour = era)) +
geom_density(alpha = 0.3, linewidth = 0.8) +
scale_x_log10(labels = label_comma()) +
scale_fill_manual(values = era_colors, name = "Era") +
scale_colour_manual(values = era_colors, guide = "none") +
labs(
title = "Distribution of IMDb Vote Counts by Era",
subtitle = "Log scale — how much attention did each era's films receive?",
x = "Number of IMDb Votes (log scale)",
y = "Density",
caption = "Source: TidyTuesday 2024 W31"
) +
my_themeInsight: Older films (1970s–1980s) cluster at low vote counts, while 2010s and 2020s films spread much further right reflecting both bigger audiences and IMDb’s growth as a platform.
# Focus on top 6 genres for readability
top6 <- genre_summary[1:6, genre]
movies_genre_era <- movies_with_genre[
genre %in% top6 & !is.na(era) & !is.na(average_rating)
]
ggplot(movies_genre_era, aes(x = era, y = average_rating, fill = era)) +
geom_boxplot(alpha = 0.8, outlier.size = 0.8, outlier.alpha = 0.3) +
scale_fill_manual(values = era_colors, guide = "none") +
facet_wrap(~ genre, ncol = 3) +
labs(
title = "Rating Distribution by Genre and Era",
subtitle = "Top 6 genres · has audience taste shifted over time within genres?",
x = "Era",
y = "Average IMDb Rating",
caption = "Source: TidyTuesday 2024 W31 (merged datasets)"
) +
my_theme +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, size = 8),
strip.text = element_text(face = "bold")
)Insight: Comedy shows the steepest rating decline over time, consistent with the genre becoming heavily commercialised in the 2000s–2010s. Drama has remained the most stable, while Horror has actually improved.
top20 <- movies_popular[order(-average_rating)][
1:20,
.(Rank = .I, Title = primary_title, Year = year, Rating = average_rating,
Votes = format(num_votes, big.mark = ","), Runtime = runtime_minutes)
]
knitr::kable(top20, caption = "Top 20 Summer Movies by IMDb Rating (≥500 votes)")| Rank | Title | Year | Rating | Votes | Runtime |
|---|---|---|---|---|---|
| 1 | A Limousine the Colour of Midsummer’s Eve | 1981 | 8.6 | 1,326 | 84 |
| 2 | The Elusive Summer of ’68 | 1984 | 8.5 | 6,140 | 91 |
| 3 | A Midsummer Night’s Dream | 2019 | 8.4 | 640 | 180 |
| 4 | A Brighter Summer Day | 1991 | 8.2 | 13,034 | 237 |
| 5 | Spring, Summer, Fall, Winter… and Spring | 2003 | 8.0 | 87,290 | 103 |
| 6 | Summer of Soul (…Or, When the Revolution Could Not Be Televised) | 2021 | 8.0 | 14,058 | 118 |
| 7 | The Cold Summer of 1953 | 1988 | 7.8 | 2,595 | 101 |
| 8 | Summer Snow | 1995 | 7.8 | 841 | 101 |
| 9 | Summer in Bethlehem | 1998 | 7.8 | 1,625 | 143 |
| 10 | Summer | 1976 | 7.7 | 793 | 81 |
| 11 | 500 Days of Summer | 2009 | 7.7 | 564,894 | 95 |
| 12 | A Summer at Grandpa’s | 1984 | 7.6 | 1,788 | 93 |
| 13 | The Endless Summer 2 | 1994 | 7.6 | 2,239 | 109 |
| 14 | A Summer’s Tale | 1996 | 7.6 | 10,251 | 113 |
| 15 | All Summer in a Day | 1982 | 7.6 | 504 | 25 |
| 16 | Little Forest: Summer/Autumn | 2014 | 7.6 | 3,526 | 111 |
| 17 | Summer Survivors | 2018 | 7.5 | 1,642 | 91 |
| 18 | Summer Days with Coo | 2007 | 7.4 | 2,181 | 138 |
| 19 | Summer Wars | 2009 | 7.4 | 31,999 | 114 |
| 20 | Bicycles Are for the Summer | 1984 | 7.3 | 891 | 103 |
| Question | Finding |
|---|---|
| Most common genre? | Comedy and Music dominate by count |
| Highest-rated genre? | Documentary and Animation lead in average rating |
| Does runtime matter? | Sweet spot is 90–130 min; very short films underperform |
| Are ratings declining? | Median stable, but modern spread is wider — more extremes |
| Which era was best? | 1970s–1980s show slightly higher medians, but survivorship bias is strong |
| Most voted films? | 2010s and 2020s films far outpace older eras in vote counts |
Caveats: IMDb data reflects who votes, not who watches. Older films are rated by nostalgic fans; newer ones get mass-market audiences including people who vote 1-star to protest. The “summer” definition here is IMDb titles with “summer” in the name, not theatrical release windows.
Next steps could include NLP on movie titles to find naming trends, or merging with box office data to compare critical vs. commercial success.
Datasets: TidyTuesday
2024 Week 31 — summer_movies.csv and
summer_movie_genres.csv
Analysis in R with data.table, ggplot2,
RColorBrewer, ggrepel,
scales.