There’s an old joke about how algebra was created when the Devil suggested putting the alphabet into math. R-based text analysis involves doing something like the opposite: Putting math into the alphabet.

The general idea is that the more often a word appears in the text of a document, the likelier it is that the document is about whatever idea that word represents. So, while reading a document is usually the best way to understand its meaning, looking at its word counts can offer a pretty good shortcut.

An example: “Four score and seven years ago …”

Consider Lincoln’s 271-word Gettysburg Address. The most common non-trivial word in the address is “here,” which occurs eight times. Next is “nation” (five times), followed by “dedicated” (four times). Knowing these words and their frequency counts certainly cannot get the full meaning and nuance of the speech. But it can help you get the idea that Lincoln was saying something about “dedication” and a “here” that was important to the “nation.”

Examining what Congress posts on X

R can quickly produce such word counts, even for documents many times the length of Lincoln’s famous address.

As an introduction to what’s possible, let’s look at word frequency counts for everything members of the U.S. Congress posted in X, formerly called Twitter, between Feb. 28, 2026, the day the U.S. and Israel launched attacks on Iran, and April 15, 2026, a few days into a ceasefire.

Below are the top 20 words used by Republicans during the period, followed by the top 20 words used by Democrats and independents during the period.

What do the two graphs suggest to you about what members of each party were posting about as the hostilities and ceasefire were unfolding?



I downloaded the posts using Brandwatch, a proprietary - and, I’m afraid, very expensive - social media content monitoring platform available to students and faculty in Middle Tennessee State University’s School of Journalism and Strategic Media.

Using Brandwatch is beyond the scope of this course. But I have extracted the posts described above, added some information about each post’s source, and put the data on my GitHub page. Let’s get it and have a look how you can use it to make the graphics shown.

Getting the data

This code will install, if needed, and load some required packages. Then, it will download the posts from a file in my GitHub space and bring it into R as a data frame called XData.

One new package here is tidytext, the go-to package for basic text mining in R. Another is kableExtra, a lightweight package for making nice-looking tables. By now, you should recognize the tidyverse code enhancement package and the plotly interactive graphics package.

# ----------------------------------------------------------
# Step 1: Install required packages (if missing)
# ----------------------------------------------------------

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

library(tidyverse)
library(plotly)
library(tidytext)
library(kableExtra)

# ----------------------------------------------------------
# Step 2: Load data
# ----------------------------------------------------------

XData <- read_csv(
  "https://github.com/drkblake/Data/raw/refs/heads/main/IranWarCongressX.csv",
  col_types = cols(
    Date = col_date()
  )
)

I get an XData data frame with 56,718 rows, with eight columns showing:

Getting word counts by party

What happens next is something you should be glad your computer will do for you so that you don’t have to do it yourself.

When you run the code below, your computer will:

Here’s the code:

# ----------------------------------------------------------
# Step 3: Tokenize Full.Text into single words
# ----------------------------------------------------------

XData <- XData %>%
  mutate(
    Full.Text = str_replace_all(Full.Text, "[’‘`]", "")
  )

X_words <- XData %>%
  unnest_tokens(word, Full.Text)

# ----------------------------------------------------------
# Step 4: Remove stop words
# ----------------------------------------------------------

data("stop_words")

X_words_clean <- X_words %>%
  anti_join(stop_words, by = "word")

# ----------------------------------------------------------
# Step 5: Remove X.com–specific noise and contractions
# ----------------------------------------------------------

x_noise <- c(
  "https", "tco", "t.co", "rt", "amp",
  "co", "com", "www",
  "twitter", "x",
  "im", "its"
)

X_words_clean <- X_words_clean %>%
  filter(
    !str_detect(word, "^@"),
    !str_detect(word, "^#"),
    !word %in% x_noise,
    str_detect(word, "[a-z]"),
    nchar(word) > 2
  )

# ----------------------------------------------------------
# Step 6: Count word usage by party
# ----------------------------------------------------------

party_word_counts <- X_words_clean %>%
  count(party, word, sort = TRUE)

Time for a peek

If you open the party_word_counts data frame in RStudio, you’ll see a row for each word mentioned in each party’s posts and a count of how many times a party’s posts mentioned the word.

The first row, for example, tells you that the word “war” appeared 6,590 times in posts by Democrats and independents - more times than any other word they used.

Among Republicans, the most-used single word was “American,” with 4,801 mentions, followed closely by the nearly identical “America,” with 3,986 mentions.

An important distinction

Remember: That 6,590 number counts the number of times Democrats and independents used the word “war.” It does not, however, count the number of posts by Democrats and independents that used the word “war.”

It is certainly possible to get that count, and the two counts are probably similar. But they are not interchangeable, and reporting your results accurately requires keeping the difference between them in mind.

A simple example might help. Imagine two posts. One reads, “I support the war.” The other reads, “All they want is war, war, war.” That would be two posts mentioning “war,” but four mentions of “war.”

A “war,” or a … something else?

If you use the sorting capability in RStudio’s viewer to sort the data frame by the word column, then scroll (way) down to where the rows for the word “war” appear, you can see that while Democrats and independents used the word 6,590 times, Republicans used it 928 times:

Is the difference between the counts trivial? Maybe. But news outlets including The New York Times, CNN, CBS News, and The Hill reported that Republicans in Congress had political and legal reasons for avoiding using the word “war” during the opening weeks of the conflict.

Visualizing the results

One way to share at least some of your results with an audience would be to show them a table of the top 20 words used by each party. Steps 7 and 8 in the code will make and display such a table:

# ----------------------------------------------------------
# Step 7: Identify TOP 10 words for each party
# ----------------------------------------------------------

top_words_by_party <- party_word_counts %>%
  group_by(party) %>%
  slice_max(n, n = 20) %>%
  ungroup()

# ----------------------------------------------------------
# Step 8: Display table
# ----------------------------------------------------------

WordTable <- top_words_by_party %>%
  arrange(party, desc(n)) %>%
  kable(
    col.names = c("Party", "Word", "Mentions"),
    caption = "Most Common Words in Congressional X Posts by Party (Top 10)"
  ) %>%
  kable_styling(full_width = FALSE)

WordTable

Most Common Words in Congressional X Posts by Party (Top 10)
Party Word Mentions
Dem/Ind war 6590
Dem/Ind trump 5987
Dem/Ind people 3440
Dem/Ind american 3432
Dem/Ind iran 3417
Dem/Ind americans 3066
Dem/Ind families 2761
Dem/Ind congress 2634
Dem/Ind republicans 2597
Dem/Ind president 2495
Dem/Ind care 2365
Dem/Ind day 2312
Dem/Ind ice 2172
Dem/Ind trumps 2033
Dem/Ind health 2004
Dem/Ind community 1964
Dem/Ind act 1910
Dem/Ind time 1758
Dem/Ind bill 1744
Dem/Ind costs 1618
Rep american 4801
Rep america 3986
Rep act 3859
Rep americans 3836
Rep democrats 3736
Rep tax 3622
Rep families 3389
Rep security 2953
Rep people 2913
Rep president 2855
Rep u.s 2606
Rep day 2515
Rep time 2383
Rep dhs 2213
Rep support 2152
Rep country 2114
Rep trump 2069
Rep iran 2043
Rep senate 1967
Rep bill 1906

Same info, but more visually

The table is informative, perhaps, but kind of boring-looking. Presenting the same results using horizontal bar charts - one for Republicans, and a second for Democrats and independents - would look a little snazzier. Step 9 produces and shows the charts.

It is possible to arrange the charts side-by-side, in a single graphic. But the bar labels end up small, and the whole thing looks kind of crowded. I think separate charts work better.

# ----------------------------------------------------------
# Step 9: Plotly visualization 
# ----------------------------------------------------------

rep_data <- top_words_by_party %>%
  filter(party == "Rep") %>%
  arrange(desc(n))

dem_data <- top_words_by_party %>%
  filter(party == "Dem/Ind") %>%
  arrange(desc(n))

rep_plot <- plot_ly(
  data = rep_data,
  x = ~n,
  y = ~word,
  type = "bar",
  orientation = "h",
  marker = list(color = "#c0392b")
) %>%
  plotly::layout(
    title = list(text = "Top Words used by Republicans", font = list(size = 18)),
    xaxis = list(
      title = "",
      tickfont = list(size = 14)
    ),
    yaxis = list(
      title = "",
      tickfont = list(size = 14),
      categoryorder = "array",
      categoryarray = rev(rep_data$word)
    ),
    margin = list(l = 140)
  )

rep_plot

dem_plot <- plot_ly(
  data = dem_data,
  x = ~n,
  y = ~word,
  type = "bar",
  orientation = "h",
  marker = list(color = "#2980b9")
) %>%
  plotly::layout(
    title = list(text = "Top Words used by Democrats / Independents", font = list(size = 18)),
    xaxis = list(
      title = "",
      tickfont = list(size = 14)
    ),
    yaxis = list(
      title = "",
      tickfont = list(size = 14),
      categoryorder = "array",
      categoryarray = rev(dem_data$word)
    ),
    margin = list(l = 140)
  )

dem_plot


Drilling down

Looking at what all members of Congress post on X.com can be informative. But what if you want a detailed look at what one particular member of Congress posted, and about one particular thing?

During the time leading up to the period we are examining, Republican U.S. Rep. Thomas Massie of Kentucky had been speaking and posting about topics that fellow members of his party tended to avoid. His X.com handle is RepThomasMassie. Let’s filter the XData data frame for posts by Massie, then rerun the analysis to see what (just) Massie posted about.

Adding this code between Step 2 and Step 3, then rerunning the code will do the trick:

# ----------------------------------------------------------
# Step 2.5: (Optional filter for a single member)
# ----------------------------------------------------------

XData <- XData %>%
  filter(Author == "RepThomasMassie")

Re-running the code gets you a table and two graphics, just like before. But this time, the table and red Republican graphic contain only Massie’s most-posted words. Note that the blue Democratic / independent graphic is blank, because there are no Democrats in the data frame now.


Most Common Words in Congressional X Posts by Party (Top 10)
Party Word Mentions
Rep war 33
Rep act 30
Rep bill 30
Rep epstein 27
Rep congress 26
Rep spending 23
Rep repthomasmassie 21
Rep vote 21
Rep files 19
Rep debt 18
Rep voted 16
Rep fbi 15
Rep national 15
Rep amendment 14
Rep government 14
Rep house 14
Rep support 14
Rep constitutional 13
Rep farm 13
Rep constitution 12
Rep meeting 12
Rep met 12



Two words in Massie’s X.com vocabulary appear unusually common for a Republican member of Congress: “war,” and “epstein,” the latter referring to convicted sex offender Jeffrey Epstein.

This code will let you type in war and epstein as search terms, then view posts from Massie that mention either term:

# ----------------------------------------------------------
# Step 10: Specify words or phrases to search for in Full.Text
# ----------------------------------------------------------

text_terms <- c(
  "war",
  "epstein"
)

# Combine into a single regex pattern (match ANY term)
text_pattern <- str_c(text_terms, collapse = "|")


# ----------------------------------------------------------
# Step 11: Filter dataset for matching Full.Text rows
# ----------------------------------------------------------

MatchingPosts <- XData %>%
  filter(
    str_detect(
      str_to_lower(Full.Text),
      str_to_lower(text_pattern)
    )
  ) %>%
  select(
    Date,
    Full.Name,
    party,
    Full.Text
  )


# ----------------------------------------------------------
# Step 12: Display matching Full.Text content in a table
# ----------------------------------------------------------

