Agenda-setting theory purports that media coverage frequency shapes issue salience. The more attention a topic receives, the more audiences deem it important, supporting the theory that the media heavily influences what we focus on and how we interpret those issues.
This theory applies to this analysis because it focuses on the amount of news coverage given to two different issues that are often spotlighted by the media. By counting stories about immigration and the Epstein files, this analysis examines which topic receives more attention (as documented by Media Cloud data).
If one topic receives substantially more coverage than the other, that could suggest that it has a more prominent place than the other. However, the number of stories does not necessarily show what the public thinks about an issue.
There are, of course, many other media theories that could be used to examine the psyche of the American people as the media outlets grapple for attention. The results could be an example of priming theory — exposure to a topic can temporarily shape how we feel and act. Elaboration Likelihood Model could explain how media may flood the news with simple messaging (peripheral route) instead of prompting people to plunge deeper into an issue in order to form an opinion (central route).
In preparation for the final project in Science of Communication, I pulled data from Media Cloud to compare the pervasiveness of coverage focused on two of the hottest topics in American media — immigration in the United States versus the prevalence of stories following the investigative records pertaining to convicted pedophile Jeffrey Epstein, more commonly referred to as “the Epstein files.”
I predict that immigration will receive significantly more news coverage than the Epstein files during the period studied.
The independent variable is the news topic, which has two categories: immigration and the Epstein files. The dependent variable is the number of online news stories returned for each topic. The analysis also considers how the number of stories changes over time.
Data has been collected using Media Cloud between Nov. 2, 2025, through Oct. 31, 2026. Search terms were created for each topic.
Immigration coverage was defined by using the following Media Cloud query:
immigration_query <-
'(
immigration OR immigrant* OR migrant* OR deportation OR
deport* OR asylum OR "border security" OR "border patrol" OR
"illegal immigration" OR "immigration policy"
)'
Coverage of the Epstein files was defined by using the following Media Cloud query:
epstein_query <-
'(
Epstein OR
"Jeffrey Epstein" OR
"Ghislaine Maxwell"
)'
R Studio was used to organize the daily story counts by topic and group them into weeks (approximately 52 weeks). Summary statistics were calculated to show the total number of stories, the lowest and highest weekly counts, the median, mean and standard deviation for each topic. An interactive graph was also created to show changes in story counts over time. Results show the stories returned by these specific searches but does not compile a complete measure of all news coverage on each topic.
A paired samples t-test (see below) was used to assess the statistical significance of coverage volume variation based on the topics.
The initial results support the hypothesis that immigration received more coverage than the Epstein files during the period studied. The Media Cloud search returned 232,343 stories for immigration, compared with 54,963 stories on the Epstein files. In some weeks, the number of immigration-related stories were nearly eight times more prevalent than the stories focused on a convicted pedophile.
Immigration is covered in the news more than four times the rate of the Epstein files (4.23 to be exact).
The Plotly graph compares weekly story counts for immigration and the Epstein files from November 2025 through the end of October 2026, providing an interactive visual comparison for each topic.
The weekly summary statistics corroborate that there is a major difference in coverage. Immigration had a weekly mean of approximately 4,468.13 stories, while the Epstein files had a weekly mean of 1056.98 stories. The median weekly counts were 4,288.5 for immigration and 798.5 for the Epstein files.
The maximum weekly count for immigration was 9,663 stories, compared with 4,829 for the Epstein files. The minimum weekly counts were 2,827 and 232, respectively. These differences suggest that both topics experienced changes in coverage, but immigration consistently had higher weekly counts in the results.
| topic | Total_Stories | Weekly_Minimum | Weekly_Maximum | Weekly_Median | Weekly_Mean | Weekly_SD |
|---|---|---|---|---|---|---|
| immigration | 232343 | 2827 | 9663 | 4288.5 | 4468.13 | 1165.03 |
| Epstein files | 54964 | 232 | 4829 | 798.5 | 1057.00 | 998.68 |
The analysis used a paired-samples t-test to assess whether the average difference between weekly immigration coverage and weekly Epstein files coverage differed significantly from zero. This boxplot summarizes the distributions of the weekly story counts for the Epstein files (V1) and immigration (V2).
This analysis is relevant to agenda-setting theory because it examines the relative prominence of two issues in news coverage. By comparing the weekly volume of stories, the test helps determine whether immigration received significantly more media attention than the Epstein files.
According to the results, immigration stories outnumbered Epstein files stories by an average of approximately 3,411 per week, with a standard deviation of 1,232 stories. The standard deviation indicates that the difference in coverage between the two topics fluctuated from week to week, although immigration consistently receives considerably more coverage than Epstein files during the period studied.
| Descriptive Statistics: Pair Differences | ||||
| count | mean | sd | min | max |
|---|---|---|---|---|
| 52.000 | 3,411.135 | 1,232.443 | 188.000 | 8,563.000 |
The results suggest that immigration received substantially more weekly media coverage than the Epstein files during the period studied. The 95% confidence interval indicates that immigration received between approximately 3,068 and 3,754 more stories per week than the Epstein files, on average.
The paired-samples t-test found the average of the pair differences to be statistically significantly different from zero. The t-value was 19.96, with an associated p-value of less than .001. Thus, the hypothesis was supported.
| Paired-Samples t-Test | |||||
| statistic | parameter | p.value | conf.low | conf.high | method |
|---|---|---|---|---|---|
| 19.9588 | 51 | 0.0000 | 3,068.0203 | 3,754.2489 | Paired t-test |
The Spapiro-Wilk test indicated that the paired differences were not normally distributed (p value < .001). With paired observations for approximately 52 weeks, the paired-samples t-test was considered sufficiently robust to moderate departures from normality.
| Normality Test (Shapiro-Wilk) | ||
| statistic | p.value | method |
|---|---|---|
| 0.8716 | 0.0000 | Shapiro-Wilk normality test |
| If the p-value is 0.05 or less, the number of pairs is fewer than 40, and the distribution of pair differences shows obvious non-normality or outliers, consider using the Wilcoxon Signed Rank Test results instead of the Paired-Samples t-Test results. | ||
Tracking the topics over the past 12 months (52 weeks) via Media Cloud shows immigration is covered at a rate of over four times that of the Epstein files. Two weeks into the data set shows a spike for both topics on Nov. 16, 2025.
On that date, President Donald Trump launched an immigration enforcement crackdown known as Operation Charlotte’s Web, represented by a jump in coverage to 5,017 stories — a bump, but not the largest one in the data set. The same day, Epstein survivors made an on-camera public service announcement urging House Republicans to vote to release all government files related to the case files. This occurred just three days before the Epstein Files Transparency Act was signed into federal law, mandating the public release of all unclassified information — prompting the highest spike of coverage in the data set at 4,829.
Coverage of immigration-related stories jumped to 9,663 on Jan. 25, 2026 — the day after 37-year-old intensive care nurse Alex Pretti was fatally shot by two Immigration and Customs Enforcement (ICE) officers during widespread protests across the city of Minneapolis following the fatal shooting of Renee Good.
Here is the code that produced the analysis.
############################################################
# STEP 1: Install and Load Required Packages
############################################################
required_packages <- c(
"tidyverse",
"httr2",
"lubridate",
"plotly",
"knitr",
"kableExtra"
)
for (pkg in required_packages) {
if (!requireNamespace(pkg, quietly = TRUE)) {
install.packages(pkg)
}
}
library(tidyverse)
library(httr2)
library(lubridate)
library(plotly)
library(knitr)
library(kableExtra)
############################################################
# STEP 2: Verify Media Cloud API Key
############################################################
if (!nzchar(Sys.getenv("MEDIACLOUD_KEY"))) {
stop(
"No Media Cloud API key was found.\n",
"Run the setup section once,\n",
"then restart R and try again."
)
}
mc_key <- Sys.getenv("MEDIACLOUD_KEY")
############################################################
# STEP 3: Define the Date Range
############################################################
start_date <- "2025-11-02"
end_date <- "2026-10-31"
############################################################
# STEP 4: Define Search Queries
############################################################
immigration_query <-
'(
immigration OR immigrant* OR migrant* OR deportation OR
deport* OR asylum OR "border security" OR "border patrol" OR
"illegal immigration" OR "immigration policy"
)'
epstein_query <-
'(
Epstein OR
"Jeffrey Epstein" OR
"Ghislaine Maxwell"
)'
############################################################
# STEP 5: Create Topic Definitions
############################################################
topics <- c(
"immigration" = immigration_query,
"Epstein files" = epstein_query
)
############################################################
# STEP 6: Create a Function to Retrieve Daily
# Story Counts
############################################################
mc_counts_over_time <- function(
query,
start_date,
end_date,
collection_id = 34412234
) {
resp <-
request(
"https://search.mediacloud.org/api/search/count-over-time"
) |>
req_headers(
Authorization = paste("Token", mc_key)
) |>
req_url_query(
q = query,
start = start_date,
end = end_date,
platform = "onlinenews-mediacloud",
cs = collection_id
) |>
req_perform()
results <- resp |>
resp_body_json()
map_dfr(
results$count_over_time$counts,
as_tibble
) |>
mutate(
date = as.Date(date)
)
}
############################################################
# STEP 7: Download Daily Counts for All Topics
############################################################
topic_data <-
imap_dfr(
topics,
function(search_string, topic_name) {
message(
"Downloading: ",
topic_name
)
results <-
mc_counts_over_time(
query = search_string,
start_date = start_date,
end_date = end_date
)
Sys.sleep(35)
results |>
mutate(
topic = topic_name
)
}
)
############################################################
# STEP 8: Examine the Downloaded Daily Data
############################################################
head(topic_data)
glimpse(topic_data)
############################################################
# STEP 9: Convert Daily Counts to Weekly Counts
############################################################
weekly_counts <-
topic_data |>
mutate(
week = floor_date(
date,
unit = "week"
)
) |>
group_by(
topic,
week
) |>
summarise(
stories = sum(count),
.groups = "drop"
)
############################################################
# STEP 10: Examine Weekly Counts
############################################################
weekly_counts
############################################################
# STEP 11: Summarize Coverage by Topic
############################################################
topic_summary <-
weekly_counts |>
group_by(topic) |>
summarise(
Total_Stories = sum(stories),
Weekly_Minimum = min(stories),
Weekly_Maximum = max(stories),
Weekly_Median = median(stories),
Weekly_Mean = round(
mean(stories),
2
),
Weekly_SD = round(
sd(stories),
2
),
.groups = "drop"
) |>
arrange(
desc(Total_Stories)
)
Table <-
topic_summary |>
kbl(
caption =
"Media Cloud Topic Summary Statistics",
align = "lrrrrrr"
) |>
kable_styling(
full_width = FALSE,
bootstrap_options = c(
"striped",
"hover",
"condensed"
)
)
Table
############################################################
# STEP 12: Create an Interactive Plotly Graph
############################################################
weekly_counts <- weekly_counts |>
mutate(
stories_formatted = formatC(
stories,
format = "d",
big.mark = ","
)
)
Graph <- plot_ly(
data = weekly_counts,
x = ~week,
y = ~stories,
color = ~topic,
colors = "Set1",
type = "scatter",
mode = "lines+markers",
customdata = ~topic,
text = ~stories_formatted,
hovertemplate =
paste(
"%{customdata}
",
"Week: %{x|%Y-%m-%d}
",
"Stories: %{text}
",
""
)
) |>
layout(
title =
"Weekly U.S. Coverage Volume",
hovermode = "x unified",
xaxis = list(
title = "Week"
),
yaxis = list(
title = "Weekly Story Count",
separatethousands = TRUE
)
)
Graph
############################################################
# Reshape the data
############################################################
weekly_counts_wide <-
weekly_counts %>%
mutate(
topic = make.names(topic)
) %>%
select(week, topic, stories) %>%
pivot_wider(
names_from = topic,
values_from = stories
)
# ============================================================
# Paired-samples t-test code
#
# Setup: Install and Load Required Packages
# ============================================================
if (!require("tidyverse")) install.packages("tidyverse")
if (!require("plotly")) install.packages("plotly")
if (!require("gt")) install.packages("gt")
if (!require("gtExtras")) install.packages("gtExtras")
if (!require("broom")) install.packages("broom")
library(tidyverse)
library(plotly)
library(gt)
library(gtExtras)
library(broom)
options(scipen = 999)
# ============================================================
# Data Import
# ============================================================
mydata <- weekly_counts_wide
# Specify the two variables involved
mydata$V1 <- mydata$Epstein.files
mydata$V2 <- mydata$immigration
# ============================================================
# Compute Pair Differences
# ============================================================
mydata$PairDifferences <- mydata$V2 - mydata$V1
# ============================================================
# Interactive Histogram of Pair Differences
# ============================================================
hist_plot <- plot_ly(
data = mydata,
x = ~PairDifferences,
type = "histogram",
marker = list(color = "#1f78b4", line = list(color = "black", width = 1))
) %>%
layout(
title = "Distribution of Pair Differences",
xaxis = list(title = "Pair Differences"),
yaxis = list(title = "Count"),
shapes = list(
list(
type = "line",
x0 = mean(mydata$PairDifferences, na.rm = TRUE),
x1 = mean(mydata$PairDifferences, na.rm = TRUE),
y0 = 0,
y1 = max(table(mydata$PairDifferences)),
line = list(color = "red", dash = "dash")
)
)
)
# ============================================================
# Descriptive Statistics
# ============================================================
desc_stats <- mydata %>%
summarise(
count = n(),
mean = mean(PairDifferences, na.rm = TRUE),
sd = sd(PairDifferences, na.rm = TRUE),
min = min(PairDifferences, na.rm = TRUE),
max = max(PairDifferences, na.rm = TRUE)
)
desc_table <- desc_stats %>%
gt() %>%
gt_theme_538(quiet = TRUE) %>%
tab_header(title = "Descriptive Statistics: Pair Differences") %>%
fmt_number(columns = where(is.numeric), decimals = 3)
# ============================================================
# Normality Test (Shapiro-Wilk)
# ============================================================
shapiro_res <- shapiro.test(mydata$PairDifferences)
shapiro_table <- tidy(shapiro_res) %>%
select(statistic, p.value, method) %>%
gt() %>%
gt_theme_538(quiet = TRUE) %>%
tab_header(title = "Normality Test (Shapiro-Wilk)") %>%
fmt_number(columns = c(statistic, p.value), decimals = 4) %>%
tab_source_note(
source_note = "If the p-value is 0.05 or less, the number of pairs is fewer than 40, and the distribution of pair differences shows obvious non-normality or outliers, consider using the Wilcoxon Signed Rank Test results instead of the Paired-Samples t-Test results."
)
# ============================================================
# Reshape Data for Repeated-Measures Plot
# ============================================================
df_long <- mydata %>%
pivot_longer(cols = c(V1, V2),
names_to = "Measure",
values_to = "Value")
# ============================================================
# Repeated-Measures Boxplot (Interactive, with Means)
# ============================================================
group_means <- df_long %>%
group_by(Measure) %>%
summarise(mean_value = mean(Value), .groups = "drop")
boxplot_measures <- plot_ly() %>%
add_trace(
data = df_long,
x = ~Measure, y = ~Value,
type = "box",
boxpoints = "outliers",
marker = list(color = "red", size = 4),
line = list(color = "black"),
fillcolor = "royalblue",
name = ""
) %>%
add_trace(
data = group_means,
x = ~Measure, y = ~mean_value,
type = "scatter", mode = "markers",
marker = list(
symbol = "diamond", size = 9,
color = "black", line = list(color = "white", width = 1)
),
text = ~paste0("Mean = ", round(mean_value, 2)),
hoverinfo = "text",
name = "Group Mean"
) %>%
layout(
title = "Boxplot of Repeated Measures (V1 vs V2) with Means",
xaxis = list(title = "Measure"),
yaxis = list(title = "Value"),
showlegend = FALSE
)
# ============================================================
# Parametric Test (Paired-Samples t-Test)
# ============================================================
t_res <- t.test(mydata$V2, mydata$V1, paired = TRUE)
t_table <- tidy(t_res) %>%
select(statistic, parameter, p.value, conf.low, conf.high, method) %>%
gt() %>%
gt_theme_538(quiet = TRUE) %>%
tab_header(title = "Paired-Samples t-Test") %>%
fmt_number(columns = c(statistic, p.value, conf.low, conf.high), decimals = 4)
t_summary <- mydata %>%
select(V1, V2) %>%
summarise_all(list(Mean = mean, SD = sd)) %>%
gt() %>%
gt_theme_538(quiet = TRUE) %>%
tab_header(title = "Group Means and SDs (t-Test)") %>%
fmt_number(columns = everything(), decimals = 3)
# ============================================================
# Nonparametric Test (Wilcoxon Signed Rank)
# ============================================================
wilcox_res <- wilcox.test(mydata$V1, mydata$V2, paired = TRUE,
exact = FALSE)
wilcox_table <- tidy(wilcox_res) %>%
select(statistic, p.value, method) %>%
gt() %>%
gt_theme_538(quiet = TRUE) %>%
tab_header(title = "Wilcoxon Signed Rank Test") %>%
fmt_number(columns = c(statistic, p.value), decimals = 4)
wilcox_summary <- mydata %>%
select(V1, V2) %>%
summarise_all(list(Mean = mean, SD = sd)) %>%
gt() %>%
gt_theme_538(quiet = TRUE) %>%
tab_header(title = "Group Means and SDs (Wilcoxon)") %>%
fmt_number(columns = everything(), decimals = 3)
# ============================================================
# Results Summary (in specified order)
# ============================================================
hist_plot
desc_table
shapiro_table
boxplot_measures
t_table
t_summary
wilcox_table
wilcox_summary