The agenda-setting theory predicts that issues that media content feature more prominently will become more prominent in the minds of their audience. This process is limited by the resources and space available for creating and publishing media content. As a result, media outlets must decide which issues to feature less to free up resources, time, and space for issues the media may consider to be more newsworthy. Therefore, issues will compete with one another for prominence on the media agenda. A media outlet may cover one issue extensively, which pushes another issue lower on the media agenda, or off the agenda entirely. However, at other times, these issues can trade places ,with one alternately higher and lower than the other on the media agenda. The two issues can also hold about the same consistency or importance on the media agenda.
Based on what first-level agenda-setting theory says about this phenomenon, my project will compare the media agenda prominence of two issues: climate change (climate crisis) and nonrenewable energy supply (energy crisis). Specifically, the analysis will compare the number of stories per week that MediaCloud.org’s United States - National News Archive collected about each crisis between the week starting on January 4, 2026 and the week ending on October 10, 2026.
The results will enhance the theoretical understanding of how similar-but-distinct issues, being two crisis’ in America, compete for attention on the media agenda.
Average weekly coverage volume of the crisis’s surrounding energy and climate differed during the first 10 months of 2026.
Weekly coverage volume of the two crisis’s served as the dependent variable for the analysis. It was measured continuously as the number of stories published per week that included terms a MediaCloud.org query specified as indicated coverage of one or the other.
The independent variable was “crisis,” measured categorically as either “Climate Crisis” or “Nonrenewable Energy Crisis” and operationalized as containing keywords likely to be unique to stories about one war or the other.
Stories about the Climate Crisis were operationalized as stories matching the MediaCloud query:
climate_query <-
'(("climate change" OR "global warming" OR "climate crisis" OR climate OR environment OR "greenhouse gas" OR "greenhouse gases" OR carbon OR warming OR planetary) AND ("extreme weather" OR "heat wave" OR "heat waves" OR wildfire* OR hurricane* OR drought* OR flood* OR disaster OR crisis OR emissions OR "record heat"))'
Meanwhile, stories about the Energy Crisis were operationalized as stories matching this MediaCloud query:
energy_query <-
'(("energy crisis" OR "gas prices" OR "oil prices" OR "fuel costs" OR "crude oil" OR "oil supply" OR "natural gas price" OR OPEC OR "fossil fuel" OR "fossil fuels") AND (spike OR shock OR surge OR shortage OR inflation OR barrel OR crunch OR "cost of living" OR squeeze OR blackout OR rationing))'
Eventually, I will use a paired-samples t-test to assess the statistical significance of coverage volume variation between the two story topics.
The figure and table below summarizes each crisis’s weekly coverage volume across the period analyzed. It appears that, for the months of March, April, and May of 2026, coverage volume of the energy crisis exceeded coverage volume of the climate crisis. The climate crisis’ average weekly coverage volume exceeded the average weekly coverage volume for the energy crisis. The same was true for the median coverage volume.
| topic | Total_Stories | Weekly_Minimum | Weekly_Maximum | Weekly_Median | Weekly_Mean | Weekly_SD |
|---|---|---|---|---|---|---|
| Climate Crisis | 51089 | 957 | 1565 | 1272.0 | 1277.22 | 157.65 |
| Nonrenewable Energy Crisis | 42149 | 215 | 2911 | 934.5 | 1053.72 | 640.01 |
The likely explanation for the surge of coverage in the energy crisis is due to the Strait of Hormuz closing in March 2026. At this time, the climate crisis was still receiving about the same amount of coverage as it was on average.
Since the averages differ in the table, it seems likely that the data will support the hypothesis.
The analysis used a paired-samples t-test to assess whether, on average, the difference between each week’s coverage volume measures fell nonrandomly above or below zero. This box plot summarizes the distributions of climate crisis weekly story counts (V1) and the nonrenewable energy crisis weekly story counts (V2):
| Group Means and SDs (t-Test) | |||
| V1_Mean | V2_Mean | V1_SD | V2_SD |
|---|---|---|---|
| 1,277.225 | 1,053.725 | 157.648 | 640.013 |
According to the results, stories mentioning the climate change crisis outnumbered the stories mentioning the nonrenewable energy crisis by an average of 223.5 stories per week. The standard deviation for the stories mentioning the nonrenewable energy crisis (640) is much higher than that of the stories mentioning the climate crisis (157.6). The high standard deviation for the volume of nonrenewable energy crisis-related stories shows the dramatic change in volume relative to the climate crisis. Where the climate crisis-related stories have a steady, consistent baseline, stories surrounding the energy crisis appear much more event driven.
The paired differences showed a non-normal distribution (p < 0.05), according to a Shapiro-Wilk normality test. This makes sense, due to the massive swings in coverage volume of the nonrenewable energy crisis.
Due to the non-normal distribution shown according to a Shapiro-Wilk normality test, a Wilcox signed-rank rest was performed. Results from the Wilcox signed-rank test confirmed a statistically significant difference in weekly coverage volume with a p value of 0.02.
The paired samples t-test found the average of the pair differences to be statistically significant. The t value was -2.15, with an associated p value of 0.04. Therefore, the hypothesis that weekly coverage volumes between the two crises would differ if supported.
| Paired-Samples t-Test | |||||
| statistic | parameter | p.value | conf.low | conf.high | method |
|---|---|---|---|---|---|
| −2.1473 | 39 | 0.0380 | −434.0283 | −12.9717 | Paired t-test |
The main explanation for the periodic surges in coverage of the nonrenewable energy crisis relative to the climate crisis is closing of the Strait of Hormuz on February 28, 2026. Since this is the main source of the gasoline and oil that is imported into the United States, it makes sense that the coverage of the crisis would drastically increase during this time frame. As the Strait of Hormuz continued to remain closed, the timeliness of the coverage volume died down, making the line lower. There are still minor spikes as the year went on, specifically in September 2026 when crude oil reached its peak price, but it never reached the height of the original spike in March 2026.
The coverage surrounding the climate change crisis remained mostly steady, with the highest peaks being reached in February and June of 2026. In February, climate coverage likely peaked due to the high winter temperatures across the United States. In June, coverage likely peaked due to massive wildfires in Canada and California.
Here is the code used to produce 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 <- "2026-01-04"
end_date <- "2026-10-10"
############################################################
# STEP 4: Define Search Queries
############################################################
climate_query <-
'(("climate change" OR "global warming" OR "climate crisis" OR climate OR environment OR "greenhouse gas" OR "greenhouse gases" OR carbon OR warming OR planetary) AND ("extreme weather" OR "heat wave" OR "heat waves" OR wildfire OR wildfires OR hurricane OR hurricanes OR drought OR droughts OR floods OR flooding OR disaster OR crisis OR emissions OR "record heat"))'
energy_query <-
'(("energy crisis" OR "gas prices" OR "oil prices" OR "fuel costs" OR "crude oil" OR "oil supply" OR "natural gas price" OR OPEC OR "fossil fuel" OR "fossil fuels") AND (spike OR shock OR surge OR shortage OR inflation OR barrel OR crunch OR "cost of living" OR squeeze OR blackout OR rationing))'
############################################################
# STEP 5: Create Topic Definitions
############################################################
topics <- c(
"Climate Crisis" = climate_query,
"Nonrenewable Energy Crisis" = energy_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$Climate.Crisis
mydata$V2 <- mydata$Nonrenewable.Energy.Crisis
# ============================================================
# 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() %>%
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() %>%
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() %>%
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() %>%
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() %>%
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() %>%
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