Posts <- MatchingPosts %>%
  arrange(Date) %>%
  kable(
    col.names = c(
      "Date",
      "Member",
      "Party",
      "Post Text"
    ),
    caption = "X.com Posts Matching User‑Specified Words or Phrases",
    align = c("l", "l", "l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width = FALSE,
    position = "left"
  ) %>%
  scroll_box(
    height = "500px",
    width = "100%"
  )

Posts

X.com Posts Matching User‑Specified Words or Phrases
Date Member Party Post Text
2026-02-28 RepThomasMassie (Thomas Massie) Rep Acts of war unauthorized by Congress. The U.S. is attacking Iran according to AP. https://t.co/Bgwk8yIdRT
2026-02-28 RepThomasMassie (Thomas Massie) Rep

I am opposed to this War.

This is not “America First.”

When Congress reconvenes, I will work with @RepRoKhanna to force a Congressional vote on war with Iran.

The Constitution requires a vote, and your Representative needs to be on record as opposing or supporting this war.
2026-03-01 RepThomasMassie (Thomas Massie) Rep PSA: Bombing a country on the other side of the globe wont make the Epstein files go away, any more than the Dow going above 50,000 will.
2026-03-01 RepThomasMassie (Thomas Massie) Rep

I exposed the global Epstein sex trafficking ring and insisted that Congress debate and authorize any possible war with Iran (to protect our soldiers), and heres the response.

Its why so many Congressmen, Republican and Democrat, are afraid to take action on several issues.
2026-03-02 RepThomasMassie (Thomas Massie) Rep @MarindaVannoy1 @DocLanceP If I had left last year, none of the Epstein files would have been released. I think its good to distrust everyone in DC, but what in particular have I done to lose your trust?
2026-03-02 RepThomasMassie (Thomas Massie) Rep @MiDiamondDave @elonmusk Its not that hard to know. These were two of the accounts reposting overseas, urging others to vote for war. https://t.co/7BEDHNIzsV
2026-03-02 RepThomasMassie (Thomas Massie) Rep @SteelrStarsMets @Wovimon @elonmusk The President called it war, and its pretty obvious at this point.
2026-03-02 RepThomasMassie (Thomas Massie) Rep

Investigate Zorro ranch, as well as the men and women at DOJ and FBI who shut this part of the Epstein investigation down.

Also, the Epstein Files Transparency Act requires DOJ to release memos and emails detailing their decisions of whether to investigate and/or prosecute.
2026-03-02 RepThomasMassie (Thomas Massie) Rep The administration admits 🇮🇱 dragged us into the 🇮🇷 war thats already cost too many American lives and billions of dollars. Before its over, the price of gas, groceries, and virtually everything else is going to go up. The only winners in 🇺🇸 are defense company shareholders.
2026-03-03 RepThomasMassie (Thomas Massie) Rep @TomHillsisyphus Congress prevented Obama from having an all-out war in Syria in 2013. Surely you didnt think going to war then would have been a good thing?
2026-03-03 RepThomasMassie (Thomas Massie) Rep Theres only one group thats clearly winning in this war… the Military Industrial Complex. Heres my $50 billion theory on why we never have peace, regardless of whos President. @TheoVon @YALiberty https://t.co/8xYuaAcvnC
2026-03-04 RepThomasMassie (Thomas Massie) Rep RT @mandyarthur Your government has raided more raw milk farms than Epstein clients.
2026-03-04 RepThomasMassie (Thomas Massie) Rep For the next two hours, Congress will be debating my War Powers Resolution. Tune in live at this link. Ill be speaking for about five minutes during this debate and will post my speech later today. https://t.co/BE7HSd1zW9
2026-03-04 RepThomasMassie (Thomas Massie) Rep @MattWalshBlog I think @Rep_Davidson has a new movie project for you… “What is a war?” https://t.co/TEMsHeclQK
2026-03-04 RepThomasMassie (Thomas Massie) Rep

Were debating the Iran War Powers Resolution I co-authored with @RepRoKhanna.

Under our Constitution, the power to initiate war rests solely with Congress.

Congress owes our service members a clearly defined mission, so that when they accomplish it, they can come home. https://t.co/TBnJH9VJQr
2026-03-05 RepThomasMassie (Thomas Massie) Rep We didnt vote for another War in the Middle East, so why are we getting one? Thank you @SenRandPaul
2026-03-05 RepThomasMassie (Thomas Massie) Rep

Yesterday the House debated my Iran War Powers Resolution for two hours.

The VOTE IS TODAY, but weve already won by forcing a debate and a vote.

Our troops deserve a clear mission, so when its done they can come home. No more forever wars.

Your Rep: https://t.co/MWSufCsG5o https://t.co/MrSRkUedcH
2026-03-05 RepThomasMassie (Thomas Massie) Rep I assume these were approvals in the first week of each of these wars and military invasions. Wars are never as popular as they are on the first day.
2026-03-05 RepThomasMassie (Thomas Massie) Rep RT @WarrenDavidson September of what year? https://t.co/1ZfcTFTVWc
2026-03-05 RepThomasMassie (Thomas Massie) Rep

Yesterday, I spoke with @emilyjashinsky about the Iran War Powers Resolution that @RepRoKhanna and I brought to the floor.

Congress must debate and vote—every member will be on the record. The vote will happen today around 4pm. Watch the full interview: https://t.co/nNYCNkQRFF
2026-03-06 RepThomasMassie (Thomas Massie) Rep

Today in @JudiciaryGOP, I voted for a bipartisan amendment to ensure federal officers dont violate American citizens Constitutional rights.

I support immigration enforcement and deportation of illegal immigrants, but judicial warrants should be obtained before entering homes. https://t.co/DCSSpPsyY6
2026-03-06 RepThomasMassie (Thomas Massie) Rep “We are not at war” Orwellian levels of double speak. https://t.co/tdNKdXDK8x
2026-03-06 RepThomasMassie (Thomas Massie) Rep

“We are not at war,” Johnson said. “We have no intention of being at war.”

Excuse me, this is war. Even those in favor of it will admit that much.

Changing the real meaning of words does not relieve Congress of its Constitutional duty to authorize War. https://t.co/vUApwmJHr6
2026-03-08 RepThomasMassie (Thomas Massie) Rep

The price of gas has gone up $0.47 and the price of diesel has gone up $0.83 in 10 days due to War with Iran.

and waging war costs American taxpayers about $1 billion per day,

which comes out to $10 per family per day, or $100 since the war began.

This isnt America First.
2026-03-08 RepThomasMassie (Thomas Massie) Rep

@Mrodgerss232 “Look away from the price of gasoline and the cost of groceries and unaffordable housing and the debt and the Epstein files. Focus on something else please!”

Say the bots.
2026-03-08 RepThomasMassie (Thomas Massie) Rep @Limare64 Correct. @grok, how much has the price of fertilizer and fertilizer futures increased since the War with Iran began less than two weeks ago? How much could the increased fertilizer prices and fuel prices affect the price of groceries?
2026-03-08 RepThomasMassie (Thomas Massie) Rep @grok @Limare64 What if the war drags on and input prices remain elevated? Will harvest and shipping costs also increase and how will that affect the price of food at grocery stores?
2026-03-08 RepThomasMassie (Thomas Massie) Rep @ensmi99700 @NetworksManager Hey @grok, wasnt I one of the most consistent members of Congress in voting against the war in Ukraine and against funding for the war in Ukraine?
2026-03-08 RepThomasMassie (Thomas Massie) Rep

RT @LouisaClary 🔥Mike Benz on the Epstein bill: “Nobody wanted to be the one to sponsor this bill.”

“Nobody wanted to be the Thomas Massie who has their whole career thrown up in the air —getting primaried, hit pieces, and disavowed… and then MTG feeling like she had to drop out of Congress altogether over this.”

“…So the mere act of sponsoring the bill put the establishment in a bind. Nobody wanted to be the one to go against it once it was sponsored.”

“Everybody feared the base. That once they voted against it, they’d be voted out…”

“427-1 it passed in the House. 99-0 in the Senate.”

🔥“This is one of those moments —you could use that same strategy, in theory, not just with the CIA and State Department files on Epstein… [but also] who’s gonna vote against declassification of CIA files for Covid-19?”

“Do it fast and furious. I’m inspired by what Thomas Massie has done.” 🇺🇸 —Mike Benz on Kibbe On Liberty

Full interview below.
2026-03-09 RepThomasMassie (Thomas Massie) Rep Im saddened to hear that the seventh U.S. military casualty was a brave Kentuckian. My prayers are with all the families of the American service members who have died in the war with Iran, and I am praying for a full recovery of those who have been seriously injured.
2026-03-10 RepThomasMassie (Thomas Massie) Rep

The “Alexander brothers” appeared in the Epstein files by first name, but I noticed DOJ redacted their last name in an FBI email contained in EFTA01660679.

But @FBIDirectorKash said no evidence of sex trafficking in the files.

https://t.co/WQCJrdeDCs

https://t.co/475168JuHw
2026-03-10 RepThomasMassie (Thomas Massie) Rep Theyre paying to bus people to the Trump event in my Congressional District. What theyll discover is Trump fans in KY-4 and across the entire Commonwealth also support my work on the Epstein files, reigning in spending, ending forever wars, draining the swamp, and food freedom! https://t.co/rfVcVYf3lh
2026-03-13 RepThomasMassie (Thomas Massie) Rep

RT @takenaps If we had a Congress full of @RepThomasMassie we would have:

Borderless Constitutional Carry Reduced National Debt Better Agricultural Policies Healthier Food + Meat Epstein Arrests Medical Freedom Single Issue Bills Voter ID Reduced Welfare State No AIPAC handlers

That’s why they hate him.
2026-03-13 RepThomasMassie (Thomas Massie) Rep

just a few of the things I strongly support:

✅The SAVE Act ✅National Constitutional Carry ✅Warrants for Americans for FISA ✅Reduce Spending ✅Convict Epstein Coconspirators ✅Healthy Food and Farm Freedom ✅Abolish the Federal Reserve ✅Border Security ✅Stop Fraud
2026-03-14 RepThomasMassie (Thomas Massie) Rep

The Foreign Intelligence Surveillance Act will expire soon.

FBI Directors Mueller, Comey, Wray, and even Patel have used this law to unconstitutionally snoop on Americans without getting a warrant.

Its easily fixed if/when reauthorized by Congress.

Add 3 words: Get a Warrant!
2026-03-17 RepThomasMassie (Thomas Massie) Rep

The National Kidney Foundation is a voluntary nonprofit that works to raise awareness and fund innovation in kidney disease research.

Thank you, Kelly Burbridge, Annie Harrison, and Teresa Villaran from @nkf for meeting with me today. https://t.co/4RdZJlvKBN
2026-03-20 RepThomasMassie (Thomas Massie) Rep

RT @amconmag Rep. Boebert is a NO on $200 billion to fund the Iran War:

“I am so tired of spending money elsewhere. Im tired of the Industrial War Complex getting our hard-earned tax dollars. Ive got folks in Colorado who cant afford to live. We need America First policies right now.” https://t.co/EAIJMPC91t
2026-03-22 RepThomasMassie (Thomas Massie) Rep @otter_blues I used to keep bees, so these may be descendants of swarms that escaped into my woods. I also have some neighbors about a mile away, who have been keeping a few hives.
2026-03-24 RepThomasMassie (Thomas Massie) Rep The Epstein Files are an encyclopedia of criminal activity committed by billionaires and politicians across the globe.
2026-03-24 RepThomasMassie (Thomas Massie) Rep

Ill continue to support the valid Constitutional position that Trump, Jordan, and Vance have all expressed strongly in the past:

No FISA reauthorization without a warrant requirement for US citizens! https://t.co/FVAXJLFScf
2026-03-24 RepThomasMassie (Thomas Massie) Rep @robertatlee @FmrRepMTG @RepBoebert @RepNancyMace When the party decides to keep the Epstein files hidden, increase spending, reauthorize warrantless spying, and perpetuate the immigrant welfare fraud, I dont “stack hands.” I stick with my constituents and the promises I made to them when I campaigned.
2026-03-25 RepThomasMassie (Thomas Massie) Rep Releasing the Epstein files is not the end goal. Until we see investigations and arrests, our system of justice is not working.
2026-03-27 RepThomasMassie (Thomas Massie) Rep

Many policies from Washington, D.C. these days, like wars abroad, excessive spending, and tariffs are causing a higher cost of living.

My PRIME Act, which made it into the Farm Bill, would make it easier for local farms to sell directly to consumers, lowering the price of meat.
2026-03-28 RepThomasMassie (Thomas Massie) Rep @WhiteHouse Can you arrest Epsteins co-conspirators instead of riffing on a porn site ?
2026-03-29 RepThomasMassie (Thomas Massie) Rep Imagine a world where hard work is rewarded, truth and justice prevail in courtrooms, the government doesnt steal your labor by debasing the currency, bureaucrats arent captured by corporations, and our taxes go toward critical infrastructure instead of wars overseas. https://t.co/pO5eSskDkO
2026-03-31 RepThomasMassie (Thomas Massie) Rep

Dan, in your first call, which I think is the first and last occasion you and I ever spoke:

  1. you seemed upset that I had received and had released FBI whistleblower information about the pipe bomb investigation.

  2. I informed you that your staff had threatened to criminally investigate my staff as retribution against me (for pipe bomb or Epstein activity?)

  3. you threatened to personally finance a defamation suit against reporters on behalf of a suspect. Perhaps it was also a veiled threat to sue me. You said “those depositions arent going to fun for the people involved” or something like that. In any case, Deputy FBI Director shouldnt be financing civil lawsuits against reporters covering cases the FBI is working on.

  4. you said you were going to call every agent in and get to the bottom of the whistleblower issue.

  5. you offered me a briefing but I was going to be tied up until at least 6pm on the Epstein files transparency act, so I asked how late i could get the briefing and you said you were going to leave the office at 5pm.

  6. I asked you a few questions on the call and your answers indicated to me that you were perhaps less informed than me on some of the issues, or you were going to be less than forthcoming.

A few hours after the call, I received and released new FBI whistleblower information regarding the all-hands meeting (which matched what you told me in #4 above), related to concern that the meeting was called to “out” the whistelblowers.

Your second (attempted) call was the evening I achieved 218 signatures on the Epstein discharge petition and I had been busy thwarting Mike Johnsons last ditch effort to derail the Epstein Files Transparency Act. Not sure why your call log shows 1:36am. You called me in the evening, maybe 8ish?

note - my staff also had the unfortunate pleasure of receiving numerous late night calls on Signal from FBI staff telling them there was absolutely nothing in the Epstein case and that I
2026-03-31 RepThomasMassie (Thomas Massie) Rep @dbongino In fact, the only question I asked Pam Bondi at that April 28, 2025 dinner at DOJ, in front of everyone when I was recognized to speak, was “when are you going to release phase 2 of the Epstein files?” There were two dozen people who heard the question.
2026-04-01 RepThomasMassie (Thomas Massie) Rep @dbongino Daniel, you have 5 times as many followers as me, yet I ratioed you three times today. When I caught you in lies, you just started new threads. Have fun interviewing a warm-body fool on April fools day. I think even you pitching softballs will recognize what a goof this guy is. https://t.co/VBaDJBq2dH
2026-04-02 RepThomasMassie (Thomas Massie) Rep

I support Trump firing Pam Bondi. Do you?

I hope the next AG will release all the Epstein files according to the law and follow up with investigations, prosecutions, and arrests.
2026-04-02 RepThomasMassie (Thomas Massie) Rep

RT @WayneWaldropW So let me get this straight.

Thomas Massie is the man who: - fought to get the Epstein Files released - spoke out against covid mandates - has a near perfect voting record - wants to decrease government spending - supports small farmers - pushes for single issue bills

Yet somehow these “Conservative influencers” think HE is the fraudster?

Thomas Massie has been consistent, and be is sticking to the mandate you all pretended to support up until last year.

Sounds like he is the most America First member of Congress if you ask me!
2026-04-03 RepThomasMassie (Thomas Massie) Rep

RT @KaceeRAllen Rep. Thomas Massie says we wont have justice in America until Jeffrey Epsteins clients get perp-walked in hand cuffs to the jail.

“At some point, somebody got to Pam Bondi and said, it’s your job to cover this up.”

https://t.co/ofvbgNBTHS
2026-04-03 RepThomasMassie (Thomas Massie) Rep Congratulations AG Blanche. Now you have 30 days to release the rest of the files before becoming criminally liable for failure to comply with the Epstein Files Transparency Act.
2026-04-06 RepThomasMassie (Thomas Massie) Rep

No one should have to beg the government to exercise a constitutionally protected right anywhere in the country.

Thank you @RepChuckEdwards for cosponsoring HR 645, the National Constitutional Carry Act.
2026-04-09 RepThomasMassie (Thomas Massie) Rep

First Lady asks Congress to bring Epstein survivors in for testimony. With all due respect, thats @DAGToddBlanches job!

@RepRoKhanna & I already gave brave survivors a chance to tell their horrific stories on Capitol Hill. @PamBondi wouldnt even acknowledge them.

PROSECUTE!
2026-04-10 RepThomasMassie (Thomas Massie) Rep

I vote with GOP 91% of the time, but thats about to go to 90%.

I wont vote to let feds spy on you without a warrant.

FISA 702 allows the government to search for your information in vast databases compiled while targeting foreigners.

The White House sent me this email today: https://t.co/BW59MlRNvY
2026-04-13 RepThomasMassie (Thomas Massie) Rep

The Epstein class of entitled billionaires and swamp dwellers hate me for bringing transparency, not for obstructing.

The Uniparty easily passes bills when Im the sole objector, but I explain whats in the bills & how they violate our Constitution.

Its why they want me gone.
2026-04-15 RepThomasMassie (Thomas Massie) Rep

I will be voting NO on final passage of the FISA 702 Reauthorization Bill if it does not include a warrant provision and other reforms to protect US citizens right to privacy.

Yesterday I offered these 3 amendments to fix the program, but they were not allowed last night.

In-class exercise

Tennessee’s delegation to the U.S. Congress consists of two U.S. senators and nine members of the U.S. House of Representatives. Here are their X.com handles, their names (in parentheses), and the number of posts by each in the XData data frame.

Blackburn and Hagerty are the two senators. The rest are members of the U.S. House. All are Republicans except for Cohen, a Democrat who represents the Memphis area:

Member (as listed in XData) Number of Posts
RepOgles (Rep. Andy Ogles) 355
MarshaBlackburn (Sen. Marsha Blackburn) 228
RepDavidKustoff (Rep. David Kustoff) 109
RepChuck (Chuck Fleischmann) 97
RepCohen (Steve Cohen) 73
SenatorHagerty (Senator Bill Hagerty) 68
RepJohnRose (Congressman John Rose) 37
DesJarlaisTN04 (Scott DesJarlais) 33
RepTimBurchett (Rep. Tim Burchett Press Office) 31

Below is the code from today’s lesson, all in one place, including the optional member filter on Step 2.5 and the drill-down code from steps 10 through 12.

Your task is to:

When you can show me your results and give me a quick verbal interpretation of what you found, you are free to leave.

# ----------------------------------------------------------
# Step 1: Install required packages (if missing)
# ----------------------------------------------------------

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

library(tidyverse)
library(plotly)
library(tidytext)
library(kableExtra)

# ----------------------------------------------------------
# Step 2: Load data
# ----------------------------------------------------------

XData <- read_csv(
  "https://github.com/drkblake/Data/raw/refs/heads/main/IranWarCongressX.csv",
  col_types = cols(
    Date = col_date()
  )
)

# ----------------------------------------------------------
# Step 2.5: (Optional filter for a single member)
# ----------------------------------------------------------

XData <- XData %>%
  filter(Author == "XHandleGoesHere")

# ----------------------------------------------------------
# Step 3: Tokenize Full.Text into single words
# ----------------------------------------------------------

XData <- XData %>%
  mutate(
    Full.Text = str_replace_all(Full.Text, "[’‘`]", "")
  )

X_words <- XData %>%
  unnest_tokens(word, Full.Text)

# ----------------------------------------------------------
# Step 4: Remove stop words
# ----------------------------------------------------------

data("stop_words")

X_words_clean <- X_words %>%
  anti_join(stop_words, by = "word")

# ----------------------------------------------------------
# Step 5: Remove X.com–specific noise and contractions
# ----------------------------------------------------------

x_noise <- c(
  "https", "tco", "t.co", "rt", "amp",
  "co", "com", "www",
  "twitter", "x",
  "im", "its"
)

X_words_clean <- X_words_clean %>%
  filter(
    !str_detect(word, "^@"),
    !str_detect(word, "^#"),
    !word %in% x_noise,
    str_detect(word, "[a-z]"),
    nchar(word) > 2
  )

# ----------------------------------------------------------
# Step 6: Count word usage by party
# ----------------------------------------------------------

party_word_counts <- X_words_clean %>%
  count(party, word, sort = TRUE)

# ----------------------------------------------------------
# Step 7: Identify TOP 10 words for each party
# ----------------------------------------------------------

top_words_by_party <- party_word_counts %>%
  group_by(party) %>%
  slice_max(n, n = 20) %>%
  ungroup()

# ----------------------------------------------------------
# Step 8: Display table
# ----------------------------------------------------------

WordTable <- top_words_by_party %>%
  arrange(party, desc(n)) %>%
  kable(
    col.names = c("Party", "Word", "Mentions"),
    caption = "Most Common Words in Congressional X Posts by Party (Top 10)"
  ) %>%
  kable_styling(full_width = FALSE)

WordTable

# ----------------------------------------------------------
# Step 9: Plotly visualization (larger labels, top-down order)
# ----------------------------------------------------------

rep_data <- top_words_by_party %>%
  filter(party == "Rep") %>%
  arrange(desc(n))

dem_data <- top_words_by_party %>%
  filter(party == "Dem/Ind") %>%
  arrange(desc(n))

rep_plot <- plot_ly(
  data = rep_data,
  x = ~n,
  y = ~word,
  type = "bar",
  orientation = "h",
  marker = list(color = "#c0392b")
) %>%
  plotly::layout(
    title = list(text = "Top Words for Republicans", font = list(size = 18)),
    xaxis = list(
      title = "",
      tickfont = list(size = 14)
    ),
    yaxis = list(
      title = "",
      tickfont = list(size = 14),
      categoryorder = "array",
      categoryarray = rev(rep_data$word)
    ),
    margin = list(l = 140)
  )

rep_plot

dem_plot <- plot_ly(
  data = dem_data,
  x = ~n,
  y = ~word,
  type = "bar",
  orientation = "h",
  marker = list(color = "#2980b9")
) %>%
  plotly::layout(
    title = list(text = "Top Words for Democrats / Independents", font = list(size = 18)),
    xaxis = list(
      title = "",
      tickfont = list(size = 14)
    ),
    yaxis = list(
      title = "",
      tickfont = list(size = 14),
      categoryorder = "array",
      categoryarray = rev(dem_data$word)
    ),
    margin = list(l = 140)
  )

dem_plot

# ----------------------------------------------------------
# Step 10: Specify words or phrases to search for in Full.Text
# ----------------------------------------------------------

text_terms <- c(
  "war",
  "epstein"
)

# Combine into a single regex pattern (match ANY term)
text_pattern <- str_c(text_terms, collapse = "|")


# ----------------------------------------------------------
# Step 11: Filter dataset for matching Full.Text rows
# ----------------------------------------------------------

MatchingPosts <- XData %>%
  filter(
    str_detect(
      str_to_lower(Full.Text),
      str_to_lower(text_pattern)
    )
  ) %>%
  select(
    Date,
    Full.Name,
    party,
    Full.Text
  )


# ----------------------------------------------------------
# Step 12: Display matching Full.Text content in a table
# ----------------------------------------------------------

Posts <- MatchingPosts %>%
  arrange(Date) %>%
  kable(
    col.names = c(
      "Date",
      "Member",
      "Party",
      "Post Text"
    ),
    caption = "X.com Posts Matching User‑Specified Words or Phrases",
    align = c("l", "l", "l", "l")
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width = FALSE,
    position = "left"
  ) %>%
  scroll_box(
    height = "500px",
    width = "100%"
  )

Posts