Rationale

First-level agenda-setting theory proposes that issues receiving more prominent coverage in the news can become more prominent in audiences’ perceptions of what matters. News outlets have limited time, space, and reporting resources, so issues can compete for attention on the media agenda. Measuring coverage volume cannot establish what audiences think, but it can describe the relative prominence of different issues in news content.

This preliminary project compares U.S. national online news coverage of immigration, artificial intelligence (AI), and climate change. All three are consequential public issues, but they differ in their political, technological, and environmental implications. Examining their weekly coverage can reveal whether one topic consistently dominates or whether their relative prominence shifts over time.

The analysis examines stories indexed in Media Cloud’s United States - National collection between January 4 and October 10, 2026. The results will provide a starting point for a final project on competition among issues on the media agenda.

Hypothesis

Average weekly coverage volume will differ among immigration, artificial intelligence, and climate change during the period examined.

Variables & method

The dependent variable is weekly coverage volume, measured as the number of Media Cloud stories matching each topic’s search query in a given week. The independent variable is topic, a categorical variable with three levels: Immigration, Artificial Intelligence, and Climate Change. Each topic is operationalized through its own keyword query.

The search is restricted to Media Cloud’s United States - National news collection (collection ID 34412234). Daily counts are retrieved through the Media Cloud search API and summed into Sunday-starting weekly counts. The final week is incomplete because the collection period ends on a Saturday, and the first week may be incomplete depending on API date-boundary conventions; comparisons should account for this limitation. Matching search queries can overlap, and keyword matching is an imperfect proxy for substantive coverage. Counts are not adjusted for the total number of stories indexed in each week.

The initial analysis is descriptive. A later inferential analysis could compare repeated weekly measurements across topics, taking account of temporal dependence and differences in scale.

Results & discussion

The following interactive graph displays the number of matching stories per week for each of the three topics. Hover over a line to inspect individual weekly counts.

The table summarizes total coverage, weekly minimum and maximum, median, mean, and standard deviation for each topic.

Media Cloud Topic Summary Statistics
topic Total_Stories Weekly_Minimum Weekly_Maximum Weekly_Median Weekly_Mean Weekly_SD
Immigration 133717 2112 8249 2954 3342.93 1224.25
Artificial Intelligence 93991 1795 3504 2168 2349.78 474.04
Climate Change 37326 708 1253 927 933.15 117.76

Across the observed period, Immigration had the highest total matching-story count (133,717), while Climate Change had the lowest (37,326). Average weekly counts were 3,342.93 and 933.15 stories, respectively. The descriptive means differ across topics, consistent with the hypothesis at a descriptive level; this does not establish statistical significance. Changes in weekly coverage may reflect major news events or editorial priorities, but the count data alone cannot establish why those changes occurred. The graph can guide later examination of specific weeks and article content.

Because this is an initial descriptive analysis, no causal conclusions can be drawn about effects on public opinion. The three keyword queries differ in breadth, which may affect comparisons. A final project could refine the search terms, examine high-volume weeks, and test the weekly differences statistically.

Code used

The complete code used to collect the data, create the summary table, and produce the interactive graph appears below.

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

# Load the saved API key
readRenviron(path.expand("~/.Renviron"))

# Retrieve the key
mc_key <- trimws(Sys.getenv("MEDIACLOUD_KEY"))

# Verify the key exists
if (!nzchar(mc_key)) {
  stop("Media Cloud API key is missing.")
}

############################################################
# STEP 3: Define the Date Range
############################################################
start_date <- "2026-01-04"
end_date <- "2026-10-10"

############################################################
# STEP 4: Define Search Queries
############################################################
immigration_query <- '(immigration OR immigrant OR immigrants OR deportation OR "border security")'
ai_query <- '("artificial intelligence" OR "generative AI" OR ChatGPT OR OpenAI)'
climate_query <- '("climate change" OR "global warming" OR "climate crisis")'

############################################################
# STEP 5: Create Topic Definitions
############################################################
topics <- c(
  "Immigration" = immigration_query,
  "Artificial Intelligence" = ai_query,
  "Climate Change" = climate_query
)

############################################################
# STEP 6: 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(search_string, start_date, end_date)
  Sys.sleep(35)
  results |> mutate(topic = topic_name)
})

############################################################
# STEP 8: Examine the Downloaded Data
############################################################
stopifnot(nrow(topic_data) > 0)

############################################################
# STEP 9: Convert Daily Counts to Weekly Counts
############################################################
weekly_counts <- topic_data |>
  mutate(week = floor_date(date, unit = "week", week_start = 7)) |>
  group_by(topic, week) |>
  summarise(stories = sum(count, na.rm = TRUE), .groups = "drop")

############################################################
# STEP 10: Examine Weekly Counts
############################################################
stopifnot(nrow(weekly_counts) > 0)

############################################################
# 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"))

############################################################
# 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 = paste0(
    "<b>%{customdata}</b><br>",
    "Week: %{x|%Y-%m-%d}<br>",
    "Stories: %{text}<extra></extra>"
  )
) |>
  layout(
    title = "Weekly U.S. News Coverage Volume by Topic",
    hovermode = "x unified",
    xaxis = list(title = "Week"),
    yaxis = list(title = "Weekly Story Count", separatethousands = TRUE)
  )