Rationale

According to McCombs and Shaw, agenda-setting theory posits that issues that receive greater attention in the news media often become more prominent in the public mind. In first-level agenda-setting, topics compete for salience in the media agenda, where differences in media attention often shape what audiences perceive as important issues. Therefore, examining differences in coverage volume between vaccine-preventable outbreaks and food recalls provides a means of comparing topic salience in the media agenda. 

Using the phenomenon as a guide, my project will compare the media agenda salience of two issues: vaccine-preventable disease outbreaks and food recalls and contamination events. Specifically, this analysis will compare the number of stories per week that MediaCloud.org’s United States - National news archive collected for each topic over one year, from October 19, 2025, through October 18, 2026.

The results will enhance understanding of how two human health issues compete for attention in the media agenda.

Hypothesis

Between October 2025 and October 2026, average weekly coverage volume differed across vaccine-preventable disease outbreaks and food recall and contamination events. 

Variables and method

The independent variable was public health issue type, categorized as either vaccine-preventable disease outbreaks or food recalls and contamination events. The dependent variable was media salience, operationalized as the weekly number of news stories retrieved via MediaCloud.org queries for each issue. The weeks analyzed were October 19, 2025, through October 18, 2026.

Stories about the vaccine-preventable disease outbreaks were operationalized with the following MediaCloud query:

vaccine_query <- “((measles OR mumps OR pertussis OR whooping cough) AND (outbreak* OR cases OR vaccine* OR infection* OR transmission OR MMR OR measles vaccine))”

Stories about the food recalls and contamination events were operationalized with the following MediaCloud query:

food_query <- “((cyclospora OR salmonella OR listeria OR e.coli) AND (food recall* OR recalled food OR recall OR recalls OR contamination OR foodborne illness OR food safety OR produce))”

Eventually, I will use a paired-samples t-test to assess the statistical significance of the difference in coverage volume between the two topics.

Results and discussion

The figure and table below display each topic’s weekly coverage volume across the period analyzed. Food recalls received substantially more media attention than vaccine-preventable disease outbreaks. Food recall coverage surged in July and August 2026, reaching its highest level during the study period. Vaccine-preventable disease outbreak coverage remained relatively low and stable, with a notable spike occurring in early January 2026. Overall, food recalls and contamination events received more than twice as much coverage as vaccine-preventable disease outbreaks.

Media Cloud Topic Summary Statistics
topic Total_Stories Weekly_Minimum Weekly_Maximum Weekly_Median Weekly_Mean Weekly_SD
Food recall and contamination events 3515 1 465 28 66.32 97.65
Vaccine-preventable disease outbreaks 1407 1 121 23 26.55 18.26

Paired-Samples T-tests

This analysis examined media coverage from October 2025 to October 2026. Preliminary analysis reveals a notable increase in coverage of food recall and contamination events beginning around June 21, 2026. This shift had the potential to influence overall results; the data set was divided into pre- and post-June 21, 2026 periods, and separate paired-samples t-tests were conducted for each period.

The results and data below summarize the distributions of weekly story counts between vaccine-preventable disease outbreaks (V1) and food recall and contamination events (V2).

Paired-Samples T-test 1: 10-19-2025 through 6-21-2026

The paired-samples t-test assumptions were not met for this period (October 19, 2025 to June 21, 2026), resulting in a non-normal distribution of pairs and a p-value of 0.0004 on the normality test. Therefore, the Wilcoxon signed-rank test was used to analyze the period instead.

The test showed no statistically significant difference in weekly coverage for these topics (p-value = .8122). The average weekly coverage for the topics of food recalls and contamination events (M=28.77, SD=17.60) and vaccine-preventable disease outbreaks (M=29.83, SD=18.93) was nearly identical. Thus, the hypothesis was not supported in this period, suggesting that both topics occupied similar positions on the media agenda prior to June 2026.

One notable finding is that although the Wilcoxon signed-rank test indicated no significant difference in overall topic salience during the period, the distribution of pair differences indicates that the two topics frequently alternated in dominance over the other. Weekly coverage levels crossed multiple times with one topic receiving greater media attention than the other, despite having nearly identical averages observed in the data set.

Normality Test (Shapiro-Wilk)
statistic p.value method
0.8612 0.0004 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.
Wilcoxon Signed Rank Test
statistic p.value method
330.0000 0.8122 Wilcoxon signed rank test with continuity correction
Group Means and SDs (Wilcoxon)
V1_Mean V2_Mean V1_SD V2_SD
29.829 28.771 18.933 17.597

