Rationale

Framing theory says that certain issues featured in news content will often be decoded by an audience in different ways, depending on the framing methods used. This happens when the same event is covered from multiple different perspectives. One way to track a variation in views is by looking into usage of certain keywords and phrases.

Drawing on what framing theory says about this phenomenon, my project will compare the media framing of deportation in 2025. I specifically want to track two different perspectives on the issue: the “humanitarian view” (seeing ICE raids and deportation as a negative), and the “goal-focused view” (seeing ICE raids and deportation as a positive success or a goal to reach). The analysis will compare the number of stories per week that APNews.com published using one framing perspective verses the other between Jan. 1 and Sept. 30, 2025.

The results will enhance theoretical understanding that one issue can be portrayed in two vastly different ways - even by the same media outlet - simply depending on words and phrases used within the story.

Hypothesis

Weekly APNews.com coverage of ICE raids and deportation was more likely to be “goal-focused” during the first nine months of 2025.

Variables & method

Weekly APNews.com coverage volume of “ICE raids” or “deportation” served as the analysis’s dependent variable. It was measured continuously as the number of stories published per week. The independent variable was “story perspective” – the two perspectives being a “humanitarian” or “goal-focused” view on the topic and operationalized as containing key words or phrases unique to the perspective.

Key words and phrases used to identify the “humanitarian view” were: “family separation”, “children left behind”, “lower-income”, “vulnerable populations”, “fear”, “labor shortage”, “protest”, “advocacy”, “inhumane” and “trauma”. Key words and phrases used to identify the “goal-focused view” were: “illegal”, “illegal aliens”, “criminals”, “threat”, “security threat”, “enforcing”, “immigration laws”, “deportation”, “border”, “border security” and “violent”.

Eventually, I will use a paired-samples t-test to assess the statistical significance of coverage volume variation between the two story perspectives.

Results & discussion

The figure below summarizes each perspective’s weekly coverage volume across the period analyzed. It appears that, for most weeks, APNews.com stories about ICE raids and deportation were positively leaning, rather than negatively leaning. Most articles framed the events as successful rather than harmful.

The most notable differences in volume appeared during weeks 5 and 26, which was in early February and the middle of June. 307 of the articles were “goal-focused” and 42 were “humanitarian focused”, showing that one frame was used more often than the other.

While the coverage of both perspectives fluctuates similarly, the amount of “goal-focused” coverage is consistently 4-7 times higher each week. Week 17 is the only week with equivalent perspectives (1 to 1) which doesn’t make much of a difference.

According to data reviewed by The Guardian, during the first 22 days of February ICE made more arrests than any month in the previous seven years. In June, ICE’s worksite enforcement became a notable flashpoint, leading to many protests around the country. This is consistet with the data.

Overall, the results suggest not only differing levels of APNews.com coverage about the two perspectives, but also possible links between the method of framing used in each story.


Code:

# ============================================
# APNews text analysis (Framing Theory version)
# ============================================

# ============================================
# --- Load required libraries ---
# ============================================

if (!require("tidyverse")) install.packages("tidyverse")
if (!require("tidytext")) install.packages("tidytext")

library(tidyverse)
library(tidytext)

# ============================================
# --- Load the APNews data ---
# ============================================

# Read the data from the web
FetchedData <- readRDS(url("https://github.com/drkblake/Data/raw/refs/heads/main/APNews.rds"))
# Save the data on your computer
saveRDS(FetchedData, file = "APNews.rds")
# remove the downloaded data from the environment
rm (FetchedData)

APNews <- readRDS("APNews.rds")

# ============================================
# --- Define and apply FilterTopic ---
# ============================================

# --- Define FilterTopic phrases ---
FilterTopic_phrases <- c(
  "ICE raids",
  "ICE",
  "Immigration",
  "Immigration enforcement",
  "Undocumented immigrants",
  "Border security",
  "Deportation",
  "Homeland Security"
)

# --- Escape regex special characters ---
escaped_FilterTopic <- str_replace_all(
  FilterTopic_phrases,
  "([\\^$.|?*+()\\[\\]{}\\\\])",
  "\\\\\\1"
)

# --- Build whole-word/phrase regex pattern ---
FilterTopic_pattern <- paste0("\\b", escaped_FilterTopic, "\\b", collapse = "|")

