From January to September 2026, immigration received noticeably more media coverage than artificial intelligence. Immigration averaged about 2,807 stories per week, compared to about 1,635 stories per week for AI. One of the biggest differences in the graph is a large spike in immigration coverage during the week of January 25, when coverage increased to around 7,500 stories before dropping back down. Overall, immigration had both a higher amount of coverage and much larger changes from week to week, while AI coverage remained more consistent.
############################################################
# STEP 1: Install and Load Packages
############################################################
if (!require("tidyverse")) install.packages("tidyverse")
if (!require("httr2")) install.packages("httr2")
if (!require("lubridate")) install.packages("lubridate")
if (!require("kableExtra")) install.packages("kableExtra")
if (!require("plotly")) install.packages("plotly")
library(tidyverse)
library(httr2)
library(lubridate)
library(kableExtra)
library(plotly)
############################################################
# STEP 2: Verify Media Cloud API Key
############################################################
if (!nzchar(Sys.getenv("MEDIACLOUD_KEY"))) {
stop(
"No Media Cloud API key was found.\n",
"Please save your key to .Renviron first."
)
}
############################################################
# STEP 3: Define Two Topics
############################################################
# Replace the topic labels and search queries below.
#
# Examples:
# "artificial intelligence"
# "climate change"
# "immigration"
# "inflation"
topics <- c(
"Artificial Intelligence" = "\"artificial intelligence\"",
"Immigration" = "\"immigration\""
)
############################################################
# STEP 4: Define the Date Range
############################################################
start_date <- "2026-01-04"
end_date <- "2026-09-05"
############################################################
# STEP 5: Create a Function to Download
# Daily Story Counts.
############################################################
get_counts <- function(query) {
Sys.sleep(35)
response <-
request(
"https://search.mediacloud.org/api/search/count-over-time"
) |>
req_headers(
Authorization = paste(
"Token",
Sys.getenv("MEDIACLOUD_KEY")
)
) |>
req_url_query(
q = query,
start = start_date,
end = end_date,
platform = "onlinenews-mediacloud",
cs = 34412234
) |>
req_perform()
results <-
response |>
resp_body_json()
map_dfr(
results$count_over_time$counts,
as_tibble
) |>
mutate(
date = as.Date(date)
)
}
############################################################
# STEP 6: Download Data for Both Topics
############################################################
topic_data <-
imap_dfr(
topics,
function(search_query, topic_name) {
message("Downloading: ", topic_name)
get_counts(search_query) |>
mutate(
topic = topic_name
)
}
)
############################################################
# STEP 7: Examine the Downloaded Data
############################################################
head(topic_data)
glimpse(topic_data)
############################################################
# STEP 8: Calculate Weekly Story Counts
############################################################
weekly_counts <-
topic_data |>
mutate(
week = floor_date(date, unit = "week")
) |>
group_by(topic, week) |>
summarise(
stories = sum(count),
.groups = "drop"
)
############################################################
# STEP 9: Display Weekly Counts
############################################################
weekly_counts |>
arrange(topic, week) |>
kbl(
caption = "Weekly Media Cloud Story Counts"
) |>
kable_styling(
full_width = FALSE,
bootstrap_options = c(
"striped",
"hover"
)
)
############################################################
# STEP 10: Summarize Total Coverage
############################################################
topic_summary <-
weekly_counts |>
group_by(topic) |>
summarise(
Total_Stories = sum(stories),
Weekly_Minimum = min(stories),
Weekly_Maximum = max(stories),
Weekly_Mean = round(mean(stories), 2),
.groups = "drop"
) |>
arrange(desc(Total_Stories))
Summary_Table <- topic_summary |>
kbl(
caption = "Coverage Summary by Topic"
) |>
kable_styling(
full_width = FALSE,
bootstrap_options = c(
"striped",
"hover"
)
)
Summary_Table
############################################################
# STEP 11: Create a Line Graph
############################################################
Line_Graph <- weekly_counts |>
ggplot(
aes(
x = week,
y = stories,
color = topic
)
) +
geom_line(
linewidth = 1
) +
geom_point(
size = 2
) +
labs(
title = "Weekly Media Coverage Volume",
subtitle = "Comparison of Two Topics",
x = "Week",
y = "Number of Stories",
color = "Topic"
) +
theme_minimal()
Line_Graph
############################################################
# STEP 12: Create Interactive Plotly Line Graph
############################################################
Plotly_Graphic <- plot_ly(
data = weekly_counts,
x = ~week,
y = ~stories,
color = ~topic,
colors = "Set1",
type = "scatter",
mode = "lines+markers",
customdata = ~topic,
hovertemplate = paste(
"<b>%{customdata}</b>",
"<br>Week: %{x|%B %d, %Y}",
"<br>Stories: %{y:,.0f}",
"<extra></extra>"
)
) |>
layout(
title = "Weekly Media Coverage Volume",
xaxis = list(
title = "Week"
),
yaxis = list(
title = "Number of Stories",
tickformat = ","
),
legend = list(
title = list(text = "Topic")
),
hovermode = "x unified"
)
Plotly_Graphic
############################################################
# STEP 13: Save Results as a CSV File
############################################################
write_csv(
weekly_counts,
"weekly_story_counts.csv"
)