[1] "Elena works at the MTA in New York, trying to speed up trains. Last week she met | with Robert, the a data scientist at Mckinsey working out of Washington DC"
2025-11-03
read_html vs read_html_liveopen screts rendered a static pagesText mining is the process of extracting quantitative insights from unstructured textual data.
politics, pop-culture, science, sports, business, leisure, etcTopic Modeling
Discover the subjects described in a group of documents
Unsupervised dimensionality reduction technique 
[1] "Elena works at the MTA in New York, trying to speed up trains. Last week she met | with Robert, the a data scientist at Mckinsey working out of Washington DC"
| name | type |
|---|---|
| Elena | person |
| MTA | organization |
| train operations | role |
| New York | location |
| Robert | person |
| data scientist | role |
| Mckinsey | organization |
| Washington DC | location |
Text is stored in a Tidy format if it is divided into tokens and it is stored in a table with one token per row.
[1] "Because I could not stop for Death -"
[2] "He kindly stopped for me -"
[3] "The Carriage held but just Ourselves -"
[4] "and Immortality"
Text is stored in a Tidy format if it is divided into tokens and it is stored in a table with one token per row.
# A tibble: 20 × 2
line word
<int> <chr>
1 1 because
2 1 i
3 1 could
4 1 not
5 1 stop
6 1 for
7 1 death
8 2 he
9 2 kindly
10 2 stopped
11 2 for
12 2 me
13 3 the
14 3 carriage
15 3 held
16 3 but
17 3 just
18 3 ourselves
19 4 and
20 4 immortality
Text is stored in a Tidy format if it is divided into tokens and it is stored in a table with one token per row.
# A tibble: 4 × 1
line
<chr>
1 because i could not stop for death -
2 he kindly stopped for me -
3 the carriage held but just ourselves -
4 and immortality

