library(dplyr)
library(tidyr)
library(stringr)
library(ggplot2)
library(tidytext)
library(janeaustenr)
library(quanteda)
library(syuzhet)
library(knitr)Assignment10A | Sentiment Analysis
Approach
For this assignment, I will reproduce the sentiment analysis example from Chapter 2 of Text Mining with R using Jane Austen’s novels. I will use the tidytext package in R to tokenize the text and apply the NRC, Bing, and AFINN sentiment lexicons to examine patterns in positive and negative language. To extend the analysis, I will use a different text corpus consisting of U.S. presidential inaugural addresses. I will also introduce the Syuzhet sentiment lexicon to compare how different dictionaries evaluate the same text.
Introduction
This assignment reproduces the sentiment analysis examples from Chapter 2 of Text Mining with R by Silge and Robinson (2017), using Jane Austen’s novels and the NRC, Bing, and AFINN sentiment lexicons.To extend the analysis, I will examine U.S. presidential inaugural addresses and introduce the Syuzhet sentiment lexicon to compare sentiment patterns across different texts and methods.
Research Question: How does sentiment vary across U.S. presidential inaugural addresses, and how do different sentiment lexicons influence the results?
The original example code is adapted from Silge and Robinson’s Chapter 2.
Reproduce Chapter 2
Jane Austen - Make the novels tidy
The authors tokenize Jane Austen’s novels into one word per row, retaining each word’s book and line number. This is the central preparation step in Chapter 2.
tidy_books <- janeaustenr::austen_books() %>%
group_by(book) %>%
mutate(
linenumber = row_number(),
chapter = cumsum(
str_detect(text, regex("^chapter [\\divxlc]", ignore_case = TRUE))
)
) %>%
ungroup() %>%
unnest_tokens(word, text)
print(head(tidy_books, 8))# A tibble: 8 × 4
book linenumber chapter word
<fct> <int> <int> <chr>
1 Sense & Sensibility 1 0 sense
2 Sense & Sensibility 1 0 and
3 Sense & Sensibility 1 0 sensibility
4 Sense & Sensibility 3 0 by
5 Sense & Sensibility 3 0 jane
6 Sense & Sensibility 3 0 austen
7 Sense & Sensibility 5 0 1811
8 Sense & Sensibility 10 1 chapter
cat("Number of novels:", n_distinct(tidy_books$book), "\n")Number of novels: 6
Sentiment analysis across Austen novels
I will retrieve Jane Austen’s six completed novels and convert the text into individual word tokens and prepared the original corpus for sentiment analysis.
tidy_books <- janeaustenr::austen_books() %>%
group_by(book) %>%
mutate(
linenumber = row_number(),
chapter = cumsum(
stringr::str_detect(
text,
stringr::regex(
"^chapter [\\divxlc]",
ignore_case = TRUE
)
)
)
) %>%
ungroup() %>%
tidytext::unnest_tokens(word, text)
# Display the first 10 rows
print(head(tidy_books, 10))# A tibble: 10 × 4
book linenumber chapter word
<fct> <int> <int> <chr>
1 Sense & Sensibility 1 0 sense
2 Sense & Sensibility 1 0 and
3 Sense & Sensibility 1 0 sensibility
4 Sense & Sensibility 3 0 by
5 Sense & Sensibility 3 0 jane
6 Sense & Sensibility 3 0 austen
7 Sense & Sensibility 5 0 1811
8 Sense & Sensibility 10 1 chapter
9 Sense & Sensibility 10 1 1
10 Sense & Sensibility 13 1 the
# Confirm the number of novels
print(dplyr::n_distinct(tidy_books$book))[1] 6
# Examine the dataset structure
dplyr::glimpse(tidy_books)Rows: 725,055
Columns: 4
$ book <fct> Sense & Sensibility, Sense & Sensibility, Sense & Sensibili…
$ linenumber <int> 1, 1, 1, 3, 3, 3, 5, 10, 10, 13, 13, 13, 13, 13, 13, 13, 13…
$ chapter <int> 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ word <chr> "sense", "and", "sensibility", "by", "jane", "austen", "181…
NRC Sentiment Analysis: Identifying Joy-Associated Words in Emma
The NRC Word-Emotion Association Lexicon categorizes words according to different emotions, including joy, trust, fear, anger, and sadness. In Chapter 2 of Text Mining with R, Silge and Robinson (2017) use this lexicon to identify words associated with joy in Jane Austen’s Emma.
To reproduce this example, I will filter the NRC lexicon to include only joy-associated words, match those words with the text of Emma, and calculate their frequencies. This analysis will help identify which joy-related terms occur most frequently in the novel.
nrc_joy <- get_sentiments("nrc") %>%
filter(sentiment == "joy")
# Match NRC joy words with Emma
emma_joy <- tidy_books %>%
filter(book == "Emma") %>%
inner_join(nrc_joy, by = "word") %>%
count(word, sort = TRUE)
# Display the 10 most frequent joy words
print(head(emma_joy, 10))# A tibble: 10 × 2
word n
<chr> <int>
1 good 359
2 friend 166
3 hope 143
4 happy 125
5 love 117
6 deal 92
7 found 92
8 present 89
9 kind 82
10 happiness 76
Interpretation: The NRC sentiment analysis identified frequently occurring words associated with joy in Jane Austen’s Emma. By matching the novel’s text with the NRC lexicon, I was able to examine which joy-related terms appeared most often.
This example demonstrates how sentiment dictionaries can transform written language into measurable word-frequency patterns. However, the presence of a joy-associated word does not necessarily mean that the entire sentence expresses happiness, since the surrounding context may influence its meaning.
library(tidytext)
library(dplyr)
nrc_joy <- get_sentiments("nrc") %>%
filter(sentiment == "joy")
print(head(nrc_joy, 10))# A tibble: 10 × 2
word sentiment
<chr> <chr>
1 absolution joy
2 abundance joy
3 abundant joy
4 accolade joy
5 accompaniment joy
6 accomplish joy
7 accomplished joy
8 achieve joy
9 achievement joy
10 acrobat joy
Bing Sentiment Analysis: Measuring Positive and Negative Sentiment Across Jane Austen’s Novels
I will reproduce the Bing sentiment analysis from Chapter 2 of Text Mining with R. Unlike the NRC analysis, which focused on joy-associated words in Emma, the Bing lexicon classifies words as either positive or negative.
I will divide Jane Austen’s novels into sections of approximately 80 lines, count the positive and negative words in each section, and calculate the difference to measure net sentiment. I will then visualize how sentiment changes throughout the six novels.
# Calculate positive and negative sentiment across novels
jane_sentiment <- tidy_books %>%
inner_join(
get_sentiments("bing"),
by = "word"
) %>%
count(
book,
index = linenumber %/% 80,
sentiment
) %>%
pivot_wider(
names_from = sentiment,
values_from = n,
values_fill = 0
) %>%
mutate(
net_sentiment = positive - negative
)
# Display the first 10 observations
print(head(jane_sentiment, 10))# A tibble: 10 × 5
book index negative positive net_sentiment
<fct> <dbl> <int> <int> <int>
1 Sense & Sensibility 0 16 32 16
2 Sense & Sensibility 1 19 53 34
3 Sense & Sensibility 2 12 31 19
4 Sense & Sensibility 3 15 31 16
5 Sense & Sensibility 4 16 34 18
6 Sense & Sensibility 5 16 51 35
7 Sense & Sensibility 6 24 40 16
8 Sense & Sensibility 7 23 51 28
9 Sense & Sensibility 8 30 40 10
10 Sense & Sensibility 9 15 19 4
# Verify that all six novels are represented
print(n_distinct(jane_sentiment$book))[1] 6
Visualizing Sentiment Patterns Across the Novels
ggplot(
jane_sentiment,
aes(
x = index,
y = net_sentiment,
fill = book
)
) +
geom_col(show.legend = FALSE) +
facet_wrap(
~ book,
ncol = 2,
scales = "free_x"
) +
labs(
title = "Sentiment Throughout Jane Austen's Novels",
subtitle = "Bing Positive and Negative Word Analysis",
x = "Narrative Section (80-Line Groups)",
y = "Net Sentiment Score"
) +
theme_minimal()Positive fluctuations: Sections with more positive than negative word matches.
Negative fluctuations: Sections with more negative than positive word matches.
Variation across novels: Differences in the frequency and magnitude of these fluctuations.
Interpretation: The Bing sentiment analysis demonstrates how positive and negative language fluctuates throughout Jane Austen’s novels. By dividing the text into approximately 80-line sections, I was able to calculate net sentiment scores and visualize how the balance of positive and negative words changes across the narratives.
The visualizations show that sentiment is not constant throughout a novel. Instead, the frequency of positive and negative language varies across different sections. This analysis also highlights an important limitation of dictionary-based sentiment analysis: individual word classifications may not fully capture the intended meaning or emotional context of a passage.
Comparing AFINN, Bing, and NRC in Pride & Prejudice
I will reproduce the sentiment lexicon comparison from Chapter 2 of Text Mining with R by applying AFINN, Bing, and NRC to Jane Austen’s Pride & Prejudice.
Although all three lexicons measure sentiment, they use different scoring methods. AFINN assigns numerical values to words, while Bing and NRC classify words as positive or negative.
By calculating sentiment across 80-line sections of the novel, I will compare whether these methods identify similar patterns throughout the narrative.
pride_prejudice <- tidy_books %>%
filter(book == "Pride & Prejudice")
# Preview the selected novel
print(head(pride_prejudice, 10))# A tibble: 10 × 4
book linenumber chapter word
<fct> <int> <int> <chr>
1 Pride & Prejudice 1 0 pride
2 Pride & Prejudice 1 0 and
3 Pride & Prejudice 1 0 prejudice
4 Pride & Prejudice 3 0 by
5 Pride & Prejudice 3 0 jane
6 Pride & Prejudice 3 0 austen
7 Pride & Prejudice 7 1 chapter
8 Pride & Prejudice 7 1 1
9 Pride & Prejudice 10 1 it
10 Pride & Prejudice 10 1 is
# Verify the number of word tokens
print(nrow(pride_prejudice))[1] 122204
AFINN Sentiment Analysis
afinn_results <- pride_prejudice %>%
inner_join(
get_sentiments("afinn"),
by = "word"
) %>%
group_by(
index = linenumber %/% 80
) %>%
summarise(
sentiment = sum(value),
.groups = "drop"
) %>%
mutate(
method = "AFINN"
)
# Display the first 10 results
print(head(afinn_results, 10))# A tibble: 10 × 3
index sentiment method
<dbl> <dbl> <chr>
1 0 29 AFINN
2 1 0 AFINN
3 2 20 AFINN
4 3 30 AFINN
5 4 62 AFINN
6 5 66 AFINN
7 6 60 AFINN
8 7 18 AFINN
9 8 84 AFINN
10 9 26 AFINN
Bing and NRC Sentiment Analysis
bing_nrc_results <- bind_rows(
# Bing sentiment
pride_prejudice %>%
inner_join(
get_sentiments("bing"),
by = "word"
) %>%
mutate(method = "Bing"),
# NRC positive and negative sentiment
pride_prejudice %>%
inner_join(
get_sentiments("nrc") %>%
filter(
sentiment %in% c("positive", "negative")
),
by = "word"
) %>%
mutate(method = "NRC")
) %>%
count(
method,
index = linenumber %/% 80,
sentiment
) %>%
pivot_wider(
names_from = sentiment,
values_from = n,
values_fill = 0
) %>%
mutate(
sentiment = positive - negative
)
# Display the first 10 observations
print(head(bing_nrc_results, 10))# A tibble: 10 × 5
method index negative positive sentiment
<chr> <dbl> <int> <int> <int>
1 Bing 0 7 21 14
2 Bing 1 20 19 -1
3 Bing 2 16 20 4
4 Bing 3 19 31 12
5 Bing 4 23 47 24
6 Bing 5 15 49 34
7 Bing 6 18 46 28
8 Bing 7 23 33 10
9 Bing 8 17 48 31
10 Bing 9 22 40 18
Combining the Sentiment Results
lexicon_comparison <- bind_rows(
afinn_results,
bing_nrc_results %>%
select(index, sentiment, method)
)
# Display the combined results
print(head(lexicon_comparison, 10))# A tibble: 10 × 3
index sentiment method
<dbl> <dbl> <chr>
1 0 29 AFINN
2 1 0 AFINN
3 2 20 AFINN
4 3 30 AFINN
5 4 62 AFINN
6 5 66 AFINN
7 6 60 AFINN
8 7 18 AFINN
9 8 84 AFINN
10 9 26 AFINN
# Verify all three lexicons are present
print(unique(lexicon_comparison$method))[1] "AFINN" "Bing" "NRC"
Visualizing the Three Sentiment Lexicons
ggplot(
lexicon_comparison,
aes(
x = index,
y = sentiment,
fill = method
)
) +
geom_col(
show.legend = FALSE
) +
facet_wrap(
~ method,
ncol = 1,
scales = "free_y"
) +
labs(
title = "Comparing Sentiment Lexicons",
subtitle = "Pride & Prejudice by Jane Austen",
x = "Narrative Section (80-Line Groups)",
y = "Sentiment Score"
) +
theme_minimal()Interpretation: The comparison of AFINN, Bing, and NRC demonstrates how different sentiment dictionaries can produce different numerical results when analyzing the same text. In Pride & Prejudice, the three methods identify broadly similar positive and negative fluctuations throughout the narrative, although the magnitude of the sentiment scores differs.
AFINN uses weighted numerical values, while Bing and NRC calculate net sentiment from positive and negative word classifications. As discussed by Silge and Robinson (2017), these methodological differences influence the resulting scores. This comparison highlights the importance of considering the sentiment lexicon when interpreting text-mining results.
Extended Sentiment Analysis
Introducing a New Text Corpus: U.S. Presidential Inaugural Addresses
To extend the original Chapter 2 sentiment analysis, I selected U.S. presidential inaugural addresses as a different text corpus. Unlike Jane Austen’s novels, which follow fictional narratives, inaugural addresses are speeches that communicate national priorities, challenges, and future goals. I will retrieve presidential inaugural addresses from 1961 onward and prepare them for sentiment analysis. I selected this corpus to investigate how positive and negative language varies across speeches and to compare how different sentiment lexicons evaluate the same text.
# Loading the corpus from the quanteda package
data("data_corpus_inaugural", package = "quanteda")
# Convert the corpus into a tidy data frame
speeches <- tibble(
year = as.integer(
quanteda::docvars(
data_corpus_inaugural, "Year"
)
),
president = as.character(
quanteda::docvars(
data_corpus_inaugural, "President"
)
),
text = as.character(data_corpus_inaugural)
) %>%
filter(year >= 1961) %>%
mutate(
speech = paste(year, president)
) %>%
arrange(year)
# Display the first six observations
print(
speeches %>%
select(year, president, speech) %>%
head(6)
)# A tibble: 6 × 3
year president speech
<int> <chr> <chr>
1 1961 Kennedy 1961 Kennedy
2 1965 Johnson 1965 Johnson
3 1969 Nixon 1969 Nixon
4 1973 Nixon 1973 Nixon
5 1977 Carter 1977 Carter
6 1981 Reagan 1981 Reagan
# Display the total number of speeches
print(
paste("Number of speeches:", nrow(speeches))
)[1] "Number of speeches: 17"
# Examine the structure of the dataset
glimpse(speeches)Rows: 17
Columns: 4
$ year <int> 1961, 1965, 1969, 1973, 1977, 1981, 1985, 1989, 1993, 1997, …
$ president <chr> "Kennedy", "Johnson", "Nixon", "Nixon", "Carter", "Reagan", …
$ text <chr> "Vice President Johnson, Mr. Speaker, Mr. Chief Justice, Pre…
$ speech <chr> "1961 Kennedy", "1965 Johnson", "1969 Nixon", "1973 Nixon", …
Interpretation: The quanteda package provided a collection of U.S. presidential inaugural addresses, including the year, president, and full speech text. I filtered the dataset to include speeches delivered from 1961 onward and created a unique identifier for each address.
This dataset will allow me to apply sentiment analysis to a different type of text while maintaining the same general methodology used in the original Jane Austen example. The next stage will involve tokenizing the speeches so that individual words can be matched with sentiment dictionaries.
Preparing Presidential Inaugural Addresses for Sentiment Analysis
Before applying sentiment lexicons, I will convert the presidential inaugural addresses into tidy text using the unnest_tokens() function. This process separates each speech into individual words while retaining the year, president, and speech identifier.
I will also calculate the total number of word tokens in each speech. Because inaugural addresses vary in length, these counts will allow me to normalize sentiment scores and compare the speeches on a more consistent basis.
# Convert each speech into individual word tokens
speech_words <- speeches %>%
select(
year,
president,
speech,
text
) %>%
unnest_tokens(word, text)
# Display the first 10 word observations
print(head(speech_words, 10))# A tibble: 10 × 4
year president speech word
<int> <chr> <chr> <chr>
1 1961 Kennedy 1961 Kennedy vice
2 1961 Kennedy 1961 Kennedy president
3 1961 Kennedy 1961 Kennedy johnson
4 1961 Kennedy 1961 Kennedy mr
5 1961 Kennedy 1961 Kennedy speaker
6 1961 Kennedy 1961 Kennedy mr
7 1961 Kennedy 1961 Kennedy chief
8 1961 Kennedy 1961 Kennedy justice
9 1961 Kennedy 1961 Kennedy president
10 1961 Kennedy 1961 Kennedy eisenhower
# Count the total number of word tokens in each speech
speech_lengths <- speech_words %>%
count(
speech,
year,
president,
name = "total_tokens"
) %>%
arrange(year)
# Display the number of tokens in each speech
print(speech_lengths)# A tibble: 17 × 4
speech year president total_tokens
<chr> <int> <chr> <int>
1 1961 Kennedy 1961 Kennedy 1367
2 1965 Johnson 1965 Johnson 1490
3 1969 Nixon 1969 Nixon 2124
4 1973 Nixon 1973 Nixon 1805
5 1977 Carter 1977 Carter 1226
6 1981 Reagan 1981 Reagan 2436
7 1985 Reagan 1985 Reagan 2571
8 1989 Bush 1989 Bush 2318
9 1993 Clinton 1993 Clinton 1600
10 1997 Clinton 1997 Clinton 2158
11 2001 Bush 2001 Bush 1585
12 2005 Bush 2005 Bush 2073
13 2009 Obama 2009 Obama 2399
14 2013 Obama 2013 Obama 2106
15 2017 Trump 2017 Trump 1446
16 2021 Biden 2021 Biden 2377
17 2025 Trump 2025 Trump 2918
# Verify the total number of speeches
print(
paste(
"Number of speeches:",
nrow(speech_lengths)
)
)[1] "Number of speeches: 17"
# Verify that the token count matches the dataset
print(
paste(
"Total word tokens:",
sum(speech_lengths$total_tokens)
)
)[1] "Total word tokens: 33999"
Interpretation: The presidential inaugural addresses were successfully converted into tidy text using unnest_tokens(), resulting in one word per row. Each word remains associated with its original speech, year, and president.
I also calculated the total number of word tokens for each address. This step is important because inaugural speeches vary in length, and comparing raw sentiment counts alone could be misleading. By retaining the speech lengths, I will be able to calculate normalized sentiment scores per 1,000 tokens in the next stage of the analysis.
Bing Sentiment Analysis of Presidential Inaugural Addresses
In this section, I will apply the Bing sentiment lexicon to U.S. presidential inaugural addresses. This extends the original Chapter 2 analysis by using the same sentiment classification method on a different text corpus.
The Bing lexicon identifies words as positive or negative. I will calculate the frequency of each category within every speech and determine the net sentiment by subtracting negative word counts from positive word counts.
Because the inaugural addresses differ in length, I will normalize the sentiment scores per 1,000 word tokens to make comparisons across speeches more meaningful.
# Match speech words with Bing sentiment classifications
bing_speeches <- speech_words %>%
inner_join(
get_sentiments("bing"),
by = "word"
) %>%
count(
speech,
year,
president,
sentiment
) %>%
pivot_wider(
names_from = sentiment,
values_from = n,
values_fill = 0
) %>%
right_join(
speech_lengths,
by = c("speech", "year", "president")
) %>%
mutate(
positive = replace_na(positive, 0L),
negative = replace_na(negative, 0L),
net_sentiment = positive - negative,
bing_per_1000 =
net_sentiment / total_tokens * 1000
) %>%
arrange(desc(bing_per_1000))
# Display results from highest to lowest
print(
bing_speeches %>%
select(
year,
president,
positive,
negative,
total_tokens,
net_sentiment,
bing_per_1000
) %>%
mutate(
bing_per_1000 = round(bing_per_1000, 2)
)
)# A tibble: 17 × 7
year president positive negative total_tokens net_sentiment bing_per_1000
<int> <chr> <int> <int> <int> <int> <dbl>
1 1989 Bush 157 35 2318 122 52.6
2 1977 Carter 96 36 1226 60 48.9
3 1973 Nixon 120 41 1805 79 43.8
4 2001 Bush 110 49 1585 61 38.5
5 2017 Trump 80 26 1446 54 37.3
6 2025 Trump 184 78 2918 106 36.3
7 1997 Clinton 123 47 2158 76 35.2
8 2005 Bush 145 74 2073 71 34.2
9 1985 Reagan 162 75 2571 87 33.8
10 1969 Nixon 123 59 2124 64 30.1
11 2013 Obama 104 50 2106 54 25.6
12 1993 Clinton 76 35 1600 41 25.6
13 1965 Johnson 82 45 1490 37 24.8
14 1981 Reagan 117 70 2436 47 19.3
15 2021 Biden 122 77 2377 45 18.9
16 2009 Obama 124 81 2399 43 17.9
17 1961 Kennedy 76 61 1367 15 11.0
Visualizing Bing Sentiment Across Presidential Inaugural Addresses
ggplot(
bing_speeches,
aes(
x = reorder(speech, bing_per_1000),
y = bing_per_1000
)
) +
geom_col(
fill = "steelblue",
width = 0.75
) +
coord_flip() +
labs(
title = "Bing Sentiment Across Presidential Inaugural Addresses",
subtitle = "Net sentiment normalized per 1,000 word tokens",
x = "Presidential Inaugural Address",
y = "Net Bing Sentiment per 1,000 Tokens"
) +
theme_minimal()Interpretation: The Bing sentiment analysis allowed me to compare positive and negative language across U.S. presidential inaugural addresses. By calculating net sentiment and normalizing the scores per 1,000 word tokens, I was able to account for differences in speech length.
The visualization shows how sentiment varies across the speeches, with higher scores representing a greater frequency of positive relative to negative dictionary matches. Unlike the original Jane Austen analysis, which examined sentiment changes throughout fictional narratives, this extension compares overall sentiment across individual political speeches.
Although the scores provide an useful measure of sentiment-associated word usage, they do not fully capture the context or intended meaning of each speech.
Additional Sentiment Lexicon: Syuzhet
To further extend the original sentiment analysis, I will introduce the Syuzhet sentiment lexicon using the syuzhet R package. Unlike the Bing lexicon, which classifies words as positive or negative, Syuzhet assigns weighted numerical sentiment scores.
I will apply Syuzhet to the same presidential inaugural addresses analyzed in the previous section. By calculating and normalizing the sentiment scores per 1,000 tokens, I will be able to compare how different sentiment dictionaries evaluate the same collection of speeches.
# Create a lookup table of unique words and sentiment values
syuzhet_words <- speech_words %>%
distinct(word) %>%
mutate(
value = syuzhet::get_sentiment(
word,
method = "syuzhet"
)
)
# Calculate the weighted sentiment for each speech
syuzhet_speeches <- speech_words %>%
left_join(
syuzhet_words,
by = "word"
) %>%
group_by(
speech,
year,
president
) %>%
summarise(
total_score = sum(value, na.rm = TRUE),
.groups = "drop"
) %>%
left_join(
speech_lengths,
by = c("speech", "year", "president")
) %>%
mutate(
syuzhet_per_1000 =
total_score / total_tokens * 1000
) %>%
arrange(desc(syuzhet_per_1000))
# Display the results
print(
syuzhet_speeches %>%
select(
year,
president,
total_tokens,
total_score,
syuzhet_per_1000
) %>%
mutate(
total_score = round(total_score, 2),
syuzhet_per_1000 = round(syuzhet_per_1000, 2)
)
)# A tibble: 17 × 5
year president total_tokens total_score syuzhet_per_1000
<int> <chr> <int> <dbl> <dbl>
1 1977 Carter 1226 73.9 60.3
2 1989 Bush 2318 123. 52.9
3 1997 Clinton 2158 110. 51.1
4 1973 Nixon 1805 87.5 48.5
5 2017 Trump 1446 66.1 45.7
6 2001 Bush 1585 72.4 45.7
7 2005 Bush 2073 93.2 45.0
8 1985 Reagan 2571 105. 40.7
9 1969 Nixon 2124 76 35.8
10 1993 Clinton 1600 54.0 33.7
11 1981 Reagan 2436 80.6 33.1
12 2009 Obama 2399 76.3 31.8
13 2013 Obama 2106 66.8 31.7
14 2025 Trump 2918 87.4 29.9
15 1965 Johnson 1490 43.0 28.8
16 2021 Biden 2377 52.8 22.2
17 1961 Kennedy 1367 29.1 21.3
# Verify that all speeches are represented
print(
paste(
"Number of speeches analyzed:",
nrow(syuzhet_speeches)
)
)[1] "Number of speeches analyzed: 17"
Visualizing Syuzhet Sentiment Across Presidential Inaugural Addresses
ggplot(
syuzhet_speeches,
aes(
x = reorder(speech, syuzhet_per_1000),
y = syuzhet_per_1000
)
) +
geom_col(
fill = "darkorange",
width = 0.75
) +
coord_flip() +
labs(
title = "Syuzhet Sentiment Across Inaugural Addresses",
subtitle = "Weighted sentiment per 1,000 word tokens",
x = "Presidential Inaugural Address",
y = "Syuzhet Score per 1,000 Tokens"
) +
theme_minimal()Interpretation: The Syuzhet sentiment analysis provided an additional method for evaluating positive and negative language across presidential inaugural addresses. Unlike Bing, which calculates sentiment using positive and negative word counts, Syuzhet assigns weighted numerical values to individual words.
After normalizing the scores per 1,000 tokens, I was able to compare the relative sentiment of speeches of different lengths. The resulting visualization provides an alternative perspective on sentiment patterns within the inaugural-address corpus.
Because the two lexicons use different scoring systems, their numerical scores should not be interpreted as directly equivalent. The next stage will compare the rankings produced by Bing and Syuzhet to evaluate the consistency of their results.
Comparison of Bing and Syuzhet Sentiment Results
To evaluate how sentiment lexicon selection influences the results, I will compare the Bing and Syuzhet sentiment scores calculated for the presidential inaugural addresses.
Both methods were applied to the same speeches, and their scores were normalized per 1,000 word tokens. However, Bing calculates sentiment using positive and negative word frequencies, while Syuzhet assigns weighted numerical values.
I will combine the results, create a comparative visualization, and calculate Spearman rank correlation to determine whether the two lexicons produce similar rankings across speeches.
sentiment_comparison <- bing_speeches %>%
select(
speech,
year,
president,
bing_per_1000
) %>%
inner_join(
syuzhet_speeches %>%
select(
speech,
syuzhet_per_1000
),
by = "speech"
) %>%
arrange(year)
# Display a formatted comparison table
sentiment_comparison %>%
mutate(
bing_per_1000 = round(bing_per_1000, 2),
syuzhet_per_1000 = round(syuzhet_per_1000, 2)
) %>%
knitr::kable(
caption = "Comparison of Bing and Syuzhet Sentiment Scores"
)| speech | year | president | bing_per_1000 | syuzhet_per_1000 |
|---|---|---|---|---|
| 1961 Kennedy | 1961 | Kennedy | 10.97 | 21.29 |
| 1965 Johnson | 1965 | Johnson | 24.83 | 28.83 |
| 1969 Nixon | 1969 | Nixon | 30.13 | 35.78 |
| 1973 Nixon | 1973 | Nixon | 43.77 | 48.48 |
| 1977 Carter | 1977 | Carter | 48.94 | 60.28 |
| 1981 Reagan | 1981 | Reagan | 19.29 | 33.09 |
| 1985 Reagan | 1985 | Reagan | 33.84 | 40.74 |
| 1989 Bush | 1989 | Bush | 52.63 | 52.87 |
| 1993 Clinton | 1993 | Clinton | 25.62 | 33.72 |
| 1997 Clinton | 1997 | Clinton | 35.22 | 51.14 |
| 2001 Bush | 2001 | Bush | 38.49 | 45.68 |
| 2005 Bush | 2005 | Bush | 34.25 | 44.96 |
| 2009 Obama | 2009 | Obama | 17.92 | 31.80 |
| 2013 Obama | 2013 | Obama | 25.64 | 31.74 |
| 2017 Trump | 2017 | Trump | 37.34 | 45.71 |
| 2021 Biden | 2021 | Biden | 18.93 | 22.23 |
| 2025 Trump | 2025 | Trump | 36.33 | 29.93 |
# Verify that all speeches are represented
print(
paste(
"Speeches in comparison:",
nrow(sentiment_comparison)
)
)[1] "Speeches in comparison: 17"
Visualizing the Relationship Between Bing and Syuzhet
ggplot(
sentiment_comparison,
aes(
x = bing_per_1000,
y = syuzhet_per_1000
)
) +
geom_point(
color = "steelblue",
size = 3,
alpha = 0.8
) +
geom_smooth(
method = "lm",
se = FALSE,
color = "darkorange"
) +
labs(
title = "Bing vs. Syuzhet Sentiment Scores",
subtitle = "U.S. Presidential Inaugural Addresses, 1961 Onward",
x = "Bing Net Sentiment per 1,000 Tokens",
y = "Syuzhet Weighted Sentiment per 1,000 Tokens"
) +
theme_minimal()Spearman Rank Correlation
spearman_result <- cor(
sentiment_comparison$bing_per_1000,
sentiment_comparison$syuzhet_per_1000,
method = "spearman",
use = "complete.obs"
)
# Display the correlation coefficient
print(
paste(
"Spearman correlation:",
round(spearman_result, 3)
)
)[1] "Spearman correlation: 0.843"
Highest-Scoring Inaugural Addresses
# Highest normalized Bing sentiment
top_bing <- sentiment_comparison %>%
slice_max(
order_by = bing_per_1000,
n = 1,
with_ties = TRUE
)
# Highest normalized Syuzhet sentiment
top_syuzhet <- sentiment_comparison %>%
slice_max(
order_by = syuzhet_per_1000,
n = 1,
with_ties = TRUE
)
# Display the results
print("Highest Bing Sentiment:")[1] "Highest Bing Sentiment:"
print(top_bing)# A tibble: 1 × 5
speech year president bing_per_1000 syuzhet_per_1000
<chr> <int> <chr> <dbl> <dbl>
1 1989 Bush 1989 Bush 52.6 52.9
print("Highest Syuzhet Sentiment:")[1] "Highest Syuzhet Sentiment:"
print(top_syuzhet)# A tibble: 1 × 5
speech year president bing_per_1000 syuzhet_per_1000
<chr> <int> <chr> <dbl> <dbl>
1 1977 Carter 1977 Carter 48.9 60.3
Interpretation: Comparing Bing and Syuzhet allowed me to examine how two different sentiment-scoring methods evaluate the same presidential inaugural addresses. Although both methods were normalized per 1,000 tokens, they use different approaches to assigning sentiment values.
The comparison scatterplot provides a visual representation of the relationship between the two methods, while Spearman rank correlation measures how similarly the speeches are ranked.
These results are important because they demonstrate that sentiment analysis can be influenced by the choice of lexicon. The findings will be discussed alongside the original Jane Austen example to evaluate how sentiment-analysis methods behave across different text corpora.
Conclusion
By analyzing Jane Austen’s novels with the NRC, Bing, and AFINN lexicons, I learned how to tokenize text, calculate sentiment scores, and visualize changes in positive and negative language throughout a narrative.
For my extended analysis, I applied the same text-mining techniques to U.S. presidential inaugural addresses and introduced Syuzhet as an additional sentiment lexicon. Comparing Bing and Syuzhet allowed me to examine how different scoring methods evaluate the same text and whether they produce similar sentiment rankings.
This analysis highlights the importance of selecting appropriate sentiment lexicons and interpreting results within the context of the documents being studied. Although lexicon-based sentiment analysis provides a systematic way to identify language patterns, it does not fully capture the complexity of human language, including context, negation, and intended meaning.