The results show that left- and right-leaning news outlets covered some of the same topics related to Donald Trump. Headlines mentioning the White House, Iran war, and Trump administration appeared consistently over several weeks. However, differences became more noticeable when looking at headlines about specific individuals other than Donald Trump.
To conduct this analysis, I broke the headlines into two-word phrases, known as bigrams, removed common stop words, and identified the 15 most frequently appearing phrases for each group.
The bar graph showed that “white house” was the most common bigram for both groups, appearing about 123 times in left-leaning headlines and about 92 times in right-leaning headlines. Other shared phrases included “supreme court” and “Iran war.”
The ridgeline plots showed how coverage changed over time. For example, “Natalie Harp” experienced a sharp spike in left-leaning headlines during August, while “El Sayed” appeared more consistently in right-leaning headlines. Another interesting finding was a spike in the phrase “9 11” around early September in both groups, possibly reflecting coverage surrounding the anniversary of the September 11 terrorist attacks. I used AI to help adapt the congressional text-mining scripts to this dataset, troubleshoot graphing issues, and help interpret the results. The graphs reveal differences in headline frequency and timing.
if (!require(tidyverse)) {
install.packages("tidyverse")
library(tidyverse)
}
download.file(
url = "https://github.com/drkblake/Data/raw/refs/heads/main/Trump_story_headlines.rds",
destfile = "Trump_story_headlines.rds",
mode = "wb"
)
headlines <- readRDS("Trump_story_headlines.rds")
# Create source ideology lookup table ----
source_ideology <- tribble(
~media_name, ~ideology,
# Left
"Talking Points Memo", "Left",
"Daily Kos", "Left",
"The New Replublic", "Left",
"Mother Jones", "Left",
"thenation.com", "Left",
"Raw Story", "Left",
"salon.com", "Left",
"Jezebel", "Left",
"slate.com", "Left",
"Truthout", "Left",
"colorlines.com", "Left",
"newsone.com", "Left",
"National Memo", "Left",
"TheRealNews.com", "Left",
"huffpost.com", "Left",
"AlterNet", "Left",
"vox.com", "Left",
"MintPress News", "Left",
"Mic", "Left",
"theintercept.com", "Left",
"theguardian.com", "Left",
"The Root", "Left",
# Right
"Hot Air", "Right",
"RedState", "Right",
"foxnews.com", "Right",
"National Review", "Right",
"NewsBusters", "Right",
"Daily Caller", "Right",
"PJ Media", "Right",
"breitbart.com", "Right",
"Townhall", "Right",
"CNS News", "Right",
"American Spectator", "Right",
"TheBlaze", "Right",
"newsmax.com", "Right",
"patriotpost.us", "Right",
"Daily Signal", "Right",
"One America News Network", "Right",
"Fox Business", "Right",
"shop.americanfreepress.net", "Right",
"National Catholic Register", "Right"
)
headlines_lr <- headlines %>%
inner_join(
source_ideology,
by = "media_name"
)
# Check the structure of the filtered dataset
glimpse(headlines_lr)
# Display the column names
names(headlines_lr)
# Count headlines by ideology
headlines_lr %>%
count(ideology)
# Preview the first six rows
head(headlines_lr)
# STEP 2: Install and load packages
packages <- c(
"tidytext",
"tidylo",
"ggplot2",
"dplyr",
"tidyr",
"stringr",
"lubridate",
"ggridges"
)
for (pkg in packages) {
if (!requireNamespace(pkg, quietly = TRUE)) {
install.packages(pkg)
}
library(pkg, character.only = TRUE)
}
# STEP 3: Create bigrams from headlines
n_size <- 2
bigrams <- headlines_lr %>%
mutate(
WeekStart = floor_date(
as.Date(publish_date),
unit = "week"
)
) %>%
unnest_tokens(
bigram,
title,
token = "ngrams",
n = n_size
)
# STEP 4: Remove low-information phrases
max_stopword_pct <- 50
bigrams_clean <- bigrams %>%
mutate(
words = str_split(bigram, " ")
) %>%
rowwise() %>%
mutate(
total_words = length(words),
stop_words_in_phrase =
sum(words %in% stop_words$word),
stop_word_pct =
stop_words_in_phrase / total_words * 100
) %>%
ungroup() %>%
filter(
stop_word_pct < max_stopword_pct
) %>%
select(-words, -total_words,
-stop_words_in_phrase, -stop_word_pct)
# STEP 5: Count bigrams by ideology
bigram_counts <- bigrams_clean %>%
count(
ideology,
bigram,
sort = TRUE
)
# STEP 6: Find the top 15 bigrams
# for each political orientation
top_bigrams <- bigram_counts %>%
group_by(ideology) %>%
slice_max(
n,
n = 15,
with_ties = FALSE
) %>%
ungroup()
# View the results
print(top_bigrams, n = 30)
# STEP 7: Create the bigram comparison graph
library(tidytext)
bigram_graph <- top_bigrams %>%
mutate(
bigram = reorder_within(bigram, n, ideology)
) %>%
ggplot(aes(x = n, y = bigram, fill = ideology)) +
geom_col() +
facet_wrap(~ideology, ncol = 1, scales = "free_y") +
scale_y_reordered() +
scale_fill_manual(
values = c("Left" = "steelblue",
"Right" = "firebrick")
) +
labs(
title = "Top 15 Bigrams in Trump-Related News Headlines",
subtitle = "Left-Leaning vs. Right-Leaning News Outlets",
x = "Number of Appearances",
y = "Bigram",
fill = "Ideology"
) +
theme_minimal()
print(bigram_graph)
# STEP 8: Count top bigrams by week
weekly_bigram_counts <- bigrams_clean %>%
semi_join(
top_bigrams,
by = c("ideology", "bigram")
) %>%
count(
ideology,
bigram,
WeekStart,
name = "weekly_count"
)
# Create a complete list of weeks
all_weeks <- seq(
min(bigrams_clean$WeekStart, na.rm = TRUE),
max(bigrams_clean$WeekStart, na.rm = TRUE),
by = "week"
)
# Fill missing weeks with zero
weekly_bigram_counts <- weekly_bigram_counts %>%
group_by(ideology, bigram) %>%
complete(
WeekStart = all_weeks,
fill = list(weekly_count = 0)
) %>%
ungroup()
# STEP 9: Left-leaning outlet ridgeline plot
library(ggridges)
left_weekly <- weekly_bigram_counts %>%
filter(ideology == "Left") %>%
left_join(
top_bigrams %>%
filter(ideology == "Left") %>%
select(bigram, n),
by = "bigram"
) %>%
mutate(
bigram = forcats::fct_reorder(bigram, n)
)
left_ridgeline <- ggplot(
left_weekly,
aes(
x = WeekStart,
y = weekly_count,
height = weekly_count,
group = bigram
)
) +
geom_ridgeline(
fill = "steelblue",
color = "steelblue",
alpha = 0.6,
scale = 0.9
) +
facet_wrap(
~bigram,
ncol = 1,
scales = "free_y"
) +
scale_x_date(date_labels = "%b %d") +
labs(
title = "Top Bigrams Over Time: Left-Leaning News Outlets",
x = "Week",
y = "Weekly Frequency"
) +
theme_minimal()
print(left_ridgeline)
# STEP 10: Right-leaning outlet ridgeline plot
right_weekly <- weekly_bigram_counts %>%
filter(ideology == "Right") %>%
left_join(
top_bigrams %>%
filter(ideology == "Right") %>%
select(bigram, n),
by = "bigram"
) %>%
mutate(
bigram = forcats::fct_reorder(bigram, n)
)
right_ridgeline <- ggplot(
right_weekly,
aes(
x = WeekStart,
y = weekly_count,
height = weekly_count,
group = bigram
)
) +
geom_ridgeline(
fill = "firebrick",
color = "firebrick",
alpha = 0.6,
scale = 0.9
) +
facet_wrap(
~bigram,
ncol = 1,
scales = "free_y"
) +
scale_x_date(date_labels = "%b %d") +
labs(
title = "Top Bigrams Over Time: Right-Leaning News Outlets",
x = "Week",
y = "Weekly Frequency"
) +
theme_minimal()
print(right_ridgeline)
# STEP 11: Fix left-leaning ridgeline plot
left_ridgeline <- ggplot(
left_weekly,
aes(
x = WeekStart,
y = bigram,
height = weekly_count,
group = bigram
)
) +
geom_ridgeline(
fill = "steelblue",
color = "steelblue",
alpha = 0.6,
scale = 0.05
) +
scale_x_date(date_labels = "%b %d") +
labs(
title = "Top Bigrams Over Time: Left-Leaning News Outlets",
x = "Week",
y = "Bigram"
) +
theme_minimal()
print(left_ridgeline)
# STEP 12: Fix right-leaning ridgeline plot
right_ridgeline <- ggplot(
right_weekly,
aes(
x = WeekStart,
y = bigram,
height = weekly_count,
group = bigram
)
) +
geom_ridgeline(
fill = "firebrick",
color = "firebrick",
alpha = 0.6,
scale = 0.05
) +
scale_x_date(date_labels = "%b %d") +
labs(
title = "Top Bigrams Over Time: Right-Leaning News Outlets",
x = "Week",
y = "Bigram"
) +
theme_minimal()
print(right_ridgeline)