tidytext R package has functions for tokenizationtoken can be words, sentences, lines, paragraphs, regex, ngrams, various others# A tibble: 13,030 × 1
text
<chr>
1 "PRIDE AND PREJUDICE"
2 ""
3 "By Jane Austen"
4 ""
5 ""
6 ""
7 "Chapter 1"
8 ""
9 ""
10 "It is a truth universally acknowledged, that a single man in possession"
# ℹ 13,020 more rows
token can be words, sentences, lines, paragraphs, regex, ngrams, various otherstoken can be words, sentences, lines, paragraphs, regex, ngrams, various othersausten_books() |> filter(book == "Pride & Prejudice") |> select(text) |>
unnest_tokens(sentence,text,token="sentences")# A tibble: 15,545 × 1
sentence
<chr>
1 "pride and prejudice"
2 "by jane austen"
3 "chapter 1"
4 "it is a truth universally acknowledged, that a single man in possession"
5 "of a good fortune, must be in want of a wife."
6 "however little known the feelings or views of such a man may be on his"
7 "first entering a neighbourhood, this truth is so well fixed in the minds"
8 "of the surrounding families, that he is considered the rightful property"
9 "of some one or other of their daughters."
10 "\"my dear mr."
# ℹ 15,535 more rows
TTwR 1.1
stop words are words that commonly occur in the text you are studying but which contain little to no meaningful information about your data analysis taskthe, of, to, a, an, forstop words are words that commonly occur in the text you are studying but which contain little to no meaningful information about your data analysis taskthe, of, to, a, an, for# A tibble: 7 × 2
line word
<int> <chr>
1 1 stop
2 1 death
3 2 kindly
4 2 stopped
5 3 carriage
6 3 held
7 4 immortality
stop_words come from?lexicon is the collection of terms that appear in a corpusstop_words combines three lexicons composed of different collections of stop words:
run, runner, running, runners, runsrunregexrun, runner, running, runners, runsrun using regexrun, runner, running, runners, runsrun using regexrun, runner, running, runners, runsrun using regex# A tibble: 73,422 × 4
text book linenumber chapter
<chr> <fct> <int> <int>
1 "SENSE AND SENSIBILITY" Sense & Sensibility 1 0
2 "" Sense & Sensibility 2 0
3 "by Jane Austen" Sense & Sensibility 3 0
4 "" Sense & Sensibility 4 0
5 "(1811)" Sense & Sensibility 5 0
6 "" Sense & Sensibility 6 0
7 "" Sense & Sensibility 7 0
8 "" Sense & Sensibility 8 0
9 "" Sense & Sensibility 9 0
10 "CHAPTER 1" Sense & Sensibility 10 1
# ℹ 73,412 more rows
TTWR Fig 2.1
afinn ranks words from -5 to 5:# A tibble: 2,477 × 2
word value
<chr> <dbl>
1 abandon -2
2 abandoned -2
3 abandons -2
4 abducted -2
5 abduction -2
6 abductions -2
7 abhor -3
8 abhorred -3
9 abhorrent -3
10 abhors -3
# ℹ 2,467 more rows
afinn ranks words from -5 to 5:bing splits words into positive or negativenrc groups words into 10 broad categoriesnrc groups words into 10 broad categoriesmodest_proposal = gutenberg_download(1080) |> unnest_tokens(word,text) |> anti_join(stop_words)
modest_proposal |>
inner_join(get_sentiments("afinn")) # A tibble: 142 × 3
gutenberg_id word value
<int> <chr> <dbl>
1 1080 preventing -1
2 1080 poor -2
3 1080 burden -2
4 1080 swift 2
5 1080 melancholy -2
6 1080 honest 2
7 1080 forced -1
8 1080 helpless -2
9 1080 leave -1
10 1080 dear 2
# ℹ 132 more rows
shakespeare = gutenberg_download(c("2266","2265","2264","2267"),meta_fields = "title") |>
unnest_tokens(word,text) |> mutate(linenumber = row_number())
shakespeare_sentiment = shakespeare |> inner_join(get_sentiments("bing")) |>
count(title, index = linenumber %/% 400, sentiment) %>%
pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
mutate(sentiment = positive - negative)
shakespeare_sentiment |> head(6)# A tibble: 6 × 5
title index negative positive sentiment
<chr> <dbl> <int> <int> <int>
1 Hamlet 63 9 1 -8
2 Hamlet 64 5 8 3
3 Hamlet 65 13 13 0
4 Hamlet 66 14 10 -4
5 Hamlet 67 14 13 -1
6 Hamlet 68 9 8 -1
Can we determine what a document is about by looking at the words?
Take a look at Shakespeare
shakespeare <- gutenberg_download(c("2266","2265","2264","2267"),meta_fields = "title")
shakespeare |> unnest_tokens(word,text) |> count(title,word,sort=TRUE)# A tibble: 17,111 × 3
title word n
<chr> <chr> <int>
1 Hamlet the 1031
2 Hamlet and 881
3 Othello i 868
4 Othello and 787
5 King Lear the 767
6 Othello the 737
7 Hamlet to 696
8 Macbeth the 687
9 King Lear and 654
10 Hamlet of 632
# ℹ 17,101 more rows
Can we determine what a document is about by looking at words?
library(forcats)
shakespeare |> unnest_tokens(word,text) |> group_by(title) |>
count(word,sort=TRUE) |>
slice_max(n,n=10) |> ungroup() |>
mutate(word = reorder(word,n)) |>
ggplot(aes(n,fct_reorder(word, n),fill = title)) +
geom_col(show.legend = FALSE) +
labs(x = "count", y = NULL) +
facet_wrap(~title, ncol = 2, scales = "free")Can we determine what a document is about by looking at words?
tf-idf stands for term frequency-inverse document frequency\[ \mathrm{tf}\cdot\mathrm{idf}(term,doc) = \frac{n_{term\_in\_doc}}{n_{words\_in\_doc}} \log(\frac{n_{docs}}{n_{docs\_with\_term}}) \]
tf-idfbind_tf_idf calculates tf-idf given text and a grouping that defines corpus documents# A tibble: 17,111 × 6
title word n tf idf tf_idf
<chr> <chr> <int> <dbl> <dbl> <dbl>
1 Hamlet the 1031 0.0340 0 0
2 Hamlet and 881 0.0291 0 0
3 Othello i 868 0.0309 0 0
4 Othello and 787 0.0280 0 0
5 King Lear the 767 0.0302 0 0
6 Othello the 737 0.0262 0 0
7 Hamlet to 696 0.0230 0 0
8 Macbeth the 687 0.0372 0 0
9 King Lear and 654 0.0258 0 0
10 Hamlet of 632 0.0208 0 0
# ℹ 17,101 more rows
tf-idfbind_tf_idf calculates tf-idf given text and a grouping that defines corpus documentsshakespeare |> unnest_tokens(word,text) |>
count(title,word,sort=TRUE) |>
bind_tf_idf(word,title,n) |>
group_by(title) |>
slice_max(tf_idf, n = 10) |>
ungroup() |>
ggplot(aes(tf_idf, fct_reorder(word, tf_idf), fill = title)) +
geom_col(show.legend = FALSE) +
facet_wrap(~title, ncol = 2, scales = "free") +
labs(x = "tf-idf", y = NULL)tf-idfbind_tf_idf calculates tf-idf given text and a grouping that defines corpus documentsn-gramsn-grams are combinations of n wordsunnest_tokens:austen_bigrams <- austen_books() %>%
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
filter(!is.na(bigram))
austen_bigrams# A tibble: 662,783 × 2
book bigram
<fct> <chr>
1 Sense & Sensibility sense and
2 Sense & Sensibility and sensibility
3 Sense & Sensibility by jane
4 Sense & Sensibility jane austen
5 Sense & Sensibility chapter 1
6 Sense & Sensibility the family
7 Sense & Sensibility family of
8 Sense & Sensibility of dashwood
9 Sense & Sensibility dashwood had
10 Sense & Sensibility had long
# ℹ 662,773 more rows
n-gramsbi-grams# A tibble: 193,209 × 2
bigram n
<chr> <int>
1 of the 2853
2 to be 2670
3 in the 2221
4 it was 1691
5 i am 1485
6 she had 1405
7 of her 1363
8 to the 1315
9 she was 1309
10 had been 1206
# ℹ 193,199 more rows
separate to split the bi-gram into two# A tibble: 187,965 × 3
word1 word2 n
<chr> <chr> <int>
1 of the 2853
2 to be 2671
3 in the 2221
4 it was 1694
5 i am 1488
6 she had 1409
7 of her 1371
8 to the 1316
9 she was 1310
10 had been 1206
# ℹ 187,955 more rows
austen_bigrams |> separate(bigram,c("word1","word2")) |>
count(word1,word2, sort = TRUE) |>
anti_join(stop_words,by = join_by("word1" == "word")) |>
anti_join(stop_words, by = join_by("word2" == "word"))# A tibble: 26,022 × 3
word1 word2 n
<chr> <chr> <int>
1 sir thomas 300
2 miss crawford 240
3 captain wentworth 167
4 miss woodhouse 152
5 lady russell 136
6 frank churchill 131
7 sir walter 123
8 colonel brandon 116
9 lady bertram 116
10 miss fairfax 111
# ℹ 26,012 more rows
austen_bigrams |> separate(bigram,c("word1","word2")) |>
count(word1,word2, sort = TRUE) |>
anti_join(stop_words,by = join_by("word1" == "word")) |>
anti_join(stop_words, by = join_by("word2" == "word")) |>
unite(bigram,word1,word2,sep=" ")# A tibble: 26,022 × 2
bigram n
<chr> <int>
1 sir thomas 300
2 miss crawford 240
3 captain wentworth 167
4 miss woodhouse 152
5 lady russell 136
6 frank churchill 131
7 sir walter 123
8 colonel brandon 116
9 lady bertram 116
10 miss fairfax 111
# ℹ 26,012 more rows
Please fill out the following google form after the meeting or watching the video: