library(tidyverse)
library(tidytext)
library(broom)
library(reshape2)
library(gutenbergr)
library(wordcloud)
library(scriptuRs)
options(repr.plot.width=15, repr.plot.height=10)
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ── ✔ ggplot2 3.3.5 ✔ purrr 0.3.4 ✔ tibble 3.1.3 ✔ dplyr 1.0.7 ✔ tidyr 1.1.3 ✔ stringr 1.4.0 ✔ readr 2.0.0 ✔ forcats 0.5.1 ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ── ✖ dplyr::filter() masks stats::filter() ✖ dplyr::lag() masks stats::lag() Attaching package: ‘reshape2’ The following object is masked from ‘package:tidyr’: smiths Loading required package: RColorBrewer
We first make a more detailed analysis on the Holy Bible, which is the most read book in the world.
The version used here is the King James Version.
bible <- kjv_bible() %>%
select(volume_title, text) %>%
mutate(book = "The Holy Bible") %>%
relocate(book, volume_title, text)
Creating a tidy table listing all words in the text:
words <- bible %>%
mutate(text = str_to_lower(text)) %>%
mutate(text = str_replace_all(text, "'s", "")) %>%
unnest_tokens(word, text)
Removing stop words, blank lines, numbers and punctuation from the text:
pattern <- c('thou', 'thy', 'ye', 'thee', 'hath')
filtered_words = words %>%
filter(!word %in% c(stop_words$word, pattern))%>%
filter(word != "") %>%
filter(!str_detect(word, "^\\d+$")) %>%
filter(!str_detect(word, "[[:punct:]]"))
Arranging the words that are more frequent in absolute frequency:
filtered_words %>%
count(word, volume_title) %>%
spread(volume_title, n, fill = 0) %>%
mutate(total = `Old Testament` + `New Testament`) %>%
relocate(word, `Old Testament`, `New Testament`, total) %>%
arrange(desc(total)) %>%
head(10)
| word | Old Testament | New Testament | total |
|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> |
| lord | 7236 | 728 | 7964 |
| god | 3100 | 1372 | 4472 |
| israel | 2501 | 75 | 2576 |
| king | 2451 | 89 | 2540 |
| son | 1970 | 422 | 2392 |
| people | 1910 | 233 | 2143 |
| house | 1817 | 206 | 2023 |
| children | 1660 | 160 | 1820 |
| day | 1420 | 319 | 1739 |
| land | 1667 | 50 | 1717 |
Calculating the odds ratio of words appearing in Old Testament or New Testament, with correction of plus 0.5:
odds_ratio <- filtered_words %>%
count(word, volume_title) %>%
spread(volume_title, n, fill = 0) %>%
relocate(word, `Old Testament`, `New Testament`) %>%
mutate(or = (`Old Testament` + 0.5) / (sum(`Old Testament`) - `Old Testament` + 0.5) /
( (`New Testament` + 0.5) / (sum(`New Testament`) - `New Testament` + 0.5)))
Showing the words that appears with higher frequency in Old Testament and New Testament, each:
odds_ratio %>% arrange(desc(or)) %>% head(10)
| word | Old Testament | New Testament | or |
|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> |
| hosts | 299 | 0 | 158.08979 |
| judah | 825 | 1 | 145.62472 |
| philistines | 254 | 0 | 134.30660 |
| beth | 225 | 0 | 118.98531 |
| joshua | 216 | 0 | 114.23132 |
| families | 174 | 0 | 92.05171 |
| moab | 174 | 0 | 92.05171 |
| zion | 153 | 0 | 80.96538 |
| jeremiah | 147 | 0 | 77.79828 |
| manasseh | 147 | 0 | 77.79828 |
odds_ratio %>% arrange(or) %>% head(10)
| word | Old Testament | New Testament | or |
|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> |
| jesus | 0 | 984 | 0.0001313515 |
| christ | 0 | 571 | 0.0002280728 |
| paul | 0 | 162 | 0.0008083798 |
| peter | 0 | 162 | 0.0008083798 |
| john | 0 | 133 | 0.0009845237 |
| gospel | 0 | 104 | 0.0012584316 |
| pharisees | 0 | 87 | 0.0015034105 |
| disciples | 1 | 243 | 0.0016159434 |
| church | 0 | 77 | 0.0016977202 |
| simon | 0 | 76 | 0.0017199451 |
Since we want to consider a larger variety of sentiments, it is used the nrc lexicon for sentiment analysis:
nrc <- get_sentiments("nrc") %>%
select(word, sentiment)
Calculating here the quantitative analysis of sentiments, in absolute numbers:
sentiment_counts <- filtered_words %>%
left_join(nrc, by = "word") %>%
count(volume_title, sentiment) %>%
spread(volume_title, n) %>%
relocate(sentiment, `Old Testament`, `New Testament`) %>%
mutate(sentiment = replace_na(sentiment, replace = "none"))
sentiment_counts %>%
arrange(desc(`Old Testament` + `New Testament`))
| sentiment | Old Testament | New Testament |
|---|---|---|
| <chr> | <int> | <int> |
| none | 142225 | 35527 |
| positive | 34336 | 10101 |
| trust | 25080 | 7893 |
| negative | 24089 | 5433 |
| fear | 13514 | 4214 |
| joy | 11422 | 4777 |
| anticipation | 11157 | 4885 |
| disgust | 13220 | 2528 |
| sadness | 8525 | 2603 |
| anger | 8134 | 2140 |
| surprise | 4004 | 1389 |
It is important to note that the Old Testament contains much more words than New Testament, as follows:
words %>% group_by(volume_title) %>% summarize(n = n())
| volume_title | n |
|---|---|
| <chr> | <int> |
| New Testament | 180392 |
| Old Testament | 610017 |
Due to this difference, the odds ratio of each sentiment is calculated.
An odds ratio bigger than 1 represents more appearance in the Old Testament:
sentiment_counts %>%
mutate(`Old Testament` = `Old Testament` / (sum(`Old Testament`) - `Old Testament`),
`New Testament` = `New Testament` / (sum(`New Testament`) - `New Testament`),
or = `Old Testament`/`New Testament`) %>%
arrange(desc(or))
| sentiment | Old Testament | New Testament | or |
|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> |
| disgust | 0.04679878 | 0.03201540 | 1.4617584 |
| negative | 0.08868738 | 0.07143327 | 1.2415417 |
| none | 0.92666193 | 0.77294781 | 1.1988674 |
| anger | 0.02828509 | 0.02696912 | 1.0487953 |
| positive | 0.13136932 | 0.14149239 | 0.9284551 |
| sadness | 0.02968511 | 0.03299656 | 0.8996425 |
| fear | 0.04788938 | 0.05453181 | 0.8781917 |
| trust | 0.09267402 | 0.10724622 | 0.8641239 |
| surprise | 0.01372634 | 0.01734061 | 0.7915719 |
| joy | 0.04017813 | 0.06227106 | 0.6452135 |
| anticipation | 0.03920942 | 0.06376868 | 0.6148695 |
The purpose is to show which sentiments are more expressed in each testament, compared to the other one.
The Old Testament has more use of: disgust, negative and anger sentiments
The New Testament have more expression of: anticipation, joy and surprise
For each sentiment, the odds ratio and confidence interval is calculated:
log_or <- sentiment_counts %>%
mutate( log_or = log( (`Old Testament` / (sum(`Old Testament`) - `Old Testament`)) / (`New Testament` / (sum(`New Testament`) - `New Testament`))),
se = sqrt( 1/`Old Testament` + 1/(sum(`Old Testament`) - `Old Testament`) + 1/`New Testament` + 1/(sum(`New Testament`) - `New Testament`)),
conf.low = log_or - qnorm(0.975)*se,
conf.high = log_or + qnorm(0.975)*se) %>%
arrange(desc(log_or))
log_or
| sentiment | Old Testament | New Testament | log_or | se | conf.low | conf.high |
|---|---|---|---|---|---|---|
| <chr> | <int> | <int> | <dbl> | <dbl> | <dbl> | <dbl> |
| disgust | 13220 | 2528 | 0.37964009 | 0.022077520 | 0.3363689457 | 0.42291124 |
| negative | 24089 | 5433 | 0.21635391 | 0.015569290 | 0.1858386634 | 0.24686916 |
| none | 142225 | 35527 | 0.18137728 | 0.007965602 | 0.1657649912 | 0.19698958 |
| anger | 8134 | 2140 | 0.04764216 | 0.024623368 | -0.0006187525 | 0.09590308 |
| positive | 34336 | 10101 | -0.07423329 | 0.012081300 | -0.0979122070 | -0.05055438 |
| sadness | 8525 | 2603 | -0.10575783 | 0.022751542 | -0.1503500353 | -0.06116563 |
| fear | 13514 | 4214 | -0.12989039 | 0.018104858 | -0.1653752610 | -0.09440552 |
| trust | 25080 | 7893 | -0.14603910 | 0.013559115 | -0.1726144815 | -0.11946373 |
| surprise | 4004 | 1389 | -0.23373460 | 0.031394347 | -0.2952663874 | -0.17220281 |
| joy | 11422 | 4777 | -0.43817397 | 0.017704235 | -0.4728736311 | -0.40347430 |
| anticipation | 11157 | 4885 | -0.48634530 | 0.017632539 | -0.5209044452 | -0.45178616 |
The analysis performed calculates the log of the odd ratios, so it is easier to notice which sentiments are more expressed in each testament: positive numbers are more frequent in the Old Testament.
A graphical visualization shows how all of these sentiments are overrepresented in one or another testament:
log_or %>%
mutate(sentiment = reorder(sentiment, log_or),) %>%
ggplot(aes(x = sentiment, ymin = conf.low, ymax = conf.high)) +
geom_errorbar() +
geom_point(aes(sentiment, log_or)) +
ylab("Log odds ratio for association between Old Testament and sentiment") +
theme(axis.text = element_text(size=14)) +
coord_flip()
It is possible to visualize which words are driving more extreme sentiments like disgust or joy:
odds_ratio %>% inner_join(nrc, by = "word") %>%
filter(sentiment == "disgust" & `Old Testament` + `New Testament` > 10) %>%
arrange(desc(or)) %>% head(10)
| word | Old Testament | New Testament | or | sentiment |
|---|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> | <chr> |
| fat | 130 | 0 | 68.825870 | disgust |
| pestilence | 47 | 0 | 25.041208 | disgust |
| burnt | 361 | 5 | 17.350655 | disgust |
| oppression | 24 | 0 | 12.914512 | disgust |
| slayer | 19 | 0 | 10.278642 | disgust |
| hanging | 18 | 0 | 9.751483 | disgust |
| terrible | 51 | 1 | 9.049990 | disgust |
| boil | 16 | 0 | 8.697182 | disgust |
| derision | 15 | 0 | 8.170040 | disgust |
| prolong | 14 | 0 | 7.642902 | disgust |
odds_ratio %>% inner_join(nrc, by = "word") %>%
filter(sentiment == "joy" & `Old Testament` + `New Testament` > 10) %>%
arrange(or) %>% head(10)
| word | Old Testament | New Testament | or | sentiment |
|---|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> | <chr> |
| church | 0 | 77 | 0.001697720 | joy |
| faith | 2 | 245 | 0.002671210 | joy |
| charity | 0 | 28 | 0.004620887 | joy |
| heavenly | 0 | 23 | 0.005604585 | joy |
| marriage | 2 | 17 | 0.037635424 | joy |
| star | 2 | 13 | 0.048790348 | joy |
| promise | 8 | 45 | 0.049190981 | joy |
| salute | 7 | 32 | 0.060779964 | joy |
| ministry | 4 | 18 | 0.064081365 | joy |
| marry | 5 | 17 | 0.082799169 | joy |
The 5 words most used to express each sentiment, for each testament, can be plotted in a graph.
Here blue symbolizes New Testament and red symbolizes Old Testament:
odds_ratio %>%
inner_join(nrc, by = "word") %>%
mutate(sentiment = factor(sentiment, levels = log_or$sentiment)) %>%
mutate(log_or = log(or)) %>%
filter(`Old Testament` + `New Testament` > 10 & abs(log_or)>1) %>%
mutate(word = reorder(word, log_or)) %>%
arrange(log_or) %>%
group_by(sentiment) %>%
filter(row_number()<=5 | row_number()>=n()-4) %>%
ungroup() %>%
ggplot(aes(word, log_or, fill = log_or < 0)) +
facet_wrap(~sentiment, scales = "free_x", nrow = 2) +
geom_bar(stat="identity", show.legend = FALSE) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))