Rationale

Framing theory explains how the media influences public understanding of an event by emphasizing certain details, perspectives, and information over others. News stories may focus on different aspects of a case, such as the people involved, the alleged crime, personal background, or public reaction. These choices can influence how audiences understand the people and events being covered.

This project applies framing theory to U.S. news coverage involving Nolan Wells and Karmelo Anthony. Specifically, it examines two framing approaches: the Personal-Context Frame and the Legal-Accountability Frame. The Personal-Context Frame emphasizes personal backgrounds, relationships, and circumstances surrounding the individuals involved, while the Legal-Accountability Frame emphasizes legal proceedings, investigations, and questions of responsibility.

By examining the weekly volume of stories associated with these frames, this project investigates which framing approach receives greater prominence in the U.S. news coverage and how that prominence changes over time. The analysis uses MediaCloud.org’s U.S. news archive to compare stories identified through searches associated with the two frames between the week starting on April 5, 2025 and the week ending on October. 17, 2026.

Comparing the frequency of these frames can help identify which perspectives receive greater visibility in news coverage. Although story counts cannot establish how audiences interpret the cases, they provide a useful starting point for understanding the relative prominence of different framing approaches in U.S. news media.

Hypothesis

The average weekly coverage volume of the Personal-Context Frame will differ from the average weekly coverage of the Legal-Accountability Frame in the U.S. news coverage of the Nolan Wells and Karmelo Anthony cases.

Variables & method

This project used framing theory to compare the Personal-Context Frame and Legal-Accountability Frame in U.S. news coverage involving Nolan Wells and Karmelo Anthony. The primary variable examined was frame type, which was categorized as either Personal-Context or Legal-Accountability. Weekly coverage volume was the dependent variable, measured by the number of stories matching each frame’s MediaCloud.org search query per week.

Descriptive statistics, including total stories, weekly means, and medians, were used to compare the frequency of the two frames. A time-series graph illustrated changes in coverage over time. The results were interpreted to determine which frame received greater prominence in news coverage.

Results & discussion

The results from the figure and table below indicate that the Personal-Context Frame received more U.S. news coverage than the Legal-Accountability Frame during the period analyzed. The Personal-Context Frame accounted for 409 stories, compared with 126 stories for the Legal-Accountability Frame. The average weekly coverage was 5.05 stories for the Personal-Context Frame and 1.56 stories for the legal-Accountability Frame. The median weekly coverage was four stories for the Personal-Context Frame and one story for the Legal-Accountability Frame.

The graph shows several peaks in Personal-Context coverage, reaching a maximum of 21 stories in one week, while the Legal-Accountability Frame peaked at 10 stories. Both frames also experienced lows, with some weeks recording zero stories. However, the Personal-Context Frame generally maintained higher coverage throughout the period.

From a framing-theory perspective, these findings suggest that personal context received greater prominence in U.S. news coverage involving Nolan Wells and Karmelo Anthony. The peaks may reflect periods of increased media attention, while the lows suggest that interest in these frames varied over time. Although coverage fluctuated over time, the higher average and median for the Personal-Context Frame support the hypothesis so far. Further analysis of the articles would be needed to determine what events contributed to these changes and how each frame presented the cases.

Media Cloud Topic Summary Statistics
topic Total_Stories Weekly_Minimum Weekly_Maximum Weekly_Median Weekly_Mean Weekly_SD
Personal-Context Frame 409 0 21 4 5.05 3.85
Legal-Accountability Frame 126 0 10 1 1.56 1.92

Code

Here is the code that produced the plot

############################################################
# 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-04-05"
end_date <- "2026-10-17"

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

personal_context_query <-
  "((Nolan Wells OR Karmelo Anthony) AND
  (friends OR family OR background OR relationships OR bullying) AND
   (community OR circumstances OR victim OR school OR son OR teen* OR black
   OR white))"

legal_accountability_query <-
  "((Nolan Wells OR Karmelo Anthony) AND
    (investigation* OR evidence OR court OR trial* OR police OR charges) AND
     (verdict* OR sentencing* OR responsibility OR prosecutors OR self-defense*
   OR murder OR death* OR guilty))"

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

topics <- c(
  "Personal-Context Frame" = personal_context_query,
  "Legal-Accountability Frame" = legal_accountability_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