This report examines the frequency and timing of two-word phrases (bigrams) in news headlines about Donald Trump from left- and right-leaning media outlets.
[The purpose of this analysis was to examine differences in the two-word phrases, or bigrams, used in news headlines about Donald Trump by left- and right-leaning media outlets.
The first graph shows that “white house” was the most frequently occurring bigram for both groups. Other phrases, including “supreme court,” “donald trump,” and “iran war,” appeared frequently in both groups, suggesting similarities in the topics being covered. However, differences were also noticeable. Left-leaning outlets frequently used phrases such as “op ed” and “trump administration,” while right-leaning outlets included “news summary” and “executive news.”
The ridgeline graphs illustrate how the frequency of these phrases changed across different weeks. Some phrases remained relatively consistent, while others experienced noticeable increases or decreases. These patterns suggest that particular news events may have influenced which topics received attention at different times. However, because the ridgeline graphs use different vertical scales, the heights of individual phrases should not be directly compared.
An important limitation is that frequently occurring phrases do not necessarily indicate political bias. For example, phrases such as “news summary” or “guardian view” may reflect recurring headline formats rather than ideological differences.
I used ChatGPT to help adapt the R scripts from our course lessons to the headline dataset, troubleshoot coding errors, create visualizations, and organize the results into an R Markdown report. I also used AI to help interpret the findings and refine my written discussion. Overall, this assignment demonstrated how text mining can identify patterns in political news coverage while also highlighting the importance of critically evaluating those patterns.]
The following is the complete R script used to prepare the data and generate the three graphs in this report.
# Report 9: Prepare Headline Data ----
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"
)
# Report 9: Bigram Analysis ----
# Prepare headline data
headline_text <- headlines_lr %>%
select(publish_date, title, ideology) %>%
filter(!is.na(title), !is.na(ideology)) %>%
mutate(
publish_date = as.Date(publish_date),
title = str_replace_all(title, "’", "'")
)
# Create two-word phrases
headline_bigrams <- headline_text %>%
unnest_tokens(
bigram,
title,
token = "ngrams",
n = 2
)
# Remove phrases containing stop words
headline_bigrams <- headline_bigrams %>%
separate(
bigram,
into = c("word1", "word2"),
sep = " ",
remove = FALSE
) %>%
filter(
!word1 %in% stop_words$word,
!word2 %in% stop_words$word
) %>%
select(-word1, -word2)
# Count bigrams by ideology
ideology_bigram_counts <- headline_bigrams %>%
count(ideology, bigram, sort = TRUE)
# Identify the 15 most frequent for each ideology
top_ideology_bigrams <- ideology_bigram_counts %>%
group_by(ideology) %>%
slice_max(n, n = 15, with_ties = FALSE) %>%
ungroup()
# Required Graph 1: Left vs. Right Bigram Frequency
bigram_comparison_chart <- top_ideology_bigrams %>%
ggplot(
aes(
x = reorder_within(bigram, n, ideology),
y = n,
fill = ideology
)
) +
geom_col(show.legend = FALSE) +
coord_flip() +
facet_wrap(~ ideology, scales = "free_y") +
scale_x_reordered() +
labs(
title = "Most Frequent Bigrams in Trump News Headlines",
subtitle = "Comparison of Left- and Right-Leaning News Outlets",
x = "Bigram",
y = "Frequency"
) +
theme_minimal()
# Report 9: Weekly Bigram Ridgeline Graphs ----
# Select the top 15 bigrams for each ideology
top_weekly_bigrams <- ideology_bigram_counts %>%
group_by(ideology) %>%
slice_max(n, n = 15, with_ties = FALSE) %>%
ungroup()
# Count bigrams by week
weekly_headline_bigrams <- headline_bigrams %>%
mutate(
WeekStart = floor_date(publish_date, unit = "week", week_start = 1)
) %>%
semi_join(
top_weekly_bigrams,
by = c("ideology", "bigram")
) %>%
count(ideology, bigram, WeekStart, name = "weekly_count") %>%
group_by(ideology, bigram) %>%
complete(
WeekStart = seq(min(WeekStart), max(WeekStart), by = "week"),
fill = list(weekly_count = 0)
) %>%
ungroup()
# Graph 2: Left-Leaning News Outlets
left_order <- top_weekly_bigrams %>%
filter(ideology == "Left") %>%
arrange(desc(n)) %>%
pull(bigram)
left_weekly <- weekly_headline_bigrams %>%
filter(ideology == "Left") %>%
mutate(bigram = factor(bigram, levels = left_order))
left_ridgeline <- ggplot(
left_weekly,
aes(
x = WeekStart,
y = weekly_count,
height = weekly_count
)
) +
geom_ridgeline(
fill = "steelblue",
color = "white",
alpha = 0.8,
scale = 1
) +
facet_wrap(
~ bigram,
ncol = 1,
scales = "free_y",
strip.position = "right"
) +
labs(
title = "Weekly Volume of Top Bigrams: Left-Leaning Outlets",
x = "Week",
y = NULL
) +
theme_ridges() +
theme(
strip.text.y.right = element_text(size = 10, angle = 0),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
panel.spacing = unit(0.15, "lines"),
legend.position = "none"
)
# Graph 3: Right-Leaning News Outlets
right_order <- top_weekly_bigrams %>%
filter(ideology == "Right") %>%
arrange(desc(n)) %>%
pull(bigram)
right_weekly <- weekly_headline_bigrams %>%
filter(ideology == "Right") %>%
mutate(bigram = factor(bigram, levels = right_order))
right_ridgeline <- ggplot(
right_weekly,
aes(
x = WeekStart,
y = weekly_count,
height = weekly_count
)
) +
geom_ridgeline(
fill = "firebrick",
color = "white",
alpha = 0.8,
scale = 1
) +
facet_wrap(
~ bigram,
ncol = 1,
scales = "free_y",
strip.position = "right"
) +
labs(
title = "Weekly Volume of Top Bigrams: Right-Leaning Outlets",
x = "Week",
y = NULL
) +
theme_ridges() +
theme(
strip.text.y.right = element_text(size = 10, angle = 0),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
panel.spacing = unit(0.15, "lines"),
legend.position = "none"
)