Analiza teksta dobiva na popularnosti zbog sve veće dostupnosti podataka i razvoja user friendly podrške za provedbu takve analize. Konceptualni pregled analize teksta je dostupan i u nedavno objavljenoj knjizi, koja se preporuča tek nakon savladavanja osnovnih tehničkih vještina i alata za obradu teksta. Provedba analize tekstualnih podataka je moguća na mnogo načina, 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 u rečenici ili paragrafu zanemaruje. Bag of words pristup je ujedno i najjednostavniji (konceptualno i računarski) pa će biti korišten u ovom predavanju.
Postupak analize teksta započinje pripremom teksta (podataka), koja je često dosta zahtjevna i uključuje: uvoz teksta, operacije sa riječima, uređivanje i tokenizaciju, izradu 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, analize sentimenta. Napredne metode analize podataka uključuju NLP, analizu pozicije riječi i sintakse…Sažeti prikaz workflow-a za analizu teksta izgleda ovako:
U ovom predavanju ćemo koristiti tidytext
pristup (i istoimeni paket) za analizu tekstualnih podatka, detaljno opisan u knjizi Text Mining with R. Ovaj paket služi kako bismo tekstualne podatke “uveli” u tidyverse ovir pomoću kojeg je moguće nestrukturirani tekst analizirati sa otprije poznatim alatima iz dplyr
i ggplot
paketa. Učitajmo potrebne pakete:
library(tidyverse)
library(tidytext)
library(data.table)
library(lubridate)
library(grid)
library(wordcloud)
library(reshape2)
library(igraph)
library(ggraph)
library(widyr)
library(topicmodels)
library(ggthemes)
library(DT)
Prije opisa podataka koje ćemo koristiti valja naglasiti da tidytext
pristup nije jedini način za rad s podatcima u R. Ovdje ga koristimo jer je kompatibilan sa pristupima koje smo do sada koristili u okviru ovog kolegija. Drugi paketi (pristupi) za rad sa tekstom u R su:
quanteda
je sveobuhvatan i funkcijama bogat paket, neophodan u za složeniju analizu teksta. Izvrstan tutorial je dostupan na linku.
text2vec
je izrazito koristan paket za ML algoritme sa tekstualnim podatcima. Posebno je pogodan za izradu dtm i tcm matrica. Paket je motiviran python-ovom Gensim knjižnicom, a tutorial je dostupan na linku.
stringr
paket je neophodan za manipulaciju string podataka u R i kao dio tidyverse
svijeta će biti izrazito koristan u čišćenju i pripremi podataka. Vrlo je praktičan za rad sa regex-om i ima nekoliko izvrsnih funkcija za pattern matching. Službeni R Tutorial je dostupan na linku.
spacyr
je wrapper paket za spaCy knjižnicu iz python-a i omogućava provedbu naprednijih NLP modela (deep learning, speech tagging, tkoenization, parsing) u R. Također je kompatibilan sa quanteda i tidytext paketima. Tutorial je dostupan na linku.
za one koji žele znati više mogu biti korisni i sljedeći resursi: vodič za tekstualnu analizu u R i kolegij za obradu prirodnog teksta u najstajnju koji sadrži i mnoštvo referenci.
Svaka analiza (teksta) počinje od podataka. Pribava tekstualnih podataka o specifičnim temama najčešće nije jednostavna. Najčešći je način preuzimanja podataka neki od dostupnih API servisa za novinske članke ili tekstualnih repozitorija ili servisi poput Twitter-a. No to često nije dovoljno ukolilko želimo analizirati specifičnu temu ili temu na specifičnom jeziku (npr. hrvatskom). Ovdje još valja napomeniti da je preuzimanje kvalitetnih tekstualnih podataka često moguće isključivo uz nadoplatu kao što je to slučaj sa novinskim člancima na hrvatskom jeziku, primjerice kroz webhose.io servis.
U ovom ćemo predavanju analizirati temu COVID-19 pandemije na osnovi članka objavljenih na tri domaća internetska portala (nethr, index, tportal) od prvog službenog slučaja zaraze u RH (2020-26-02) do danas (2020-15-12). Analiza nema specifičan cilj već je namjera demonstrirati mogućnosti i tehnike analize teksta u R na aktualnoj temi. Članci su preuzeti web-scraping tehnikama, a identifikacija COVID-19 povezanih članka je napravljena ukoliko se u tekstu spominje riječ “korona”. Na taj su način sa tri navedena portala prikupljena 2.623 članka. Zbog jednostavnosti su analizirani naslovi pripadajućih tekstova. Analiza teksta koju ćemo provesti uključuje: čišćenje, uređivanje i prilagodbu podataka, dekriptivnu statistiku na tekstualnim podatcima, analizu sentimenta, analizu frekvencija i tematsku analizu.
Analiza teksta kao metodološki pristup je korištena u uistinu širokom spektru područja, a sveobuhvatni pregled bi zahtijevao posebno predavanje, odnosno čitav kolegij.
Podatci za analizu su prikupljeni na prethodno opisan način i dostupni u GitHub repozitoriju kolegija (Dta folder;korona.csv file). Podataci uključuju i članke sa nekih drugih portala, ali u kraćem vremenskom rasponu pa su izostavljeni iz analize. Učitajmo podatke:
<- read.csv2("D:/LUKA/Academic/HS/NASTAVA/20-21/Obrada podataka/Dta/korona.csv") #, encoding="UTF-8"
covid str(covid)
## 'data.frame': 5927 obs. of 15 variables:
## $ X : int 1 2 3 4 5 6 7 8 9 10 ...
## $ naziv : chr "c7d7cdc7e099f0021ecbfddf2b4a26d2" "3d6ff1f086635ff61029666e64771306" "2f9a3680ad575c00fe74e0a56d95d780" "01b71372992144d559a2710c039a84d5" ...
## $ id : chr "20201207" "680433" "2020-11-14-11" "20201204" ...
## $ naslov : chr "Koliko nas je koštala korona? S porastom broja oboljelih, vrtoglavo rastu i troškovi vezani uz Covid-19" "Konferencija o korona virusu odgođena zbog korona virusa..." "OVAJ OTOK VIŠE NIJE KORONA-FREE, MJEŠTANI LJUTI NA STOŽER: ‘Živjeli smo ljetni san dok se korona širila… Otočka"| __truncated__ "Bačić objasnio zašto je Franković mogao glasati videovezom iako je korona negativan" ...
## $ datum : chr "2020-12-07" "2020-03-11" "2020-11-13" "2020-12-04" ...
## $ vrijeme : chr "9:45 AM" "9:34 AM" "10:37 PM" "4:21 PM" ...
## $ pogledi : int 0 8 0 0 0 0 0 0 0 30 ...
## $ label : chr NA "https://www.24sata.hr/news/konferencija-o-korona-virusu-odgodena-zbog-korona-virusa-680433/komentari" NA NA ...
## $ brojKomentara: int 0 1187 0 0 0 0 0 0 0 11520 ...
## $ linkKomentari: chr "nagrižen zdravstveni budžet" "amerika\nsad\nkonferencija\ndonald trump\nvirus\nburza" "VIRUS HARA" "nije sporno" ...
## $ autor : chr "I. Ba./Hina" "HINA," "Autor: Danas.hr" "I. Ba./Hina" ...
## $ domena : chr "tportal" "24sata" "nethr" "tportal" ...
## $ poveznica : chr "https://www.tportal.hr/vijesti/clanak/koliko-nas-je-kostala-korona-s-porastom-broja-oboljelih-vrtoglavo-rastu-i"| __truncated__ "https://www.24sata.hr/news/konferencija-o-korona-virusu-odgodena-zbog-korona-virusa-680433" "https://net.hr/danas/hrvatska/ovaj-otok-vise-nije-korona-free-mjestani-ljuti-na-stozer-zivjeli-smo-ljetni-san-d"| __truncated__ "https://www.tportal.hr/vijesti/clanak/bacic-objasnio-zasto-je-frankovic-mogao-glasati-videovezom-iako-je-corona"| __truncated__ ...
## $ kandidat : chr "korona" "korona" "korona" "korona" ...
## $ txtVector : chr "U samo tjedan dana troškovi zdravstvenog sustava vezani uz epidemiju koronavirusom narasli su za vrtoglavih 80,"| __truncated__ "U svijetu je od korona virusa zaraženo više od sto tisuća ljudi. Italija je proglasila epidemiju, cijela država"| __truncated__ "Pojedini hrvatski otoci, poput Lastova, bili su imuni na koronu. No, virus se počeo širiti otocima pa je tako n"| __truncated__ "\"Imali smo sličan slučaj s Marijom Kapulicom kojemu je također određena pasivna samoizolacija i preporučeno da"| __truncated__ ...
Nakon što smo učitali podatke u radni prostor R, potrebno je učitati i druge podatke koji su nam potrebni za ovu analizu. Osim članaka, potrebni su nam leksikoni i stop riječi. Leksikone ćemo preuzeti iz FER-ovog repozitorija, a “stop riječi” ćemo napraviti sami.
# UČITAJ LEKSIKONE
<- read.delim("C:/Users/Lukas/Dropbox/Mislav@Luka/crosentilex-negatives.txt",
CroSentilex_n header = FALSE,
sep = " ",
stringsAsFactors = FALSE,
fileEncoding = "UTF-8") %>%
rename(word = "V1", sentiment = "V2" ) %>%
mutate(brija = "NEG")
<- read.delim("C:/Users/Lukas/Dropbox/Mislav@Luka/crosentilex-positives.txt",
CroSentilex_p header = FALSE,
sep = " ",
stringsAsFactors = FALSE,
fileEncoding = "UTF-8") %>%
rename(word = "V1", sentiment = "V2" ) %>%
mutate(brija = "POZ")
<- rbind(setDT(CroSentilex_n), setDT(CroSentilex_p))
Crosentilex_sve
<- read.delim2("C:/Users/Lukas/Dropbox/Mislav@Luka/gs-sentiment-annotations.txt",
CroSentilex_Gold header = FALSE,
sep = " ",
stringsAsFactors = FALSE) %>%
rename(word = "V1", sentiment = "V2" )
Encoding(CroSentilex_Gold$word) <- "UTF-8"
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))
CroSentilex_Gold
# STVORI "STOP RIJEČI"
<- get_stopwords(language = "hr", source = "stopwords-iso")
stopwords_cro <- tibble(
my_stop_words word = c(
"jedan","mjera", "može", "mogu", "kad", "sada", "treba", "ima", "osoba",
"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"," aee", "ae"
),lexicon = "lux"
)<- my_stop_words %>%
stop_corpus bind_rows(stopwords_cro)
U sljedećem koraku ćemo prilagoditi podatke u tidy format koji je prikladan za analizu. Pri tome pretvaramo podatke u dataframe
, izabiremo varijable za analizu, specificiramo vremenski pečat članka kao datumsku varijablu, pripisujemo id svakom članku, izabiremo vremenski raspon analize i portale:
# prilagodi podatke
<- covid %>%
newsCOVID as.data.frame() %>%
select(naslov, datum, domena) %>%
mutate(datum = as.Date(datum,"%Y-%m-%d")) %>%
mutate(clanak = 1:n()) %>%
filter(datum > "2020-02-25") %>%
filter(domena %in% c("nethr", "tportal", "index"))
# brzi pregled strukture podataka
glimpse(newsCOVID)
## Rows: 2,623
## Columns: 4
## $ naslov <chr> "Koliko nas je koštala korona? S porastom broja oboljelih, vrto~
## $ datum <date> 2020-12-07, 2020-11-13, 2020-12-04, 2020-12-08, 2020-12-08, 20~
## $ domena <chr> "tportal", "nethr", "tportal", "tportal", "tportal", "tportal",~
## $ clanak <int> 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,~
# izgled podataka
%>%
newsCOVID sample_n(.,10)
## naslov
## 1 Vozači kamiona koji su bili na sjeveru Italije ne žele u karantenu
## 2 NEKI SU CIJENE SNIZILI ZA POLA, A NEKI IH DRŽE KAO DA SE NIŠTA NE DOGAĐA: Novinar analizirao našu glavnu konkurenciju – dosta su jeftiniji
## 3 Hrvatska odmah iza Švedske po broju pozitivnih na ukupan broj covid-19 testova
## 4 ANALITIČAR UPOZORAVA: ‘Privatni sektor će smanjiti zaposlenost i plaće, ali državni nastavlja trošiti kao da se ništa ne događa’
## 5 Unatoč koronavirusu i dalje je duhovita: Holivudska glumica i supruga Toma Hanksa putem Twittera zatražila pomoć
## 6 U Bruxellesu se lome koplja oko fonda za obnovu EU-a. Ima puno nepoznanica
## 7 Dvostruka olimpijska pobjednica: Korona je Božja kazna jer ste vrijeđali moju zemlju
## 8 Učenici nisu mogli vjerovati što je profesorica napravila tijekom nastave online
## 9 IMAJU LI SMISLA TESTOVI NA PRISUTNOST ANTITIJELA KORONAVIRUSA? Točnost im je 99 posto, ali ni to nije dovoljno…
## 10 JE LI KORONA UBRZALA ODUMIRANJE ‘ŠUŠKAVIH’ NOVČANICA? Dok ljudi sve više plaćaju karticama, neki poručuju: ‘To je sve urota!’
## datum domena clanak
## 1 2020-03-03 index 2630
## 2 2020-07-01 nethr 3638
## 3 2020-07-17 index 1208
## 4 2020-08-21 nethr 557
## 5 2020-03-14 tportal 5558
## 6 2020-05-21 tportal 4319
## 7 2020-03-28 index 820
## 8 2020-03-20 index 2311
## 9 2020-06-06 nethr 3846
## 10 2020-06-29 nethr 621
#DT::datatable(newsCOVID)
U sljedećem koraku provodimo tokenizaciju, odnosno pretvaranje teksta na jedinice analize. U ovom slučaju su to riječi:
# tokenizacija
%>%
newsCOVID unnest_tokens(word, naslov) -> newsCOVID_token
#newsCOVID_token$word <- stri_encode(newsCOVID_token$word, "", "UTF-8") # prilagodi encoding
%>%
newsCOVID_token sample_n(.,10)
## datum domena clanak word
## 1 2020-11-03 nethr 2849 komentirao
## 2 2020-08-24 nethr 3207 komentirao
## 3 2020-09-29 tportal 3009 skulirao
## 4 2020-08-17 nethr 565 napraviti
## 5 2020-11-25 nethr 221 itekako
## 6 2020-06-09 index 3861 prvi
## 7 2020-08-28 nethr 3180 je
## 8 2020-11-14 nethr 432 prvoligaša
## 9 2020-03-12 index 5142 otkrio
## 10 2020-10-11 nethr 2969 bombardiraju
Potom valja očistiti riječi od brojeva i nepotrebnih riječi. Na tako uređenim podatcima ćemo napraviti deskriptivno- statistički pregled teksta.
## 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)
## datum domena clanak word
## 1 2020-07-15 index 3518 zemalja
## 2 2020-07-18 index 3499 jednoj
## 3 2020-07-21 index 3451 njemaeki
## 4 2020-10-17 nethr 2943 pola
## 5 2020-07-31 nethr 1165 lokaciju
## 6 2020-05-26 nethr 696 gube
## 7 2020-05-25 nethr 4207 ipak
## 8 2020-07-13 nethr 609 brzo
## 9 2020-08-13 index 3306 novi
## 10 2020-06-23 index 1293 masku
Na tako uređenim podatcima ćemo napraviti deskriptivno-statistički pregled teksta:
# DESKRIPTIVNI PREGLED PODATAKA
## Vremenski raspon analize
range(newsCOVID_token$datum)
## [1] "2020-02-26" "2020-12-08"
## Najčešće riječi
%>%
newsCOVID_tokenTidy count(word, sort = T) %>%
head(25)
## word n
## 1 korona 419
## 2 ae 247
## 3 ljudi 115
## 4 imamo 109
## 5 koronavirusa 108
## 6 novozaraženih 102
## 7 koronavirus 89
## 8 korone 83
## 9 mjere 82
## 10 hrvatska 81
## 11 novi 77
## 12 novih 72
## 13 zaraženih 68
## 14 nove 65
## 15 stožer 63
## 16 zašto 63
## 17 otkrio 58
## 18 protiv 57
## 19 video 57
## 20 dana 56
## 21 danas 53
## 22 zagrebu 53
## 23 nova 52
## 24 slueajeva 52
## 25 zaraze 52
## Vizualizacija najčešćih riječi
%>%
newsCOVID_tokenTidy count(word, sort = T) %>%
filter(n > 50) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
xlab(NULL) +
coord_flip() +
theme_economist()
## Vizualizacija najčešćih riječi kroz vrijeme
%>%
newsCOVID_tokenTidy mutate(Datum = floor_date(datum, "day")) %>%
group_by(Datum) %>%
count(word) %>%
mutate(gn = sum(n)) %>%
filter(word %in% c("virus", "mjere", "korona", "stožer", "beroš", "maske")) %>%
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())+
theme_economist()
Također je moguće napraviti i deskriptivno-statistički pregled domena:
# DESKRIPTIVNI PREGLED DOMENA
## Broj domena
%>%
newsCOVID_tokenTidy summarise(Domena = n_distinct(domena))
## Domena
## 1 3
## Broj članaka po domeni
%>%
newsCOVID drop_na(.) %>%
group_by(domena) %>%
summarise(n = n()) %>%
arrange(desc(n)) %>%
head(20)
## # A tibble: 3 x 2
## domena n
## <chr> <int>
## 1 nethr 1170
## 2 index 1002
## 3 tportal 451
## Broj članaka po domeni kroz vrijeme
%>%
newsCOVID mutate(Datum = floor_date(datum, "week")) %>%
group_by(Datum, domena) %>%
summarise(n = n()) %>%
ungroup() %>%
ggplot(., aes(Datum, n)) +
geom_line() +
ggtitle("Članci o pandemiji COVID-19 na najvažnijim RH portalima") +
ylab("Broj objavljenih COVID članaka") +
geom_smooth() +
facet_wrap(~ domena, scales = "free_y") +
theme_economist()
Nakon uređivanja podataka i osnovnog pregleda najvažnijih riječi, dinamike kretanja članaka kroz vrijeme i pregleda deskriptivne statistike domena ćemo provesti analizu sentimenta. Za analizu sentimenta je potrebno preuzeti leksikone sentimenta koji su za hrvatski jezik dostupni kroz FER-ov Croatian Sentiment Lexicon. Analiza sentimenta i uključuje sentiment kroz vrijeme, doprinos riječi sentimentu, ‘wordCloud’ i analizu negativnosti portala.
Pogledajmo prvo kako izgledaju leksikoni (koje smo učitali na početku):
## Pregled leksikona
%>% sample_n(10) CroSentilex_n
## word sentiment brija
## 1: zatrpavanje 0.28143 NEG
## 2: muslimanski 0.52062 NEG
## 3: lager 0.16996 NEG
## 4: nacionalnost 0.55685 NEG
## 5: pogodovanje 0.33142 NEG
## 6: prestar 0.33501 NEG
## 7: četrdesetogodišnjak 0.25620 NEG
## 8: stupno 0.16414 NEG
## 9: predosjećati 0.31410 NEG
## 10: dijagonalan 0.27887 NEG
%>% sample_n(10) CroSentilex_p
## word sentiment brija
## 1: georgio 0.332670 POZ
## 2: neodobravanje 0.128540 POZ
## 3: molunat 0.051524 POZ
## 4: poklon 0.565020 POZ
## 5: monitoring 0.357950 POZ
## 6: burrell 0.142170 POZ
## 7: ilijašević 0.137140 POZ
## 8: zimbabveanski 0.099059 POZ
## 9: miošić 0.183830 POZ
## 10: larga 0.054864 POZ
%>% sample_n(10) Crosentilex_sve
## word sentiment brija
## 1: angloamerički 0.45456 NEG
## 2: čitač 0.30338 NEG
## 3: angažman 0.43367 NEG
## 4: isplatititi 0.41216 POZ
## 5: nazdraviti 0.14007 NEG
## 6: poklanjati 0.51084 NEG
## 7: sršen 0.35366 POZ
## 8: alon 0.29173 POZ
## 9: dor 0.44062 NEG
## 10: uskrs 0.57030 POZ
%>% sample_n(10) CroSentilex_Gold
## word sentiment
## 1 ambicija 2
## 2 vozilo 0
## 3 društven 2
## 4 čitav 0
## 5 posvetiti 0
## 6 krajnji 0
## 7 raniti 1
## 8 sporan 1
## 9 rekordan 0
## 10 kupac 0
Provjerimo kretanje sentimenta u vremenu:
## Kretanje sentimenta kroz vrijeme
<- function(dataset, frq = "week") {
vizualiziraj_sentiment
%>%
dataset inner_join( Crosentilex_sve, by = "word") %>%
filter(!is.na(word)) %>%
select(word, brija, datum, sentiment) %>%
unique() %>%
spread(. , brija, sentiment) %>%
mutate(sentiment = POZ - NEG) %>%
select(word, datum, sentiment) %>%
group_by(word) %>%
mutate(count = n()) %>%
arrange(desc(count)) %>%
mutate( score = sentiment*count) %>%
ungroup() %>%
group_by(datum) %>%
arrange(desc(datum)) -> sm
%>%
sm select(datum, score) %>%
group_by(Datum = floor_date(datum, 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") +
theme_economist()-> gg_sentiment_kroz_vrijeme_qv
gg_sentiment_kroz_vrijeme_qv
}
vizualiziraj_sentiment(newsCOVID_tokenTidy,"week")
Korisno je i promotriti koje riječi najviše doprinose sentimentu (pozitivnom, negativnom i neutralnom):
## Doprinos sentimentu
<- function(dataset, no = n) {
doprinos_sentimentu %>%
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",
== 1 ~ "NEGATIVNO",
sentiment == 2 ~ "POZITIVNO")) %>%
sentiment 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() +
theme_economist() -> gg_doprinos_sentimentu
gg_doprinos_sentimentu
}
doprinos_sentimentu(newsCOVID_tokenTidy,15)
Korisno je pogledati i WordCloud sentiment. Pogledajmo “obični” WordCloud prije toga:
## 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))
Ovako izgleda WordCloud koji sadržava i prikaz sentimenta:
## ComparisonCloud
%>%
newsCOVID_tokenTidy inner_join(CroSentilex_Gold,by="word") %>%
count(word, sentiment) %>%
top_n(200) %>%
mutate(sentiment = case_when(sentiment == 0 ~ "+/-",
== 1 ~ "-",
sentiment == 2 ~ "+")) %>%
sentiment acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("firebrick3", "deepskyblue3","darkslategray"),
max.words = 120)
Analiza sentimenta se može iskoristiti za pregled negativnosti pojedinih portala:
## Najnegativniji portali
<- newsCOVID_tokenTidy %>%
wCount group_by(domena) %>%
summarise(word = n())
<- CroSentilex_Gold %>% filter(sentiment == 1)
CroSentilex_Gold_neg <- CroSentilex_Gold %>% filter(sentiment == 2)
CroSentilex_Gold_poz
%>%
newsCOVID_tokenTidy semi_join(CroSentilex_Gold_neg, by= "word") %>%
group_by(domena) %>%
summarise(negWords = n()) %>%
left_join(wCount, by = "domena") %>%
mutate(negativnostIndex = (negWords/word)*100) %>%
arrange(desc(negativnostIndex))
## # A tibble: 3 x 4
## domena negWords word negativnostIndex
## <chr> <int> <int> <dbl>
## 1 nethr 226 13581 1.66
## 2 tportal 62 4674 1.33
## 3 index 99 7660 1.29
…također i pozitivnosti portala:
## Najpozitivniji portali
<- CroSentilex_Gold %>% filter(sentiment == 2)
CroSentilex_Gold_poz
%>%
newsCOVID_tokenTidy semi_join(CroSentilex_Gold_poz, by= "word") %>%
group_by(domena) %>%
summarise(pozWords = n()) %>%
left_join(wCount, by = "domena") %>%
mutate(pozitivnostIndex = (pozWords/word)*100) %>%
arrange(desc(pozitivnostIndex))
## # A tibble: 3 x 4
## domena pozWords word pozitivnostIndex
## <chr> <int> <int> <dbl>
## 1 index 138 7660 1.80
## 2 tportal 81 4674 1.73
## 3 nethr 204 13581 1.50
Nakon analize sentimenta je korisno analizirati i najbitnije riječi. To se radi pomoću IDF (inverse document frequency) metode. IDF metoda omogućuje identifikaciju važnih (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
<- newsCOVID %>%
domenaWords unnest_tokens(word,naslov) %>%
count(domena, word, sort = T)
<- domenaWords %>%
ukupnoWords group_by(domena) %>%
summarise(totWords = sum(n))
<- left_join(domenaWords, ukupnoWords)
domenaWords
# domenaWords %>% head(15)
# domenaWords %>%
# ggplot(., aes(n/totWords, fill = domena)) +
# geom_histogram(show.legend = FALSE) +
# xlim(NA, 0.0009) +
# facet_wrap(~domena, ncol = 2, scales = "free_y")
## Najbitnije riječi po domenma
<- domenaWords %>%
idf bind_tf_idf(word, domena, n)
%>% head(10) idf
## domena word n totWords tf idf tf_idf
## 1 nethr u 647 21015 0.030787533 0 0
## 2 nethr je 580 21015 0.027599334 0 0
## 3 nethr i 527 21015 0.025077326 0 0
## 4 index u 469 11447 0.040971434 0 0
## 5 nethr se 393 21015 0.018700928 0 0
## 6 nethr na 380 21015 0.018082322 0 0
## 7 index je 311 11447 0.027168690 0 0
## 8 tportal u 300 7253 0.041362195 0 0
## 9 index se 205 11447 0.017908622 0 0
## 10 nethr su 199 21015 0.009469427 0 0
# idf %>%
# select(-totWords) %>%
# arrange(desc(tf_idf))
%>%
idf arrange(desc(tf_idf)) %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>%
mutate(domena = factor(domena)) %>%
group_by(domena) %>%
top_n(10) %>%
ungroup() %>%
ggplot(aes(word, tf_idf, fill = domena)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
facet_wrap(~domena, ncol = 2, scales = "free") +
coord_flip() +
theme_economist()
Do sada smo analizirali tekst tako da je tekst tokeniziran na jednu riječ. To može prikriti bitne nalaze do kojih je moguće doći kada 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 %>%
newsCOVID_bigram unnest_tokens(bigram, naslov, token = "ngrams", n = 2)
%>% head(10) newsCOVID_bigram
## datum domena clanak bigram
## 1 2020-12-07 tportal 1 koliko nas
## 2 2020-12-07 tportal 1 nas je
## 3 2020-12-07 tportal 1 je koštala
## 4 2020-12-07 tportal 1 koštala korona
## 5 2020-12-07 tportal 1 korona s
## 6 2020-12-07 tportal 1 s porastom
## 7 2020-12-07 tportal 1 porastom broja
## 8 2020-12-07 tportal 1 broja oboljelih
## 9 2020-12-07 tportal 1 oboljelih vrtoglavo
## 10 2020-12-07 tportal 1 vrtoglavo rastu
%>%
newsCOVID_bigram count(bigram, sort = T) %>%
head(15)
## bigram n
## 1 u hrvatskoj 110
## 2 da je 82
## 3 u zagrebu 52
## 4 da se 46
## 5 ae se 45
## 6 novih slueajeva 45
## 7 to je 45
## 8 što je 44
## 9 ovo je 38
## 10 je u 32
## 11 koji je 30
## 12 ae biti 29
## 13 je korona 29
## 14 u srbiji 29
## 15 na koronavirus 28
<- newsCOVID_bigram %>%
newsCOVID_bigram_sep separate(bigram, c("word1","word2"), sep = " ")
<- newsCOVID_bigram_sep %>%
newsCOVID_bigram_tidy 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 %>%
newsCOVID_bigram_tidy_bigram_counts count(word1, word2, sort = TRUE)
#newsCOVID_bigram_tidy_bigram_counts
<- newsCOVID_bigram_tidy %>%
bigrams_united drop_na(.) %>%
unite(bigram, word1, word2, sep = " ")
#bigrams_united
%>%
bigrams_united count(clanak,bigram,sort = T) -> topicBigram
# Najvažniji bigrami po domenama
<- bigrams_united %>%
bigram_tf_idf count(domena, bigram) %>%
bind_tf_idf(bigram, domena, n) %>%
arrange(desc(tf_idf))
%>%
bigram_tf_idf arrange(desc(tf_idf)) %>%
mutate(bigram = factor(bigram, levels = rev(unique(bigram)))) %>%
group_by(domena) %>%
top_n(7) %>%
ungroup() %>%
ggplot(aes(bigram, tf_idf, fill = domena)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
facet_wrap(~domena, ncol = 2, scales = "free") +
coord_flip() +
theme_economist()
# Analiza bigramskih fraza
%>%
newsCOVID_bigram_tidy filter(word1 == "covid") %>%
count(word1,word2,sort=T)
# Vizualiziraj bigrame
<- newsCOVID_bigram_tidy_bigram_counts %>%
bigram_graph filter(n >50) %>%
graph_from_data_frame()
<- grid::arrow(type = "closed", length = unit(.15, "inches"))
a
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()
Provjerimo koje su riječi najviše korelirane sa izabranim ključnim riječima:
# Korelacije riječi ( R crash na T=30)
#newsCOVID_tokenTidy %>%
# filter(published == "2020-04-22") %>%
# pairwise_count(word, domena, sort = T) %>%
# filter_all(any_vars(!is.na(.))) -> pairsWords
%>%
newsCOVID_tokenTidy # filter(datum > "2020-02-20") %>%
group_by(word) %>%
filter(n() > 20) %>%
filter(!is.na(word)) %>%
pairwise_cor(word,datum, sort = T) -> corsWords
#corsWords %>%
# filter(item1 == "oporavak")
%>%
corsWords filter(item1 %in% c("stožer", "beroš", "mjere", "maske")) %>%
group_by(item1) %>%
top_n(10) %>%
ungroup() %>%
mutate(item2 = reorder(item2, correlation)) %>%
ggplot(aes(item2, correlation)) +
geom_bar(stat = "identity") +
facet_wrap(~ item1, scales = "free") +
coord_flip() +
theme_economist()
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 tokenizirane 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
<- LDA(dtm, k = 4, control = list(seed = 1234))
newsCOVID_LDA
<- tidy(newsCOVID_LDA, matrix = "beta")
newsCOVID_LDA_tidy #newsCOVID_LDA_tidy
<- newsCOVID_LDA_tidy %>%
newsCOVID_terms drop_na(.) %>%
group_by(topic) %>%
top_n(15, 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()
Tematsku analizu je moguće i napraviti na bigramski tokeniziranom tekstu. Tada je često moguće doći do preciznijih i kontekstualno relevantnijih uvida:
# Bigrami
%>%
topicBigram cast_dtm(clanak, bigram,n) -> dtmB
<- LDA(dtmB, k = 4, control = list(seed = 1234))
newsCOVID_LDA
<- tidy(newsCOVID_LDA, matrix = "beta")
newsCOVID_LDA_tidy #newsCOVID_LDA_tidy
<- newsCOVID_LDA_tidy %>%
newsCOVID_terms 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()
U ovom smo predavanju dali uvodni pregled mogućnosti analize teksta u okviru tidytext
paketa. Riječ je o skupu alata koji omogućavaju “prilagodbu” teksta u tidy format i daljnu analizu s tidyverse
alatima koje smo do sada već dobro upoznali. tidytext
nije jedini dostupan okvir za analizu teksta u R, već postoji i niz drugih paketa (vidi na početku) koji omogućavaju korištenje naprednijih (algoritamkskih tehnika.
U predavanju su korišteni tekstovi objavljeni na tri domaća portala o temi COVID-19 u razdoblju od prvog zabilježenog slučaja u RH do danas. Analiza je pokazala mogućnosti tekstualne analize te osnovnih tehnika i alata na aktualnom primjeru.
Analiza teksta je trenutno (brzo) rastuće istraživačko područje sa sve većim brojem primjena, novih metodoloških pristupa i perspektiva. Dostupno je mnoštvo kvalitetnih i korisnih resursa pa se zainteresiranim studentima preporuča uključivanje u ovu (vrlo perspektivnu) istraživačku paradigmu.