Analiza teksta dobiva na popularnosti zbog sve veće dostupnosti podataka i razvoja user friendly podrške za provedbu takve analize. Analiza tekstualnih podataka je moguća kroz mnoštvo različitih pristupa, a najšire korišten pristup je bag-of-words u kojem je frekvencija riječi polazište za analizu dok se (npr.) pozicija riječi zanemaruje. Postupak analize teksta započinje pripremom teksta (podataka), koja uključuje: uvoz teksta, operacije sa riječima, uređivanje i tokenizacija, izrada matrice pojmova, filtiranje i ponderiranje podataka. Pri tome valja imati na umu da vrsta analize i korištena metoda određuju način na koji je potrebno pripremiti podatke za daljnu analizu te da svaka metoda ima svoje specifičnosti. Nakon pripreme podataka se vrši analiza teksta (podataka) metodama nadziranog strojnog učenja, ne-nadziranog strojnog učenja, statistike na tekstualnim podatcima, analize riječnika. Napredne metode analize podataka uključuju NLP i analizu pozicije riječi i sintakse.
Procedura za analizu teksta.
U ovom ćemo pregledu koristiti ‘tidytext’ pristup za analizu tekstualnih podatka, detaljno opisan u knjizi Text Mining with R. Analizirati ćemo temu COVID-19 pandemije na osnovi članka objavljenih na velikom broju domaćih internetskih portala u zadnjih 30 dana. Eksplorativna analiza teksta koju ćemo provesti uključuje: uvoz podataka pomoću API, čišćenje, uređivanje i prilagodbu podataka, dekriptivnu statistiku na tekstualnim podatcima, analizu sentimenta, analizu frekvencija i tematsku analizu.
Podatci za analizu su preuzeti kroz API i prikuljeni su sa velikog broja domaćih internetskih portala. Članci su identificirani ako sadrže “COVID” bilo gdje u tekstu članka,a članak je objavljen u zadnjih 30 dana.
# POVUCI PODATKE SA API
wh_token("6f9a2e1a-ba97-43a9-af55-918b50c644f4")
wh_news(q = '"COVID" is_first:true language:croatian site_type:news thread.country:HR',
ts = (Sys.time() - (30 * 24 * 60 * 60))) %>%
wh_paginate(100) %>%
wh_collect(TRUE) -> cro_corona_news
# UREDI PODATKE I POSPREMI
cro_corona_news %>%
as.data.frame(.) %>%
select(url, author, published, title, text, thread.site) %>%
write.csv2(., file = "E:/Luka/Academic/HS/MULTIVARIJARNE METODE/7_TEXT/ccn2.csv")
Nakon što smo povukli članke, izabrali varijable od interesa i lokalno posporemili podatke, u sljedećem koraku učitavamo podatke u radni prostor R i učitavamo druge podatke koji su nam potrebni za analizu. Osim članaka, potrebni su nam leksikoni i stop riječi. Leksikone ćemo preuzeti sa iz FER-ovog repozitorija, a “stop riječi” ćemo napraviti sami.
# UČITAJ ČLANKE
cro_corona_news <- read.csv2("E:/Luka/Academic/HS/MULTIVARIJARNE METODE/7_TEXT/ccn2.csv", stringsAsFactors = F)
# UČITAJ LEKSIKONE
CroSentilex_n <- read.delim("C:/Users/msagovac/Dropbox/Mislav@Luka/crosentilex-negatives.txt",
header = FALSE,
sep = " ",
stringsAsFactors = FALSE,
fileEncoding = "UTF-8") %>%
rename(word = "V1", sentiment = "V2" ) %>%
mutate(brija = "NEG")
CroSentilex_p <- read.delim("C:/Users/msagovac/Dropbox/Mislav@Luka/crosentilex-positives.txt",
header = FALSE,
sep = " ",
stringsAsFactors = FALSE,
fileEncoding = "UTF-8") %>%
rename(word = "V1", sentiment = "V2" ) %>%
mutate(brija = "POZ")
Crosentilex_sve <- rbind(setDT(CroSentilex_n), setDT(CroSentilex_p))
CroSentilex_Gold <- read.delim2("C:/Users/msagovac/Dropbox/Mislav@Luka/gs-sentiment-annotations.txt",
header = FALSE,
sep = " ",
stringsAsFactors = FALSE) %>%
rename(word = "V1", sentiment = "V2" )
Encoding(CroSentilex_Gold$word) <- "UTF-8"
CroSentilex_Gold[1,1] <- "dati"
CroSentilex_Gold$sentiment <- str_replace(CroSentilex_Gold$sentiment , "-", "1")
CroSentilex_Gold$sentiment <- str_replace(CroSentilex_Gold$sentiment , "\\+", "2")
CroSentilex_Gold$sentiment <- as.numeric(unlist(CroSentilex_Gold$sentiment))
# STVORI "STOP RIJEČI"
stopwords_cro <- get_stopwords(language = "hr", source = "stopwords-iso")
my_stop_words <- tibble(
word = c(
"jedan",
"e","prvi", "dva","dvije","drugi",
"tri","treći","pet","kod",
"ove","ova", "ovo","bez", "kod",
"evo","oko", "om", "ek",
"mil","tko","šest", "sedam",
"osam", "čim", "zbog",
"prema", "dok","zato", "koji",
"im", "čak","među", "tek",
"koliko", "tko","kod","poput",
"baš", "dakle", "osim", "svih",
"svoju", "odnosno", "gdje",
"kojoj", "ovi", "toga",
"ubera", "vozača", "hrvatskoj", "usluge", "godine", "više", "taksi", "taxi", "taksija", "taksija", "kaže", "rekao", "19"
),
lexicon = "lux"
)
stop_corpus <- my_stop_words %>%
bind_rows(stopwords_cro)
U sljedećem koraku ćemo prilagoditi podatke u tidy format koji je prikladan za analizu. Za to je potrebno provesti tokenizaciju te očistiti riječi od brojeva i nepotrebnih riječi. Na tako uređenim podatcima ćemo napraviti osnovni statistički pregled i provesti jednostavnu statističku analizu ključnih riječi i domena.
# UREDI ČLANKE
newsCOVID <- cro_corona_news %>%
as.data.frame() %>%
select(url, author, published, title, text, thread.site) %>%
mutate(published = gsub("T.*","",published) ) %>%
mutate(published = as.Date(published,"%Y-%m-%d")) %>%
mutate(clanak = 1:n()) %>%
group_by(thread.site) %>%
mutate(domenaBr = n()) %>%
ungroup()
glimpse(newsCOVID)
## Rows: 7,919
## Columns: 8
## $ url <chr> "https://podravski.hr/najnovije-nema-novih-zarazenih-u-...
## $ author <chr> "Ivica Barać", "Iva Međugorac", "Željko Marušić", "Tea ...
## $ published <date> 2020-04-26, 2020-04-26, 2020-04-26, 2020-04-26, 2020-0...
## $ title <chr> "NAJNOVIJE! Nema novih zaraženih u našoj županiji, ozdr...
## $ text <chr> "NAJNOVIJE! Nema novih zaraženih u našoj županiji, ozdr...
## $ thread.site <chr> "podravski.hr", "dnevno.hr", "dnevno.hr", "dnevno.hr", ...
## $ clanak <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ...
## $ domenaBr <int> 70, 103, 103, 103, 41, 269, 269, 439, 16, 294, 439, 64,...
# TOKENIZACIJA
newsCOVID %>%
unnest_tokens(word, text) -> newsCOVID_token
glimpse(newsCOVID_token)
## Rows: 4,799,432
## Columns: 8
## $ url <chr> "https://podravski.hr/najnovije-nema-novih-zarazenih-u-...
## $ author <chr> "Ivica Barać", "Ivica Barać", "Ivica Barać", "Ivica Bar...
## $ published <date> 2020-04-26, 2020-04-26, 2020-04-26, 2020-04-26, 2020-0...
## $ title <chr> "NAJNOVIJE! Nema novih zaraženih u našoj županiji, ozdr...
## $ thread.site <chr> "podravski.hr", "podravski.hr", "podravski.hr", "podrav...
## $ clanak <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
## $ domenaBr <int> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,...
## $ word <chr> "najnovije", "nema", "novih", "zaraženih", "u", "našoj"...
newsCOVID_token %>%
head(10)
## # A tibble: 10 x 8
## url author published title thread.site clanak domenaBr word
## <chr> <chr> <date> <chr> <chr> <int> <int> <chr>
## 1 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 najn~
## 2 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 nema
## 3 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 novih
## 4 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 zara~
## 5 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 u
## 6 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 našoj
## 7 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 župa~
## 8 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 ozdr~
## 9 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 ukup~
## 10 https://pod~ Ivica ~ 2020-04-26 NAJNOVIJE!~ podravski.~ 1 70 14
newsCOVID_token %>%
sample_n(.,10)
## # A tibble: 10 x 8
## url author published title thread.site clanak domenaBr word
## <chr> <chr> <date> <chr> <chr> <int> <int> <chr>
## 1 http://www~ "Z NET" 2020-04-08 ŠKOLSKA SH~ znet.hr 4612 99 razv~
## 2 https://ww~ "" 2020-04-17 POVJERENIC~ jutarnji.hr 2149 774 prob~
## 3 https://ww~ "Mirja~ 2020-04-15 U Hrvatsko~ icv.hr 2570 99 mjere
## 4 http://met~ "" 2020-04-10 Proglašene~ metro-porta~ 3825 88 za
## 5 https://ww~ "maxpo~ 2020-03-29 Dr. Lidija~ maxportal.hr 7269 41 mjer~
## 6 https://ww~ "" 2020-04-06 VODIČ ZA E~ jutarnji.hr 5215 774 osoba
## 7 https://ww~ "uredn~ 2020-04-10 Pučka prav~ civilnodrus~ 3726 23 slož~
## 8 https://ww~ "Bljes~ 2020-04-20 UK će test~ bljesak.info 1415 92 prež~
## 9 https://vl~ "" 2020-04-07 Stožer: 60~ gov.hr 4763 64 juče~
## 10 http://www~ "" 2020-04-18 Globalna p~ glas-slavon~ 1722 278 sve
## Ukloni "stop words", brojeve, veznike i pojedinačna slova
newsCOVID_token %>%
anti_join(stop_corpus, by = "word") %>%
mutate(word = gsub("\\d+", NA, word)) %>%
mutate(word = gsub("^[a-zA-Z]$", NA, word)) %>%
drop_na(.)-> newsCOVID_tokenTidy
newsCOVID_tokenTidy %>%
sample_n(10)
## # A tibble: 10 x 8
## url author published title thread.site clanak domenaBr word
## <chr> <chr> <date> <chr> <chr> <int> <int> <chr>
## 1 http://ww~ "" 2020-04-13 Veterinar~ tportal.hr 3317 294 skrbe
## 2 https://w~ "" 2020-04-07 Dvaput do~ rtl.hr 5016 267 stva~
## 3 http://ww~ "" 2020-04-24 Osječke u~ osijek031.~ 450 47 natj~
## 4 https://n~ "" 2020-04-24 Butković:~ jutarnji.hr 314 774 butk~
## 5 https://w~ "" 2020-04-14 U TIJEKU ~ jutarnji.hr 2755 774 post~
## 6 https://w~ "" 2020-04-11 Hrvatska ~ rtl.hr 3462 267 podr~
## 7 http://ww~ "24sata.h~ 2020-04-11 OŠTEĆENJA~ znet.hr 3569 99 češći
## 8 https://s~ "" 2020-04-13 NAJAVIO J~ jutarnji.hr 3196 774 bres~
## 9 https://n~ "Narod.hr" 2020-03-31 (VIDEO) D~ narod.hr 6767 72 devet
## 10 https://w~ "Dijana M~ 2020-04-02 Sve bliže~ 24sata.hr 6361 429 izol~
# DESKRIPTIVNI PREGLED PODATAKA
## Vremenski raspon
range(newsCOVID_token$published)
## [1] "2020-03-26" "2020-04-26"
## Najčešće riječi
newsCOVID_tokenTidy %>%
count(word, sort = T) %>%
head(25)
## # A tibble: 25 x 2
## word n
## <chr> <int>
## 1 covid 13977
## 2 osoba 13068
## 3 dana 9053
## 4 ljudi 8966
## 5 koronavirusa 8892
## 6 mjere 8375
## 7 mjera 7851
## 8 sada 7632
## 9 zaštite 7544
## 10 osobe 7388
## # ... with 15 more rows
## Vizualizacija najčešćih riječi
newsCOVID_tokenTidy %>%
count(word, sort = T) %>%
filter(n > 5000) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
xlab(NULL) +
coord_flip()
## Vizualizacija najčešćih riječi kroz vrijeme
newsCOVID_tokenTidy %>%
mutate(Datum = floor_date(published, "day")) %>%
group_by(Datum) %>%
count(word) %>%
mutate(gn = sum(n)) %>%
filter(word %in% c("covid", "kriza", "ekonomija", "oporavak")) %>%
ggplot(., aes(Datum, n / gn)) +
geom_point() +
ggtitle("Učestalost korištenja riječi u člancima o pandemiji COVID-19") +
ylab("% ukupnih riječi") +
geom_smooth() +
facet_wrap(~ word, scales = "free_y") +
scale_y_continuous(labels = scales::percent_format())
## Broj domena
newsCOVID_tokenTidy %>%
summarise(Domena = n_distinct(thread.site))
## # A tibble: 1 x 1
## Domena
## <int>
## 1 160
## Broj članaka po domeni
newsCOVID %>%
drop_na(.) %>%
group_by(thread.site) %>%
summarise(n = n()) %>%
arrange(desc(n)) %>%
head(20)
## # A tibble: 20 x 2
## thread.site n
## <chr> <int>
## 1 slobodnadalmacija.hr 790
## 2 jutarnji.hr 774
## 3 novilist.hr 439
## 4 24sata.hr 429
## 5 net.hr 383
## 6 tportal.hr 294
## 7 glas-slavonije.hr 278
## 8 dnevnik.hr 269
## 9 rtl.hr 267
## 10 glasistre.hr 214
## 11 poslovni.hr 212
## 12 dalmacijanews.hr 135
## 13 telegram.hr 135
## 14 lider.media 121
## 15 dalmatinskiportal.hr 117
## 16 ipress.hr 111
## 17 dnevno.hr 103
## 18 hkm.hr 102
## 19 icv.hr 99
## 20 znet.hr 99
## Najveći portali
newsCOVID %>%
group_by(thread.site) %>%
summarise(n = n()) %>%
arrange(desc(n)) %>%
top_n(6) %>%
select(thread.site) %>%
pull() -> topPortali
## Broj članaka po domeni kroz vrijeme
newsCOVID %>%
mutate(Datum = floor_date(published, "day")) %>%
group_by(Datum) %>%
count(thread.site) %>%
mutate(gn = sum(n)) %>%
ungroup() %>%
filter(thread.site %in% topPortali) %>%
group_by(Datum, thread.site) %>%
ggplot(., aes(Datum, n / gn)) +
geom_point() +
ggtitle("Članci o pandemiji COVID-19 na najvažnijim RH portalima") +
ylab("% ukupno objavljenih članaka") +
geom_smooth() +
facet_wrap(~ thread.site, scales = "free_y") +
scale_y_continuous(labels = scales::percent_format())
Nakon uređivanja podataka i osnovnog pregleda najvažnijih riječi i dinamike kretanja članaka u vremenu, provesti ćemo analizu sentimenta. Za analizu sentimenta je potrebno preuzeti leksikone sentimenta koji su za hrvatski jezik dostupni kroz FER-ov Croatian Sentiment Lexicon. Analiza sentimenta će biti provedena za cijeli korpus članaka te za domene i uključuje sentiment kroz vrijeme, doprinos riječi sentimentu, ‘wordCloud’ i analizu negativnosti portala.
## Pregled leksikona
CroSentilex_n %>% sample_n(10)
## word sentiment brija
## 1 zečić 0.22811 NEG
## 2 istegnusti 0.28867 NEG
## 3 srečko 0.12995 NEG
## 4 rodilište 0.47950 NEG
## 5 preokretati 0.44251 NEG
## 6 izvoz 0.36509 NEG
## 7 portoriko 0.30171 NEG
## 8 jakljan 0.32017 NEG
## 9 lampaš 0.15640 NEG
## 10 paradis 0.07237 NEG
CroSentilex_p %>% sample_n(10)
## word sentiment brija
## 1 garros 0.510340 POZ
## 2 osama 0.451340 POZ
## 3 vanek 0.057987 POZ
## 4 santoro 0.106860 POZ
## 5 dvokrevetan 0.256320 POZ
## 6 milja 0.527150 POZ
## 7 vikend 0.277400 POZ
## 8 krzysztof 0.166890 POZ
## 9 bjeliš 0.380770 POZ
## 10 horwath 0.120390 POZ
Crosentilex_sve %>% sample_n(10)
## word sentiment brija
## 1 električan 0.37711 NEG
## 2 marulić 0.37298 NEG
## 3 usluga 0.30013 POZ
## 4 sjemenište 0.32842 NEG
## 5 zvukovan 0.56314 POZ
## 6 šetanje 0.12224 POZ
## 7 položen 0.45734 POZ
## 8 prepisivanje 0.22789 POZ
## 9 benedikt 0.56247 POZ
## 10 depozitan 0.11399 NEG
CroSentilex_Gold %>% sample_n(10)
## word sentiment
## 1 opasan 1
## 2 vino 0
## 3 soba 0
## 4 brak 0
## 5 uvjeren 0
## 6 snimka 0
## 7 ukraden 1
## 8 platiti 0
## 9 lak 0
## 10 vremenski 0
## Kretanje sentimenta kroz vrijeme
vizualiziraj_sentiment <- function(dataset, frq = "day") {
dataset %>%
inner_join( Crosentilex_sve, by = "word") %>%
filter(!is.na(word)) %>%
select(word, brija, published, sentiment) %>%
unique() %>%
spread(. , brija, sentiment) %>%
mutate(sentiment = POZ - NEG) %>%
select(word, published, sentiment) %>%
group_by(word) %>%
mutate(count = n()) %>%
arrange(desc(count)) %>%
mutate( score = sentiment*count) %>%
ungroup() %>%
group_by(published) %>%
arrange(desc(published)) -> sm
sm %>%
select(published, score) %>%
group_by(Datum = floor_date(published, frq)) %>%
summarise(Dnevni_sent = sum(score, na.rm = TRUE)) %>%
ggplot(., aes(Datum, Dnevni_sent)) +
geom_bar(stat = "identity") +
ggtitle(paste0("Sentiment kroz vrijeme;frekvencija podataka:", frq)) +
ylab("SentimentScore") -> gg_sentiment_kroz_vrijeme_qv
gg_sentiment_kroz_vrijeme_qv
}
vizualiziraj_sentiment(newsCOVID_tokenTidy,"day")
## Doprinos sentimentu
doprinos_sentimentu <- function(dataset, no = n) {
dataset %>%
inner_join(CroSentilex_Gold, by = "word") %>%
count(word, sentiment,sort = TRUE) %>%
group_by(sentiment) %>%
top_n(no) %>%
ungroup() %>%
mutate(sentiment = case_when(sentiment == 0 ~ "NEUTRALNO",
sentiment == 1 ~ "NEGATIVNO",
sentiment == 2 ~ "POZITIVNO")) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
ggtitle( "Doprinos sentimentu") +
labs( x = "Riječ", y = "Broj riječi") +
facet_wrap(~ sentiment, scales = "free_y") +
coord_flip() -> gg_doprinos_sentimentu
gg_doprinos_sentimentu
}
doprinos_sentimentu(newsCOVID_tokenTidy,15)
## WordCloud(vulgaris)
newsCOVID_tokenTidy %>%
anti_join(CroSentilex_Gold,by="word") %>%
count(word) %>%
arrange(desc(n)) %>%
top_n(100) %>%
with(wordcloud(word, n, max.words = 80))
## ComparisonCloud
newsCOVID_tokenTidy %>%
inner_join(CroSentilex_Gold,by="word") %>%
count(word, sentiment) %>%
top_n(200) %>%
mutate(sentiment = case_when(sentiment == 0 ~ "+/-",
sentiment == 1 ~ "-",
sentiment == 2 ~ "+")) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("firebrick3", "deepskyblue3","darkslategray"),
max.words = 120)
## Najnegativniji portali
wCount <- newsCOVID_tokenTidy %>%
group_by(thread.site) %>%
summarise(word = n())
CroSentilex_Gold_neg <- CroSentilex_Gold %>% filter(sentiment == 1)
CroSentilex_Gold_poz <- CroSentilex_Gold %>% filter(sentiment == 2)
newsCOVID_tokenTidy %>%
semi_join(CroSentilex_Gold_neg, by= "word") %>%
group_by(thread.site) %>%
summarise(negWords = n()) %>%
left_join(wCount, by = "thread.site") %>%
mutate(negativnostIndex = (negWords/word)*100) %>%
arrange(desc(negativnostIndex))
## # A tibble: 150 x 4
## thread.site negWords word negativnostIndex
## <chr> <int> <int> <dbl>
## 1 voljatel.hr 3 130 2.31
## 2 alexandar.org 7 371 1.89
## 3 makora.hr 22 1176 1.87
## 4 hkv.hr 409 24085 1.70
## 5 drvo-namjestaj.hr 12 829 1.45
## 6 hrportfolio.hr 41 2923 1.40
## 7 vozim.hr 13 966 1.35
## 8 pcchip.hr 4 299 1.34
## 9 klik.hr 22 1667 1.32
## 10 vecernji.hr 107 8402 1.27
## # ... with 140 more rows
## Najpozitivniji portali
newsCOVID_tokenTidy %>%
semi_join(CroSentilex_Gold_poz, by= "word") %>%
group_by(thread.site) %>%
summarise(pozWords = n()) %>%
left_join(wCount, by = "thread.site") %>%
mutate(pozitivnostIndex = (pozWords/word)*100) %>%
arrange(desc(pozitivnostIndex))
## # A tibble: 156 x 4
## thread.site pozWords word pozitivnostIndex
## <chr> <int> <int> <dbl>
## 1 sportnedjeljom.hr 11 198 5.56
## 2 profightstore.hr 8 196 4.08
## 3 pcchip.hr 12 299 4.01
## 4 ekozadar.hr 14 366 3.83
## 5 soundguardian.com 5 142 3.52
## 6 infozona.hr 4 114 3.51
## 7 gameperspectives.hr 59 1820 3.24
## 8 redea.hr 62 1969 3.15
## 9 sensa.hr 55 1765 3.12
## 10 hpb.hr 6 196 3.06
## # ... with 146 more rows
### Veliki portali
newsCOVID %>%
group_by(thread.site) %>%
count %>%
arrange(desc(n)) %>%
head(10) %>%
pull(thread.site) -> najveceDomene
newsCOVID %>%
group_by(thread.site) %>%
count %>%
arrange(desc(n)) %>%
head(5) %>%
pull(thread.site) -> najveceDomene5
newsCOVID_tokenTidy %>%
filter(thread.site %in% najveceDomene) %>%
semi_join(CroSentilex_Gold_neg, by= "word") %>%
group_by(thread.site) %>%
summarise(negWords = n()) %>%
left_join(wCount, by = "thread.site") %>%
mutate(negativnostIndex = (negWords/word)*100) %>%
arrange(desc(negativnostIndex))
## # A tibble: 10 x 4
## thread.site negWords word negativnostIndex
## <chr> <int> <int> <dbl>
## 1 24sata.hr 1968 177938 1.11
## 2 slobodnadalmacija.hr 3450 314073 1.10
## 3 glasistre.hr 558 53506 1.04
## 4 jutarnji.hr 3271 315739 1.04
## 5 glas-slavonije.hr 957 93121 1.03
## 6 net.hr 1081 106967 1.01
## 7 tportal.hr 689 72569 0.949
## 8 novilist.hr 1407 148393 0.948
## 9 rtl.hr 676 75066 0.901
## 10 dnevnik.hr 897 108804 0.824
newsCOVID_tokenTidy %>%
filter(thread.site %in% najveceDomene) %>%
semi_join(CroSentilex_Gold_poz, by= "word") %>%
group_by(thread.site) %>%
summarise(pozWords = n()) %>%
left_join(wCount, by = "thread.site") %>%
mutate(pozitivnostIndex = (pozWords/word)*100) %>%
arrange(desc(pozitivnostIndex))
## # A tibble: 10 x 4
## thread.site pozWords word pozitivnostIndex
## <chr> <int> <int> <dbl>
## 1 24sata.hr 3524 177938 1.98
## 2 glas-slavonije.hr 1835 93121 1.97
## 3 glasistre.hr 1024 53506 1.91
## 4 slobodnadalmacija.hr 6008 314073 1.91
## 5 novilist.hr 2777 148393 1.87
## 6 net.hr 1973 106967 1.84
## 7 rtl.hr 1318 75066 1.76
## 8 jutarnji.hr 5541 315739 1.75
## 9 tportal.hr 1216 72569 1.68
## 10 dnevnik.hr 1693 108804 1.56
Poslije analize sentimenta je korisno analizirati najbitnije riječi, a to je moguće putem IDF (inverse document frequency) metode. IDF metoda omogućuje identifikaciju važnih, a ne nužno čestih riječi u korpusu i može poslužiti za analizu najvažnijih pojmova po domenama.
## Udio riječi po domenama
domenaWords <- newsCOVID %>%
unnest_tokens(word,text) %>%
count(thread.site, word, sort = T)
ukupnoWords <- domenaWords %>%
group_by(thread.site) %>%
summarise(totWords = sum(n))
domenaWords <- left_join(domenaWords, ukupnoWords)
domenaWords %>% head(15)
## # A tibble: 15 x 4
## thread.site word n totWords
## <chr> <chr> <int> <int>
## 1 jutarnji.hr je 19475 522108
## 2 icv.hr je 18476 324085
## 3 jutarnji.hr u 18094 522108
## 4 slobodnadalmacija.hr je 17630 524303
## 5 slobodnadalmacija.hr i 17545 524303
## 6 slobodnadalmacija.hr u 17010 524303
## 7 jutarnji.hr i 16185 522108
## 8 slobodnadalmacija.hr se 10150 524303
## 9 24sata.hr je 10070 294027
## 10 24sata.hr u 9764 294027
## 11 24sata.hr i 9689 294027
## 12 icv.hr i 9578 324085
## 13 jutarnji.hr se 9482 522108
## 14 icv.hr u 9313 324085
## 15 jutarnji.hr na 9174 522108
domenaWords %>% filter(thread.site %in% najveceDomene) %>%
ggplot(., aes(n/totWords, fill = thread.site)) +
geom_histogram(show.legend = FALSE) +
xlim(NA, 0.0009) +
facet_wrap(~thread.site, ncol = 2, scales = "free_y")
## Najbitnije riječi po domenma
idf <- domenaWords %>%
bind_tf_idf(word, thread.site, n)
idf %>% head(10)
## # A tibble: 10 x 7
## thread.site word n totWords tf idf tf_idf
## <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
## 1 jutarnji.hr je 19475 522108 0.0373 0.00627 0.000234
## 2 icv.hr je 18476 324085 0.0570 0.00627 0.000357
## 3 jutarnji.hr u 18094 522108 0.0347 0 0
## 4 slobodnadalmacija.hr je 17630 524303 0.0336 0.00627 0.000211
## 5 slobodnadalmacija.hr i 17545 524303 0.0335 0 0
## 6 slobodnadalmacija.hr u 17010 524303 0.0324 0 0
## 7 jutarnji.hr i 16185 522108 0.0310 0 0
## 8 slobodnadalmacija.hr se 10150 524303 0.0194 0.00627 0.000121
## 9 24sata.hr je 10070 294027 0.0342 0.00627 0.000215
## 10 24sata.hr u 9764 294027 0.0332 0 0
idf %>%
select(-totWords) %>%
arrange(desc(tf_idf))
## # A tibble: 806,633 x 6
## thread.site word n tf idf tf_idf
## <chr> <chr> <int> <dbl> <dbl> <dbl>
## 1 infozona.hr dvk 4 0.0248 5.08 0.126
## 2 lokalnahrvatska.hr iframes 90 0.0206 5.08 0.104
## 3 zse.hr d.d 13 0.0565 1.78 0.101
## 4 serijala.com knope 3 0.0180 5.08 0.0912
## 5 serijala.com leslie 3 0.0180 5.08 0.0912
## 6 lokalnahrvatska.hr browser 90 0.0206 4.38 0.0902
## 7 lokalnahrvatska.hr p 180 0.0412 1.86 0.0764
## 8 zse.hr ažurirana 4 0.0174 4.38 0.0762
## 9 pcchip.hr capcom 9 0.0168 4.38 0.0734
## 10 soundguardian.com novoselić 3 0.0144 5.08 0.0728
## # ... with 806,623 more rows
idf %>%
filter(thread.site %in% najveceDomene5) %>%
arrange(desc(tf_idf)) %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>%
mutate(thread.site = factor(thread.site, levels = najveceDomene5)) %>%
group_by(thread.site) %>%
top_n(10) %>%
ungroup() %>%
ggplot(aes(word, tf_idf, fill = thread.site)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
facet_wrap(~thread.site, ncol = 2, scales = "free") +
coord_flip()
Do sada smo analizirali tekst na način da je (jedna) riječ osnova za analizu. Taj način može sakriti bitne nalaze do kojih je moguće doći ukoliko se tekst tokenizira na fraze (dvije ili N riječi). U sljedećemo koraku ćemo tokenizirati tekst na bigrame (dvije riječi) kako bismo proveli frazeološku analizu. Korištenje bigrama omogućava korištenje dodatnih metoda pa ćemo provesti i analizu korelacije među riječima.
newsCOVID_bigram <- newsCOVID %>%
unnest_tokens(bigram, text, token = "ngrams", n = 2)
newsCOVID_bigram %>% head(10)
## # A tibble: 10 x 8
## url author published title thread.site clanak domenaBr bigram
## <chr> <chr> <date> <chr> <chr> <int> <int> <chr>
## 1 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 francu~
## 2 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 nobelo~
## 3 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 korona~
## 4 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 je sig~
## 5 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 sigurn~
## 6 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 nastao~
## 7 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 u labo~
## 8 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 labora~
## 9 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 u wuha~
## 10 http://alex~ "" 2020-04-19 Francuski~ alexandar.~ 1712 2 wuhanu~
newsCOVID_bigram %>%
count(bigram, sort = T) %>%
head(15)
## # A tibble: 15 x 2
## bigram n
## <chr> <int>
## 1 covid 19 12222
## 2 da je 12058
## 3 da se 11829
## 4 je u 8871
## 5 rekao je 6520
## 6 koji su 6474
## 7 je da 6090
## 8 civilne zaštite 6077
## 9 će se 5765
## 10 što je 5706
## 11 da će 5429
## 12 kako bi 5176
## 13 da su 4953
## 14 u hrvatskoj 4319
## 15 je i 4304
newsCOVID_bigram_sep <- newsCOVID_bigram %>%
separate(bigram, c("word1","word2"), sep = " ")
newsCOVID_bigram_tidy <- newsCOVID_bigram_sep %>%
filter(!word1 %in% stop_corpus$word) %>%
filter(!word2 %in% stop_corpus$word) %>%
mutate(word1 = gsub("\\d+", NA, word1)) %>%
mutate(word2 = gsub("\\d+", NA, word2)) %>%
mutate(word1 = gsub("^[a-zA-Z]$", NA, word1)) %>%
mutate(word2 = gsub("^[a-zA-Z]$", NA, word2)) %>%
drop_na(.)
newsCOVID_bigram_tidy_bigram_counts <- newsCOVID_bigram_tidy %>%
count(word1, word2, sort = TRUE)
newsCOVID_bigram_tidy_bigram_counts
## # A tibble: 691,620 x 3
## word1 word2 n
## <chr> <chr> <int>
## 1 civilne zaštite 6077
## 2 stožera civilne 2958
## 3 stožer civilne 1929
## 4 javno zdravstvo 1575
## 5 nacionalnog stožera 1404
## 6 bolesti covid 1269
## 7 zaraze koronavirusom 1262
## 8 vili beroš 1255
## 9 ministar zdravstva 1205
## 10 novih slučajeva 1136
## # ... with 691,610 more rows
bigrams_united <- newsCOVID_bigram_tidy %>%
drop_na(.) %>%
unite(bigram, word1, word2, sep = " ")
bigrams_united
## # A tibble: 1,712,053 x 8
## url author published title thread.site clanak domenaBr bigram
## <chr> <chr> <date> <chr> <chr> <int> <int> <chr>
## 1 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 francus~
## 2 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 nobelov~
## 3 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 sigurno~
## 4 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 wuhanu ~
## 5 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 foto epa
## 6 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 epa efe
## 7 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 efe kon~
## 8 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 kontrov~
## 9 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 francus~
## 10 http://alex~ "" 2020-04-19 Francusk~ alexandar.~ 1712 2 znanstv~
## # ... with 1,712,043 more rows
bigrams_united %>%
count(clanak,bigram,sort = T) -> topicBigram
# Najvažniji bigrami po domenama
bigram_tf_idf <- bigrams_united %>%
count(thread.site, bigram) %>%
bind_tf_idf(bigram, thread.site, n) %>%
arrange(desc(tf_idf))
bigram_tf_idf %>%
filter(thread.site %in% najveceDomene5) %>%
arrange(desc(tf_idf)) %>%
mutate(bigram = factor(bigram, levels = rev(unique(bigram)))) %>%
mutate(thread.site = factor(thread.site, levels = najveceDomene5)) %>%
group_by(thread.site) %>%
top_n(10) %>%
ungroup() %>%
ggplot(aes(bigram, tf_idf, fill = thread.site)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
facet_wrap(~thread.site, ncol = 2, scales = "free") +
coord_flip()
# Analiza bigramskih fraza
newsCOVID_bigram_tidy %>%
filter(word1 == "covid") %>%
count(word1,word2,sort=T)
## # A tibble: 208 x 3
## word1 word2 n
## <chr> <chr> <int>
## 1 covid odjelu 164
## 2 covid infekciju 67
## 3 covid ambulante 64
## 4 covid score 56
## 5 covid odjel 52
## 6 covid ambulanti 42
## 7 covid bolnici 42
## 8 covid odjela 39
## 9 covid pozitivnih 34
## 10 covid pozitivna 20
## # ... with 198 more rows
# Vizualiziraj bigrame
bigram_graph <- newsCOVID_bigram_tidy_bigram_counts %>%
filter(n>950) %>%
graph_from_data_frame()
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))
ggraph(bigram_graph, layout = "fr") +
geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
arrow = a, end_cap = circle(.07, 'inches')) +
geom_node_point(color = "lightblue", size = 5) +
geom_node_text(aes(label = name), vjust = 1, hjust = 1) +
theme_void()
# Korelacije riječi ( R crash na T=30)
newsCOVID_tokenTidy %>%
filter(published == "2020-04-22") %>%
pairwise_count(word, domenaBr, sort = T) %>%
filter_all(any_vars(!is.na(.))) -> pairsWords
newsCOVID_tokenTidy %>%
filter(published > "2020-04-20") %>%
group_by(word) %>%
filter(n() > 20) %>%
filter(!is.na(word)) %>%
pairwise_cor(word,thread.site, sort = T) -> corsWords
corsWords %>%
filter(item1 == "oporavak")
## # A tibble: 4,567 x 3
## item1 item2 correlation
## <chr> <chr> <dbl>
## 1 oporavak period 0.666
## 2 oporavak odlučila 0.640
## 3 oporavak siječnju 0.634
## 4 oporavak kafići 0.630
## 5 oporavak linija 0.629
## 6 oporavak who 0.616
## 7 oporavak negativan 0.612
## 8 oporavak brojke 0.607
## 9 oporavak koronavirusu 0.604
## 10 oporavak policijske 0.601
## # ... with 4,557 more rows
corsWords %>%
filter(item1 %in% c("kriza", "gospodarstvo", "oporavak", "mjere")) %>%
group_by(item1) %>%
top_n(8) %>%
ungroup() %>%
mutate(item2 = reorder(item2, correlation)) %>%
ggplot(aes(item2, correlation)) +
geom_bar(stat = "identity") +
facet_wrap(~ item1, scales = "free") +
coord_flip()
Na kraju provodimo tematsku analizu kao najsloženiji dio do sada provedene analize. Pri tome koristimo LDA(Latent Dirichlet allocation) algoritam kako bismo pronašli najvažnije riječi u algoritamski identificiranim temama. Ovdje je važno primijetiti da prije provedbe LDA modela valja tokeniirane riječi pretvoriti u matricu pojmova (document term matrix) koju ćemo kasnije koristiti kao input za LDA algoritam.
newsCOVID_tokenTidy %>%
count(clanak, word, sort = TRUE) %>%
cast_dtm(clanak, word,n) -> dtm
newsCOVID_LDA <- LDA(dtm, k = 3, control = list(seed = 1234))
newsCOVID_LDA_tidy <- tidy(newsCOVID_LDA, matrix = "beta")
newsCOVID_LDA_tidy
## # A tibble: 381,168 x 3
## topic term beta
## <int> <chr> <dbl>
## 1 1 osoba 0.000245
## 2 2 osoba 0.0133
## 3 3 osoba 0.00101
## 4 1 the 0.000456
## 5 2 the 0.000128
## 6 3 the 0.000375
## 7 1 ukupno 0.000215
## 8 2 ukupno 0.00633
## 9 3 ukupno 0.0000243
## 10 1 ministar 0.000407
## # ... with 381,158 more rows
newsCOVID_terms <- newsCOVID_LDA_tidy %>%
drop_na(.) %>%
group_by(topic) %>%
top_n(10, beta) %>%
ungroup() %>%
arrange(topic, -beta)
newsCOVID_terms
## # A tibble: 30 x 3
## topic term beta
## <int> <chr> <dbl>
## 1 1 covid 0.00405
## 2 1 mjere 0.00295
## 3 1 mjera 0.00263
## 4 1 pandemije 0.00245
## 5 1 vrijeme 0.00232
## 6 1 koronavirusa 0.00214
## 7 1 kuna 0.00209
## 8 1 sada 0.00204
## 9 1 mogu 0.00190
## 10 1 hrvatske 0.00189
## # ... with 20 more rows
newsCOVID_terms %>%
mutate(term = reorder_within(term, beta, topic)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip() +
scale_x_reordered()
# Bigrami topic
topicBigram %>%
cast_dtm(clanak, bigram,n) -> dtmB
newsCOVID_LDA <- LDA(dtmB, k = 3, control = list(seed = 1234))
newsCOVID_LDA_tidy <- tidy(newsCOVID_LDA, matrix = "beta")
newsCOVID_LDA_tidy
newsCOVID_terms <- newsCOVID_LDA_tidy %>%
drop_na(.) %>%
group_by(topic) %>%
top_n(10, beta) %>%
ungroup() %>%
arrange(topic, -beta)
newsCOVID_terms
newsCOVID_terms %>%
mutate(term = reorder_within(term, beta, topic)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip() +
scale_x_reordered() +
theme_economist()