This report uses Media Cloud data from 246 major English-language news outlets that primarily cover the United States. The graph compares weekly articles mentioning “artificial intelligence” and “immigration” from Jan. 1 to Sept. 1, 2026. Immigration received significantly more coverage early in the year, including a major January spike, but the gap narrowed around April and remained much smaller through September. The spike could reflect major news or policy developments, while “artificial intelligence” coverage remained more consistent throughout the period because it remains to be a topic of discussion. Coverage of “immigration” has really high peaks towards the beginning of the year.

CODE

############################################################
# 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")
library(tidyverse)
library(httr2)
library(lubridate)
library(kableExtra)

############################################################
# 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-01"
end_date <- "2026-09-01"

############################################################
# 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 an Interactive Plotly Line Graph
############################################################

# Install plotly if needed
if (!require("plotly")) install.packages("plotly")

# Load plotly
library(plotly)

Line_Graph <- plot_ly(
  data = weekly_counts,
  x = ~week,
  y = ~stories,
  color = ~topic,
  colors = "Set1",
  type = "scatter",
  mode = "lines+markers",
  
  # Create custom text for the hover window
  text = ~paste0(
    "Topic: ", topic,
    "<br>Story Count: ", format(stories, big.mark = ","),
    "<br>Week: ", format(week, "%B %d, %Y")
  ),
  
  hovertemplate = "%{text}<extra></extra>"
) |>
  layout(
    title = list(
      text = "Weekly Media Coverage Volume"
    ),
    xaxis = list(
      title = "Week"
    ),
    yaxis = list(
      title = "Number of Stories",
      tickformat = ","
    ),
    legend = list(
      title = list(
        text = "Topic"
      )
    ),
    hovermode = "x unified"
  )

Line_Graph