Ishodi učenja:
Provesti pretprocesiranje, tokenizaciju i izračun TF/TF-IDF.
Usporediti reprezentacije (bag-of-words, n-gram) i njihove posljedice.
Analizirati najvažnije pojmove po dokumentima/korpusima.
Izraditi reproducibilan pipeline analize teksta u R-u.
U analizi podataka tekst predstavlja poseban tip informacije jer je nestrukturiran. Za razliku od numeričkih ili kategorijskih varijabli, tekstualni zapisi (objave, komentari, dokumenti) sastoje se od prirodnog jezika, koji je potrebno pretvoriti u analitički prikladan oblik.
Proces pretvaranja teksta u podatke naziva se text mining ili text analytics. Njegov cilj je izdvojiti korisne obrasce, pojmove ili teme iz velikih korpusa teksta. U praksi se taj proces može shvatiti kao transformacija teksta iz neorganiziranog stanja u strukturirani skup značajki (features) koji se mogu analizirati statističkim metodama.
Tipični koraci u analizi teksta uključuju:
U ovoj lekciji fokus je na osnovnim metodama reprezentacije teksta i njihovoj analizi u R-u.
Osnovna jedinica analize teksta je dokument.
Dokument može biti:
Skup dokumenata naziva se korpus.
Formalno:
\[ Corpus = \{d_1, d_2, ..., d_n\} \]
gdje je:
Korpus je početna struktura nad kojom se provodi analiza teksta.
Prije analize tekst je potrebno očistiti i standardizirati.
Ovaj proces naziva se pretprocesiranje.
Tipični koraci uključuju:
Cilj je ukloniti šum u podacima kako bi analiza bila fokusirana na informativne pojmove.
Primjer u R-u
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(stringr)
dat <- tibble(
doc = c(
"Data science is powerful.",
"Text mining extracts insights from text!",
"Netw*rk models are am@zing!",
"Let's play with text mining and get powerful insights..."
)
)
dat_clean <- dat |>
mutate(
doc = str_to_lower(doc),
doc = str_remove_all(doc, "[[:punct:]]")
)
dat_clean
## # A tibble: 4 × 1
## doc
## <chr>
## 1 data science is powerful
## 2 text mining extracts insights from text
## 3 netwrk models are amzing
## 4 lets play with text mining and get powerful insights
Nakon čišćenja tekst se dijeli na manje jedinice – tokene.
Token može biti:
Najčešći pristup u analizi teksta je tokenizacija na riječi.
U paketu tidytext koristi se funkcija:
unnest_tokens()
Primjer u R-u
library(tidytext)
tokens <- dat_clean |>
unnest_tokens(word, doc)
tokens
## # A tibble: 23 × 1
## word
## <chr>
## 1 data
## 2 science
## 3 is
## 4 powerful
## 5 text
## 6 mining
## 7 extracts
## 8 insights
## 9 from
## 10 text
## # ℹ 13 more rows
Rezultat je tablica u kojoj svaki red predstavlja jedan token.
Najjednostavnija mjera važnosti pojma je frekvencija pojavljivanja.
Matematički:
\[ TF_{t,d} = \frac{f_{t,d}}{N_d} \]
gdje je:
Interpretacija:
Primjer u R-u
tf <- tokens |>
count(word, sort = TRUE)
tf
## # A tibble: 18 × 2
## word n
## <chr> <int>
## 1 text 3
## 2 insights 2
## 3 mining 2
## 4 powerful 2
## 5 amzing 1
## 6 and 1
## 7 are 1
## 8 data 1
## 9 extracts 1
## 10 from 1
## 11 get 1
## 12 is 1
## 13 lets 1
## 14 models 1
## 15 netwrk 1
## 16 play 1
## 17 science 1
## 18 with 1
Term Frequency – Inverse Document Frequency
što se može prevesti kao:
Term Frequency (TF) – frekvencija pojma u dokumentu
Inverse Document Frequency (IDF) – inverzna frekvencija dokumenta.
Drugim riječima, TF-IDF je mjera koja procjenjuje koliko je neki pojam važan za određeni dokument u odnosu na cijeli korpus.
Ključna ideja je da riječ može biti česta u dokumentu, ali ne nužno informativna. TF-IDF daje veću težinu riječima koje su:
Zato se koristi TF-IDF, koji smanjuje težinu riječi koje su česte u cijelom korpusu.
Težina pojma računa se kao:
\[ TF\text{-}IDF_{t,d} = TF_{t,d} \times IDF_t \]
gdje je:
IDF komponenta definira se kao:
\[ IDF_t = \log\left(\frac{N}{n_t}\right) \]
gdje je:
Riječi koje se pojavljuju u mnogim dokumentima imaju niži TF-IDF, dok riječi specifične za pojedini dokument dobivaju višu težinu.
Pritom se važnost pojma procjenjuje u odnosu na dokument u kojem se pojavljuje.
Primjer u R-u
#not run
# u ovom primjeru svaki red tretiramo kao dokument
tfidf <- tokens |>
count(document = row_number(), word) |>
bind_tf_idf(word, document, n)
tfidf
Hrvatski je morfološki bogat jezik. Ako ne lematiziramo riječi (npr. “mreža”, “mreži”, “mrežom”), sustav će ih tretirati kao tri potpuno različita pojma, što razvodnjava rezultate. Za računalo su “mreža” i “mreži” različiti nizovi znakova.
Pogledajmo što se događa s frekvencijama ako analiziramo tekst bez prethodne obrade oblika riječi:
Bez lematizacije (sirovi podaci)
Pretpostavimo da u tekstu imamo sljedeću rečenicu:
“Analiziramo mrežu jer u toj mreži vidimo strukturu, a s mrežom lakše upravljamo.”
Računalo će generirati ovakvu tablicu:
| Riječ | Frekvencija |
|---|---|
| mrežu | 1 |
| mreži | 1 |
| mrežom | 1 |
Glavni pojam (“mreža”) se ne ističe kao dominantan jer je njegova frekvencija podijeljena na tri različita oblika.
Uz lematizaciju (napredna obrada)
Lematizator prepoznaje da su svi ovi oblici zapravo ista riječ i svodi ih na osnovni oblik (kanonsku formu, nominativ jednine):
| Riječ (Lema) | Frekvencija |
|---|---|
| mreža | 3 |
Lematizacija nam omogućuje da vidimo pravu “težinu” pojma u tekstu.
U R-u se za hrvatski jezik najčešće koristi lematizacija putem vanjskih alata (poput sustava Classla ili rječnika za lematizaciju).
library(dplyr)
library(tidytext)
# Simulirani tekst
tekst <- c("mreža", "mreži", "mrežom", "mreže", "mrežu")
# Prikaz bez obrade
tablica_bez <- tibble(word = tekst) %>% count(word)
# Simulirana lematizacija (ručno za potrebe demonstracije)
tablica_lema <- tibble(word = tekst) %>%
mutate(lemma = "mreža") %>% # Zamislimo da je lematizator ovo odradio
count(lemma)
# Rezultati
tablica_bez # Pokazuje 5 redova s frekvencijom 1
## # A tibble: 5 × 2
## word n
## <chr> <int>
## 1 mreža 1
## 2 mreže 1
## 3 mreži 1
## 4 mrežom 1
## 5 mrežu 1
tablica_lema # Pokazuje 1 red s frekvencijom 5
## # A tibble: 1 × 2
## lemma n
## <chr> <int>
## 1 mreža 5
Alati za obradu hrvatskog jezika u R-u
Dok se za engleski jezik često koristi jednostavna funkcija
stemDocument() iz paketa tm, ona za hrvatski
jezik ne daje dobre rezultate. Umjesto toga, preporučuje se korištenje
sljedećih pristupa:
udpipe (Svestrano rješenje)Paket udpipe je trenutno najpopularnije rješenje za R
jer omogućuje preuzimanje već treniranih modela za preko 60 jezika,
uključujući i hrvatski.
Funkcija: udpipe_annotate() i
udpipe_download_model()
Automatski prepoznaje padeže i vraća osnovni oblik riječi (lemu), ali i vrstu riječi (imenica, glagol, pridjev).
classla (Vrhunska preciznost)Za najprecizniju obradu hrvatskog, srpskog i slovenskog jezika
koristi se sustav CLASSLA (zasnovan na Stanza modelu). Iako je primarno
razvijen za Python, u R-u mu se može pristupiti putem paketa
reticulate ili korištenjem predtretiranih tablica.
Ako ne želite koristiti složene lingvističke modele, možete koristiti rječnike za lematizaciju (npr. Hrvatski morfološki leksikon).
left_join iz paketa dplyr kako
bi se svaka tokenizirana riječ uparila s njenom lemom iz vanjske
.csv tablice.library(udpipe)
# 1. Preuzimanje i učitavanje modela za hrvatski
dl <- udpipe_download_model(language = "croatian")
## Downloading udpipe model from https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/croatian-set-ud-2.5-191206.udpipe to C:/Users/Korisnik/Documents/SNA/croatian-set-ud-2.5-191206.udpipe
## - This model has been trained on version 2.5 of data from https://universaldependencies.org
## - The model is distributed under the CC-BY-SA-NC license: https://creativecommons.org/licenses/by-nc-sa/4.0
## - Visit https://github.com/jwijffels/udpipe.models.ud.2.5 for model license details.
## - For a list of all models and their licenses (most models you can download with this package have either a CC-BY-SA or a CC-BY-SA-NC license) read the documentation at ?udpipe_download_model. For building your own models: visit the documentation by typing vignette('udpipe-train', package = 'udpipe')
## Downloading finished, model stored at 'C:/Users/Korisnik/Documents/SNA/croatian-set-ud-2.5-191206.udpipe'
model <- udpipe_load_model(dl$file_model)
# 2. Lematizacija teksta
analiza <- udpipe_annotate(model, x = "mrežama upravljamo lakše")
rezultat <- as.data.frame(analiza)
rezultat$lemma
## [1] "mreža" "upravljati" "lako"
Tekst se može reprezentirati na različite načine.
Najčešće su:
Tekst se promatra kao skup riječi bez reda.
Primjer:
text mining is useful
pretvara se u:
| riječ | frekvencija |
|---|---|
| text | 1 |
| mining | 1 |
| useful | 1 |
Prednosti:
Nedostatak:
N-grami zadržavaju lokalni kontekst riječi.
Primjeri:
bigram
text mining
mining methods
trigram
text mining methods
Primjer u R-u
bigrams <- dat_clean |>
unnest_tokens(bigram, doc, token = "ngrams", n = 2)
bigrams
## # A tibble: 19 × 1
## bigram
## <chr>
## 1 data science
## 2 science is
## 3 is powerful
## 4 text mining
## 5 mining extracts
## 6 extracts insights
## 7 insights from
## 8 from text
## 9 netwrk models
## 10 models are
## 11 are amzing
## 12 lets play
## 13 play with
## 14 with text
## 15 text mining
## 16 mining and
## 17 and get
## 18 get powerful
## 19 powerful insights
Jedan od ciljeva osnovne analize teksta je identificirati ključne pojmove.
To se može raditi na razini:
Primjer:
top_terms <- tokens |>
count(word, sort = TRUE) |>
slice_head(n = 5)
top_terms
## # A tibble: 5 × 2
## word n
## <chr> <int>
## 1 text 3
## 2 insights 2
## 3 mining 2
## 4 powerful 2
## 5 amzing 1
Rezultati se često prikazuju pomoću:
U analizi podataka važno je da postupak bude reproducibilan.
To znači da se cijela analiza može ponoviti istim kodom.
Tipičan pipeline u R-u:
#not run
library(tidyverse)
library(tidytext)
text_data |>
mutate(text = str_to_lower(text)) |>
unnest_tokens(word, text) |>
anti_join(stop_words) |>
count(word, sort = TRUE)
Prednosti pipeline pristupa:
Važno je razumjeti ograničenja osnovnih metoda.
Bag-of-words pristup:
Zbog toga se u naprednijim analizama koriste:
No osnovne metode i dalje su vrlo korisne za eksploratornu analizu korpusa.
Analiza teksta u R-u temelji se na više specijaliziranih paketa koji omogućuju obradu prirodnog jezika, statističku analizu i primjenu modernih modela strojnog učenja. U ovoj lekciji koriste se tri paketa koji pokrivaju različite razine analize: string manipulaciju, klasični text mining i napredne NLP modele.
stringrPaket stringr dio je tidyverse
ekosustava i služi za rad s tekstualnim nizovima
(strings). U analizi teksta koristi se prvenstveno u fazi
pretprocesiranja, kada je potrebno očistiti i
standardizirati tekst prije tokenizacije.
Tipične operacije uključuju:
Primjeri funkcija:
str_to_lower() – pretvara tekst u mala slovastr_remove_all() – uklanja uzorke iz tekstastr_detect() – provjerava pojavljuje li se uzorakstr_replace_all() – zamjenjuje uzorkePrimjer:
library(stringr)
text_clean <- dat$doc |>
str_to_lower() |>
str_remove_all("[[:punct:]]")
text_clean
## [1] "data science is powerful"
## [2] "text mining extracts insights from text"
## [3] "netwrk models are amzing"
## [4] "lets play with text mining and get powerful insights"
Ovaj paket omogućuje jednostavne i konzistentne operacije nad tekstom, što je posebno važno u pretprocesiranju.
stopwordsPaket stopwords pruža standardizirane liste stop riječi za različite jezike. Stop riječi su česte riječi male informativnosti (npr. i, je, da, the, and) koje se često uklanjaju iz korpusa prije analize.
Primjer:
library(stopwords)
stopwords("en")
## [1] "i" "me" "my" "myself" "we"
## [6] "our" "ours" "ourselves" "you" "your"
## [11] "yours" "yourself" "yourselves" "he" "him"
## [16] "his" "himself" "she" "her" "hers"
## [21] "herself" "it" "its" "itself" "they"
## [26] "them" "their" "theirs" "themselves" "what"
## [31] "which" "who" "whom" "this" "that"
## [36] "these" "those" "am" "is" "are"
## [41] "was" "were" "be" "been" "being"
## [46] "have" "has" "had" "having" "do"
## [51] "does" "did" "doing" "would" "should"
## [56] "could" "ought" "i'm" "you're" "he's"
## [61] "she's" "it's" "we're" "they're" "i've"
## [66] "you've" "we've" "they've" "i'd" "you'd"
## [71] "he'd" "she'd" "we'd" "they'd" "i'll"
## [76] "you'll" "he'll" "she'll" "we'll" "they'll"
## [81] "isn't" "aren't" "wasn't" "weren't" "hasn't"
## [86] "haven't" "hadn't" "doesn't" "don't" "didn't"
## [91] "won't" "wouldn't" "shan't" "shouldn't" "can't"
## [96] "cannot" "couldn't" "mustn't" "let's" "that's"
## [101] "who's" "what's" "here's" "there's" "when's"
## [106] "where's" "why's" "how's" "a" "an"
## [111] "the" "and" "but" "if" "or"
## [116] "because" "as" "until" "while" "of"
## [121] "at" "by" "for" "with" "about"
## [126] "against" "between" "into" "through" "during"
## [131] "before" "after" "above" "below" "to"
## [136] "from" "up" "down" "in" "out"
## [141] "on" "off" "over" "under" "again"
## [146] "further" "then" "once" "here" "there"
## [151] "when" "where" "why" "how" "all"
## [156] "any" "both" "each" "few" "more"
## [161] "most" "other" "some" "such" "no"
## [166] "nor" "not" "only" "own" "same"
## [171] "so" "than" "too" "very" "will"
Stop riječi se obično uklanjaju spajanjem s tokeniziranim tekstom:
tokens |>
anti_join(stop_words)
## Joining with `by = join_by(word)`
## # A tibble: 16 × 1
## word
## <chr>
## 1 data
## 2 science
## 3 powerful
## 4 text
## 5 mining
## 6 extracts
## 7 insights
## 8 text
## 9 netwrk
## 10 models
## 11 amzing
## 12 play
## 13 text
## 14 mining
## 15 powerful
## 16 insights
tmPaket tm (text mining) jedan je od najstarijih i najpoznatijih paketa za analizu teksta u R-u. Razvijen je za klasični pristup text miningu koji se temelji na korpusu i dokument–termin matrici (Document-Term Matrix).
U ovom pristupu tekst se organizira u matricu:
\[ DTM = \begin{bmatrix} f_{1,1} & f_{1,2} & ... & f_{1,m} \\ f_{2,1} & f_{2,2} & ... & f_{2,m} \\ \vdots & \vdots & & \vdots \\ f_{n,1} & f_{n,2} & ... & f_{n,m} \end{bmatrix} \]
gdje je:
Ovaj paket omogućuje:
Primjer:
#not run
library(tm)
texts <- c("Ovo je prvi dokument.", "Ovo je drugi dokument.")
corpus <- Corpus(VectorSource(texts))
dtm <- DocumentTermMatrix(corpus)
Iako se danas često koristi tidytext, paket
tm i dalje je važan jer predstavlja klasični
pristup analizi teksta.
Checklist za pretprocesiranje: Redoslijed je važan!
Prilikom čišćenja korpusa pomoću tm_map, važno je
slijediti logičan redoslijed kako ne biste uništili strukturu
informacija koju želite ukloniti. Preporučeni redoslijed:
http:// ili
<br>).tolower) (Osigurava da
Stopwords i vlastita imena budu tretirani
jednako).stopwords) (Često je bolje
ukloniti ih dok je interpunkcija još tu, ovisno o jeziku).removePunctuation) (Tek
nakon što smo sigurni da nam simboli više ne trebaju za prepoznavanje
uzoraka).removeNumbers) (Osim ako su
brojevi ključni za analizu).stripWhitespace) (Uvijek
na kraju, kako bi se počistile praznine nastale prethodnim
koracima).U R-u, uz paket tm, to bi izgledalo ovako (pazite na
redoslijed linija):
library(tm)
# Definiranje funkcije za URL-ove prije svega
removeURL <- function(x) gsub("http[^[:space:]]*", "", x)
# Redoslijed u tm_map
korpus <- tm_map(korpus, content_transformer(removeURL)) # 1. URL-ovi
korpus <- tm_map(korpus, content_transformer(tolower)) # 2. Mala slova
korpus <- tm_map(korpus, removeWords, stopwords("croatian")) # 3. Stop-riječi
korpus <- tm_map(korpus, removePunctuation) # 4. Interpunkcija
korpus <- tm_map(korpus, removeNumbers) # 5. Brojevi
korpus <- tm_map(korpus, stripWhitespace) # 6. Praznine
tidytextPaket tidytext omogućuje analizu teksta u okviru tidy data paradigme. Umjesto klasičnih dokument–termin matrica, tekst se transformira u tidy format, gdje svaki red predstavlja jedan token.
To omogućuje jednostavnu integraciju s paketima kao što su:
dplyrggplot2tidyrNajvažnije funkcije u ovom paketu su:
unnest_tokens() – tokenizacija tekstabind_tf_idf() – izračun TF–IDF težinareorder_within() – pomoć pri vizualizaciji pojmovaanti_join(stop_words) – uklanjanje stop riječiPrimjer tokenizacije:
#not run
library(tidytext)
tokens <- text_data |>
unnest_tokens(word, text)
tokens
Prednost ovog pristupa je što omogućuje reproducibilan i
transparentan pipeline analize teksta koji je kompatibilan s
ostatkom tidyverse ekosustava.
quantedaPaket quanteda jedan je od najmoćnijih paketa za obradu teksta u R-u i često se koristi u računalnoj lingvistici i društvenim znanostima.
Njegova glavna prednost je izuzetno brza obrada velikih korpusa.
Omogućuje:
Primjer:
#not run
library(quanteda)
tok <- tokens(texts)
dfm <- dfm(tok)
U praksi se quanteda često koristi kada je potrebno
analizirati velike količine tekstualnih podataka,
primjerice političke govore, parlamentarne rasprave ili objave na
društvenim mrežama.
topicmodelsPaket topicmodels omogućuje modeliranje tema u tekstu pomoću metode Latent Dirichlet Allocation (LDA).
Topic modeling je metoda koja identificira skrivene tematske strukture u korpusu. Pretpostavlja da svaki dokument sadrži kombinaciju više tema, a svaka tema predstavlja distribuciju pojmova.
Formalno, LDA pretpostavlja:
Model procjenjuje:
Primjer:
#not run
library(topicmodels)
lda_model <- LDA(dtm, k = 5)
gdje je:
dtm dokument–termin matricak broj tema koje želimo identificirati.Topic modeling često se koristi u analizi:
textmineRPaket textmineR razvijen je za napredniju statističku analizu teksta i često se koristi u istraživačkim projektima.
Podržava metode kao što su:
Primjer izrade dokument–termin matrice:
#not run
library(textmineR)
dtm <- CreateDtm(
doc_vec = documents,
ngram_window = c(1,2)
)
Ovaj paket posebno je koristan kada se prelazi s eksploratorne analize na modeliranje tema u tekstu.
textPaket text predstavlja most između R-a i modernih metoda dubokog učenja za obradu prirodnog jezika (NLP). Ovaj paket omogućuje korištenje transformer modela razvijenih u okviru platforme HuggingFace.
Transformer modeli predstavljaju suvremeni pristup analizi teksta jer uvažavaju kontekst riječi unutar rečenice, za razliku od klasičnih metoda poput bag-of-words.
Primjene uključuju:
Jedna od ključnih funkcija je:
#not run
textEmbed()
koja generira semantičke reprezentacije teksta (embeddings) pomoću transformera.
Primjer:
#not run
library(text)
embeddings <- textEmbed(
texts = c("Data science is useful", "Text mining is powerful"),
model = "bert-base-uncased"
)
embeddings
Rezultat bi bili numerički vektori koji predstavljaju semantičko značenje teksta i mogu se koristiti u daljnjim analizama, primjerice u klasteriranju ili klasifikaciji.
Metode predstavljene u ovoj lekciji (tokenizacija, TF i TF-IDF) pripadaju klasičnom pristupu analizi teksta. One se često koriste u eksploratornoj analizi jer su jednostavne, interpretabilne i računalno učinkovite.
S druge strane, moderni pristupi temeljeni na transformer modelima omogućuju dublje razumijevanje značenja teksta jer uzimaju u obzir kontekst i semantičke odnose među riječima.
Zbog toga se u praksi često koristi kombinacija pristupa:
Vizualizacija je važan korak u eksploratornoj analizi korpusa jer omogućuje brzo uočavanje dominantnih pojmova ili tema.
wordcloudPaket wordcloud omogućuje izradu klasičnih oblaka riječi u kojima veličina riječi odgovara njezinoj frekvenciji u korpusu.
Primjer:
#not run
library(wordcloud)
wordcloud(
words = terms$word,
freq = terms$n
)
Ova metoda daje intuitivan pregled najčešćih pojmova u tekstu.
ggwordcloudPaket ggwordcloud predstavlja moderniju
implementaciju wordcloud vizualizacije temeljenu na
ggplot2. Prednost ovog pristupa je veća fleksibilnost u
dizajnu i bolja integracija s tidyverse ekosustavom.
Primjer:
#not run
library(ggwordcloud)
ggwordcloud(terms, aes(label = word, size = n)) +
geom_text_wordcloud()
Ako se svi spomenuti paketi promatraju zajedno, dobiva se pregled tipičnog text mining ekosustava u R-u:
| faza analize | paketi |
|---|---|
| čišćenje teksta | stringr, stopwords |
| tokenizacija | tidytext, quanteda |
| frekvencijska analiza | tidytext, tm |
| dokument–termin matrice | tm, quanteda,
textmineR |
| modeliranje tema | topicmodels, textmineR |
| moderni NLP modeli | text |
| vizualizacija | wordcloud, ggwordcloud,
ggplot2 |
U praksi se paketi koriste u kombinaciji kroz sljedeće faze analize teksta:
stringr, stopwordstidytexttextmineR, textwordcloud, ggwordcloudTakva kombinacija omogućuje izgradnju reproducibilnog i transparentnog pipeline-a analize teksta u R-u, od čišćenja podataka do interpretacije rezultata.
Paket gutenbergr omogućuje preuzimanje tekstova iz
Project Gutenberga, koja sadrži velik broj književnih djela u javnoj
domeni (djela autora kojima su istekla autorska prava), npr.:
| Autor | Djelo | gutenberg_id |
|---|---|---|
| Lewis Carroll | Alice’s Adventures in Wonderland | 11 |
| Mary Shelley | Frankenstein; or, The Modern Prometheus | 84 |
| Jane Austen | Pride and Prejudice | 1342 |
| Bram Stoker | Dracula | 345 |
| Arthur Conan Doyle | The Adventures of Sherlock Holmes | 1661 |
| Lewis Carroll | Through the Looking-Glass | 12 |
| Jane Austen | Sense and Sensibility | 161 |
| H. G. Wells | The War of the Worlds | 36 |
| H. G. Wells | The Time Machine | 35 |
| Oscar Wilde | The Picture of Dorian Gray | 174 |
| Herman Melville | Moby Dick | 2701 |
| J. M. Barrie | Peter Pan | 16 |
U Project Gutenbergu svaka knjiga ima stabilni numerički
identifikator (gutenberg_id) putem kojeg možemo preuzeti
djelo.
U nastavku ćemo koristiti Alice’s Adventures in Wonderland, ali možete isprobati postupak samostalno na ostalim djelima.
library(gutenbergr)
alice <- gutenberg_download(11) # Alice's Adventures in Wonderland, Lewis Carroll (1865)
## Mirror list unavailable. Falling back to <https://aleph.pglaf.org>.
str(alice)
## tibble [6,760 × 2] (S3: tbl_df/tbl/data.frame)
## $ gutenberg_id: int [1:6760] 11 11 11 11 11 11 11 11 11 11 ...
## $ text : chr [1:6760] "[Illustration]" "[Illustration]" "" "" ...
Rezultat je tibble u kojem svaki redak predstavlja jedan
redak teksta knjige, a stupac gutenberg_id označava
identifikator izvornog djela.
Nakon što je tekst preuzet iz Project Gutenberga, prvi korak je osnovni uvid u podatke. U ovoj fazi cilj je razumjeti strukturu korpusa i način na koji je tekst organiziran prije početka čišćenja i transformacije.
head(alice, 20)
## # A tibble: 20 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "[Illustration]"
## 2 11 "[Illustration]"
## 3 11 ""
## 4 11 ""
## 5 11 ""
## 6 11 ""
## 7 11 ""
## 8 11 ""
## 9 11 ""
## 10 11 ""
## 11 11 "Alice’s Adventures in Wonderland"
## 12 11 "Alice’s Adventures in Wonderland"
## 13 11 ""
## 14 11 ""
## 15 11 "by Lewis Carroll"
## 16 11 "by Lewis Carroll"
## 17 11 ""
## 18 11 ""
## 19 11 "THE MILLENNIUM FULCRUM EDITION 3.0"
## 20 11 "THE MILLENNIUM FULCRUM EDITION 3.0"
Primijetit ćemo da svaki redak predstavlja jedan redak izvornog teksta, a ne cijelu rečenicu ili poglavlje. Osim stvarnog sadržaja knjige, dataset može sadržavati i dodatne elemente poput:
oznaka poglavlja
praznih redaka
uredničkih napomena ili metapodataka.
Zato je prije analize potrebno provesti pretprocesiranje teksta.
Najprije možemo provjeriti osnovne dimenzije skupa podataka.
nrow(alice)
## [1] 6760
Koliko je praznih redaka?
sum(alice$text == "")
## [1] 1772
Prazni retci često se pojavljuju u digitaliziranim knjigama i obično se uklanjaju u fazi čišćenja.
Korisno je pogledati i nasumični uzorak redaka kako bismo dobili bolji dojam o tekstu.
alice |>
slice_sample(n = 10)
## # A tibble: 10 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "“You mean you can’t take _less_,” said the Hatter: “it’s very …
## 2 11 "It was all very well to say “Drink me,” but the wise little Al…
## 3 11 "“It was the _best_ butter,” the March Hare meekly replied."
## 4 11 ""
## 5 11 "“But I don’t want to go among mad people,” Alice remarked."
## 6 11 "living would be like, but it puzzled her too much, so she went…
## 7 11 ""
## 8 11 "unrolled the parchment scroll, and read as follows:—"
## 9 11 "March Hare. Visit either you like: they’re both mad.”"
## 10 11 "Alice was more and more puzzled, but she thought there was no …
Ovakav pregled pomaže uočiti potencijalne probleme poput:
interpunkcije
velikih i malih slova
posebnih znakova
oznaka poglavlja
dupliciranih redaka.
Prvi korak u čišćenju teksta obično je uklanjanje praznih redaka.
library(dplyr)
alice_clean <- alice |>
filter(text != "")
head(alice_clean)
## # A tibble: 6 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 [Illustration]
## 2 11 [Illustration]
## 3 11 Alice’s Adventures in Wonderland
## 4 11 Alice’s Adventures in Wonderland
## 5 11 by Lewis Carroll
## 6 11 by Lewis Carroll
Provjerimo novu veličinu skupa podataka:
nrow(alice_clean)
## [1] 4988
Nakon osnovnog uvida u podatke sljedeći korak je pretprocesiranje teksta, odnosno uklanjanje elemenata koji ne nose semantičku informaciju i mogu otežati analizu. Tipični koraci uključuju normalizaciju teksta, uklanjanje uredničkih oznaka te filtriranje vrlo čestih riječi male informativnosti.
U ovom primjeru provest ćemo:
uklanjanje duplikata
normalizaciju na mala slova
uklanjanje interpunkcije
uklanjanje uredničkih oznaka (npr. [Illustration])
uklanjanje stop riječi.
Provjera dupliciranja
alice_clean |>
slice(1:20)
## # A tibble: 20 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "[Illustration]"
## 2 11 "[Illustration]"
## 3 11 "Alice’s Adventures in Wonderland"
## 4 11 "Alice’s Adventures in Wonderland"
## 5 11 "by Lewis Carroll"
## 6 11 "by Lewis Carroll"
## 7 11 "THE MILLENNIUM FULCRUM EDITION 3.0"
## 8 11 "THE MILLENNIUM FULCRUM EDITION 3.0"
## 9 11 "Contents"
## 10 11 "Contents"
## 11 11 " CHAPTER I. Down the Rabbit-Hole"
## 12 11 " CHAPTER I. Down the Rabbit-Hole"
## 13 11 " CHAPTER II. The Pool of Tears"
## 14 11 " CHAPTER II. The Pool of Tears"
## 15 11 " CHAPTER III. A Caucus-Race and a Long Tale"
## 16 11 " CHAPTER III. A Caucus-Race and a Long Tale"
## 17 11 " CHAPTER IV. The Rabbit Sends in a Little Bill"
## 18 11 " CHAPTER IV. The Rabbit Sends in a Little Bill"
## 19 11 " CHAPTER V. Advice from a Caterpillar"
## 20 11 " CHAPTER V. Advice from a Caterpillar"
Uklanjanje uzastopnih duplikata
library(dplyr)
alice_nodup <- alice_clean |>
filter(text != lag(text, default = ""))
nrow(alice_clean)
## [1] 4988
nrow(alice_nodup)
## [1] 2491
head(alice_nodup, 30)
## # A tibble: 30 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "[Illustration]"
## 2 11 "Alice’s Adventures in Wonderland"
## 3 11 "by Lewis Carroll"
## 4 11 "THE MILLENNIUM FULCRUM EDITION 3.0"
## 5 11 "Contents"
## 6 11 " CHAPTER I. Down the Rabbit-Hole"
## 7 11 " CHAPTER II. The Pool of Tears"
## 8 11 " CHAPTER III. A Caucus-Race and a Long Tale"
## 9 11 " CHAPTER IV. The Rabbit Sends in a Little Bill"
## 10 11 " CHAPTER V. Advice from a Caterpillar"
## # ℹ 20 more rows
Pretvaranje teksta u mala slova
Radi standardizacije sve riječi prevodimo u mala slova.
library(stringr)
alice_nodup <- alice_nodup |>
mutate(text = str_to_lower(text))
head(alice_nodup, 20)
## # A tibble: 20 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "[illustration]"
## 2 11 "alice’s adventures in wonderland"
## 3 11 "by lewis carroll"
## 4 11 "the millennium fulcrum edition 3.0"
## 5 11 "contents"
## 6 11 " chapter i. down the rabbit-hole"
## 7 11 " chapter ii. the pool of tears"
## 8 11 " chapter iii. a caucus-race and a long tale"
## 9 11 " chapter iv. the rabbit sends in a little bill"
## 10 11 " chapter v. advice from a caterpillar"
## 11 11 " chapter vi. pig and pepper"
## 12 11 " chapter vii. a mad tea-party"
## 13 11 " chapter viii. the queen’s croquet-ground"
## 14 11 " chapter ix. the mock turtle’s story"
## 15 11 " chapter x. the lobster quadrille"
## 16 11 " chapter xi. who stole the tarts?"
## 17 11 " chapter xii. alice’s evidence"
## 18 11 "chapter i."
## 19 11 "down the rabbit-hole"
## 20 11 "alice was beginning to get very tired of sitting by her sister…
Uklanjanje brojeva
Brojevi se u književnom tekstu rijetko koriste kao nositelji semantičke informacije, pa ih možemo ukloniti.
alice_nodup <- alice_nodup |>
mutate(text = str_remove_all(text, "[0-9]+"))
head(alice_nodup, 20)
## # A tibble: 20 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "[illustration]"
## 2 11 "alice’s adventures in wonderland"
## 3 11 "by lewis carroll"
## 4 11 "the millennium fulcrum edition ."
## 5 11 "contents"
## 6 11 " chapter i. down the rabbit-hole"
## 7 11 " chapter ii. the pool of tears"
## 8 11 " chapter iii. a caucus-race and a long tale"
## 9 11 " chapter iv. the rabbit sends in a little bill"
## 10 11 " chapter v. advice from a caterpillar"
## 11 11 " chapter vi. pig and pepper"
## 12 11 " chapter vii. a mad tea-party"
## 13 11 " chapter viii. the queen’s croquet-ground"
## 14 11 " chapter ix. the mock turtle’s story"
## 15 11 " chapter x. the lobster quadrille"
## 16 11 " chapter xi. who stole the tarts?"
## 17 11 " chapter xii. alice’s evidence"
## 18 11 "chapter i."
## 19 11 "down the rabbit-hole"
## 20 11 "alice was beginning to get very tired of sitting by her sister…
Uklanjanje interpunkcije
Interpunkcija se također uklanja prije tokenizacije.
alice_nodup <- alice_nodup |>
mutate(text = str_remove_all(text, "[[:punct:]]"))
head(alice_nodup, 20)
## # A tibble: 20 × 2
## gutenberg_id text
## <int> <chr>
## 1 11 "illustration"
## 2 11 "alices adventures in wonderland"
## 3 11 "by lewis carroll"
## 4 11 "the millennium fulcrum edition "
## 5 11 "contents"
## 6 11 " chapter i down the rabbithole"
## 7 11 " chapter ii the pool of tears"
## 8 11 " chapter iii a caucusrace and a long tale"
## 9 11 " chapter iv the rabbit sends in a little bill"
## 10 11 " chapter v advice from a caterpillar"
## 11 11 " chapter vi pig and pepper"
## 12 11 " chapter vii a mad teaparty"
## 13 11 " chapter viii the queens croquetground"
## 14 11 " chapter ix the mock turtles story"
## 15 11 " chapter x the lobster quadrille"
## 16 11 " chapter xi who stole the tarts"
## 17 11 " chapter xii alices evidence"
## 18 11 "chapter i"
## 19 11 "down the rabbithole"
## 20 11 "alice was beginning to get very tired of sitting by her sister…
U tidytext pristupu tekst se najčešće najprije tokenizira, pri čemu se istodobno provodi većina ovih koraka.
Funkcija unnest_tokens() iz paketa tidytext
razbija tekst na tokene (u ovom slučaju riječi) te automatski (po
zadanim postavkama):
library(tidytext)
library(dplyr)
alice_tokens <- alice_nodup |>
unnest_tokens(word, text)
Rezultat je dataset u kojem vrijedi načelo jedan redak = jedna riječ.
Provjerimo prvih nekoliko redaka:
head(alice_tokens, 10)
## # A tibble: 10 × 2
## gutenberg_id word
## <int> <chr>
## 1 11 illustration
## 2 11 alices
## 3 11 adventures
## 4 11 in
## 5 11 wonderland
## 6 11 by
## 7 11 lewis
## 8 11 carroll
## 9 11 the
## 10 11 millennium
Uklanjanje stop riječi
Stop riječi su vrlo česte riječi (npr. the, and, to, of) koje obično nose malo semantičke informacije.
U paketu tidytext dostupna je lista stop riječi
stop_words.
data(stop_words)
alice_tokens <- alice_tokens |>
anti_join(stop_words, by = "word")
head(alice_tokens, 10)
## # A tibble: 10 × 2
## gutenberg_id word
## <int> <chr>
## 1 11 illustration
## 2 11 alices
## 3 11 adventures
## 4 11 wonderland
## 5 11 lewis
## 6 11 carroll
## 7 11 millennium
## 8 11 fulcrum
## 9 11 edition
## 10 11 contents
Funkcija anti_join() uklanja sve riječi koje se nalaze u
listi stop riječi.
Rezultat je tokenizirani i očišćeni korpus u kojem su:
Takva struktura predstavlja standardni oblik podataka za većinu metoda analize teksta.
Najprije možemo izračunati koliko se puta svaka riječ pojavljuje u korpusu.
Ako je \(f_i\) frekvencija riječi \(i\), tada vrijedi
\[ TF_i = f_i \]
gdje je:
U ovom slučaju računamo frekvenciju u cijelom tekstu.
library(dplyr)
word_freq <- alice_tokens |>
count(word, sort = TRUE)
head(word_freq, 20)
## # A tibble: 20 × 2
## word n
## <chr> <int>
## 1 alice 385
## 2 queen 68
## 3 time 68
## 4 king 61
## 5 dont 60
## 6 im 57
## 7 mock 57
## 8 turtle 56
## 9 gryphon 55
## 10 hatter 55
## 11 head 48
## 12 voice 47
## 13 looked 45
## 14 rabbit 44
## 15 round 41
## 16 tone 40
## 17 dormouse 39
## 18 duchess 39
## 19 mouse 38
## 20 cat 35
Takva lista često otkriva:
Grafički prikaz najčešćih riječi često je pregledniji od tablice.
top_words <- word_freq |>
slice_max(n, n = 20)
library(ggplot2)
top_words |>
ggplot(aes(x = reorder(word, n), y = n)) +
geom_col() +
coord_flip() +
labs(
x = "Riječ",
y = "Frekvencija",
title = "Najčešće riječi u romanu Alice in Wonderland"
)
Ovakav graf omogućuje brzi uvid u dominantne pojmove u tekstu.
Wordcloud je vizualizacija u kojoj je veličina riječi proporcionalna frekvenciji pojavljivanja.
library(wordcloud)
## Loading required package: RColorBrewer
wordcloud(
words = word_freq$word,
freq = word_freq$n,
max.words = 100
)
Iako wordcloud nije analitički vrlo precizan, često je atraktivan i daje intuitivan pregled vokabulara.
Nakon tokenizacije tekst možemo promatrati kroz bag-of-words reprezentaciju. U tom pristupu dokument se opisuje samo frekvencijama riječi, bez uzimanja u obzir redoslijeda u kojem se riječi pojavljuju.
U našem primjeru bag-of-words reprezentacija izgleda ovako:
word_freq |>
slice_head(n = 10)
## # A tibble: 10 × 2
## word n
## <chr> <int>
## 1 alice 385
## 2 queen 68
## 3 time 68
## 4 king 61
## 5 dont 60
## 6 im 57
## 7 mock 57
## 8 turtle 56
## 9 gryphon 55
## 10 hatter 55
Svaki red predstavlja:
riječ | frekvencija
Ovakva reprezentacija vrlo je jednostavna, ali ima jedno važno ograničenje: gubi se red riječi i lokalni kontekst.
Primjerice, rečenice
alice likes the rabbit
the rabbit likes alice
u bag-of-words pristupu izgledaju identično.
Jedan od načina da se djelomično očuva red riječi je korištenje n-grama.
N-gram je sekvenca od \(n\) uzastopnih tokena.
Najčešći su:
Bigrami u tekstu mogu izgledati ovako:
white rabbit
mad hatter
march hare
U paketu tidytext n-grami se mogu dobiti funkcijom
unnest_tokens().
alice_bigrams <- alice_nodup |>
unnest_tokens(bigram, text, token = "ngrams", n = 2)
head(alice_bigrams, 10)
## # A tibble: 10 × 2
## gutenberg_id bigram
## <int> <chr>
## 1 11 <NA>
## 2 11 alices adventures
## 3 11 adventures in
## 4 11 in wonderland
## 5 11 by lewis
## 6 11 lewis carroll
## 7 11 the millennium
## 8 11 millennium fulcrum
## 9 11 fulcrum edition
## 10 11 <NA>
Sada svaki red predstavlja par uzastopnih riječi. Isprobajmo trigrame
alice_trigrams <- alice_nodup |>
unnest_tokens(trigram, text, token = "ngrams", n = 3)
head(alice_trigrams, 10)
## # A tibble: 10 × 2
## gutenberg_id trigram
## <int> <chr>
## 1 11 <NA>
## 2 11 alices adventures in
## 3 11 adventures in wonderland
## 4 11 by lewis carroll
## 5 11 the millennium fulcrum
## 6 11 millennium fulcrum edition
## 7 11 <NA>
## 8 11 chapter i down
## 9 11 i down the
## 10 11 down the rabbithole
Možemo izračunati i najčešće bi/trigrame:
trigram_freq <- alice_trigrams |>
count(trigram, sort = TRUE)
trigram_freq |>
slice_head(n = 20)
## # A tibble: 20 × 2
## trigram n
## <chr> <int>
## 1 <NA> 143
## 2 the mock turtle 48
## 3 the march hare 29
## 4 said the king 28
## 5 said the hatter 21
## 6 said the mock 19
## 7 said the caterpillar 18
## 8 the white rabbit 18
## 9 she went on 16
## 10 one of the 15
## 11 said the duchess 15
## 12 said the gryphon 15
## 13 said to herself 15
## 14 as she could 14
## 15 said the cat 14
## 16 she said to 14
## 17 said the queen 12
## 18 it said the 11
## 19 said to the 11
## 20 there was a 11
Rezultat često otkriva:
Ako se pri izradi trigrama pojave NA vrijednosti, one najčešće upućuju na to da neki retci nakon čišćenja ne sadrže dovoljno riječi za formiranje trigrama ili sadrže prazne ili nespecifične zapise.
Za razliku od bag-of-words pristupa, n-grami djelomično čuvaju kontekst, ali imaju i nedostatke:
Zbog toga se u praksi često kombiniraju:
Još jedan jednostavan, ali zanimljiv pokazatelj je veličina vokabulara, odnosno broj jedinstvenih riječi.
Ako je
\[ V = |{w_1, w_2, \ldots, w_k}| \]
gdje je:
Možemo ga izračunati ovako:
vocab_size <- n_distinct(alice_tokens$word)
vocab_size
## [1] 2332
U većini tekstova vrijedi Zipfov zakon, prema kojem mali broj riječi ima vrlo visoku frekvenciju, dok se većina riječi pojavljuje rijetko. To je jedna od temeljnih empirijskih zakonitosti u analizi teksta, koji opisuje raspodjelu frekvencija riječi u prirodnom jeziku.
Zipfov zakon kaže da je frekvencija riječi obrnuto proporcionalna njezinu rangu u listi frekvencija. Drugim riječima, ako riječi sortiramo prema učestalosti pojavljivanja, tada približno vrijedi:
\[f(r) \propto \frac{1}{r}\]
gdje je:
\(f(r)\) — frekvencija riječi ranga \(r\)
\(r\) — rang riječi u listi sortiranoj prema frekvenciji
\(\propto\) — proporcionalnost.
To znači da:
najčešća riječ pojavljuje se vrlo često
druga riječ pojavljuje se otprilike dvostruko rjeđe
treća riječ približno tri puta rjeđe, itd.
Drugim riječima, mali broj riječi pojavljuje se vrlo često, dok se velika većina riječi pojavljuje rijetko.
U prirodnim jezicima to dovodi do raspodjele u kojoj vrijedi:
nekoliko riječi (the, and, to) čini velik dio teksta
velik broj riječi pojavljuje se samo jednom ili nekoliko puta.
To možemo vizualizirati:
word_freq |>
ggplot(aes(x = n)) +
geom_histogram(bins = 50) +
labs(
x = "Frekvencija riječi",
y = "Broj riječi",
title = "Distribucija frekvencija riječi"
)
No, ovdje smo samo vidjeli frekvencije riječi, a ne Zipfov zakon. Za to imamo još par koraka.
zipf_data <- word_freq |>
arrange(desc(n)) |>
mutate(rank = row_number())
head(zipf_data)
## # A tibble: 6 × 3
## word n rank
## <chr> <int> <int>
## 1 alice 385 1
## 2 queen 68 2
## 3 time 68 3
## 4 king 61 4
## 5 dont 60 5
## 6 im 57 6
Sad možemo prikazati odnos između ranga riječi i njezine frekvencije. Zipfov zakon najjasnije se uočava na grafu s logaritamskim skalama.
library(ggplot2)
zipf_data |>
ggplot(aes(x = rank, y = n)) +
geom_point(alpha = 0.6) +
scale_x_log10() +
scale_y_log10() +
labs(
x = "Rang riječi",
y = "Frekvencija riječi",
title = "Zipfov zakon u tekstu Alice in Wonderland"
)
Ako riječi u tekstu približno slijede Zipfov zakon, odnos između ranga i frekvencije na log-log grafu bit će približno linearan. To znači da frekvencija riječi opada vrlo brzo: nekoliko riječi dominira tekstom, dok se većina riječi pojavljuje rijetko. Na grafu se vidi vrlo velik pad nakon prve najčešće riječi, zatim blaže opadanje među riječima najvišeg ranga, a potom postupno i približno pravilno smanjivanje frekvencije. Od približno 10. ranga nadalje odnos između ranga i frekvencije može se aproksimirati pravcem na log-log skali, što je u skladu s očekivanjem prema Zipfovu zakonu. To znači da mali broj riječi dominira tekstom, dok se velika većina riječi pojavljuje rijetko.
Zipfov zakon važan je za analizu teksta jer objašnjava:
zašto je potrebno uklanjati stop riječi
zašto su dokument–termin matrice često rijetke (sparse)
zašto metode poput TF-IDF naglašavaju riječi koje nisu česte u cijelom korpusu.
Nastavljamo s TF-IDF analizom. Za ovu analizu dokument ćemo definirati kao poglavlje.
Najprije iz teksta izdvajamo poglavlja. Ovdje su poglavlja pisana velikim štampanim slovima, a numerirana su rimskim brojevima.
Prvo ćemo izdvojiti sve izraze koji sadrže chapter kako bismo dobili uvid.
library(dplyr)
library(stringr)
alice_nodup |>
mutate(row_id = row_number()) |>
filter(str_detect(text, "chapter")) |>
select(row_id, text)
## # A tibble: 24 × 2
## row_id text
## <int> <chr>
## 1 6 " chapter i down the rabbithole"
## 2 7 " chapter ii the pool of tears"
## 3 8 " chapter iii a caucusrace and a long tale"
## 4 9 " chapter iv the rabbit sends in a little bill"
## 5 10 " chapter v advice from a caterpillar"
## 6 11 " chapter vi pig and pepper"
## 7 12 " chapter vii a mad teaparty"
## 8 13 " chapter viii the queens croquetground"
## 9 14 " chapter ix the mock turtles story"
## 10 15 " chapter x the lobster quadrille"
## # ℹ 14 more rows
Iz ovog ispisa sada je jasno što se događa: svako poglavlje pojavljuje se dvaput, ali to nije zato što je svaki redak dupliciran, nego zato što se u tekstu nalaze dva bloka poglavlja:
prvi blok je sadržaj (table of contents)
drugi blok je stvarni tekst romana.
Prvo ćemo izvući sve pojave poglavlja.
chapter_rows <- alice_nodup |>
mutate(row_id = row_number()) |>
filter(str_detect(text, "^\\s*chapter"))
chapter_rows$row_id
## [1] 6 7 8 9 10 11 12 13 14 15 16 17 18 201 374
## [16] 531 748 962 1198 1431 1663 1888 2096 2278
chapter_rows[1:24,]
## # A tibble: 24 × 3
## gutenberg_id text row_id
## <int> <chr> <int>
## 1 11 " chapter i down the rabbithole" 6
## 2 11 " chapter ii the pool of tears" 7
## 3 11 " chapter iii a caucusrace and a long tale" 8
## 4 11 " chapter iv the rabbit sends in a little bill" 9
## 5 11 " chapter v advice from a caterpillar" 10
## 6 11 " chapter vi pig and pepper" 11
## 7 11 " chapter vii a mad teaparty" 12
## 8 11 " chapter viii the queens croquetground" 13
## 9 11 " chapter ix the mock turtles story" 14
## 10 11 " chapter x the lobster quadrille" 15
## # ℹ 14 more rows
Najjednostavnije rješenje je uzeti drugu polovicu, nakon sadržaja u kojem se pojavljuje popis svih poglavlja.
chapter_rows <- alice_nodup |>
mutate(row_id = row_number()) |>
filter(str_detect(text, "^\\s*chapter"))
start_text <- chapter_rows$row_id[13]
start_text
## [1] 18
library(dplyr)
library(stringr)
alice_main <- alice_nodup |>
slice(start_text:n())
alice_chapters <- alice_main |>
mutate(chapter = cumsum(str_detect(text, "^chapter [ivxlcdm]+")))
head(alice_chapters,10)
## # A tibble: 10 × 3
## gutenberg_id text chapter
## <int> <chr> <int>
## 1 11 chapter i 1
## 2 11 down the rabbithole 1
## 3 11 alice was beginning to get very tired of sitting by her… 1
## 4 11 bank and of having nothing to do once or twice she had … 1
## 5 11 the book her sister was reading but it had no pictures … 1
## 6 11 conversations in it and what is the use of a book thoug… 1
## 7 11 without pictures or conversations 1
## 8 11 so she was considering in her own mind as well as she c… 1
## 9 11 hot day made her feel very sleepy and stupid whether th… 1
## 10 11 making a daisychain would be worth the trouble of getti… 1
tail(alice_chapters, 10)
## # A tibble: 10 × 3
## gutenberg_id text chapter
## <int> <chr> <int>
## 1 11 lastly she pictured to herself how this same little sis… 12
## 2 11 would in the aftertime be herself a grown woman and how… 12
## 3 11 keep through all her riper years the simple and loving … 12
## 4 11 childhood and how she would gather about her other litt… 12
## 5 11 and make their eyes bright and eager with many a strang… 12
## 6 11 perhaps even with the dream of wonderland of long ago a… 12
## 7 11 would feel with all their simple sorrows and find a ple… 12
## 8 11 their simple joys remembering her own childlife and the… 12
## 9 11 days 12
## 10 11 the end 12
Ovdje:
str_detect() prepoznaje početak poglavljacumsum() numerira poglavlja.Tokenizacija
library(tidytext)
alice_words <- alice_chapters |>
unnest_tokens(word, text) |>
anti_join(stop_words, by = "word")
head(alice_words)
## # A tibble: 6 × 3
## gutenberg_id chapter word
## <int> <int> <chr>
## 1 11 1 chapter
## 2 11 1 rabbithole
## 3 11 1 alice
## 4 11 1 beginning
## 5 11 1 tired
## 6 11 1 sitting
Rezultat je dataset:
chapter | word
što znači:
\[ \text{jedan redak} = \text{jedna riječ u jednom poglavlju} \]
Frekvencije riječi po poglavlju
word_counts <- alice_words |>
count(chapter, word, sort = TRUE)
head(word_counts)
## # A tibble: 6 × 3
## chapter word n
## <int> <chr> <int>
## 1 7 alice 50
## 2 9 alice 47
## 3 6 alice 43
## 4 8 alice 39
## 5 5 alice 35
## 6 7 hatter 32
Ovdje dobivamo:
chapter | word | n
gdje je \(n\) frekvencija riječi u poglavlju.
Izračun TF-IDF
Paket tidytext ima funkciju bind_tf_idf()
koja automatski računa TF, IDF i TF-IDF.
tfidf_words <- word_counts |>
bind_tf_idf(word, chapter, n)
head(tfidf_words)
## # A tibble: 6 × 6
## chapter word n tf idf tf_idf
## <int> <chr> <int> <dbl> <dbl> <dbl>
## 1 7 alice 50 0.0714 0 0
## 2 9 alice 47 0.0659 0 0
## 3 6 alice 43 0.0575 0 0
## 4 8 alice 39 0.0524 0 0
## 5 5 alice 35 0.0567 0 0
## 6 7 hatter 32 0.0457 1.39 0.0634
Rezultat sadrži tri nove varijable:
tfidftf_idf.Najvažnije riječi po poglavlju
tfidf_top <- tfidf_words |>
group_by(chapter) |>
slice_max(tf_idf, n = 5) |>
ungroup()
tfidf_top
## # A tibble: 64 × 6
## chapter word n tf idf tf_idf
## <int> <chr> <int> <dbl> <dbl> <dbl>
## 1 1 bats 4 0.00683 2.48 0.0170
## 2 1 key 6 0.0102 1.39 0.0142
## 3 1 cake 3 0.00512 2.48 0.0127
## 4 1 candle 3 0.00512 2.48 0.0127
## 5 1 dark 3 0.00512 2.48 0.0127
## 6 1 poison 3 0.00512 2.48 0.0127
## 7 1 rabbithole 3 0.00512 2.48 0.0127
## 8 2 mouse 16 0.0257 0.875 0.0225
## 9 2 swam 5 0.00804 2.48 0.0200
## 10 2 pool 8 0.0129 1.39 0.0178
## # ℹ 54 more rows
To daje riječi koje su najkarakterističnije za svako poglavlje.
Grafički prikaz često je vrlo informativan.
library(ggplot2)
tfidf_top |>
ggplot(aes(x = reorder(word, tf_idf), y = tf_idf)) +
geom_col() +
coord_flip() +
facet_wrap(~ chapter, scales = "free") +
labs(
x = "Riječ",
y = "TF-IDF",
title = "Najkarakterističnije riječi po poglavljima"
)
Rezultat TF-IDF analize pokazuje riječi koje su specifične za pojedina poglavlja. Takve riječi često označavaju:
Za razliku od jednostavne frekvencije riječi, TF-IDF omogućuje identificiranje pojmova koji najbolje razlikuju pojedine dijelove teksta.
U ovom primjeru vidjeli smo kako iz teksta možemo dobiti:
Takva analiza opisuje statističku strukturu teksta, ali ne govori mnogo o njegovom značenju ili emocijama. U sljedećoj lekciji proširit ćemo analizu metodama sentiment analize, koje omogućuju procjenu emocionalnog tona teksta.
Korpus: Skup dokumenata za analizu.
Dokument: Jedna jedinica teksta (objava, članak, komentar).
Token: Osnovna jedinica nakon tokenizacije (riječ, bigram).
Tokenizacija: Razbijanje teksta na tokene.
Pretprocesiranje: Skup koraka prije analize (čišćenje, normalizacija, tokenizacija).
Normalizacija teksta: Usklađivanje zapisa (npr. mala slova, uklanjanje interpunkcije).
Stop riječi: Česte riječi male informativnosti (npr. i, je, da, the, and).
Frekvencija riječi: Broj pojavljivanja tokena u tekstu.
TF (Term Frequency): Koliko je pojam čest u dokumentu.
TF–IDF: Težina pojma koja naglašava riječi česte u jednom dokumentu, ali rijetke u korpusu.
Bag-of-words: Reprezentacija teksta u kojoj se red riječi zanemaruje, a koristi se samo frekvencija pojmova.
N-grami: Sekvence od n uzastopnih tokena (bigram, trigram).
Vokabular: Skup svih jedinstvenih tokena u korpusu.
Sparsity: Velik broj nula u dokument–termin matrici.
Zipfov zakon: Empirijska pravilnost prema kojoj je frekvencija riječi obrnuto proporcionalna njezinu rangu u listi frekvencija.
Vizualizacija teksta: Grafički prikaz rezultata analize (bar grafovi, wordcloud, mreže).
Lematizacija: Svođenje riječi na osnovni oblik (lemma).
Stemming: Svođenje riječi na korijen; grublji postupak od lematizacije.
Carroll, L. (1865). Alice’s Adventures in Wonderland. London: Macmillan. (Project Gutenberg varijanta korištena u ovoj analizi.)
Kwartler, T. (2017). Text mining in practice with R. John Wiley & Sons.
Silge, J., & Robinson, D. (2016). tidytext: Text mining and analysis using tidy data principles in R. Journal of Open Source Software, 1(3), 37. https://doi.org/10.21105/joss.00037
Robinson, D., & Silge, J. (2023). tidytext: Text mining using dplyr, ggplot2, and other tidy tools. R package version.
Benoit, K., Watanabe, K., Wang, H., Nulty, P., Obeng, A., Müller, S., & Matsuo, A. (2018). quanteda: An R package for the quantitative analysis of textual data. Journal of Open Source Software, 3(30), 774.
Feinerer, I., Hornik, K., & Meyer, D. (2008). Text mining infrastructure in R. Journal of Statistical Software, 25(5), 1–54.
Ljubešić, N., Tercon, L., & Dobrovoljc, K. (2024). CLASSLA-Stanza: The Next Step for Linguistic Processing of South Slavic Languages. https://pypi.org/project/classla/
Manning, C. D., Raghavan, P., & Schütze, H. (2008). Introduction to information retrieval. Cambridge University Press.
Pedersen, T. L. (2023). ggwordcloud: A word cloud geom for ggplot2. R package.
Fellows, I. (2018). wordcloud: Word clouds. R package.
Bengtsson, H. (2023). stopwords: Multilingual stopword lists. R package.
Wickham, H. (2023). stringr: Simple, consistent wrappers for common string operations. R package.
Wijffels, J. (2017). udpipe: Tokenization, Parts of Speech Tagging, Lemmatization and Dependency Parsing with the ‘UDPipe’ ‘NLP’ Toolkit R package.
Leifeld, P., & Roberts, M. E. (2017). text: Analyses of text using transformer models. R package.
Peterson, B., & others. (2023). textmineR: Functions for text mining and topic modeling. R package.
a, c
a, b, e
a, b, d
a, b, c
a, b, d
a, b, c
a, b, c, e
a, b, d
a, b, d
a, c, d
a, b, c, e
a, b, d
a, b, d
a, b, d
a, b, d
a, b, d, e
a, b, d
a, b, d
a, b, c
a, b, d, e