This analysis examines 54,686 news story records from a MediaCloud dataset. The stories came from the United States–National collection, were filtered for English, mentioned “Donald Trump” in their text, and were published between August 3, 2026 and October 4, 2026. The headlines themselves do not necessarily mention Trump.
I tokenized the headlines into lowercase single words, or unigrams. I removed standard stop words, URLs, account mentions, social media artifacts, numbers, and single-letter tokens. I also removed selected generic words, including “Trump,” “Donald,” “president,” and common reporting terms.
Before counting and ranking, I used an AI assistant to combine certain similar words because my original attempt produced similar words within the top 25 most common terms. For example, my original attempt had separate entries within the list of common terms, such as “Republicans,” “Republican,” and “GOP.” I improved the script to group those types of words so the script would produce a more accurate list of common terms from this headline dataset. I applied an explicit dictionary to all retained word tokens. The dictionary combines the following terms:
The dictionary includes corresponding plural forms for consistency. All unlisted words retain their original lowercase forms. Grouping uses exact matches after tokenization, rather than automatic stemming or general synonym detection.
These are analyst-defined categories. Some combine singular and plural forms or alternative party labels, while others combine related but distinct concepts. For example, not every election is a midterm, and a judge is not the same thing as a court. The grouped labels make these choices visible. Terms such as “supreme” and “senate” remain separate, and the analysis does not reconstruct phrases such as “Supreme Court” or “White House.”
The analysis sums token occurrences within each group across the supplied story records, including repeated headlines. A headline containing both “GOP” and “Republicans” contributes two occurrences to Republican / GOP, not one story. Grouping preserves the total number of retained token occurrences but can change their ranking.
I select the 25 most frequent terms after grouping the full retained vocabulary, with alphabetical order breaking ties. The chart therefore shows a mixture of grouped categories and individual words. The exported dictionary and contribution table document the mapping and each original word’s contribution.
Figure 1. The 25 most frequent retained terms after dictionary grouping. Counts represent token occurrences.
After grouping, Republican / GOP is the most frequent retained term, with 4,204 occurrences. The chart and table below reflect the recalculated results rather than the original ungrouped ranking.
| Term | Occurrences |
|---|---|
| Republican / GOP | 4,204 |
| Elections / midterms | 3,769 |
| Courts / judges | 3,450 |
| iran | 3,142 |
| house | 2,811 |
| war | 2,189 |
| white | 1,983 |
| ai | 1,935 |
| Democrats | 1,672 |
| senate | 1,585 |
| supreme | 1,241 |
| administration | 1,215 |
| canada | 1,161 |
| china | 1,132 |
| oil | 1,092 |
| top | 1,064 |
| center | 1,044 |
| ice | 1,027 |
| deal | 999 |
| america | 989 |
| military | 926 |
| prices | 922 |
| voters | 894 |
| ap | 887 |
| primary | 853 |
The Republican / GOP and Democrats
categories consolidate the specified party labels, making their combined
frequency easier to compare. Elections / midterms
captures a broader electoral category, while Courts /
judges combines references to courts and judges. Each category
total adds all retained occurrences of its member words, including
occurrences outside the original top 25. The
term_contributions.csv file shows how much each word
contributes.
These groupings change the unit displayed in the chart. A grouped category may rank above an individual word because it combines several kinds of references. Its position should not be interpreted as the frequency of a single word or the number of separate stories about that topic.
Unigram counts also cannot show whether words occur together, whether a court reference is political, or whether a headline supports or criticizes a person or party. Individual headlines would need to be examined to assess those questions.
My exclusions deliberately shift attention toward topics. I removed “Trump,” “Donald,” and “president” because they primarily identify the collection’s central figure, along with generic reporting or presentation words such as “says,” “said,” “new,” “watch,” and “video.” I retained party labels, institutions, countries, and “administration” because they can carry political meaning. Both the exclusions and grouping dictionary are analytical choices that influence the resulting ranking.
Repeated exposure to these associations could contribute to framing or second-level agenda-setting. Still, frequency does not measure tone, endorsement, audience effects, or causation. These results describe this selected collection, rather than all news coverage or public opinion.
The following script reproduces the analysis. It is displayed for documentation without running a second time.
# Install missing packages and load libraries.
required_packages <- c(
"dplyr", "stringr", "tidytext", "ggplot2", "scales"
)
missing_packages <- required_packages[
!vapply(
required_packages,
requireNamespace,
logical(1),
quietly = TRUE
)
]
if (length(missing_packages) > 0) {
install.packages(
missing_packages,
repos = "https://cloud.r-project.org"
)
}
library(dplyr)
library(stringr)
library(tidytext)
library(ggplot2)
# Download the dataset if it is not already available.
data_file <- "Trump_story_headlines.rds"
if (!file.exists(data_file)) {
download.file(
url = paste0(
"https://github.com/drkblake/Data/raw/",
"refs/heads/main/Trump_story_headlines.rds"
),
destfile = data_file,
mode = "wb"
)
}
headlines <- readRDS(data_file)
if (!is.data.frame(headlines) ||
!"title" %in% names(headlines)) {
stop("The dataset must contain a column named 'title'.")
}
if (!is.character(headlines$title)) {
stop("The title column must contain text.")
}
# Remove blank titles, URLs, and account mentions.
# Preserve repeated headlines in the supplied story records.
clean_headlines <- headlines %>%
transmute(
story_id = row_number(),
title = title
) %>%
filter(
!is.na(title),
str_trim(title) != ""
) %>%
mutate(
title = str_replace_all(
title,
"https?://\\S+|www\\.\\S+",
" "
),
title = str_replace_all(
title,
"@[[:alnum:]_]+",
" "
),
title = str_replace_all(
title,
"[\u2018\u2019]",
"'"
),
# Convert possessives such as Trump's to Trump.
title = str_replace_all(
title,
regex(
"\\b([[:alpha:]]+)'s\\b",
ignore_case = TRUE
),
"\\1"
)
)
social_artifacts <- c(
"rt", "amp", "http", "https",
"www", "com", "t", "co"
)
# Exclude the search subject and generic reporting terms.
additional_words <- c(
"trump", "donald", "president",
"says", "said", "say",
"new", "news", "latest",
"live", "updates", "update",
"breaking", "watch", "video",
"photos", "photo"
)
# Split headlines into lowercase single-word tokens.
unigrams <- clean_headlines %>%
unnest_tokens(
word,
title,
token = "words",
to_lower = TRUE
) %>%
anti_join(
tidytext::stop_words,
by = "word"
) %>%
filter(
str_detect(word, "^[a-z]+$"),
nchar(word) > 1,
!word %in% social_artifacts
)
# Retain a baseline before additional exclusions.
before_additional <- unigrams %>%
count(word, sort = TRUE)
# Explicit grouping dictionary.
# Matches apply to entire tokens, not parts of words.
term_dictionary <- tibble::tribble(
~word, ~term,
"republican", "Republican / GOP",
"republicans", "Republican / GOP",
"gop", "Republican / GOP",
"democrat", "Democrats",
"democrats", "Democrats",
"midterm", "Elections / midterms",
"midterms", "Elections / midterms",
"election", "Elections / midterms",
"elections", "Elections / midterms",
"judge", "Courts / judges",
"judges", "Courts / judges",
"court", "Courts / judges",
"courts", "Courts / judges"
)
# Each original word must map to only one group.
stopifnot(!anyDuplicated(term_dictionary$word))
# Apply exclusions, then group all retained tokens.
# Words outside the dictionary remain unchanged.
grouped_tokens <- unigrams %>%
filter(!word %in% additional_words) %>%
left_join(term_dictionary, by = "word") %>%
mutate(term = coalesce(term, word))
# Record each original word's contribution to its group.
term_contributions <- grouped_tokens %>%
count(term, word, name = "n") %>%
arrange(term, desc(n), word)
# Count occurrences after grouping.
word_counts <- grouped_tokens %>%
count(word = term, name = "n") %>%
arrange(desc(n), word)
# Confirm that grouping preserves total occurrences.
stopifnot(sum(word_counts$n) == nrow(grouped_tokens))
# Select the top 25 after grouping the full vocabulary.
# Alphabetical order breaks ties reproducibly.
top25 <- word_counts %>%
slice_head(n = 25)
if (nrow(top25) < 25) {
stop("Fewer than 25 eligible terms remain.")
}
# Create the chart.
top25_plot <- ggplot(
top25,
aes(x = word, y = n)
) +
geom_col(
fill = "#21618C",
width = 0.75
) +
geom_text(
aes(label = scales::comma(n)),
hjust = -0.12,
size = 3.3
) +
scale_x_discrete(limits = rev(top25$word)) +
coord_flip() +
scale_y_continuous(
labels = scales::comma,
expand = expansion(mult = c(0, 0.16))
) +
labs(
title = "25 most common terms in Trump-related headlines",
subtitle = paste(
"Selected word forms and related terms grouped;",
"stop words and generic reporting terms removed"
),
x = NULL,
y = "Number of word occurrences",
caption = paste(
"Source: instructor-supplied MediaCloud headline file.",
"Counts sum token occurrences within each term, not unique stories."
)
) +
theme_minimal(base_size = 12) +
theme(
panel.grid.major.y = element_blank(),
plot.title = element_text(face = "bold"),
plot.caption = element_text(
hjust = 0,
size = 8
),
plot.background = element_rect(
fill = "white",
color = NA
)
)
# Save results in the document's working folder.
write.csv(
word_counts,
"grouped_term_counts.csv",
row.names = FALSE
)
write.csv(
top25,
"top25_grouped_terms.csv",
row.names = FALSE
)
write.csv(
term_dictionary,
"term_dictionary.csv",
row.names = FALSE
)
write.csv(
term_contributions,
"term_contributions.csv",
row.names = FALSE
)
ggsave(
filename = "top25_grouped_terms.png",
plot = top25_plot,
width = 11,
height = 8.5,
dpi = 160,
bg = "white"
)