High‑quality content that wins mass‑market audiences. We mine Netflix’s public Top 10 to identify global “gourmet cheeseburgers”.
<span class="badge">Reproducible</span><span class="badge">KPI Cards</span><span class="badge">Interactive Tables</span><span class="badge">Beautiful Charts</span>
Executive Summary. We acquire the Netflix Tudum Top 10 data
(global + per‑country), fix minor quirks (e.g., “N/A”
season labels), and conduct an EDA to spotlight programs with durable,
multinational appeal. Results are summarized as press releases for PR
usage.
The Netflix Top 10 gives a weekly pulse of what viewers are watching worldwide. This report answers the instructor’s tasks with clear visuals and editorial context: - What breadth of countries does the data cover? - Which programs sustain long runs or achieve exceptional total hours? - How do iconic titles (e.g., Stranger Things) trend over time? - Where are the PR‑worthy stories?
Source: Netflix Tudum weekly Top 10 datasets (global and per‑country).
if (!require("tidyverse")) install.packages("tidyverse")
## Loading required package: tidyverse
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.1 ✔ stringr 1.5.2
## ✔ ggplot2 4.0.0 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.1.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
if (!require("DT")) install.packages("DT")
## Loading required package: DT
if (!require("lubridate")) install.packages("lubridate")
if (!require("stringr")) install.packages("stringr")
if (!require("scales")) install.packages("scales")
## Loading required package: scales
##
## Attaching package: 'scales'
##
## The following object is masked from 'package:purrr':
##
## discard
##
## The following object is masked from 'package:readr':
##
## col_factor
library(tidyverse)
library(readr); library(dplyr)
library(DT); library(lubridate); library(stringr); library(scales)
# Create data folder and download files if needed
if(!dir.exists(file.path("data", "mp01"))){ dir.create(file.path("data", "mp01"), showWarnings=FALSE, recursive=TRUE) }
GLOBAL_TOP_10_FILENAME <- file.path("data", "mp01", "global_top10_alltime.tsv")
if(!file.exists(GLOBAL_TOP_10_FILENAME)){
download.file("https://www.netflix.com/tudum/top10/data/all-weeks-global.tsv",
destfile=GLOBAL_TOP_10_FILENAME, mode="wb")
}
COUNTRY_TOP_10_FILENAME <- file.path("data", "mp01", "country_top10_alltime.tsv")
if(!file.exists(COUNTRY_TOP_10_FILENAME)){
download.file("https://www.netflix.com/tudum/top10/data/all-weeks-countries.tsv",
destfile=COUNTRY_TOP_10_FILENAME, mode="wb")
}
file.exists(GLOBAL_TOP_10_FILENAME) & file.exists(COUNTRY_TOP_10_FILENAME)
## [1] TRUE
Two changes get us analysis‑ready: (1) parse TSVs; (2) change literal
“N/A”
to proper NA
in
season_title
.
GLOBAL_TOP_10 <- read_tsv(GLOBAL_TOP_10_FILENAME, show_col_types = FALSE) |>
mutate(season_title = if_else(season_title == "N/A", NA_character_, season_title))
COUNTRY_TOP_10 <- read_tsv(COUNTRY_TOP_10_FILENAME, na = c("N/A"), show_col_types = FALSE)
glimpse(GLOBAL_TOP_10, width = 80)
## Rows: 8,880
## Columns: 9
## $ week <date> 2025-09-28, 2025-09-28, 2025-09-28, 2025-0…
## $ category <chr> "Films (English)", "Films (English)", "Film…
## $ weekly_rank <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, …
## $ show_title <chr> "KPop Demon Hunters", "Ruth & Boaz", "The W…
## $ season_title <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "aka Ch…
## $ weekly_hours_viewed <dbl> 32200000, 15900000, 13500000, 15700000, 112…
## $ runtime <dbl> 1.6667, 1.5500, 1.7833, 2.4333, 1.8333, 1.7…
## $ weekly_views <dbl> 19300000, 10300000, 7600000, 6500000, 61000…
## $ cumulative_weeks_in_top_10 <dbl> 15, 1, 3, 5, 2, 1, 1, 1, 1, 3, 2, 1, 1, 2, …
glimpse(COUNTRY_TOP_10, width = 80)
## Rows: 413,620
## Columns: 8
## $ country_name <chr> "Argentina", "Argentina", "Argentina", "Arg…
## $ country_iso2 <chr> "AR", "AR", "AR", "AR", "AR", "AR", "AR", "…
## $ week <date> 2025-09-28, 2025-09-28, 2025-09-28, 2025-0…
## $ category <chr> "Films", "Films", "Films", "Films", "Films"…
## $ weekly_rank <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, …
## $ show_title <chr> "Sonic the Hedgehog 3", "KPop Demon Hunters…
## $ season_title <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Bi…
## $ cumulative_weeks_in_top_10 <dbl> 2, 15, 1, 2, 1, 1, 2, 5, 1, 2, 2, 1, 1, 1, …
n_countries <- COUNTRY_TOP_10 |> distinct(country_name) |> filter(!is.na(country_name)) |> nrow()
noneng_top <- GLOBAL_TOP_10 |>
filter(str_detect(category, "Films"), !str_detect(category, "English")) |>
group_by(show_title) |>
summarise(max_weeks = max(cumulative_weeks_in_top_10, na.rm=TRUE), .groups="drop") |>
arrange(desc(max_weeks)) |>
slice(1)
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `max_weeks = max(cumulative_weeks_in_top_10, na.rm = TRUE)`.
## Caused by warning in `max()`:
## ! no non-missing arguments to max; returning -Inf
add_runtime_minutes <- function(df){ df |> mutate(runtime_minutes = if_else(!is.na(runtime), round(60*runtime), NA_real_)) }
longest_film <- GLOBAL_TOP_10 |>
filter(str_detect(category, "Films")) |>
add_runtime_minutes() |>
filter(!is.na(runtime_minutes)) |>
arrange(desc(runtime_minutes)) |>
slice(1) |>
select(show_title, runtime_minutes)
squid_total <- GLOBAL_TOP_10 |>
filter(str_detect(show_title, regex("Squid Game", ignore_case=TRUE)), str_detect(category,"TV")) |>
summarise(total_hours = sum(weekly_hours_viewed, na.rm=TRUE))
Answer: 94 countries.
Context. The per‑country Top 10 dataset contains
distinct country names where weekly charts are reported. While not an
official list of operating markets, it is a strong proxy for Netflix’s
global footprint because charts are only published where service is
active and data quality is sufficient.
Answer: **** with ** weeks** in the global Top
10.
Why it matters. Long global runs for non‑English titles
show that audiences readily cross language barriers when the story
travels. That sustained visibility supports marketing beats, late‑window
discovery, and catalog uplift.
# Extra context: how typical is this run among non-English films?
noneng_weeks <- GLOBAL_TOP_10 |>
filter(str_detect(category,"Films"), !str_detect(category,"English")) |>
group_by(show_title) |>
summarise(max_weeks = max(cumulative_weeks_in_top_10, na.rm=TRUE), .groups="drop")
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `max_weeks = max(cumulative_weeks_in_top_10, na.rm = TRUE)`.
## Caused by warning in `max()`:
## ! no non-missing arguments to max; returning -Inf
summary_noneng <- noneng_weeks |>
summarise(median_weeks = median(max_weeks, na.rm=TRUE), p90 = quantile(max_weeks, .90, na.rm=TRUE))
summary_noneng
Reading guide. The winner’s run substantially exceeds the median non‑English film (median = NA weeks; 90th percentile ≈ NA weeks), reinforcing its exceptional staying power.
Answer: Pushpa 2: The Rule (Reloaded
Version) with a runtime of 224 minutes.
Context. Runtime can influence completion and repeat
viewing. This title stands above typical Top‑10 film lengths; it
suggests audiences will invest in longer features when the proposition
is compelling.
film_runtime_stats <- GLOBAL_TOP_10 |>
filter(str_detect(category,"Films")) |>
add_runtime_minutes() |>
summarise(median_min = median(runtime_minutes, na.rm=TRUE),
p75 = quantile(runtime_minutes, .75, na.rm=TRUE))
film_runtime_stats
On average, Top‑10 film runtimes cluster around 106 minutes (median), with a heavier tail up to 121 minutes (75th percentile).
See Category leaders chart above.
Context. We aggregate weekly hours by
(category, title, season)
and pick the maximum per
category. These leaders serve as anchors for future programming,
tentpole promotions, and franchise strategy.
is_tv <- str_detect(COUNTRY_TOP_10$category, "TV")
ctv <- COUNTRY_TOP_10[is_tv, ] |>
arrange(country_name, show_title, season_title, week) |>
group_by(country_name, show_title, season_title) |>
mutate(
week = as.Date(week),
gap = as.integer(week - lag(week)),
new_run = if_else(is.na(gap) | gap > 7, 1L, 0L),
run_id = cumsum(replace_na(new_run, 0L))
) |>
ungroup()
run_spans <- ctv |>
group_by(country_name, show_title, season_title, run_id) |>
summarise(start = min(week), end = max(week), weeks = as.integer((end - start)/7) + 1L, .groups="drop") |>
arrange(desc(weeks)) |>
slice(1)
Answer: Pablo Escobar, el patrón del
mal held a 102‑week consecutive run in
Colombia (from 2021-07-04 to
2023-06-11).
Why it matters. Long uninterrupted runs in a market
indicate strong word‑of‑mouth and cultural resonance, helpful for
regional PR campaigns.
coverage <- COUNTRY_TOP_10 |>
group_by(country_name) |>
summarise(n_weeks = n_distinct(week), last_week = max(week), .groups="drop") |>
arrange(n_weeks)
truncated <- coverage |> slice(1)
Answer: Russia, with final
reporting week 2022-02-27.
Note. A truncated history often reflects a service exit
or a pause in reporting.
Answer: 5,310,000,000 hours
globally across all seasons.
Context. This consolidates weekly hours across seasons
and weeks at a global level. The magnitude underscores the show’s
event‑level status.
rn_runtime_hours <- 1 + 58/60
rn_2021 <- GLOBAL_TOP_10 |>
filter(show_title == "Red Notice", lubridate::year(week) == 2021) |>
summarise(total_hours = sum(weekly_hours_viewed, na.rm=TRUE))
rn_views_2021 <- rn_2021$total_hours / rn_runtime_hours
Answer: About 201,732,203 views in
2021.
Method. Total 2021 hours divided by runtime (in hours)
gives an approximate “complete‑view” count.
us_films <- COUNTRY_TOP_10 |>
filter(country_name == "United States", str_detect(category, "Films")) |>
group_by(show_title) |>
summarise(first_rank = min(weekly_rank, na.rm=TRUE),
ever_num1 = any(weekly_rank == 1, na.rm=TRUE),
last_week = max(week), .groups="drop")
risers <- us_films |> filter(ever_num1, first_rank > 1) |> arrange(desc(last_week))
Answer: 0 films achieved a climb to
#1 after a lower debut; most recent climber: NA (last
chart week NA).
Interpretation. Climbing to #1 post‑launch signals
growing momentum, often driven by social chatter, PR, or recommendation
loops.
country_debuts <- COUNTRY_TOP_10 |>
filter(str_detect(category, "TV")) |>
group_by(show_title, season_title, country_name) |>
summarise(debut_week = min(week), .groups="drop")
spread <- COUNTRY_TOP_10 |>
inner_join(country_debuts, by=c("show_title","season_title","country_name")) |>
filter(week == debut_week) |>
group_by(show_title, season_title, week) |>
summarise(n_countries = n_distinct(country_name), .groups="drop") |>
arrange(desc(n_countries)) |>
slice(1)
Answer: Emily in Paris
(Emily in Paris: Season 2) debuted in 94
countries in the week of 2021-12-26.
Why it matters. Wide day‑one reach indicates a truly
global hook and helps justify global launches and cross‑market
creative.
Los Gatos, CA — October 02, 2025. Across its first four seasons, Stranger Things amassed 2,967,980,000 hours globally and maintained a durable presence in weekly Top 10 charts across 94 markets. With multi‑season momentum and worldwide appeal, Season 5 is poised for another blockbuster debut.
Amsterdam — October 02, 2025. Non‑English features continue to show extended global legs. The top performer () held the global Top 10 for weeks, far above the typical run (median NA). The sustained performance highlights the universal resonance of local stories.
London — October 02, 2025. Category leaders (see charts) underscore robust appetite for English‑language tentpoles with global reach. The breadth of engagement supports coordinated cross‑market campaigns and talent spotlights.