Among the most widely known social media apps, X uniquely enables individuals to easily share their thoughts, opinions, and news. Political figures in particular have taken to the platform to share updates on policy, making it easier for the public to be informed about where politicians stand on such matters. But what factors determine which policy issues political leaders discuss more frequently than others?
Conceptually grounded in the First-Level Agenda-Setting and Framing media theories, this study explores which of three topics individual members of the current U.S. Congress have posted about most often on their official X (formerly Twitter) accounts. The topics in question are Immigration, Economy, and the order to release the Epstein Files. Drawing upon the two media theories, the study examines which topics members have prioritized, how members have constructed narratives around the topics, and how political party affiliation and external political developments have influenced topic prioritization and narrative construction over time.
The data for the study consist of all X.com posts by members of the 119th United States Congress since Jan. 1, 2025. As of Jan. 31, 2026, the analysis had collected 483,132 posts. The Scholars Week poster would include posts collected through March 21, the week preceding the poster session. The posts are being gathered via a Brandwatch query made possible by the MTSU School of Journalism and Strategic Media’s Social Media Insights lab. A custom R script was developed to compile and analyze the data. Posts from the first two weeks of September 2025 are missing, due to a failure in the connection between X.com and Brandwatch. We are investigating ways to acquire the missing posts. We are considering deploying inferential statistical analysis to assess volume differences by topic and party. However, inferential statistics may provide little additional value to the analysis, given that the dataset includes all Congressional X.com posts rather than a mere sample of the posts.
This chart visualizes data collected so far.
# ============================================
# Congressional X Posts text analysis
# ============================================
# ============================================
# --- Load required libraries ---
# ============================================
if (!require("tidyverse")) install.packages("tidyverse")
if (!require("tidytext")) install.packages("tidytext")
library(tidyverse)
library(tidytext)
# ============================================
# --- Load the data from project folder ---
# ============================================
Data <- readRDS("Latest119thData.rds")
# ============================================
# --- Add "Week" variable ---
# ============================================
Data <- Data %>%
mutate(Date = as.Date(Date)) %>%
mutate(
WeekStart = lubridate::floor_date(Date, unit = "week", week_start = 1)
)
first_week <- min(Data$WeekStart, na.rm = TRUE)
Data <- Data %>%
mutate(Week = as.integer(difftime(WeekStart, first_week, units = "weeks")) + 1)
# ============================================
# --- Combine Democrats and independents ---
# ============================================
Data <- Data %>%
mutate(
party = case_when(
party %in% c("Democrat", "Independent") ~ "Dem/Ind",
party == "Republican" ~ "Rep",
TRUE ~ "error"
)
)
# ============================================
# --- Optional prefilter ---
# (unchanged; left commented)
# ============================================
#
# phrases_prefilter <- c(
# "Trump",
# "the president",
# "our president",
# "White House",
# "Oval Office"
# )
#
# escaped_phrases_prefilter <- str_replace_all(
# phrases_prefilter,
# "([\\^$.|?*+()\\[\\]{}\\\\])",
# "\\\\\\1"
# )
#
# pattern_prefilter <- paste0("\\b", escaped_phrases_prefilter, "\\b", collapse = "|")
#
# Data <- Data %>%
# mutate(
# Full.Text.clean = str_squish(Full.Text),
# Prefilter = if_else(
# str_detect(Full.Text.clean, regex(pattern_prefilter, ignore_case = TRUE)),
# "Yes",
# "No"
# )
# )
#
# Data <- Data %>%
# filter(Prefilter == "Yes")
# ============================================
# --- Optional ngram analysis ---
# (unchanged; left commented)
# ============================================
#
# ngram_size <- 1 # <- change to 1, 2, 3, 4, etc.
#
# Ngram_Frequencies <- Data %>%
# mutate(Full.Text.clean = stringr::str_squish(Full.Text)) %>%
# unnest_tokens(
# output = "ngram",
# input = Full.Text.clean,
# token = "ngrams",
# n = ngram_size) %>%
# count(ngram, sort = TRUE) %>%
# filter(!is.na(ngram), ngram != "")
# ============================================
# --- Define topic labels for graphs ---
# ============================================
Topic1Label <- "Immigration"
Topic2Label <- "Economy"
Topic3Label <- "Epstein" # <<< NEW
# ============================================
# --- Flag Topic1-related posts ---
# ============================================
phrases_topic1 <- c(
"immigration",
"immigrants",
"immigrant",
"asylum seekers",
"refugee",
"refugees",
"alien",
"aliens",
"illegals",
"border crossings",
"cross the border",
"crossed the border",
"crossing the border",
"the southern border",
"border security",
"secure the border",
"secure the borders",
"securing the border",
"securing the borders",
"secure border",
"border security",
"ICE",
"I.C.E.",
"CBP",
"C.B.P.",
"Renee Good",
"Pretti"
)
escaped_phrases_topic1 <- str_replace_all(
phrases_topic1,
"([\\^$.|?*+()\\[\\]{}\\\\])",
"\\\\\\1"
)
pattern_topic1 <- paste0("\\b", escaped_phrases_topic1, "\\b", collapse = "|")
Data <- Data %>%
mutate(
Full.Text.clean = str_squish(Full.Text),
Topic1 = if_else(
str_detect(Full.Text.clean, regex(pattern_topic1, ignore_case = TRUE)),
"Yes",
"No"
)
)
# ============================================
# --- Flag Topic2-related posts ---
# ============================================
phrases_topic2 <- c(
"economy",
"economic",
"inflation",
"tariff",
"tariffs",
"trade",
"trading partners",
"prices",
"price of",
"paying more for",
"affordable",
"affordability",
"afford",
"affording"
)
escaped_phrases_topic2 <- str_replace_all(
phrases_topic2,
"([\\^$.|?*+()\\[\\]{}\\\\])",
"\\\\\\1"
)
pattern_topic2 <- paste0("\\b", escaped_phrases_topic2, "\\b", collapse = "|")
Data <- Data %>%
mutate(
Full.Text.clean = str_squish(Full.Text),
Topic2 = if_else(
str_detect(Full.Text.clean, regex(pattern_topic2, ignore_case = TRUE)),
"Yes",
"No"
)
)
# ============================================
# --- Flag Topic3-related posts --- <<< NEW
# ============================================
phrases_topic3 <- c(
"Epstein",
"release the files",
"Ghislaine Maxwell"
)
escaped_phrases_topic3 <- str_replace_all(
phrases_topic3,
"([\\^$.|?*+()\\[\\]{}\\\\])",
"\\\\\\1"
)
pattern_topic3 <- paste0("\\b", escaped_phrases_topic3, "\\b", collapse = "|")
Data <- Data %>%
mutate(
Full.Text.clean = str_squish(Full.Text),
Topic3 = if_else(
str_detect(Full.Text.clean, regex(pattern_topic3, ignore_case = TRUE)),
"Yes",
"No"
)
)
# ============================================
# --- Visualize weekly counts (stacked by party) ---
# ============================================
if (!require("plotly")) install.packages("plotly")
library(plotly)
# --- Build Week -> WeekStart lookup so hover can show a date ---
WeekDates <- Data %>%
distinct(Week, WeekStart) %>%
arrange(Week)
# --- Summarize weekly counts by Party for Topic1 ---
Topic1_weekly_party <- Data %>%
filter(party %in% c("Dem/Ind", "Rep"), Topic1 == "Yes") %>%
group_by(party, Week) %>%
summarize(Count = n(), .groups = "drop") %>%
mutate(Topic = Topic1Label)
# --- Summarize weekly counts by Party for Topic2 ---
Topic2_weekly_party <- Data %>%
filter(party %in% c("Dem/Ind", "Rep"), Topic2 == "Yes") %>%
group_by(party, Week) %>%
summarize(Count = n(), .groups = "drop") %>%
mutate(Topic = Topic2Label)
# --- Summarize weekly counts by Party for Topic3 --- <<< NEW
Topic3_weekly_party <- Data %>%
filter(party %in% c("Dem/Ind", "Rep"), Topic3 == "Yes") %>%
group_by(party, Week) %>%
summarize(Count = n(), .groups = "drop") %>%
mutate(Topic = Topic3Label)
# --- Combine Topic1 + Topic2 + Topic3 weekly counts --- <<< UPDATED
Weekly_counts_party <- bind_rows(Topic1_weekly_party, Topic2_weekly_party, Topic3_weekly_party) %>%
mutate(
# ensure all three topics exist (even if one has zero rows)
Topic = factor(Topic, levels = c(Topic1Label, Topic2Label, Topic3Label))
) %>%
tidyr::complete(
party,
Topic,
Week = full_seq(range(Data$Week, na.rm = TRUE), 1),
fill = list(Count = 0)
) %>%
left_join(WeekDates, by = "Week") %>%
arrange(party, Topic, Week)
# --- Compute a padded y max to avoid clipping the top point ---
y_max_raw <- max(Weekly_counts_party$Count, na.rm = TRUE)
y_max <- max(pretty(c(0, y_max_raw)))
# Alternative: y_max <- max(1, ceiling(y_max_raw * 1.05))
# --- Hover template (shows week number + WeekStart date) ---
hover_tpl <- paste0(
"<b>%{fullData.name}</b><br>",
"Week: %{x}<br>",
"Week start: %{customdata|%Y-%m-%d}<br>",
"Count: %{y}<extra></extra>"
)
# --- Choose a 3-color palette (Topic1, Topic2, Topic3)
topic_colors <- c("steelblue", "firebrick", "darkgreen")
# --- Build party-specific plots ---
# Show legend ONLY on the top subplot (Dem/Ind)
p_demind <- plot_ly(
data = Weekly_counts_party %>% filter(party == "Dem/Ind"),
x = ~Week,
y = ~Count,
color = ~Topic,
colors = topic_colors,
legendgroup = ~Topic, # group by topic so toggling applies to both panels
showlegend = TRUE, # legend here (top panel only)
type = "scatter",
mode = "lines+markers",
line = list(width = 2),
marker = list(size = 6),
customdata = ~WeekStart,
hovertemplate = hover_tpl
) %>%
layout(
title = "",
xaxis = list(title = ""),
yaxis = list(title = "Number of Posts", range = c(0, y_max)),
hovermode = "x unified",
legend = list(title = list(text = "Topic"))
)
# Hide legend on the bottom subplot (Rep)
p_rep <- plot_ly(
data = Weekly_counts_party %>% filter(party == "Rep"),
x = ~Week,
y = ~Count,
color = ~Topic,
colors = topic_colors,
legendgroup = ~Topic, # same legend groups as above
showlegend = FALSE, # hide legend here (bottom panel)
type = "scatter",
mode = "lines+markers",
line = list(width = 2),
marker = list(size = 6),
customdata = ~WeekStart,
hovertemplate = hover_tpl
) %>%
layout(
title = "",
xaxis = list(
title = "Week Number (Week 1 starts at first observed week in data)",
dtick = 1
),
yaxis = list(title = "Number of Posts", range = c(0, y_max)),
hovermode = "x unified"
)
# --- Tile them vertically (stacked) with title + styled subtitle ---
AS_party <- subplot(
p_demind, p_rep,
nrows = 2,
shareX = TRUE,
shareY = TRUE,
titleX = TRUE,
titleY = TRUE
) %>%
layout(
title = list(
text = paste0(
"Weekly topic mentions by party",
"<br><span style='font-size:0.80em; color:#6c757d;'>",
"Top = Dem/Ind, Bottom = Rep",
"</span>"
)
),
showlegend = TRUE,
# Force identical vertical scales on both subplots with padded max
yaxis = list(title = "Number of Posts", range = c(0, y_max)),
yaxis2 = list(title = "Number of Posts", range = c(0, y_max))
)
# ============================================
# --- Show the chart ---
# ============================================
AS_party