# --- Flag stories matching the FilterTopic ---
APNews <- APNews %>%
  mutate(
    Full.Text.clean = str_squish(Full.Text),
    FilterTopic = if_else(
      str_detect(Full.Text.clean, regex(FilterTopic_pattern, ignore_case = TRUE)),
      "Yes",
      "No"
    )
  )

# --- Create a TopicNews data frame consisting only of FilterTopic stories ---
TopicNews <- APNews %>%
  filter(FilterTopic == "Yes")

# ============================================
# --- Flag Topic1-related stories (within TopicNews) ---
# ============================================

# --- Define Topic1 phrases ---
phrases <- c(
  "family separation",
  "children left behind",
  "lower-income",
  "vulnerable populations",
  "fear",
  "labor shortage",
  "protest",
  "advocacy",
  "inhumane",
  "trauma"
)

# --- Escape regex special characters ---
escaped_phrases <- str_replace_all(
  phrases,
  "([\\^$.|?*+()\\[\\]{}\\\\])",
  "\\\\\\1"
)

# --- Build pattern and apply matching ---
pattern <- paste0("\\b", escaped_phrases, "\\b", collapse = "|")

TopicNews <- TopicNews %>%
  mutate(
    Topic1 = if_else(
      str_detect(Full.Text.clean, regex(pattern, ignore_case = TRUE)),
      "Yes",
      "No"
    )
  )

# ============================================
# --- Flag Topic2-related stories (within TopicNews) ---
# ============================================

# --- Define Topic2 phrases ---
phrases <- c(
  "illegal",
  "illegal aliens",
  "criminals",
  "threat",
  "security threat",
  "enforcing",
  "immigration laws",
  "deportation",
  "border",
  "border security",
  "violent"
)

# --- Escape regex special characters ---
escaped_phrases <- str_replace_all(
  phrases,
  "([\\^$.|?*+()\\[\\]{}\\\\])",
  "\\\\\\1"
)

# --- Build pattern and apply matching ---
pattern <- paste0("\\b", escaped_phrases, "\\b", collapse = "|")

TopicNews <- TopicNews %>%
  mutate(
    Topic2 = if_else(
      str_detect(Full.Text.clean, regex(pattern, ignore_case = TRUE)),
      "Yes",
      "No"
    )
  )

# ============================================
# --- Visualize weekly counts of Topic1- and Topic2-related stories ---
# ============================================

if (!require("plotly")) install.packages("plotly")
library(plotly)

# --- Summarize weekly counts for Topic1 = "Yes" ---
Topic1_weekly <- TopicNews %>%
  filter(Topic1 == "Yes") %>%
  group_by(Week) %>%
  summarize(Count = n(), .groups = "drop") %>%
  mutate(Topic = "Humanitarian View") # Note custom Topic1 label

# --- Summarize weekly counts for Topic2 = "Yes" ---
Topic2_weekly <- TopicNews %>%
  filter(Topic2 == "Yes") %>%
  group_by(Week) %>%
  summarize(Count = n(), .groups = "drop") %>%
  mutate(Topic = "Goal-focused View") # Note custom Topic2 label

# --- Combine both summaries into one data frame ---
Weekly_counts <- bind_rows(Topic2_weekly, Topic1_weekly)

# --- Fill in missing combinations with zero counts ---
Weekly_counts <- Weekly_counts %>%
  tidyr::complete(
    Topic,
    Week = full_seq(range(Week), 1),  # generate all week numbers
    fill = list(Count = 0)
  ) %>%
  arrange(Topic, Week)

# --- Create interactive plotly line chart ---
FR1 <- plot_ly(
  data = Weekly_counts,
  x = ~Week,
  y = ~Count,
  color = ~Topic,
  colors = c("steelblue", "firebrick"),
  type = "scatter",
  mode = "lines+markers",
  line = list(width = 2),
  marker = list(size = 6)
) %>%
  layout(
    title = "Weekly Counts of Topic1- and Topic2-Related Stories within the FilterTopic Dataset",
    xaxis = list(
      title = "Week Number (starting with Week 1 of 2025)",
      dtick = 1
    ),
    yaxis = list(title = "Number of Articles"),
    legend = list(title = list(text = "Topic")),
    hovermode = "x unified"
  )

# ============================================
# --- Show the chart ---
# ============================================

FR1