Paired-Samples T-test 2: 6-21-2026 through 10-18-2026

The paired-samples t-test assumptions were not met for this period (June 21, 2026 to October 18, 2026), resulting in a non-normal distribution of pairs and a p-value of 0.0118 on the normality test. Therefore, the Wilcoxon signed-rank test was used to analyze the period instead.

The test revealed a statistically significant difference in weekly coverage between the two topics (p-value = 0.0024). Food recall and contamination events (M=139.33, SD=141.43) received substantially greater weekly coverage than vaccine-preventable disease outbreaks (M=20.17, SD=15.44). Consistent with first-level agenda-setting theory, these findings suggest that food recalls were a more salient topic in the media during this period. These findings support the hypothesis for this period, given the notable jump in food recall and contamination events.

Normality Test (Shapiro-Wilk)
statistic p.value method
0.8591 0.0118 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.
Wilcoxon Signed Rank Test
statistic p.value method
12.0000 0.0024 Wilcoxon signed rank test with continuity correction
Group Means and SDs (Wilcoxon)
V1_Mean V2_Mean V1_SD V2_SD
20.167 139.333 15.436 141.435

Discussion

The findings suggest that differences in salience between the two topics were not consistent throughout the study period. Prior to June 2026, vaccine-preventable diseases and outbreaks received media coverage comparable to that of food recall and contamination events. However, a notable increase in coverage of food recall and contamination events in the latter part of the period produced a significant growth in coverage volume.

The surge appears to coincide with extensive news coverage of the Cyclospora outbreak linked to Taylor Farms, with sustained media attention throughout July 2026 and into September 2026. These findings suggest that issue salience can shift rapidly during major public and human health events, rather than showing a significant difference throughout the year. The results indicate the food recall and contamination events became more salient only in periods of heightened newsworthiness.

Code:

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-10-19"
end_date   <- "2026-10-18"

############################################################
# STEP 4: Define Search Queries
############################################################

vaccine_query <-
  "((measles OR mumps OR pertussis OR whooping cough) AND
(outbreak* OR cases OR vaccine* OR infection* OR transmission OR MMR OR measles vaccine))"

food_query <-
  "((cyclospora OR salmonella OR listeria OR e.coli) 
AND (food recall* OR recalled food OR recall OR recalls OR contamination OR foodborne illness OR food safety OR produce))"

############################################################
# STEP 5: Create Topic Definitions
############################################################

topics <- c(
  "Vaccine-preventable disease outbreaks" = vaccine_query,
  "Food recall and contamination events" = food_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

#####################################

weekly_counts_wide <-
  weekly_counts %>%
  mutate(
    topic = make.names(topic)
  ) %>%
  select(week, topic, stories) %>%
  pivot_wider(
    names_from  = topic,
    values_from = stories
  )

##########################################################

# ============================================================
#  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$Vaccine.preventable.disease.outbreaks
mydata$V2 <- mydata$Food.recall.and.contamination.events

mydata <- mydata %>%
filter(week < as.Date("2026-06-21"))

# ============================================================
#  Compute Pair Differences
# ============================================================
mydata$PairDifferences <- mydata$V2 - mydata$V1

# ============================================================
#  Interactive Histogram of Pair Differences
# ============================================================
hist_plot <- ggplot(
  mydata,
  aes(x = PairDifferences)
) +
  geom_histogram(
    bins = 10,
    fill = "#1f78b4",
    color = "black"
  ) +
  geom_vline(
    xintercept = mean(mydata$PairDifferences, na.rm = TRUE),
    color = "red",
    linetype = "dashed",
    linewidth = 1
  ) +
  labs(
    title = "Distribution of Pair Differences",
    x = "Pair Differences",
    y = "Count"
  ) +
  theme_minimal()

hist_plot
# ============================================================
#  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

####################
# Testing second period
####################

# ============================================================
#  Data Import
# ============================================================
mydata <- weekly_counts_wide

# Specify the two variables involved
mydata$V1 <- mydata$Vaccine.preventable.disease.outbreaks
mydata$V2 <- mydata$Food.recall.and.contamination.events

mydata <-mydata %>%
  filter(week >= as.Date("2026-06-21"))

# ============================================================
#  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