STA 279 Lab 3
Complete all Questions.
The Goal
In our last class, we talked about TF-IDF, which is a tool we can use if we want to figure out what words characterize certain types of text. Today, we are going to see an application of how we can use this in practice.
The Data Set
The Federalist papers are a famous series of 97 articles written in the 1700s in the early days of the United States. They debated and proposed ideas for the new government. Three famous authors of these papers were Alexander Hamilton, James Madison, and John Jay.
Most of the Federalist papers (85 of them) were known to have been written by either Hamilton, Madison, or Jay, but for 12 papers, the papers were submitted anonymously. After Madison’s death, Hamilton declared that he had written the articles. Since then, many scholars have worked to determine which of the three authors had written the anonymous papers using text analysis.
Our goal for today is to see if we can use the text analysis skills we have learned so far to determine who wrote the mystery papers.
To read in the data on the \(n = 97\) Federalist papers, use the following code:
# Load the data
Federalist <- read.csv("https://www.dropbox.com/scl/fi/lb7n6eoc094epytqqkuc2/FederalistAll.csv?rlkey=nhrr5m90iyy7bueggffslxm0e&st=5jvps3wz&dl=1")
# Convert to a data frame
Federalist <- data.frame(Federalist)
# Make sure author is treated as categorical
Federalist[, "author"] <- as.factor(Federalist[, "author"])The columns are:
paper: the number given to the paper; think of this like an identifier for the article.text: the text of the entire paper.author: either Hamilton or Madison or Jay or mystery (for the 12 papers with unknown author)
Once you have loaded the data, load the packages you will need for this lab:
Feature Engineering
Our goal for today is to determine which of the three authors (Hamilton, Madison, or Jay) likely wrote each of the mystery papers. This means we need to figure out which words characterize or distinguish each author’s writing, and then see if those words are present in the mystery papers!
It may seem natural to use frequency as a way to determine which words we should use as features. In other words, we could the number of times each word occurs in a text (in this case a paper) and we choose the words that are most frequent for each author as our features. However, it turns out that this is not the best approach. Let’s see why.
Suppose we want to find the top 5 words in Madison’s articles after removing stop words. I’ve given you a skeleton of the code you need below:
top5_Madison <- Federalist |>
filter(author == ...) |>
unnest_tokens(word,... ) |>
anti_join(stop_words, by = join_by(word)) |>
count( ... ) |>
slice_max(n , n = ... )Question 1
Complete the code above by filling in the … and annotate the code.
Note: You will note I used
anti_join(stop_words, by = join_by(word)) rather than
anti_join(stop_words). The codes do
exactly the same thing. The only difference is that
some of you have noticed you get annoying warnings about
Join by: by = join_by(word) when you run
anti_join(stop_words). The adaptation I have in the code
above just removes that warning.
Question 2
Create and show a plot to show the top 5 words in Madison’s texts. Make sure your plot is well formatted and well labelled.
Hint: Here is a skeleton code to get you started:
This shows us potential words to use as features for Madison, but we also need words for Hamilton and Jay and the mystery author. We could repeat the process 3 times, but it turns out we actually don’t have to. As we saw in class, we can use grouping in R to find the top 5 words for each author in one code, without having to repeat! A skeleton code for this is included below.
top5_all <- Federalist |>
unnest_tokens(word,... ) |>
anti_join(stop_words, by = join_by(word)) |>
group_by( ... ) |>
count( ... ) |>
slice_max(n , n = ... )Question 3
Complete the code above. As the answer to this question, state the top most frequent word in (a) Hamilton’s papers and (b) Jay’s papers and (c) the mystery author’s papers..
We can also create a plot to compare the authors using the following code.
ggplot( top5_all , aes(n, reorder_within( word , n, author),fill =author)) +
geom_col(show.legend = FALSE) +
facet_wrap(~author,ncol = 2, scales = "free_y") +
scale_y_reordered() +
labs( x = ... , y = ..., title = ...)You will note two new things in this plotting code.
reorder_within: We usefct_reorderwhen we want to make sure that the words appear in order on our bar graph. However, because we now have three different authors, we actually need to make sure that the words appear in order for all authors.reorder_withinallows us to include author in our ordering so that happens!facet_wrap(): This is what allows us to make one plot for each group. In this case,facet_wrap(~author)allows to make one plot for each author.
Question 4
Finish the code above to create the plot to compare the top 5 words in Madison’s, Hamilton’s, Jay’s, and the mystery author’s texts. Show the plot.
Question 5
Are there any words that show up in the top 5 list for more than one author? In other words, are there words that show up in more than one of the plots in Question 4?
The goal is for us to find words we can use as features in a model to predict author. For this purpose, it is helpful if we can find words that help us separate the writing of the three authors, meaning finding words that tend to be used commonly by one author but not the others. A word that is used often by multiple authors is not useful in determining among these authors for prediction.
All of this means that frequency alone is not enough. Instead, we need something that will help us find words that are commonly used by an author, but not commonly used by other authors. This is exactly the set up that motivates TF-IDF.
TF-IDF
The TF-IDF is a number that is high if a word is commonly used by an author but not commonly used by other authors. In other words, words with high TF-IDF scores fulfill the properties that we want in features for our Naive Bayes model to predict author.
As we learned in class, the TF-IDF score has two components: the TF score and IDF score.
Question 6
What does the TF score measure? In other words, briefly explain what having a high TF score tells us about a word.
Recall that the TF of word \(i\) in text group \(j\) is defined as:
\[TF_{i,j} = \frac{\text{Number of times word i appears in group j}}{\text{Total Number of words in group j}}\]
Question 7
How many groups of text are we working with today?
Note: This should not be a large number, and the number of groups is always the number of things we are trying to tell apart in the data set!
Question 8
The word “will” occurs 703 times in Hamilton’s papers, and there are 114321 words in total in Hamilton’s papers. Based on this, state and interpret the TF of the word “will” in Hamilton’s papers.
Once we have the TF score, the next step is to compute the IDF score.
Question 9
What does the IDF score measure? In other words, briefly explain what having a high IDF score tells us about a word.
Question 10
For these data, there are only four possible values for the IDF. State what those numeric values are and in what situations we would use each. In other words, in what situation would we get each of the 4 different values of the IDF?
Question 11
In addition to appearing 703 times in Hamilton’s papers, the word “will” occurs 247 times in Madison’s papers, 105 times in Jay’s papers, and 228 times in the mystery author’s papers. Based on this, what is the IDF of “will”?
The TF-IDF is computed by multiplying the TF and the IDF together.
Question 12
What is the TF-IDF for the word “will” in Hamilton’s papers? Based on this, is “will” going to be a useful word in distinguishing Hamilton’s papers from Madison’s or Jay’s? Explain.
Question 13
The word “representatives” occurs 59 times in Hamilton’s papers, 141 times in Madison’s papers, 112 times in the mystery author’s papers, and not at all in Jay’s papers. There are 114321 words in total in Hamilton’s papers. What is the TF-IDF of “representatives” in Hamilton’s papers?
Computing TF-IDF in R
Now that we know the TF-IDF will be useful for finding words we can use as features in our model, and we have reviewed how the TF-IDF is computed, the next step is to compute the TF-IDF for every word across the 85 Federalist papers. We then choose the words with the highest TF-IDF for each author as our features.
Given that there are over 14000 unique words in the papers, this would take a while. Luckily, R has one nice function that we can use to get the TF-IDF score for all words at once. .
tfidf_all <- Federalist |>
unnest_tokens(word, text)|>
group_by(author) |>
count(word) |>
# NEW!! Create the TF IDF Score
bind_tf_idf(word, author, n)Question 14
Annotate the code above!
Hint: I know the last line of code is new. To see what it does, I
recommend running the code with and without this final line so you can
see what it does! The structure of the bind_tf_idf code
requires (1) each word, (2) the groups, and (3) the number of times each
word appears in each group (n).
When you are done running the code above, you have a data set called
tfidf_all with 14505 rows and 6 columns. Each row is a
unique word in the Federalist papers, and for each author we are given
(1) the frequency of that word, (2) the TF score, (3) the IDF score, and
(4) the TF-IDF score of that word.
Question 15
Open up tfidf_all. What do you notice is unusual about
the first few words that are listed?
This data set, like many in text, requires some cleaning before we proceed. To handle the issue in Question 15, we add a line of a code we have seen before:
tfidf_all <- Federalist |>
unnest_tokens(word, text)|>
filter(!grepl('[0-9]', word)) |>
group_by(author) |>
count(word) |>
bind_tf_idf(word, author, n)Question 16
Open up tfidf_all again. What do you notice is unusual
now about the first few words that are listed?
This is a new one for us - weird punctuation. We have to deal with this a lot in text data, but luckily it can be handled with one additional line of code:
tfidf_all <- Federalist |>
# Tokenize
unnest_tokens(word, text)|>
# Remove the numbers
filter(!grepl('[0-9]', word)) |>
# Remove the punctuation
filter(!grepl('[[:punct:]]', word)) |>
# Group by author
group_by(author) |>
# Count the number of times each word appears
# for each author
count(word) |>
# Compute the tf-idf
bind_tf_idf(word, author, n)Question 17
Open up tfidf_all again. Do the first few words listed
look okay now, meaning they are words without numbers or symbols?
Most of the time when we have cleaning issues in text, we discover them just as we did today, meaning during the course of an analysis. Text can be very long, and it is difficult to anticipate all the cleaning that might need to be done. This means our job is to look at the data as we go and keep our eyes out for anything that might look odd so we can handle it. There is no short cut for this - we just have to be vigilant.
The Top Few Words
At this point, we have the TF-IDF score for every unique word in the Federalist papers. However, we do not want to use every single word as a feature. Instead, we want to choose words with high TF-IDF scores. These are the words that help distinguish the different authors.
If we only want the to find the top 5 words in terms of TF-IDF for each author, we use a very similar code as we did for counting the top 5 words in terms of frequency:
tfidf_top5 <- Federalist |>
unnest_tokens(word, text)|>
filter(!grepl('[0-9]', word)) |>
filter(!grepl('[[:punct:]]', word)) |>
group_by(author) |>
count(word) |>
bind_tf_idf(word, author, n) |>
slice_max( tf_idf, n = 5)The only change we have made to this code is in the
slice_max part. Usually, we have
slice_max( n , n = 5). This is because the column
n in the data set holds the counts, meaning the number of
times each word occurs in a group. If we want the top 5 words in terms
of count, we want the top 5 (n=5) in the column
n. However, now we want the top 10 words in terms of
TF-IDF. If we want the top 5 words in terms of TF-IDF, we want the top 5
(n=5) in the column tf_idf.
Question 18
Look at the top words 5 in terms of TF-IDF for Madison. There should be one word in that is surprising. What is it?
Another cleaning issue!! Each paper has the author’s name in it, listed as the author. The mystery papers were written anonymously, so the author’s names are not useful features.
Question 19
So far in our code, we have seem that
filter(!grepl'something',word) means to remove all
instances of 'something' from the word column
in the data set.
Right now, we want to remove madison, jay,
and hamilton from the words in our data set. Adapt the code
above Question 18 to do this, and state the top 10 words for each author
in terms of TF=IDF.
Wow this code is getting long!! As a note, you can store pieces of this code as you go, and then you don’t have to run the whole long thing every time. However, because we keep tweaking the code as we find new cleaning issues, for me it’s sometimes easier to just work with the whole code so I know where to make adjustments as needed.
Question 20
After all our cleaning steps, create a plot showing the top 5 words
for each author in terms of the TF-IDF using tfidf_top5
above.
Hint: You can use the code you already used to create a plot in
Questions 3 and 4 for this! The only difference is that no longer want
words with the highest count (n); we want words with the
highest tf_idf. Let me know if you get stuck!
Question 21
Recall that Hamilton claimed to have written all 12 test papers. Based on your current results, our historian client wants us to say whether or not we think this claim is true. State and justify your conclusion for your client.
So far, we have only looked at the top 5 words. We could do this for the top 10, 15, 20, 50, and so on! At that point, though, it becomes tedious to look down the list. SO, we are going to have R count for us! Copy and paste the following into an R chunk and press play.
check_tfidf_match <- function( howmany , whichauthor){
# Find the tf=idf
tfidf_top <- Federalist |>
unnest_tokens(word, text)|>
filter(!grepl('[0-9]', word)) |>
filter(!grepl('[[:punct:]]', word)) |>
filter(!grepl('madison', word)) |>
filter(!grepl('hamilton', word)) |>
filter(!grepl('jay', word)) |>
group_by(author) |>
count(word) |>
bind_tf_idf(word, author, n) |>
slice_max( tf_idf, n = howmany)
whichauthor = subset(tfidf_top, author == whichauthor)
mystery = subset(tfidf_top, author == "mystery")
sum(whichauthor$word %in% mystery$word)/nrow(mystery)
}I just wrote a function for you that (1) find the top
howmany words in terms of TF-IDF for each author and then
(2) tells you what proportion of the top howmany TF-IDF
words for the mystery author are also in the list for
whichauthor. For example,
returns .3, means 30% of the mystery’s authors top 10 words in terms of TF-IDF are also on Madison’s list of top 10 words.
Question 22
Use the check_tfidf_match function to find what percent
of words on the mystery author’s list of top 100 words in terms of
TF-IDF are also on (a) Hamilton’s, (b) Jay’s, and (c) Madison’s top 100
TF-IDF lists.
Question 23
Recall that Hamilton claimed to have written all 12 test papers. Based on your current results, our historian client wants us to say whether or not we think this claim is true. State and justify your conclusion for your client.
References
Data
The data come from https://github.com/nicholasjhorton/FederalistPapers, the GitHub repository of Dr. Nicholas J Horton. Citation: Horton, Nicholas J. Federalist Papers, Retrieved July 20, 2024 from https://github.com/nicholasjhorton/FederalistPapers.
Code
The code was adapted from Chapter 3 of “Text Mining with R: A Tidy Approach”, written by Julia Silge and David Robinson. The book was last built on 2024-06-20.
Activity
This
work was created by Nicole Dalzell is licensed under a
Creative
Commons Attribution-NonCommercial 4.0 International License. Last
updated 2026 August 22.