Ishodi učenja:
Primijeniti leksikonski sentiment i izračunati agregatne metrike.
Uočiti probleme u podacima i ublažiti ih.
Koristiti rezultate sentiment analize kao atribute u prikazu mreža.
Interpretirati nalaze sentiment analize.
Analiza teksta i analiza sentimenata sastavni su dijelovi šireg interdisciplinarnog polja koje se naziva NLP (Natural Language Processing) ili obrada prirodnog jezika. NLP kombinira lingvistiku, statistiku i strojno učenje kako bi omogućio računalima da ‘razumiju’ ljudski jezik. Dok se tradicionalna statistika bavi brojevima, NLP pretvara rečenice u matematičke objekte (vektore) koje potom možemo analizirati modelima koje smo učili u prethodnim lekcijama.
Analiza sentimenata predstavlja postupak procjene emocionalne ili evaluativne orijentacije teksta. Cilj analize je utvrditi izražava li tekst pozitivan, negativan ili neutralan stav prema nekoj temi, događaju ili objektu. U tom smislu govori se o sentimentu kao općoj emocionalnoj procjeni teksta, dok se polaritet odnosi na smjer tog sentimenta, odnosno na to je li on pozitivan ili negativan (ono što se u psihologiji naziva hedonistički ton).
U najjednostavnijem obliku analiza sentimenata razlikuje tri osnovne kategorije: pozitivan sentiment, negativan sentiment i neutralan sentiment. Pozitivan sentiment označava tekst u kojem prevladavaju pozitivni izrazi, evaluacije ili emocije. Negativan sentiment označava tekst s prevladavajućim negativnim izrazima ili kritikama. Neutralan sentiment označava tekst koji ne sadrži jasnu evaluaciju, nego se primarno sastoji od informativnih ili deskriptivnih tvrdnji.
Važno je razlikovati sentiment od pojma subjektivnosti. Subjektivnost označava mjeru u kojoj tekst izražava osobni stav, mišljenje ili procjenu, za razliku od objektivnog iznošenja činjenica. Tekst može biti subjektivan, ali bez jasnog polariteta. Primjerice, rečenica „Ovaj film je zanimljiv” izražava subjektivnu procjenu, ali nije jasno je li ona pozitivna ili negativna.
U proširenim modelima analize sentimenata ne promatra se samo polaritet, nego i emocionalne kategorije poput radosti, straha, ljutnje ili iznenađenja. U tom slučaju analiza prelazi iz binarne procjene polariteta prema detaljnijoj analizi emocionalnog sadržaja teksta.
Prije bilo kakve analize sentimenata potrebno je odrediti što je zapravo dokument koji analiziramo. Ovdje ćemo analizirati mali nastavni korpus sastavljen od About us opisa vodećih poduzeća u Hrvatskoj prema Lider Media popisu najvećih poduzeća od 12. ožujka 2026. (bitno je navesti datum, jer se stranica redovito ažurira). Početni popis je proširen na više od deset poduzeća kako bi nakon čišćenja i isključivanja problematičnih stranica ostalo približno deset upotrebljivih organizacijskih opisa.
Kod korporativnih web-stranica određivanje dokumenta nije uvijek trivijalno jer se odjeljak O nama često sastoji od više međusobno povezanih podstranica. Na primjer, INA unutar odjeljka About INA ima zasebne stranice za profil kompanije, povijest, core business, misiju i vrijednosti, etičko poslovanje i privatnost, a HEP unutar O HEP grupi ima zasebne stranice za misiju, strateške ciljeve, povijest, publikacije i upravljačku strukturu. Slično tome, Lidl i Zagrebačka banka imaju više podstranica unutar korporativnog predstavljanja, dok Petrol odvaja korporativno predstavljanje od stranice o ekologiji i društvu.
Zbog toga je prvi korak ručno upoznavanje sa sadržajem. To znači da ne treba odmah “pokupiti sve”, nego pregledati koje stranice doista čine organizacijski samopis, a koje su pravne, tehničke ili administrativne naravi. Za potrebe ove lekcije razumno je uključiti stranice koje opisuju identitet organizacije, djelatnosti, misiju, vrijednosti, povijest i strateško usmjerenje, a isključiti stranice poput politike privatnosti, certificiranja, publikacija, detaljne upravljačke strukture i sličnih sadržaja koji nisu primarno namijenjeni samopredstavljanju.
Drugim riječima, prije automatizacije radimo analitičku selekciju izvora. Kvaliteta analize teksta u velikoj mjeri ovisi o tome što je uključeno u korpus, a ne samo o tome kako se tekst poslije obrađuje.
U sljedećem koraku izrađujemo tablicu svih kandidatskih URL-ova i ručno označavamo treba li ih uključiti u korpus. Ovdje ne pokušavamo biti “potpuno automatizirani”; upravo suprotno, demonstriramo da je ručna kuracija često nužna i metodološki opravdana.
U ovoj fazi preporučljivo je ručno otvoriti nekoliko URL-ova i odgovoriti na tri pitanja:
Ako je odgovor na prva dva pitanja da, a na treće ne, takvu stranicu obično vrijedi uključiti. Priprema podataka nije samo “čišćenje stringova”, nego i konceptualno određivanje jedinice analize.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.1.6
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rvest)
##
## Attaching package: 'rvest'
##
## The following object is masked from 'package:readr':
##
## guess_encoding
library(stringr)
library(purrr)
library(tidytext)
pages <- tribble(
~company, ~url, ~section, ~include,
"INA", "https://www.ina.hr/en/about-ina/profil-kompanije/", "profil kompanije", TRUE,
"INA", "https://www.ina.hr/en/about-ina/profil-kompanije/povijest/", "povijest", TRUE,
"INA", "https://www.ina.hr/en/about-ina/profil-kompanije/certificates/", "certifikati", FALSE,
"INA", "https://www.ina.hr/en/about-ina/core-business/", "core business", TRUE,
"INA", "https://www.ina.hr/en/about-ina/core-business/exploration-and-production/", "istrazivanje i proizvodnja", TRUE,
"INA", "https://www.ina.hr/en/about-ina/core-business/refining-and-marketing/", "prerada i marketing", TRUE,
"INA", "https://www.ina.hr/en/about-ina/core-business/consumer-services-retail/", "maloprodaja i usluge", TRUE,
"INA", "https://www.ina.hr/en/about-ina/mission-vision-and-core-values/", "misija vizija vrijednosti", TRUE,
"INA", "https://www.ina.hr/en/about-ina/ethical-business-and-reporting-irregularities/", "eticko poslovanje", TRUE,
"INA", "https://www.ina.hr/en/about-ina/privacy-policy/", "politika privatnosti", FALSE,
"HEP", "https://www.hep.hr/o-hep-grupi/25", "o hep grupi", TRUE,
"HEP", "https://www.hep.hr/o-hep-grupi/misija-vizija-i-temeljne-vrijednosti/37", "misija vizija vrijednosti", TRUE,
"HEP", "https://www.hep.hr/o-hep-grupi/strateski-ciljevi/51", "strateski ciljevi", TRUE,
"HEP", "https://www.hep.hr/o-hep-grupi/povijest/54", "povijest", TRUE,
"HEP", "https://www.hep.hr/drustva-hep-grupe/29", "drustva u grupi", FALSE,
"HEP", "https://www.hep.hr/o-hep-grupi/hep-d-d-upravljacka-struktura/53", "upravljacka struktura", FALSE,
"HEP", "https://www.hep.hr/o-hep-grupi/publikacije/55", "publikacije", FALSE,
"PPD", "https://www.ppd.hr/upoznajte-nas", "upoznajte nas", TRUE,
"KONZUM", "https://tvrtka.konzum.hr/", "korporativna stranica", TRUE,
"MET", "https://hr.met.com/en/about-us/about-our-company/", "about our company", TRUE,
"LIDL", "https://tvrtka.lidl.hr/o-nama", "o nama", TRUE,
"LIDL", "https://tvrtka.lidl.hr/o-nama/temeljna-nacela-tvrtke", "temeljna nacela", TRUE,
"LIDL", "https://tvrtka.lidl.hr/o-nama/compliance", "compliance", TRUE,
"LIDL", "https://tvrtka.lidl.hr/o-nama/povijest", "povijest", TRUE,
"PETROL", "https://www.petrol.eu/hr/petrol-d-o-o/predstavitev", "predstavljanje", TRUE,
"PETROL", "https://www.petrol.eu/hr/ekologija-i-drustvo", "ekologija i drustvo", TRUE,
"SPAR", "https://www.spar.hr/o-nama", "o nama", TRUE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama", "o nama", TRUE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama/pregled", "pregled", TRUE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama/misija-i-vrijednosti", "misija i vrijednosti", TRUE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama/povijest", "povijest", TRUE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama/nas-brend", "nas brend", TRUE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama/struktura", "struktura", FALSE,
"ZABA", "https://www.zaba.hr/home/o-nama/o-nama/doprinos-zajednici", "doprinos zajednici", TRUE,
"PLODINE", "https://www.plodine.hr/o-nama", "o nama", TRUE,
"HT", "https://www.hrvatskitelekom.hr/ht-grupa/o-nama/profil-grupe", "o nama", TRUE,
"KAUFLAND", "https://tvrtka.kaufland.hr/kaufland.html", "o nama", TRUE,
"KAUFLAND", "https://tvrtka.kaufland.hr/kaufland/nase-vrijednosti.html", "nase vrijednosti", TRUE,
"KAUFLAND", "https://tvrtka.kaufland.hr/kaufland/nagrade-priznanja.html", "nagrade i priznanja", TRUE,
"KAUFLAND", "https://tvrtka.kaufland.hr/kaufland/kronika.html", "povijest", TRUE,
"KAUFLAND", "https://tvrtka.kaufland.hr/kaufland/tu-smo-za-tebe.html", "ostalo", TRUE
)
Ova tablica je već važan dio istraživačkog procesa. Ona ne služi samo tehničkoj organizaciji URL-ova, nego i bilježi istraživačku odluku o tome što ulazi u korpus.
Nakon ručne selekcije dohvaćamo samo one stranice koje smo odlučili uključiti. Budući da se HTML strukture razlikuju među web-stranicama, najpraktičnije je primijeniti generičku funkciju koja pokušava izvući tekst iz tipičnih sadržajnih elemenata.
# Za ovaj dio koda korišten je Claude Sonnet 4.6, uz manje naknadne dorade
extract_page_text <- function(url) {
tryCatch({
response <- httr::GET(
url,
httr::add_headers(
`Accept-Language` = "hr,en;q=0.9",
`User-Agent` = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
)
# Dohvati raw bajtove i eksplicitno dekodiraj kao UTF-8
raw_bytes <- httr::content(response, as = "raw")
html_string <- rawToChar(raw_bytes)
Encoding(html_string) <- "UTF-8"
page <- xml2::read_html(html_string, encoding = "UTF-8")
nodes <- page |>
html_elements(
"main p, main li, article p, article li,
.content p, .content li,
.main-content p, .main-content li,
.page-content p, .page-content li,
.entry-content p, .entry-content li,
h1, h2, h3"
)
txt <- nodes |>
html_text2() |>
str_squish()
txt <- txt[!is.na(txt) & txt != ""] |>
str_c(collapse = " ")
if (is.na(txt) || txt == "") NA_character_ else txt
}, error = function(e) {
message("Greška za URL: ", url, " — ", conditionMessage(e))
NA_character_
})
}
Sada dohvaćamo tekst samo za uključene stranice.
raw_pages <- pages |>
filter(include) |>
mutate(
text_raw = map_chr(url, extract_page_text),
n_char = str_length(text_raw)
)
library(stringi)
raw_pages <- raw_pages |>
mutate(
text_raw = stri_enc_toutf8(text_raw)
)
glimpse(raw_pages)
## Rows: 35
## Columns: 7
## $ X <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18…
## $ company <chr> "INA", "INA", "INA", "INA", "INA", "INA", "INA", "INA", "HEP"…
## $ url <chr> "https://www.ina.hr/en/about-ina/profil-kompanije/", "https:/…
## $ section <chr> "profil kompanije", "povijest", "core business", "istrazivanj…
## $ include <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, T…
## $ text_raw <chr> "About INA Company profile INA Group Corporate governance Pol…
## $ n_char <int> 4453, 5730, 3900, 3407, 5399, 3995, 4285, 4392, 11, 72, 17, 3…
Dobro je odmah provjeriti jesu li sve stranice uspješno dohvaćene i
kolika je duljina dobivenog teksta (n_char).
library(DT)
datatable_preview(raw_pages, text_col = "text_raw", n = 100, page_len = 5)
Kvaliteta dohvaćenog teksta nije jednaka za sve stranice. To je
očekivano i upravo zato je sljedeći korak kontrola
kvalitete. Nakon scrapinga treba ručno pregledati barem
nekoliko zapisa. Cilj je provjeriti sadrži li text_raw
doista opis kompanije ili su u njemu ostali elementi navigacije,
izbornika i tehničkog sadržaja.
Ako neka stranica vraća previše šuma, postoje dvije mogućnosti. Prva je da se za tu domenu napiše precizniji CSS selektor. Druga je da se stranica isključi iz korpusa.
Na primjer, vidimo da redovi 1-8 svi počinju s istim navigacijskim tekstom “About INA Company profile…”. To je header/navigacija stranice. Provjerimo koji dio je jednak:
library(stringr)
texts <- raw_pages$text_raw[1:8]
# Usporedi znak po znak
min_len <- min(nchar(texts))
common <- substr(texts[1], 1, min_len)
for (txt in texts[-1]) {
while (!startsWith(txt, common)) {
common <- substr(common, 1, nchar(common) - 1)
}
}
common
## [1] "About INA Company profile INA Group Corporate governance Policies and documents Company information History Certificates INA Group Corporate governance Policies and documents Policies and documents Company information History Certificates Core business From production and processing to the sale of gas and petroleum products Exploration & Production Exploration Geothermal Energy and New Energy Field Development Drilling & Well Workover Production E&P Project Management & Permitting Exploration & Production Laboratory Refining & Marketing Development Logistics Fuels New and Sustainable Businesses Value Chain Management INA has completed the construction of key systems for the new refinery unit Additional information Internal acts Professional Training Centre INA, d.d. Consumer Services & Retail About The pure power of INA<U+2019>s fuels Grab a quick bite or a drink at the Fresh Corner A wide range of services Save money with INA Loyalty From production and processing to the sale of gas and petroleum products Exploration & Production Exploration Geothermal Energy and New Energy Field Development Drilling & Well Workover Production E&P Project Management & Permitting Exploration & Production Laboratory Exploration Geothermal Energy and New Energy Field Development Drilling & Well Workover Production E&P Project Management & Permitting Exploration & Production Laboratory Refining & Marketing Development Logistics Fuels New and Sustainable Businesses Value Chain Management INA has completed the construction of key systems for the new refinery unit Additional information Internal acts Professional Training Centre INA, d.d. Development Logistics Fuels New and Sustainable Businesses Value Chain Management INA has completed the construction of key systems for the new refinery unit Additional information Internal acts Professional Training Centre INA, d.d. Internal acts Professional Training Centre INA, d.d. Consumer Services & Retail About The pure power of INA<U+2019>s fuels Grab a quick bite or a drink at the Fresh Corner A wide range of services Save money with INA Loyalty About The pure power of INA<U+2019>s fuels Grab a quick bite or a drink at the Fresh Corner A wide range of services Save money with INA Loyalty Mission, vision and values Ethical business and reporting irregularities Privacy policy VIDEO SURVEILLANCE POLICY AT INA GROUP SITES MANAGED BY INA, d.d. Access Control Policy at INA Group locations managed by INA d.d. Archive of modifications and completions Cookie Policy VIDEO SURVEILLANCE POLICY AT INA GROUP SITES MANAGED BY INA, d.d. Access Control Policy at INA Group locations managed by INA d.d. Archive of modifications and completions Cookie Policy News "
Provjerimo prvo preklapaju li se opisi u potpunosti, pa možda možemo jednostavno ukloniti retke 2 - 8.
length(unique(raw_pages$text_raw[1:8])) == 1
## [1] FALSE
Nisu u potpunosti jednaki. Pristupamo uklanjanju teksta koji se ponavlja.
raw_pages$text_raw[2:8] <- gsub(common, "", raw_pages$text_raw[2:8], fixed = TRUE)
# ponovimo uvid
substring(raw_pages$text_raw[1:8], 1, 100) # samo prvih 100 znakova za svaki dokument
## [1] "About INA Company profile INA Group Corporate governance Policies and documents Company information "
## [2] "History INA was founded on January 1, 1964 through the merger of Naftaplin Zagreb, the Rijeka Oil Re"
## [3] "Core business Core business of INA, d.d. and its subsidiaries The principal activities of INA and it"
## [4] "Exploration & Production INA Group Exploration & Production has more than 70 years of experience and"
## [5] "Refining & Marketing Refining and Marketing is in charge of the refining operations in Rijeka, activ"
## [6] "Consumer Services & Retail Consumer Services and Retail operate a modernized regional network of mor"
## [7] "Mission, vision and values INA is a modern, socially responsible and transparent company in constant"
## [8] "Ethical business and reporting irregularities INA Group Code of Ethics INA Group Code of Ethics (CoE"
Ponavljamo postupak za retke [16:19].
library(stringr)
texts <- raw_pages$text_raw[16:19]
# Usporedi znak po znak
min_len <- min(nchar(texts))
common <- substr(texts[1], 1, min_len)
for (txt in texts[-1]) {
while (!startsWith(txt, common)) {
common <- substr(common, 1, nchar(common) - 1)
}
}
common
## [1] "Lidl.hr Karijera Nekretnine O nama Kvaliteta dostupna svima Odr<U+017E>ivost u Lidlu Press centar Kontakt O nama Pregled Povijest tvrtke Temeljna na<U+010D>ela tvrtke Compliance Odr<U+017E>ivost u Lidlu Pregled Dobro za planet Dobro za tebe Na<U+010D>ela kompanije Slu<U+017E>bena stajali<U+0161>ta Izvje<U+0161>taji o odr<U+017E>ivosti Ostale publikacije WWF partnerstvo Dobro za ljude Dobro za planet Pregled Za<U+0161>tita klime Po<U+0161>tovanje bioraznolikosti O<U+010D>uvanje resursa Na<U+010D>ela kompanije Pregled Kodeks pona<U+0161>anja za poslovne partnere Ostale publikacije Pregled Transparentnost u lancu opskrbe neprehrambenim proizvodima UN Global Compact Dobro za ljude Pregled Vo<U+0111>enje dijaloga Po<U+0161>teno postupanje Osvije<U+0161>tena prehrana Promicanje zdravlja O nama "
raw_pages$text_raw[17:19] <- gsub(common, "", raw_pages$text_raw[17:19], fixed = TRUE)
# ponovimo uvid
substring(raw_pages$text_raw[16:19], 1, 100) # samo prvih 100 znakova za svaki dokument
## [1] "Lidl.hr Karijera Nekretnine O nama Kvaliteta dostupna svima Odr<U+017E>ivost u Lidlu Press centar Ko"
## [2] "Temeljna na<U+010D>ela tvrtke Primjetili smo da Java Script nije aktiviran. Kako biste nesmetano mog"
## [3] "Compliance Primjetili smo da Java Script nije aktiviran. Kako biste nesmetano mogli koristiti na<U+0"
## [4] "Povijest tvrtke Primjetili smo da Java Script nije aktiviran. Kako biste nesmetano mogli koristiti n"
Uočavamo simpatičan komentar “NE stavljamo link za zadnji breadcrumb O nama”. Uklonimo ga.
raw_pages$text_raw <- gsub("NE stavljamo link za zadnji breadcrumb O nama", "", raw_pages$text_raw, fixed = TRUE)
substring(raw_pages$text_raw[23:28], 1, 100) # samo prvih 500 znakova za svaki dokument
## [1] "O nama Banka Pregled Tko smo, kako i za<U+0161>to smo tu ve<U+0107> vi<U+0161>e od 100 godina Misij"
## [2] "Pregled O nama Profil kompanije Financijski podaci Zagreba<U+010D>ka banka vode<U+0107>a je banka u"
## [3] "Misija i vrijednosti O nama Misija Mi zaposlenici Zagreba<U+010D>ke banke kao dio Grupe UniCredit p"
## [4] "Povijest O nama Povijest Vremenski trezor Monografija Zagreba<U+010D>ka banka zapo<U+010D>ela je s "
## [5] "Na<U+0161> brend O nama Na<U+0161>a strategija Dugoro<U+010D>no stvaranje vrijednosti kroz stabilno"
## [6] "Doprinos zajednici O nama Sponzorstva i donacije S ciljem osna<U+017E>ivanja razvoja zajednice Zagr"
Također, uočili smo i da su dokumenti 22 i 30 prazni, pa ih uklanjamo.
raw_pages <- raw_pages[!is.na(raw_pages$text_raw), ]
glimpse(raw_pages)
## Rows: 33
## Columns: 7
## $ X <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18…
## $ company <chr> "INA", "INA", "INA", "INA", "INA", "INA", "INA", "INA", "HEP"…
## $ url <chr> "https://www.ina.hr/en/about-ina/profil-kompanije/", "https:/…
## $ section <chr> "profil kompanije", "povijest", "core business", "istrazivanj…
## $ include <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, T…
## $ text_raw <chr> "About INA Company profile INA Group Corporate governance Pol…
## $ n_char <int> 4453, 5730, 3900, 3407, 5399, 3995, 4285, 4392, 11, 72, 17, 3…
U nekim opisima uočavamo neuobičajene znakove, pa ćemo te znakove ukloniti.
library(stringi)
# Pretvorimo <U+xxxx> u prave znakove
raw_pages$text_raw <- stri_replace_all_regex(
raw_pages$text_raw,
"<U\\+([0-9A-Fa-f]+)>",
"\\\\u$1"
)
raw_pages$text_raw <- stri_unescape_unicode(raw_pages$text_raw)
raw_pages$text_raw <- stri_trans_general(raw_pages$text_raw, "Latin-ASCII")
datatable_preview(raw_pages, text_col = "text_raw", n = 100, page_len = 5)
Vidimo da neke stranice imaju opis na hrvatskom, a neke na engleskom.
library(cld2)
raw_pages <- raw_pages |>
mutate(
language = detect_language(text_raw)
)
raw_pages$language
## [1] "en" "en" "en" "en" "en" "en" "en" "en" NA "bs" NA "hr" "hr" "hr" "en"
## [16] "hr" "hr" "hr" "hr" "hr" "hr" NA "hr" "hr" "hr" "hr" "hr" "hr" "hr" "hr"
## [31] "hr" "hr" "hr"
Za prijevod koristimo polyglotr s Google Translate free
endpointom. Generalno je stabilan, ali Google povremeno blokira botove.
Rate limiting je obavezan. Bez Sys.sleep() Google će nas
blokirati već nakon ~10 poziva. S 35 redova i pauzama od 0.3s, cijeli
dataset će biti gotov za ~20 sekundi (još uvijek vremenski izvediv za
manji nastavni skup podataka). Dugi tekstovi su problem. Web scrapani
tekstovi su vjerojatno dugi — strwrap() u kodu će ih rezati
na chunkove od 4500 znakova. Provjerimo koliko su dugački:
raw_pages |> summarise(max_chars = max(n_char, na.rm = TRUE),
mean_chars = mean(n_char, na.rm = TRUE))
## max_chars mean_chars
## 1 10948 2959.667
Provodimo tekst na engleski:
# za ovaj dio koda korišten je Claude Sonnet 4.6, uz naknadno ručno dodane korekcije
# install.packages("polyglotr")
library(polyglotr)
library(dplyr)
library(purrr)
# Wrapper s rate limitingom i error handlingom
translate_to_english <- function(text, source_lang = NULL) {
if (is.na(text) || nchar(text) == 0) return(NA_character_)
tryCatch({
# Google Translate free endpoint - max ~5000 znakova po pozivu
# Ako je tekst duži, treba ga razdvojiti
if (nchar(text) > 4500) {
# Razdvaja na rečenice, prevodi u chunkovima
chunks <- strwrap(text, width = 4500, simplify = TRUE)
translated <- map_chr(chunks, ~ {
Sys.sleep(0.5) # rate limiting
google_translate(.x, target_language = "en", source_language = source_lang %||% "auto")
})
return(paste(translated, collapse = " "))
}
result <- google_translate(text, target_language = "en", source_language = source_lang %||% "auto")
Sys.sleep(0.3) # rate limiting - važno da ne dobiješ blokadu
result
}, error = function(e) {
message("Prijevod neuspješan: ", conditionMessage(e))
NA_character_
})
}
# Primjena - prevodi samo ako nije već engleski
raw_pages <- raw_pages |>
mutate(
text_en = case_when(
is.na(text_raw) ~ NA_character_,
language == "en" ~ text_raw, # već engleski, preskoči
TRUE ~ map_chr(text_raw, translate_to_english)
)
)
Provjera:
raw_pages <- raw_pages |>
mutate(
language = detect_language(text_en)
)
raw_pages$language
## [1] "en" "en" "en" "en" "en" "en" "en" "en" "en" "en" NA "en" "en" "en" "en"
## [16] "en" "en" "en" "en" "en" "en" "en" "en" "en" "en" "en" "en" "en" "en" "en"
## [31] "en" "en" "en"
datatable_preview(raw_pages[,c(2,3,5,10)], text_col = "text_en", n = 100, page_len = 5)
Kad smo zadovoljni dohvatom sadržaja, tekst treba standardizirati tako da bude prikladan za tokenizaciju i kasniju sentiment analizu. Još jednom “provlačimo” kroz čišćenje.
clean_pages <- raw_pages |>
mutate(
text_clean = text_en |>
str_to_lower() |>
str_replace_all("[^\\p{L}\\s]", " ") |>
str_replace_all("\\s+", " ") |>
str_squish()
)
Provjerimo kako tekst izgleda:
substring(raw_pages$text_en, 1, 100)
## [1] "About INA Company profile INA Group Corporate governance Policies and documents Company information "
## [2] "History INA was founded on January 1, 1964 through the merger of Naftaplin Zagreb, the Rijeka Oil Re"
## [3] "Core business Core business of INA, d.d. and its subsidiaries The principal activities of INA and it"
## [4] "Exploration & Production INA Group Exploration & Production has more than 70 years of experience and"
## [5] "Refining & Marketing Refining and Marketing is in charge of the refining operations in Rijeka, activ"
## [6] "Consumer Services & Retail Consumer Services and Retail operate a modernized regional network of mor"
## [7] "Mission, vision and values INA is a modern, socially responsible and transparent company in constant"
## [8] "Ethical business and reporting irregularities INA Group Code of Ethics INA Group Code of Ethics (CoE"
## [9] "That HEP group"
## [10] "Mission, vision and core values â\u0080\u008bâ\u0080\u008bMission Vision Core values"
## [11] "Strategic goals"
## [12] "History The first alternating power system in Croatia HPP Krka - Sibenik HPP Kraljevac was built, th"
## [13] "Get to know us Get to know us Our cooperation with strong international partners brings safety and r"
## [14] "Home About us Responsibility Cooperation Media Careers konzum.hr a year with you throughout life as "
## [15] "About our company MET Group About our company Company data MET CROATIA AT A GLANCE"
## [16] "Lidl.hr Career Real Estate About us Quality accessible to everyone Sustainability at Lidl Press cent"
## [17] "Company Fundamentals We noticed that Java Script is not activated. In order to be able to use our si"
## [18] "Compliance We noticed that Java Script is not activated. In order to be able to use our site smoothl"
## [19] "Company History We noticed that Java Script is not activated. In order to be able to use our site sm"
## [20] "Representation of Petrol d.o.o. Presentation of the company Petrol d.o.o. Seeing the future, before "
## [21] "Ecology and society Ecology and society Petrol and ecology Petrol is a company that operates on the "
## [22] "About us Bank Overview Who we are, how and why we have been here for more than 100 years Mission and"
## [23] "Overview About us Company profile Financial data Zagrebacka banka is a leading bank in Croatia and a"
## [24] "Mission and values â\u0080\u008bâ\u0080\u008bAbout us Mission We Zagrebacka banka employees as part of the UniCredit Gro"
## [25] "History About us History Vremenski trezor Monograph Zagrebacka banka started operating as early as 1"
## [26] "Our brand About us Our strategy Long-term value creation through stability, innovation and trust. Aw"
## [27] "Contribution to the community About us Sponsorships and donations With the aim of strengthening comm"
## [28] "Company About us Company Plodine d.d. was founded in 1993 in Rijeka, where the first sales center wa"
## [29] "About Kaufland Kaufland is a store chain for which efficiency is very important. The results we have"
## [30] "Our values: customer satisfaction and fair treatment Your satisfaction is a key value in our daily w"
## [31] "Awards and recognition Product quality and customer satisfaction are our top priority, so we want to"
## [32] "Chronicle How was Kaufland created? When was the first business unit opened? Who founded the company"
## [33] "We are here for you. Our activities are always focused on efficiency, dynamism and fairness. Whether"
Budući da pojedine kompanije imaju više uključenih podstranica, jedna korisna odluka jest spojiti ih u jedan dokument po kompaniji. Time dobivamo upravo onaj tip inputa koji je praktičan za sentiment analizu na razini organizacije.
company_corpus <- clean_pages |>
group_by(company) |>
summarise(
url_n = n(),
pages_included = str_c(section, collapse = "; "),
text_clean = str_c(text_clean, collapse = " "),
.groups = "drop"
) |>
mutate(
doc_id = row_number(),
n_char = str_length(text_clean)
) |>
select(doc_id, company, url_n, pages_included, n_char, text_clean)
Sada je svaki redak jedna kompanija, a sve relevantne podstranice spojene su u jedan tekst.
datatable_preview(company_corpus, text_col = "text_clean", n = 100, page_len = 5)
Ako želimo prijeći na leksikonsku sentiment analizu, tekst treba rastaviti na riječi i dodatno očistiti te provesti lematizaciju i stemming. Prvi korak je tokenizacija.
tokens <- company_corpus |>
select(doc_id, company, text_clean) |>
unnest_tokens(word, text_clean)
Dobivena tablica tokens sadrži barem ova tri stupca:
doc_idcompanywordhead(tokens, 10)
## # A tibble: 10 × 3
## doc_id company word
## <int> <chr> <chr>
## 1 1 HEP that
## 2 1 HEP hep
## 3 1 HEP group
## 4 1 HEP mission
## 5 1 HEP vision
## 6 1 HEP and
## 7 1 HEP core
## 8 1 HEP values
## 9 1 HEP â
## 10 1 HEP â
<U+00E2> je tzv. mojibake - pojava kad se
tekst enkodiran u jednom standardu (npr. UTF-8) pročita kao da je u
drugom (npr. Latin-1/ISO-8859-1). U ovom slučaju
<U+00E2> je â, što je tipičan znak
mojibakea - najčešće se pojavljuje kad UTF-8 navodnici ili crtice (“, –)
nisu ispravno pročitani.
tokens %>% filter(!stri_enc_isascii(word)) %>% count(word, sort = TRUE)
## # A tibble: 2 × 2
## word n
## <chr> <int>
## 1 â 32
## 2 zagrebaä 6
tokens <- tokens %>% filter(stri_enc_isascii(word))
#ponovimo provjeru
tokens %>% filter(!stri_enc_isascii(word)) %>% count(word, sort = TRUE)
## # A tibble: 0 × 2
## # ℹ 2 variables: word <chr>, n <int>
Preostaje još samo ukloniti stop_words. To su najčešće
riječi u jeziku koje same po sebi ne nose značenje — prijedlozi,
veznici, zamjenice i slično (npr. the, a, is, in, of, and).
U analizi teksta uklanjamo ih jer ne doprinose razumijevanju sadržaja. Bez uklanjanja, dominirali bi u svakoj analizi frekvencije, ali nam ne bi rekli ništa korisno o temi ili tonu teksta.
library(tidytext)
head(stop_words)
## # A tibble: 6 × 2
## word lexicon
## <chr> <chr>
## 1 a SMART
## 2 a's SMART
## 3 able SMART
## 4 about SMART
## 5 above SMART
## 6 according SMART
No, pritom trebamo paziti da ne uklonimo negacije, jer one imaju svoj sentiment.
stop_words_custom <- stop_words %>%
filter(!word %in% c("no", "not", "nor", "never"))
tokens <- tokens %>%
anti_join(stop_words_custom, by = "word")
Provjera:
str(tokens)
## tibble [6,050 × 3] (S3: tbl_df/tbl/data.frame)
## $ doc_id : int [1:6050] 1 1 1 1 1 1 1 1 1 1 ...
## $ company: chr [1:6050] "HEP" "HEP" "HEP" "HEP" ...
## $ word : chr [1:6050] "hep" "mission" "vision" "core" ...
Provodimo lematizaciju.
library(textstem)
## Loading required package: koRpus.lang.en
## Loading required package: koRpus
## Loading required package: sylly
## For information on available language packages for 'koRpus', run
##
## available.koRpus.lang()
##
## and see ?install.koRpus.lang()
##
## Attaching package: 'koRpus'
## The following object is masked from 'package:readr':
##
## tokenize
tokens <- tokens %>%
mutate(word = lemmatize_words(word))
Za leksikonsku sentiment analizu najčešće je najbolje eventualno
raditi samo lematizaciju (bez stemminga, pa ćemo preskočiti taj
korak). Razlog je što su leksikoni poput bing,
afinn i nrc već zadani u konkretnim oblicima
riječi. Ako ih previše “izrežemmo” stemmingom, možemo izgubiti
podudaranja.
Sad možemo nastaviti s analizom. Prije nego krenemo sa sentiment analizom, pogledajmo najčešće riječi.
tokens %>%
count(word, sort = TRUE) %>%
head(20)
## # A tibble: 20 × 2
## word n
## <chr> <int>
## 1 company 102
## 2 ina 89
## 3 business 78
## 4 croatia 70
## 5 award 55
## 6 product 52
## 7 kaufland 50
## 8 zagreb 50
## 9 bank 49
## 10 employee 45
## 11 banka 44
## 12 lidl 43
## 13 oil 42
## 14 market 39
## 15 quality 39
## 16 croatian 36
## 17 service 36
## 18 production 34
## 19 customer 33
## 20 project 33
Najčešće riječi u korpusu upućuju na to da se organizacijski opisi pretežno temelje na samopredstavljanju, poslovnoj djelatnosti i reputacijskim signalima. Dominiraju opći korporativni pojmovi poput company, business, market, service, production i customer, što pokazuje da tekstovi naglašavaju identitet organizacije, područje poslovanja i odnos prema tržištu. Istodobno se među najučestalijim riječima pojavljuju i pojmovi poput award, quality i employee, što sugerira da organizacije u svojim narativima ne opisuju samo vlastitu djelatnost, nego i nastoje oblikovati reputacijsku sliku o sebi, tj. istaknuti kvalitetu, priznanja i važnost zaposlenika kao dio pozitivne slike o sebi. Prisutnost naziva kompanija i lokacijskih oznaka, poput ina, kaufland, lidl, zagreb i croatia, također pokazuje da dio najčešćih riječi proizlazi iz specifičnosti korpusa, pa takve riječi valja interpretirati oprezno jer više govore o identitetu izvora nego o emocionalnom tonu teksta.
Tokens je standardni ulaz za spajanje s leksikonima poput bing, afinn ili nrc.
Kako bi analiza bila reproducibilna, dobro je pohraniti korištene tekstove i korpuse teksta za naknadno korištenje ili validaciju.
write_csv(clean_pages, "about_pages_raw_cleaned.csv")
write_csv(company_corpus, "about_company_corpus.csv")
write_csv(tokens, "about_company_tokens.csv")
Nakon što smo identificirali relevantne stranice, dohvatili tekst, ručno provjerili njegovu kvalitetu, očistili ga i po potrebi spojili više podstranica u jedan dokument, imamo korpus spreman za analizu. Tek sada ima smisla prijeći na pitanje kako sentiment mjerimo i koje su razlike između leksikonskog pristupa i pristupa temeljenih na modelima.
U analizi sentimenata najčešće se razlikuju dva osnovna metodološka pristupa: leksikonski pristup i pristup temeljen na strojnom učenju.
Leksikonski pristup koristi unaprijed definirane rječnike u kojima su riječima pridružene oznake sentimenta ili numeričke vrijednosti. Jednostavan je za implementaciju i interpretaciju, no ne uzima u obzir kontekst u kojem se riječ pojavljuje. Ovo je i dalje jedan od najčešće korištenih pristupa.
Pristup temeljen na strojnom učenju koristi algoritme koji uče prepoznavati sentiment iz označenih primjera. Fleksibilniji je i često točniji, osobito za složenije tekstove, ali zahtijeva označene podatke za treniranje i manje je transparentan od leksikonskog pristupa.
Nakon identifikacije riječi iz sentiment leksikona potrebno je izračunati sentiment score, odnosno numeričku mjeru ukupnog sentimenta teksta.
Jednostavan način izračuna temelji se na razlici između broja pozitivnih i negativnih riječi:
\[ Sentiment = N_{pos} - N_{neg} \]
gdje je:
Dobivena vrijednost predstavlja ukupni polaritet teksta. Ako je rezultat pozitivan, tekst ima prevladavajući pozitivan sentiment. Ako je rezultat negativan, prevladava negativan sentiment. Vrijednosti blizu nule upućuju na neutralan tekst ili uravnotežen odnos pozitivnih i negativnih izraza.
Ako leksikon sadrži numeričke vrijednosti sentimenta, ukupni sentiment može se izračunati kao zbroj svih sentiment vrijednosti:
\[ Sentiment = \sum_{i=1}^{n} w_i \]
gdje je:
Ovaj pristup uvodi pojam valencije, odnosno intenziteta emocionalne procjene. Riječi s većom apsolutnom vrijednošću imaju snažniji utjecaj na konačni sentiment score.
Nakon izračuna sentimenta na razini pojedinih riječi ili rečenica, rezultati se obično agregiraju kako bi se dobila sažeta procjena sentimenta na višoj razini.
Agregacija može biti provedena na različitim razinama analize:
Na primjer, u analizi društvenih mreža moguće je izračunati prosječni sentiment svih objava pojedinog korisnika ili prosječni sentiment komunikacije između dvije skupine aktera.
Agregacija omogućuje sažimanje velikih količina tekstualnih podataka u nekoliko ključnih indikatora koji se mogu vizualizirati i dalje analizirati.
Leksikonski pristup temelji se na unaprijed definiranom rječniku riječi kojem su pridružene informacije o sentimentu. Takav rječnik naziva se sentiment leksikon. Svaka riječ u leksikonu ima pridruženu oznaku polariteta (npr. pozitivno ili negativno) ili numeričku vrijednost koja označava intenzitet sentimenta.
Analiza se provodi tako da se tekst tokenizira na pojedinačne riječi, nakon čega se te riječi uspoređuju s rječnikom. Ako se riječ nalazi u leksikonu, njezin sentiment doprinosi ukupnoj procjeni sentimenta teksta.
Prednost leksikonskog pristupa je njegova transparentnost i jednostavnost implementacije. Analitičar može jasno vidjeti koje su riječi utjecale na konačni rezultat. Nedostatak je što takav pristup često ne uzima u obzir širi kontekst u kojem se riječ pojavljuje.
U R okruženju često se koriste sljedeći sentiment leksikoni:
Usporedba riječnika - po prvih 100 riječi
library(textdata)
library(tidytext)
library(tidyverse)
afinn <- get_sentiments("afinn")
bing <- get_sentiments("bing")
nrc <- get_sentiments("nrc")
comparison <- data.frame(
afinn_word = get_sentiments("afinn")[1:100,1],
afinn_value = get_sentiments("afinn")[1:100,2],
bing_word = get_sentiments("bing")[1:100,1],
bing_sentiment = get_sentiments("bing")[1:100,2],
nrc_word = get_sentiments("nrc")[1:100,1],
nrc_sentiment = get_sentiments("nrc")[1:100,2]
)
colnames(comparison) <- c("afinn_word", "afinn_value", "bing_word", "bing_sentiment", "nrc_word", "nrc_sentiment")
library(DT)
datatable(
comparison,
options = list(
pageLength = 5,
autoWidth = TRUE,
scrollX = TRUE
),
rownames = FALSE
)
Bing leksikon klasificira svaku riječ kao pozitivnu ili negativnu. Spajanjem s tokenima dobivamo broj pozitivnih i negativnih riječi po tvrtki.
library(tidytext)
library(tidyverse)
bing <- get_sentiments("bing")
sentiment_bing <- tokens %>%
inner_join(bing, by = "word") %>%
count(company, sentiment) %>%
pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
mutate(score = positive - negative)
sentiment_bing
## # A tibble: 9 × 4
## company negative positive score
## <chr> <int> <int> <int>
## 1 HEP 5 3 -2
## 2 INA 11 81 70
## 3 KAUFLAND 10 175 165
## 4 KONZUM 0 22 22
## 5 LIDL 23 43 20
## 6 PETROL 9 11 2
## 7 PLODINE 2 29 27
## 8 PPD 1 7 6
## 9 ZABA 11 89 78
Napomena: inner_join zadržava samo one riječi koje se
nalaze i u našem tekstu i u rječniku sentimenta. Sve neutralne riječi
ili riječi koje rječnik ne poznaje bit će odbačene iz ove analize.
library(tidyr)
library(reshape2)
##
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
##
## smiths
library(wordcloud)
## Loading required package: RColorBrewer
tokens %>%
inner_join(bing, by = "word") %>%
count(word, sentiment, sort = TRUE) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("firebrick3", "deepskyblue3"),
max.words = 150,
title.size = 1.5)
Jedna tvrtka vjerojatno nema niti jednu riječ koja se podudara s bing leksikonom - provjera:
tokens %>%
filter(company == "MET") %>%
inner_join(bing, by = "word")
## # A tibble: 0 × 4
## # ℹ 4 variables: doc_id <int>, company <chr>, word <chr>, sentiment <chr>
sentiment_bing %>%
pivot_longer(cols = c(positive, negative), names_to = "sentiment", values_to = "n") %>%
ggplot(aes(x = reorder(company, n), y = n, fill = sentiment)) +
geom_col(position = "dodge") +
coord_flip() +
scale_fill_manual(values = c("positive" = "#2ecc71", "negative" = "#e74c3c")) +
labs(title = "Bing sentiment po tvrtki",
x = NULL, y = "Broj riječi", fill = "Sentiment") +
theme_minimal()
AFINN leksikon dodjeljuje svakoj riječi numeričku vrijednost između −5 (vrlo negativno) i +5 (vrlo pozitivno). Zbrajanjem vrijednosti dobivamo ukupni sentiment score po tvrtki. Pogledajmo prvo output spajanja s riječima iz leksikona.
library(tidyr)
library(reshape2)
library(wordcloud)
afinn <- get_sentiments("afinn")
tokens %>%
inner_join(afinn, by = "word") %>%
# KLJUČNI KORAK: Pretvaramo brojčani 'value' u kategoriju
mutate(sentiment = ifelse(value > 0, "positive", "negative")) %>%
count(word, sentiment, sort = TRUE) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("firebrick3", "deepskyblue3"),
max.words = 150,
title.size = 1.5)
Ovdje već vidimo da različiti rječnici daju različiti output.
library(textdata)
sentiment_afinn <- tokens %>%
inner_join(afinn, by = "word") %>%
mutate(sentiment = ifelse(value > 0, "positive", "negative")) %>%
group_by(company, sentiment) %>%
summarise(score = sum(value), .groups = "drop") %>%
pivot_wider(names_from = sentiment, values_from = score, values_fill = 0) %>%
mutate(total = positive + negative) %>%
arrange(desc(total))
sentiment_afinn
## # A tibble: 9 × 4
## company negative positive total
## <chr> <dbl> <dbl> <dbl>
## 1 KAUFLAND -27 334 307
## 2 ZABA -8 160 152
## 3 INA -10 138 128
## 4 PLODINE -6 53 47
## 5 LIDL -14 58 44
## 6 KONZUM -1 26 25
## 7 PETROL -9 32 23
## 8 PPD 0 17 17
## 9 HEP -7 5 -2
U slučaju AFINN leksikona varijabla total predstavlja
neto ukupni sentiment score, odnosno očekivan konačan prevladavajući
dojam nakon zbrajanja pozitivnih i negativnih vrijednosti riječi u
tekstu.
sentiment_afinn %>%
pivot_longer(cols = c(positive, negative), names_to = "sentiment", values_to = "n") %>%
ggplot(aes(x = reorder(company, total), y = n, fill = sentiment)) +
geom_col(position = "dodge") +
coord_flip() +
scale_fill_manual(values = c("positive" = "#2ecc71", "negative" = "#e74c3c")) +
labs(title = "AFINN sentiment po tvrtki",
x = NULL, y = "Score", fill = "Sentiment") +
theme_minimal()
NRC leksikon kategorizira riječi prema osam emocija (strah, radost, povjerenje, iznenađenje…) i dvama polaritetima. Prvo ćemo pogledati učestalost riječi po emociji u oblaku riječi.
nrc <- get_sentiments("nrc")
# filtriranje (izbacujemo opće kategorije 'positive' i 'negative'
# kako bismo se fokusirali samo na specifične emocije)
nrc_emotions <- nrc %>%
filter(!sentiment %in% c("positive", "negative"))
library(RColorBrewer)
# izrada oblaka
tokens %>%
inner_join(nrc_emotions, by = "word", relationship = "many-to-many") %>%
count(word, sentiment, sort = TRUE) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = brewer.pal(8, "Dark2"), # Koristimo paletu s 8 boja
max.words = 150,
title.size = 1.2,
scale = c(3, 0.5)) # Prilagodba veličine riječi
Prikazujemo distribuciju emocija po tvrtki.
nrc <- get_sentiments("nrc")
sentiment_nrc <- tokens %>%
inner_join(nrc, by = "word", relationship = "many-to-many") %>% # NRC leksikon dodjeljuje jednoj riječi više emocija istovremeno - "many-to-many"
count(company, sentiment)
# sentiment_nrc - ispis nije pregledan
sentiment_nrc_za_graf <- sentiment_nrc %>%
filter(!sentiment %in% c("positive", "negative")) # zadržimo samo emocije
sentiment_nrc_za_graf %>%
ggplot(aes(x = sentiment, y = n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~company, scales = "free_y") +
coord_flip() +
labs(title = "NRC emocije po tvrtki",
x = NULL, y = "Broj riječi") +
theme_minimal()
Usporedba rezultata dobivenih koristeći različite leksikone jest jedan način validacije rezultata.
# Bing već imamo u prikladnom obliku
bing_compare <- sentiment_bing %>%
transmute(
company,
bing_negative = negative,
bing_positive = positive,
bing_score = score
)
# AFINN preimenujemo radi preglednosti
afinn_compare <- sentiment_afinn %>%
transmute(
company,
afinn_negative = negative,
afinn_positive = positive,
afinn_score = total
)
# NRC: zadržavamo samo pozitivno i negativno
nrc_compare <- sentiment_nrc %>%
filter(sentiment %in% c("positive", "negative")) %>%
pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
mutate(score = positive - negative) %>%
transmute(
company,
nrc_negative = negative,
nrc_positive = positive,
nrc_score = score
)
# objedinjena tablica
sentiment_compare <- full_join(bing_compare, afinn_compare, by = "company") %>%
full_join(nrc_compare, by = "company") %>%
arrange(company)
sentiment_compare
## # A tibble: 9 × 10
## company bing_negative bing_positive bing_score afinn_negative afinn_positive
## <chr> <int> <int> <int> <dbl> <dbl>
## 1 HEP 5 3 -2 -7 5
## 2 INA 11 81 70 -10 138
## 3 KAUFLAND 10 175 165 -27 334
## 4 KONZUM 0 22 22 -1 26
## 5 LIDL 23 43 20 -14 58
## 6 PETROL 9 11 2 -9 32
## 7 PLODINE 2 29 27 -6 53
## 8 PPD 1 7 6 0 17
## 9 ZABA 11 89 78 -8 160
## # ℹ 4 more variables: afinn_score <dbl>, nrc_negative <int>,
## # nrc_positive <int>, nrc_score <int>
Usporedna tablica pokazuje rezultate sentiment analize dobivene korištenjem triju različitih leksikona: Bing, AFINN i NRC. Iako svaki leksikon koristi različitu metodologiju procjene sentimenta, opći obrasci u rezultatima su vrlo slični.
Prvo, smjer sentimenta uglavnom je konzistentan među leksikonima. Sve kompanije, osim HEP-a, imaju pozitivan neto sentiment u svim leksikonima. HEP je jedina organizacija kod koje Bing i AFINN pokazuju blago negativan rezultat, dok NRC pokazuje pozitivan rezultat. Takva razlika može nastati jer NRC klasificira riječi u više emocionalnih kategorija, a neke riječi koje Bing ili AFINN prepoznaju kao negativne mogu se u NRC-u pojaviti u kontekstu drugih emocija.
Drugo, relativni poredak kompanija vrlo je sličan među leksikonima. Primjerice, kompanije poput Kauflanda, ZABA-e i INA-e imaju najviše pozitivnih rezultata u svim metodama, dok kompanije poput PPD-a, Petrol-a i Konzuma imaju znatno umjerenije vrijednosti. To sugerira da različiti leksikoni prepoznaju sličan emocionalni ton u tekstovima.
Treće, intenzitet sentimenta razlikuje se među leksikonima. Bing koristi binarnu klasifikaciju (riječi su samo pozitivne ili negativne), dok AFINN dodjeljuje numeričke težine sentimentu, a NRC broji pojave polariteta unutar šireg emocionalnog leksikona. Zbog toga su vrijednosti u NRC-u i AFINN-u često veće od Bing rezultata, ali razlike u apsolutnim vrijednostima ne znače nužno i razliku u interpretaciji.
Općenito, usporedba pokazuje da su rezultati relativno stabilni među različitim leksikonima, što povećava pouzdanost interpretacije sentimenta u analiziranom korpusu.
sentiment_compare_scores <- data.frame(
company = sentiment_compare$company,
bing_afinn_neg = abs(sentiment_compare$bing_negative - abs(sentiment_compare$afinn_negative)),
afinn_nrc_neg = abs(abs(sentiment_compare$afinn_negative)-sentiment_compare$nrc_negative),
nrc_bing_neg = abs(sentiment_compare$nrc_negative - sentiment_compare$bing_negative),
bing_afinn_pos = abs(sentiment_compare$bing_positive - abs(sentiment_compare$afinn_positive)),
afinn_nrc_pos = abs(abs(sentiment_compare$afinn_positive)-sentiment_compare$nrc_positive),
nrc_bing_pos = abs(sentiment_compare$nrc_positive - sentiment_compare$bing_positive)
)
sentiment_compare_scores
## company bing_afinn_neg afinn_nrc_neg nrc_bing_neg bing_afinn_pos
## 1 HEP 2 4 2 2
## 2 INA 1 22 21 57
## 3 KAUFLAND 17 4 21 159
## 4 KONZUM 1 2 3 4
## 5 LIDL 9 13 4 15
## 6 PETROL 0 3 3 21
## 7 PLODINE 4 2 2 24
## 8 PPD 1 0 1 10
## 9 ZABA 3 5 2 71
## afinn_nrc_pos nrc_bing_pos
## 1 34 36
## 2 105 162
## 3 6 153
## 4 23 27
## 5 110 125
## 6 18 39
## 7 16 40
## 8 9 19
## 9 55 126
Analiza apsolutnih odstupanja između rezultata dobivenih različitim leksikonima pokazuje da u nekim slučajevima postoje relativno velike razlike u broju prepoznatih pozitivnih i negativnih riječi. Takve razlike su očekivane jer svaki leksikon koristi različit skup riječi i različita pravila klasifikacije sentimenta.
Leksikoni se razlikuju po veličini i strukturi rječnika. NRC leksikon, primjerice, uključuje velik broj riječi povezanih s emocijama, pa često prepoznaje znatno više pojavnica pozitivnog ili negativnog sentimenta nego Bing. Zbog toga je u nekim slučajevima razlika između NRC i Bing rezultata relativno velika.
Osim toga, leksikoni se razlikuju po logici vrednovanja riječi. Bing klasificira riječi isključivo kao pozitivne ili negativne, dok AFINN koristi numeričke vrijednosti sentimenta, a NRC osim polariteta uključuje i emocijske kategorije. Posljedica toga je da isti tekst može generirati različite apsolutne vrijednosti sentimenta ovisno o korištenom leksikonu.
Također, čak važnije od apsolutnih razlika u broju riječi jest slaganje u smjeru sentimenta i relativnom poretku dokumenta. U ovom primjeru većina kompanija ima pozitivan neto sentiment u svim leksikonima, a kompanije s najvišim vrijednostima (npr. Kaufland, ZABA i INA) pojavljuju se među najpozitivnijima u svim metodama. Takva konzistentnost sugerira da leksikoni, unatoč razlikama u apsolutnim vrijednostima, prepoznaju sličan emocionalni ton u tekstovima.
Zbog toga velike razlike u apsolutnim vrijednostima ne znače nužno da su rezultati nepouzdani. Problem bi nastao tek kada bi različiti leksikoni davali suprotan smjer sentimenta ili potpuno različit relativni poredak dokumenata.
Kakve su razlike zapravo problem?
Ne postoji univerzalni “prag”, ali u praksi gledamo tri stvari:
1. znak sentimenta (najvažnije): ako jedan leksikon kaže: +50, a drugi -40, to je problem.
2. korelacija između leksikona: Ako su rezultati korelirani, analiza je stabilna.
cor(sentiment_compare$bing_negative, abs(sentiment_compare$afinn_negative))
## [1] 0.5764037
cor(abs(sentiment_compare$afinn_negative), sentiment_compare$nrc_negative)
## [1] 0.7990541
cor(sentiment_compare$nrc_negative, sentiment_compare$bing_negative)
## [1] 0.7594557
cor(sentiment_compare$bing_positive, sentiment_compare$afinn_positive)
## [1] 0.9944219
cor(sentiment_compare$afinn_positive, sentiment_compare$nrc_positive)
## [1] 0.9262679
cor(sentiment_compare$nrc_positive, sentiment_compare$bing_positive)
## [1] 0.9523314
U ovom primjeru korelacije između leksikona pokazuju visok stupanj slaganja, osobito za pozitivni sentiment. Korelacija između Bing i AFINN pozitivnih rezultata iznosi 0.99, dok su korelacije između AFINN i NRC (0.93) te NRC i Bing (0.95) također vrlo visoke. Takve vrijednosti upućuju na to da različiti leksikoni prepoznaju vrlo sličan obrazac pozitivnog sentimenta u analiziranim tekstovima.
Za negativni sentiment korelacije su nešto niže, ali i dalje relativno visoke. Korelacija između NRC i Bing negativnih rezultata iznosi 0.76, dok korelacija između AFINN i NRC negativnih rezultata iznosi 0.80. Korelacija između Bing i AFINN negativnih rezultata je niža - umjereno izražena (0.58), što može biti posljedica različite strukture leksikona i činjenice da AFINN koristi numeričke težine sentimenta, dok Bing klasificira riječi isključivo kao pozitivne ili negativne.
Općenito, visoke korelacije među leksikonima sugeriraju da se opći obrazac sentimenta u tekstovima prepoznaje konzistentno, iako se apsolutni broj prepoznatih riječi može razlikovati. Takva analiza predstavlja dodatnu provjeru stabilnosti rezultata sentiment analize.
3. relativni poredak: Ako isti tekstovi imaju sličan poredak sentimenta, rezultati su validni.
Ako se leksikoni uglavnom slažu u predznaku sentimenta, pokazuju sličan relativni poredak dokumenata i umjereno do visoko koreliraju, tada rezultate možemo smatrati dovoljno stabilnima za interpretaciju.
Kao što je ranije navedeno, sljedeći korak je agregacija outputa leksikonske analize. NRC leksikon omogućuje analizu emocionalne strukture teksta, a ne samo ukupnog polariteta. Svaka riječ može biti povezana s jednom ili više emocionalnih kategorija, poput joy, trust, fear ili anger. Nakon tokenizacije i spajanja s NRC leksikonom dobivamo broj pojavljivanja pojedine emocije u tekstu.
U tablici sentiment_nrc svaki red predstavlja jednu
kombinaciju tvrtke i emocije, a varijabla
n označava koliko se puta riječ iz te emocionalne
kategorije pojavljuje u tekstu.
str(sentiment_nrc)
## tibble [83 × 3] (S3: tbl_df/tbl/data.frame)
## $ company : chr [1:83] "HEP" "HEP" "HEP" "HEP" ...
## $ sentiment: chr [1:83] "anger" "anticipation" "fear" "joy" ...
## $ n : int [1:83] 3 21 11 16 3 39 1 8 29 12 ...
Kako bismo identificirali tipične obrasce emocionalnog tona, možemo primijeniti klastersku analizu. Jedna od najčešće korištenih metoda je k-means klasteriranje, koje grupira objekte prema sličnosti njihovih numeričkih obilježja. No, pritom prolazimo kroz nekoliko klasičnih koraka koji obuhvaćaju dodatne uvide i pripremu podataka.
Kako bismo mogli usporediti tvrtke prema njihovom emocionalnom profilu, potrebno je podatke transformirati u matricu emocija, gdje svaki red predstavlja tvrtku, a svaki stupac jednu emociju, odnosno ukupan pozitivni i negativni sentiment.
emotion_matrix <- sentiment_nrc %>%
pivot_wider(names_from = sentiment, values_from = n, values_fill = 0)
emotion_matrix
## # A tibble: 9 × 11
## company anger anticipation fear joy negative positive sadness surprise
## <chr> <int> <int> <int> <int> <int> <int> <int> <int>
## 1 HEP 3 21 11 16 3 39 1 8
## 2 INA 12 89 22 36 32 243 3 10
## 3 KAUFLAND 7 136 4 135 31 328 3 55
## 4 KONZUM 3 8 1 12 3 49 0 3
## 5 LIDL 22 58 22 27 27 168 19 12
## 6 PETROL 3 11 9 8 12 50 2 2
## 7 PLODINE 4 34 2 23 4 69 2 7
## 8 PPD 0 7 1 4 0 26 2 0
## 9 ZABA 5 102 9 64 13 215 12 34
## # ℹ 2 more variables: trust <int>, disgust <int>
U ovom obliku svaka tvrtka ima vlastiti emocionalni vektor.
Budući da se ukupna duljina tekstova razlikuje među tvrtkama, korisno
je opažanja standardizirati kako bi bila usporedive.
Koristimo funkciju scale() koja primijenjuje izraz za
standardizirano obilježje po svakoj varijabli, tj. stupcu: \(z_{ij}=\frac{x_{ij}-
\overline{x_j}}{s_j}\):
emotion_features <- emotion_matrix %>%
select(-company)
emotion_features
## # A tibble: 9 × 10
## anger anticipation fear joy negative positive sadness surprise trust
## <int> <int> <int> <int> <int> <int> <int> <int> <int>
## 1 3 21 11 16 3 39 1 8 29
## 2 12 89 22 36 32 243 3 10 116
## 3 7 136 4 135 31 328 3 55 204
## 4 3 8 1 12 3 49 0 3 26
## 5 22 58 22 27 27 168 19 12 98
## 6 3 11 9 8 12 50 2 2 23
## 7 4 34 2 23 4 69 2 7 58
## 8 0 7 1 4 0 26 2 0 15
## 9 5 102 9 64 13 215 12 34 141
## # ℹ 1 more variable: disgust <int>
emotion_scaled <- scale(emotion_features)
emotion_scaled
## anger anticipation fear joy negative positive
## [1,] -0.53134451 -0.6508887 0.2425356 -0.487427716 -0.84613338 -0.8462434
## [2,] 0.81362129 0.7871759 1.5764816 -0.002692971 1.40734430 1.0122528
## [3,] 0.06641806 1.7811324 -0.6063391 2.396744020 1.32963818 1.7866262
## [4,] -0.53134451 -0.9258129 -0.9701425 -0.584374665 -0.84613338 -0.7551406
## [5,] 2.30802773 0.1315876 1.5764816 -0.220823606 1.01881367 0.3289822
## [6,] -0.53134451 -0.8623688 0.0000000 -0.681321615 -0.14677824 -0.7460303
## [7,] -0.38190387 -0.3759646 -0.8488747 -0.317770555 -0.76842726 -0.5729351
## [8,] -0.97966645 -0.9469609 -0.9701425 -0.778268564 -1.07925177 -0.9646769
## [9,] -0.23246322 1.0621000 0.0000000 0.675935673 -0.06907211 0.7571651
## sadness surprise trust disgust
## [1,] -0.6140351 -0.3605832 -0.7632330 -1.1039746
## [2,] -0.2982456 -0.2505748 0.5677502 0.9283422
## [3,] -0.2982456 2.2246152 1.9140320 0.9283422
## [4,] -0.7719298 -0.6356043 -0.8091290 -1.1039746
## [5,] 2.2280702 -0.1405663 0.2923743 0.7025293
## [6,] -0.4561404 -0.6906086 -0.8550250 0.7025293
## [7,] -0.4561404 -0.4155874 -0.3195720 -1.1039746
## [8,] -0.4561404 -0.8006170 -0.9774142 -0.8781616
## [9,] 1.1228070 1.0695265 0.9502166 0.9283422
## attr(,"scaled:center")
## anger anticipation fear joy negative positive
## 6.555556 51.777778 9.000000 36.111111 13.888889 131.888889
## sadness surprise trust disgust
## 4.888889 14.555556 78.888889 4.888889
## attr(,"scaled:scale")
## anger anticipation fear joy negative positive
## 6.691620 47.285774 8.246211 41.259679 12.868998 109.766166
## sadness surprise trust disgust
## 6.333333 18.180423 65.365213 4.428443
Sada svaka vrijednost predstavlja odstupanje od prosjeka stupca.
Prije nego odaberemo broj klastera, korisno je provjeriti kako se mijenja suma kvadrata unutar grupa (within-group sum of squares, WSS) za različite vrijednosti \(k\). Ideja elbow metode jest pronaći točku nakon koje dodatno povećanje broja klastera više ne donosi znatno poboljšanje.
wss <- sapply(1:8, function(k) {
kmeans(emotion_scaled, centers = k, nstart = 50, iter.max = 15)$tot.withinss
})
plot(
1:8, wss, type = "b",
xlab = "Broj klastera (k)",
ylab = "Within-group sum of squares"
)
Na temelju elbow metode uobičajeno možemo odabrati razuman broj klastera. Ovdje se ne uočava posve jasno najizraženija promjena nagiba na krivulji (specifično, jesu li bolja dva ili tri klastera), pa ćemo, u ovom primjeru pomalo proizvoljno koristiti tri klastera, što je često dovoljno da se razlikuju osnovni tipovi emocionalnih profila bez pretjerane fragmentacije uzorka, osobito zbog malog uzorka.
set.seed(123)
km_result <- kmeans(emotion_scaled, centers = 3, nstart = 25)
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
fviz_cluster(
km_result,
data = emotion_scaled,
geom = c("point", "text"),
repel = TRUE,
ellipse.type = "convex",
labelsize = 4,
ggtheme = theme_minimal()
)
Dobivamo grupiranje tvrtki prema sličnosti njihovog emocionalnog profila. Izvršit ćemo brzu usporedbu s dva i četiri klastera.
set.seed(123)
km_result2 <- kmeans(emotion_scaled, centers = 2, nstart = 25)
km_result4 <- kmeans(emotion_scaled, centers = 4, nstart = 25)
fviz_cluster(
km_result2,
data = emotion_scaled,
geom = c("point", "text"),
repel = TRUE,
ellipse.type = "convex",
labelsize = 4,
ggtheme = theme_minimal()
)
fviz_cluster(
km_result4,
data = emotion_scaled,
geom = c("point", "text"),
repel = TRUE,
ellipse.type = "convex",
labelsize = 4,
ggtheme = theme_minimal()
)
Kako bismo procijenili kvalitetu rješenja, možemo usporediti nekoliko vrijednosti \(k\) i izračunati više kriterija validacije. U nastavku koristimo:
library(cluster)
library(clusterCrit)
X <- emotion_scaled
ks <- 2:6
metrics <- lapply(ks, function(k) {
set.seed(123)
km <- kmeans(X, centers = k, nstart = 50)
sil_avg <- mean(silhouette(km$cluster, dist(X))[, 3])
int <- intCriteria(
as.matrix(X),
as.integer(km$cluster),
c("Calinski_Harabasz", "Davies_Bouldin")
)
data.frame(
k = k,
silhouette = sil_avg,
calinski_harabasz = int$calinski_harabasz,
davies_bouldin = int$davies_bouldin
)
})
metrics <- do.call(rbind, metrics)
metrics
## k silhouette calinski_harabasz davies_bouldin
## 1 2 0.4969981 10.89200 0.7355693
## 2 3 0.4884855 11.99750 0.7325656
## 3 4 0.3817078 11.17148 0.4686847
## 4 5 0.3550601 13.64961 0.2009238
## 5 6 0.2008689 21.49235 0.2085681
Ovakva tablica pomaže u argumentiranju izbora broja klastera. U praksi različiti kriteriji ne moraju uvijek sugerirati isto rješenje, pa je izbor broja klastera često kompromis između statističke kvalitete i interpretativne smislenosti.
Kako bi se procijenio optimalan broj klastera, analizirano je nekoliko standardnih pokazatelja kvalitete klasteriranja: silhouette indeks, Calinski–Harabasz indeks i Davies–Bouldin indeks. Svaki od tih pokazatelja mjeri različite aspekte strukture klastera, poput njihove međusobne razdvojenosti i unutarnje kompaktnosti.
Silhouette indeks mjeri koliko je svaki objekt sličan vlastitom klasteru u odnosu na ostale klastere. Veće vrijednosti upućuju na bolje razdvojene i kompaktnije klastere. U ovom slučaju najveća vrijednost silhouette indeksa dobivena je za k = 2 (0.497), što sugerira da bi dva klastera mogla predstavljati relativno jasno razdvojene skupine. Međutim, vrijednost za k = 3 (0.488) također je visoka i pokazuje da i rješenje s tri klastera zadržava dobru razinu razdvojenosti.
Calinski–Harabasz indeks uspoređuje varijaciju između klastera i varijaciju unutar klastera. Veće vrijednosti ukazuju na bolju strukturu klastera. U dobivenim rezultatima ovaj indeks ima relativno bliske vrijednosti za k = 2 (10.89) i k = 3 (11.998), što znači da oba rješenja pokazuju sličnu razinu kvalitete u smislu diferencijacije skupina (iako se najviša razina postiže sa šest klastera - pri čemu bi se, za samo 9 opažanja, izgubio smisao klasteriranja).
Davies–Bouldin indeks mjeri prosječnu sličnost između klastera, pri čemu su niže vrijednosti poželjne jer upućuju na veću međusobnu razdvojenost skupina. Najniža vrijednost u ovom primjeru dobivena je za k = 6 (0.21), dok je vrijednost za k = 3 (0.73) gotovo ista kao rješenje s dva klastera.
Budući da različiti kriteriji ne sugeriraju jednoznačno isti broj klastera, izbor optimalnog rješenja u praksi često predstavlja kompromis između statističkih pokazatelja i interpretativne smislenosti rezultata. U ovom primjeru odabiremo tri klastera, jer takvo rješenje omogućuje jasnu diferencijaciju između emocionalnih profila kompanija, a pritom ostaje interpretativno pregledno i prikladno za analizu komunikacijskih stilova.
Rezultat klasteriranja možemo pridružiti natrag tablici tvrtki.
clustered_companies <- emotion_matrix %>%
select(company) %>%
mutate(cluster = km_result$cluster)
clustered_companies
## # A tibble: 9 × 2
## company cluster
## <chr> <int>
## 1 HEP 2
## 2 INA 3
## 3 KAUFLAND 1
## 4 KONZUM 2
## 5 LIDL 3
## 6 PETROL 2
## 7 PLODINE 2
## 8 PPD 2
## 9 ZABA 1
Nakon primjene k-means klasteriranja moguće je analizirati prosječne vrijednosti emocija unutar svakog klastera kako bi se razumjelo koje karakteristike definiraju pojedinu skupinu. Tablica prosječnih vrijednosti emocija po klasteru omogućuje uvid u tipične emocionalne obrasce korporativnih narativa:
cluster_profiles <- as.data.frame(emotion_scaled) %>%
mutate(cluster = factor(km_result$cluster)) %>%
group_by(cluster) %>%
summarise(across(everything(), mean), .groups = "drop")
cluster_profiles
## # A tibble: 3 × 11
## cluster anger anticipation fear joy negative positive sadness surprise
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 -0.0830 1.42 -0.303 1.54 0.630 1.27 0.412 1.65
## 2 2 -0.591 -0.752 -0.509 -0.570 -0.737 -0.777 -0.551 -0.581
## 3 3 1.56 0.459 1.58 -0.112 1.21 0.671 0.965 -0.196
## # ℹ 2 more variables: trust <dbl>, disgust <dbl>
Na grafu svaka točka predstavlja jednu tvrtku, a boja označava klaster kojem pripada.
Rezultati pokazuju da se analizirane kompanije mogu podijeliti u tri skupine prema emocionalnom tonu njihovih organizacijskih opisa. Klasteri se razlikuju prvenstveno prema intenzitetu pozitivnih emocija, razini anticipacije i povjerenja te relativnoj prisutnosti negativnih emocija.
Prvi klaster, u kojem se nalaze tvrtke Kaufland i Zagrebačka banka, karakterizira izrazito visok udio pozitivnih emocija, uz izbjegavanje negativnih emocija (anger i fear). Prosječne vrijednosti za kategorije positive, joy, trust i anticipation u ovom su klasteru znatno više nego u ostalim skupinama. Takav emocionalni profil sugerira komunikacijski stil koji snažno naglašava uspjeh, razvoj i organizacijske vrijednosti. U ovakvim narativima organizacije često ističu rast poslovanja, inovacije, zadovoljstvo kupaca ili pozitivne učinke poslovanja na zajednicu. Zbog snažnog naglašavanja pozitivnih elemenata ovaj se tip komunikacije može opisati kao optimistični ili promotivni narativ, u kojem organizacija aktivno gradi pozitivan identitet i naglašava vlastitu uspješnost.
Drugi klaster obuhvaća većinu analiziranih organizacija - HEP, Konzum, Petrol, Plodine i PPD. Emocionalni profil u ovom klasteru pokazuje ispodprosječnu izraženost emocija. U usporedbi s prvim i trećim klasterom, intenzitet pozitivnih emocija poput joy, trust i anticipation znatno je niži, dok su negativne emocije također ispodprosječno zastupljene. Takav obrazac upućuje na komunikaciju koja je prvenstveno informativna i institucionalna, s naglaskom na opis organizacije, njezine djelatnosti, povijest ili strukture poslovanja. U tim tekstovima emocionalni ton nije dominantan element komunikacije, nego se organizacija predstavlja kroz činjenice, organizacijsku povijest i osnovne informacije o poslovanju. Ovaj tip narativa može se opisati kao neutralni - institucionalni ili tehnički korporativni stil komunikacije.
Treći klaster, koji uključuje kompanije INA i Lidl, karakterizira relativno visok intenzitet emocija općenito, izuzev radosti (joy) i iznenađenja (surprise). U ovom klasteru pozitivne emocije poput trust i anticipation te pozitivnog sentimenta, imaju vrijednosti iznad prosjeka, no niže nego u opisima prvog klastera. Istodobno su povišene i neke negativne emocije poput anger, fear i sadness. Takva kombinacija upućuje na komunikaciju koja koristi širi raspon emocionalnih izraza. Ovakav obrazac može se pojaviti u tekstovima koji ne naglašavaju samo pozitivne aspekte organizacije, nego također referiraju na izazove, odgovornost, sigurnost, regulatorne okvire, upravljanje rizicima ili druge kompleksnije teme poslovanja. Takav stil komunikacije može se opisati kao emocionalno intenzivan ili kontrastni narativ, u kojem se pozitivne poruke o razvoju i uspjehu kombiniraju s referencama na rizike, odgovornost, održivost ili društvene izazove.
Dobiveni klasteri ne predstavljaju strogo definirane kategorije, nego analitičke skupine temeljene na sličnosti emocionalnih profila. Budući da se analiza temelji na relativno malom uzorku organizacija, rezultate prije svega treba promatrati kao ilustraciju metodološkog pristupa. Ipak, klasteriranje jasno pokazuje da se korporativni narativi razlikuju u načinu na koji koriste emocionalni ton, čime sentiment analiza prelazi iz jednostavne procjene polariteta prema tipologiji organizacijskih komunikacijskih stilova.
Drugi pristup analizi sentimenata temelji se na modelima strojnog učenja. U tom slučaju model uči prepoznavati sentiment iz skupa tekstova.
U slučaju primjene strojnog učenja, model se trenira na skupu tekstova koji su ručno označeni (engl. labeled data). Na primjer, svaka recenzija (ili tekst) može biti označena kao pozitivna ili negativna. Na temelju takvih primjera model uči koje riječi i obrasci u tekstu najčešće razlikuju pozitivne i negativne tekstove. Takav pristup naziva se nadzirano učenje (supervised learning), jer model uči uz pomoć unaprijed poznatih oznaka.
Suprotno tome, nenadzirane metode (unsupervised learning) pokušavaju otkriti strukturu u tekstu bez prethodno označenih podataka. Takve metode mogu identificirati, primjerice, tematske skupine dokumenata ili latentne strukture u tekstu.
U praksi se oba pristupa često koriste zajedno. U ovoj lekciji fokus je na leksikonskom pristupu, jer je metodološki jednostavniji i transparentniji. Ipak, kako bi se razumjela logika naprednijih metoda analize teksta, u nastavku ćemo ilustrirati dva osnovna pristupa strojnog učenja.
Kako bismo ilustrirali osnovnu ideju nadziranog učenja, koristimo mali skup tekstova s unaprijed označenim sentimentom.
Cilj modela je naučiti koje riječi i kombinacije riječi najčešće signaliziraju pozitivan ili negativan sentiment.
Za demonstraciju koristimo naivni Bayesov klasifikator, jedan od klasičnih i često korištenih modela u obradi teksta.
library(tidyverse)
library(tidytext)
library(caret)
library(e1071)
# Primjer označenih tekstova (pozitivno = 1, negativno = 0)
primjeri <- tibble(
tekst = c(
"excellent quality and great service",
"wonderful experience highly recommend",
"outstanding performance and reliable",
"poor quality and bad experience",
"terrible service would not recommend",
"disappointing and unreliable product"),
sentiment = factor(c(1, 1, 1, 0, 0, 0), labels = c("negativno", "pozitivno"))
)
primjeri
## # A tibble: 6 × 2
## tekst sentiment
## <chr> <fct>
## 1 excellent quality and great service pozitivno
## 2 wonderful experience highly recommend pozitivno
## 3 outstanding performance and reliable pozitivno
## 4 poor quality and bad experience negativno
## 5 terrible service would not recommend negativno
## 6 disappointing and unreliable product negativno
# Tokenizacija i document-term matrix
dtm <- primjeri %>%
mutate(company = row_number()) %>%
unnest_tokens(word, tekst) %>%
anti_join(stop_words_custom, by = "word") %>%
count(company, word) %>%
pivot_wider(names_from = word, values_from = n, values_fill = 0)
dtm
## # A tibble: 6 × 18
## company excellent quality service experience highly recommend wonderful
## <int> <int> <int> <int> <int> <int> <int> <int>
## 1 1 1 1 1 0 0 0 0
## 2 2 0 0 0 1 1 1 1
## 3 3 0 0 0 0 0 0 0
## 4 4 0 1 0 1 0 0 0
## 5 5 0 0 1 0 0 1 0
## 6 6 0 0 0 0 0 0 0
## # ℹ 10 more variables: outstanding <int>, performance <int>, reliable <int>,
## # bad <int>, poor <int>, not <int>, terrible <int>, disappointing <int>,
## # product <int>, unreliable <int>
# Dodaj oznake sentimenta
dtm_labeled <- dtm %>%
left_join(primjeri %>% mutate(company = row_number()) %>% select(company, sentiment),
by = "company") %>%
select(-company)
# Naivni Bayes klasifikator
model_nb <- naiveBayes(sentiment ~ ., data = dtm_labeled)
predikcije <- predict(model_nb, dtm_labeled)
# Matrica konfuzije
confusionMatrix(predikcije, dtm_labeled$sentiment)
## Confusion Matrix and Statistics
##
## Reference
## Prediction negativno pozitivno
## negativno 3 0
## pozitivno 0 3
##
## Accuracy : 1
## 95% CI : (0.5407, 1)
## No Information Rate : 0.5
## P-Value [Acc > NIR] : 0.01563
##
## Kappa : 1
##
## Mcnemar's Test P-Value : NA
##
## Sensitivity : 1.0
## Specificity : 1.0
## Pos Pred Value : 1.0
## Neg Pred Value : 1.0
## Prevalence : 0.5
## Detection Rate : 0.5
## Detection Prevalence : 0.5
## Balanced Accuracy : 1.0
##
## 'Positive' Class : negativno
##
Matrica konfuzije prikazuje usporedbu između stvarnih oznaka sentimenata (Reference) i predikcija modela (Prediction).
U ovom primjeru redovi predstavljaju predikcije modela, dok stupci predstavljaju stvarne oznake u podacima.
| stvarno negativno | stvarno pozitivno | |
|---|---|---|
| predviđeno negativno | 3 | 0 |
| predviđeno pozitivno | 0 | 3 |
Iz tablice vidimo da je model:
Drugim riječima, sve se vrijednosti nalaze na glavnoj dijagonali matrice, što znači da su predikcije potpuno točne.
Accuracy = 1 - Accuracy predstavlja udio točnih klasifikacija. Vrijednost 1 znači da je model točno klasificirao sve tekstove u uzorku.
Sensitivity = 1 - Sensitivity (osjetljivost) pokazuje koliki udio stvarno negativnih tekstova model uspijeva ispravno prepoznati.
Specificity = 1 - Specificity pokazuje koliki udio pozitivnih tekstova model ispravno klasificira kao pozitivne.
Kappa = 1 - Kappa mjeri koliko je model bolji od slučajnog pogađanja. Vrijednost 1 označava savršeno slaganje između predikcija i stvarnih oznaka.
Savršeni rezultat u ovom primjeru ne znači da je model nužno vrlo dobar. Budući da je skup podataka vrlo mali (samo šest tekstova), model može lako zapamtiti obrasce u podacima. U stvarnim analizama modeli se treniraju i testiraju na znatno većim skupovima podataka.
Važno je naglasiti da je ovaj primjer vrlo mali i služi isključivo za ilustraciju logike modela. U stvarnim primjenama modeli se treniraju na znatno većim korpusima tekstova.
Nesupervizirane metode ne koriste unaprijed označene podatke, nego pokušavaju identificirati latentne strukture u tekstu.
Jedna od najpoznatijih metoda u analizi teksta je Latent Dirichlet Allocation (LDA). LDA pretpostavlja da se svaki dokument sastoji od kombinacije nekoliko tema, dok je svaka tema definirana distribucijom riječi.
Drugim riječima, model pokušava odgovoriti na pitanje: Koje se skupine riječi u tekstu pojavljuju zajedno i mogu predstavljati istu temu?
Važno je naglasiti da LDA ne analizira sentiment, nego tematsku strukturu teksta. Ili, još preciznije - LDA je ovdje kao ilustracija nenadziranog text mining pristupa, a ne sentiment metode. Ipak, takve metode često predstavljaju prvi korak u istraživanju većih tekstualnih korpusa, pa će se ovdje prikazati mini-primjer.
library(topicmodels)
library(tidytext)
# Priprema document-term matrice iz tokens
dtm_lda <- tokens %>%
count(company, word) %>%
cast_dtm(company, word, n)
dtm_lda
## <<DocumentTermMatrix (documents: 10, terms: 1548)>>
## Non-/sparse entries: 2607/12873
## Sparsity : 83%
## Maximal term length: 16
## Weighting : term frequency (tf)
# LDA s k temama
set.seed(42)
lda_model <- LDA(dtm_lda, k = 3, control = list(seed = 42))
# Najvažnije riječi po temi
teme <- tidy(lda_model, matrix = "beta")
top_words <- teme %>%
group_by(topic) %>%
slice_max(beta, n = 10) %>%
ungroup()
top_words
## # A tibble: 30 × 3
## topic term beta
## <int> <chr> <dbl>
## 1 1 kaufland 0.0235
## 2 1 award 0.0202
## 3 1 company 0.0168
## 4 1 product 0.0160
## 5 1 quality 0.0136
## 6 1 food 0.0108
## 7 1 croatia 0.0108
## 8 1 employee 0.00984
## 9 1 business 0.00971
## 10 1 croatian 0.00795
## # ℹ 20 more rows
top_words %>%
mutate(term = reorder_within(term, beta, topic)) %>%
ggplot(aes(x = term, y = beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~topic, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
labs(title = "Top 10 riječi po LDA temi",
x = NULL, y = "Beta (vjerojatnost riječi u temi)") +
theme_minimal()
Nakon treniranja modela možemo izdvojiti riječi koje imaju najveću vjerojatnost pripadnosti pojedinoj temi. Te riječi pomažu u interpretaciji tema koje je model identificirao.
Preciznije, rezultati LDA modela prikazuju riječi koje imaju najveću vjerojatnost pripadnosti pojedinoj temi. Na grafu svaki stupčasti dijagram predstavlja jednu temu, dok stupci prikazuju riječi s najvećom vrijednošću parametra \(\beta\), koji označava vjerojatnost da se određena riječ pojavljuje unutar određene teme. Što je vrijednost \(\beta\) veća, to je riječ tipičnija za tu temu.
Važno je naglasiti da LDA ne daje izravne nazive tema. Model identificira skupine riječi koje se statistički često pojavljuju zajedno, a istraživač zatim interpretira značenje tih skupina. Također, nazivi kompanija mogu dominirati temama i da ih je u ozbiljnijoj analizi često korisno ukloniti iz tokena prije modeliranja. Budući da se na grafu pojavljuju riječi poput ina, lidl, kaufland, netko bi mogao pomisliti da je to “prava tema”, a zapravo je dijelom i artefakt korpusa.
U prvoj temi pojavljuju se riječi poput company, business, overview, quality, compliance i principles. Ove riječi često se nalaze u dijelovima korporativnih stranica koji opisuju organizaciju, njezine vrijednosti ili načela poslovanja. Takav skup riječi upućuje na temu koja se može interpretirati kao opći organizacijski opis i korporativna načela poslovanja.
U takvim tekstovima organizacije obično predstavljaju svoju strukturu, poslovnu filozofiju, standarde kvalitete i regulatorne okvire u kojima posluju.
Druga tema uključuje riječi poput oil, production, business, company te nazive organizacija poput ina ili banka. Takav skup pojmova sugerira tekstove koji se odnose na osnovne poslovne aktivnosti organizacija, poput proizvodnje, energetike ili financijskih usluga. Ova tema može se interpretirati kao operativni ili sektorski opis poslovanja, odnosno dio korporativnog narativa u kojem organizacije opisuju svoje ključne djelatnosti i tržišni kontekst.
Treća tema sadrži riječi poput award, products, employees, management i health. Ove riječi često se pojavljuju u dijelovima tekstova koji naglašavaju organizacijska postignuća, razvoj zaposlenika, održivost ili upravljačku strukturu.
Takav skup riječi upućuje na temu koja se može interpretirati kao reputacijski narativ, odnosno dio komunikacije u kojem organizacije ističu nagrade, organizacijsku kulturu ili brigu za zaposlenike i društvenu zajednicu.
Važno je naglasiti da LDA analiza identificira statističke obrasce u raspodjeli riječi, a ne jasno definirane semantičke kategorije. Zbog toga interpretacija tema uvijek uključuje određeni stupanj istraživačke procjene.
U ovom primjeru korpus je relativno malen, pa rezultati služe prvenstveno kao ilustracija metode. U većim tekstualnim zbirkama teme bi obično bile jasnije diferencirane i stabilnije.
U kontekstu analize mreža sentiment može biti povezan s odnosima između aktera. U takvim analizama sentiment se ne promatra samo kao svojstvo pojedinog teksta, nego i kao karakteristika komunikacije između čvorova mreže.
Primjerice, moguće je analizirati sentiment poruka koje jedan korisnik upućuje drugome. Ako su poruke pretežno pozitivne, veza između ta dva aktera može se interpretirati kao pozitivna interakcija. Ako su poruke pretežno negativne, veza može ukazivati na konflikt ili kritiku.
Na taj način sentiment analiza omogućuje proučavanje emocionalne strukture mreže, odnosno načina na koji se pozitivni i negativni stavovi šire kroz relacijske strukture. Takve analize mogu otkriti kohezivne zajednice, konfliktne skupine ili utjecajne aktere koji šire određene emocionalne tonove unutar mreže.
Kako bi se ilustrirala ideja sentimenta u mrežama, zamislimo jednostavnu mrežu komunikacije između korisnika društvene mreže. Svaka poruka predstavlja usmjerenu vezu između pošiljatelja i primatelja.
messages <- tibble(
from = c("A","A","B","C","D","E","F","G","B","D"),
to = c("B","C","D","E","F","G","A","C","E","B"),
text = c(
"great collaboration on the project",
"excellent analysis and presentation",
"this is a terrible decision and consequences will be devastating",
"good results so far, let's hope for the best",
"very disappointing outcome",
"outstanding teamwork and support",
"bad coordination in the last phase",
"very promising results",
"poor communication in the team",
"great improvement compared to last year"
)
)
messages
## # A tibble: 10 × 3
## from to text
## <chr> <chr> <chr>
## 1 A B great collaboration on the project
## 2 A C excellent analysis and presentation
## 3 B D this is a terrible decision and consequences will be devastating
## 4 C E good results so far, let's hope for the best
## 5 D F very disappointing outcome
## 6 E G outstanding teamwork and support
## 7 F A bad coordination in the last phase
## 8 G C very promising results
## 9 B E poor communication in the team
## 10 D B great improvement compared to last year
Najprije izračunavamo sentiment svake poruke koristeći leksikonski pristup.
sentiment_messages <- messages %>%
unnest_tokens(word, text) %>%
inner_join(get_sentiments("bing"), by = "word") %>%
mutate(score = if_else(sentiment == "positive", 1, -1)) %>% # samo ukupni score radi jednostavnosti
group_by(from, to) %>%
summarise(sentiment_score = sum(score), .groups = "drop")
sentiment_messages
## # A tibble: 10 × 3
## from to sentiment_score
## <chr> <chr> <dbl>
## 1 A B 1
## 2 A C 1
## 3 B D -2
## 4 B E -1
## 5 C E 2
## 6 D B 2
## 7 D F -1
## 8 E G 2
## 9 F A -1
## 10 G C 1
Dobiveni rezultat predstavlja sentiment veze između aktera. Pozitivna vrijednost označava pretežno pozitivan ton komunikacije, dok negativna vrijednost upućuje na kritiku ili konflikt.
Takvi se rezultati mogu interpretirati kao emocionalne težine veza u mreži, što omogućuje analizu strukture pozitivnih i negativnih interakcija.
U ovom primjeru sentiment se najprije računa na razini pojedine poruke, a zatim agregira na razini veze između dvaju aktera. Time ista veza može sažeti više poruka i predstavljati njihov prevladavajući emocionalni ton. Riječ je o pojednostavljenoj ilustraciji, ali ona jasno pokazuje kako se tekstualni sadržaj može prevesti u mrežni atribut.
U mrežnoj analizi sentiment se može koristiti kao atribut veze (edge attribute). Na primjer, pozitivne veze mogu predstavljati podršku ili suradnju, dok negativne veze mogu označavati sukob ili kritiku.
Na taj način moguće je analizirati kako se emocionalni ton komunikacije raspoređuje unutar mreže. Takva analiza može otkriti:
library(igraph)
##
## Attaching package: 'igraph'
## The following objects are masked from 'package:lubridate':
##
## %--%, union
## The following objects are masked from 'package:dplyr':
##
## as_data_frame, groups, union
## The following objects are masked from 'package:purrr':
##
## compose, simplify
## The following object is masked from 'package:tidyr':
##
## crossing
## The following object is masked from 'package:tibble':
##
## as_data_frame
## The following objects are masked from 'package:stats':
##
## decompose, spectrum
## The following object is masked from 'package:base':
##
## union
g <- igraph::graph_from_data_frame(sentiment_messages, directed = TRUE)
E(g)$sent <- sentiment_messages$sentiment_score
E(g)$col <- ifelse(E(g)$sent >0, "green", "red")
plot(g,
vertex.size = 15,
vertex.color = "grey80",
edge.color = E(g)$col,
edge.arrow.size = 0.5,
edge.width = abs(E(g)$sent))
U stvarnim istraživanjima ovakav se pristup često primjenjuje na velikim skupovima komunikacijskih podataka, poput poruka na društvenim mrežama, e-mail komunikacije ili online rasprava. U takvim slučajevima sentiment analiza omogućuje povezivanje tekstualnog sadržaja i mrežne strukture, čime se otvara mogućnost analize širenja emocija i stavova kroz društvene mreže.
Kao primjer primjene analize sentimenata može se razmotriti istraživanje predstavljeno u članku “Corporate Self-Representation on Official Websites: Strategic Signifiers and Sentiment Profiles” (Kostelić i Gonan Božac, 2026). Istraživanje analizira način na koji se poduzeća predstavljaju na vlastitim službenim web-stranicama te kakav emocionalni ton koriste u komunikaciji s javnošću.
Autori su prikupili uzorak 100 tekstualnih opisa iz “About us” sekcije službenih web-stranica poduzeća u Hrvatskoj s Liderove liste 1000 najvećih. Kao i u ovoj lekciji, ovi tekstovi predstavljaju posebno zanimljiv izvor podataka jer su namjerno konstruirani komunikacijski sadržaj: organizacije u njima pokušavaju oblikovati vlastiti identitet, naglasiti strateške ciljeve i poslati signal o vrijednostima koje zastupaju.
Cilj istraživanja bio je identificirati:
U analizi je primijenjen leksikonski pristup sentiment analizi. Tekstovi su najprije prikupljeni na hrvatskom jeziku, a zatim strojno prevedeni na engleski kako bi se mogli koristiti standardni sentiment leksikoni razvijeni za engleski jezik.
Sentiment je analiziran pomoću tri poznata rječnika:
Na temelju tih rječnika izračunati su:
Rezultati su zatim agregirani na razini poduzeća, što znači da je svaki dokument (opis poduzeća) dobio vlastiti sentiment profil.
Izvor: Kostelić i Gonan Božac,
2026
Analiza je pokazala da u korporativnim narativima dominira pozitivan emocionalni ton, osobito emocije povjerenja (trust) i anticipacije (anticipation). To upućuje na komunikacijsku strategiju kojom organizacije nastoje naglasiti stabilnost, pouzdanost i budući razvoj.
Istraživanje slijedi relativno jasan pipeline analize teksta, koji se može generalizirati za slične projekte.
Prvi korak je prikupljanje tekstualnog korpusa.
U ovom slučaju:
Rezultat je skup tekstova u kojem svaki dokument predstavlja jednu organizaciju.
Budući da su dostupni sentiment leksikoni uglavnom razvijeni za engleski jezik, tekstovi su:
Ovaj korak ilustrira važan metodološki problem višejezične analize sentimenata.
Sljedeći korak uključuje tipične postupke pripreme teksta:
Ovaj korak omogućuje povezivanje riječi s elementima sentiment leksikona.
Nakon tokenizacije riječi se uspoređuju s rječnicima sentimenta.
Za svaku riječ dobiva se:
Rezultati se zatim agregiraju na razini dokumenta kako bi se dobio sentiment profil pojedinog poduzeća.
Za svaku organizaciju formira se vektor emocija, primjerice:
Vrijednosti tih emocija normaliziraju se kako bi se mogli uspoređivati različiti dokumenti.
U završnoj fazi istraživanja primijenjena je k-means klasterska analiza nad emocionalnim vektorima i izvršena je validacija klasteringa.
Izvor: Kostelić i Gonan Božac,
2026
Na temelju toga identificirana su tri tipična sentiment profila korporativnih narativa:
Ova analiza pokazuje da se organizacije razlikuju u načinu na koji koriste emocionalni ton u komunikaciji.
Rezultati sugeriraju da emocije poput povjerenja i iščekivanja mogu funkcionirati kao strateški komunikacijski signali. Organizacije time nastoje signalizirati pouzdanost, stabilnost i orijentaciju na budućnost.
Istodobno, pojedine organizacije koriste transparentniji ton koji uključuje i negativne aspekte poslovanja, ali bez narušavanja ukupno pozitivnog sentimenta.
Autori zaključuju da analiza sentimenata može poslužiti kao analitički alat za audit korporativne komunikacije, odnosno za procjenu usklađenosti organizacijskog narativa s očekivanjima dionika.
Sirovi podaci i kod dostupni su putem Open Science Framework-a.
Ova je studija korisna jer pokazuje da sentiment ne mora nužno biti samo binarna podjela na pozitivno i negativno, nego se može operacionalizirati i kao višedimenzionalno raspoloženje.
Bollen, Mao i Zeng (2011) polaze od ideje iz bihevioralne ekonomije da emocije utječu na donošenje odluka pojedinaca, pa postavljaju pitanje vrijedi li to i na razini društva. Drugim riječima, ako se raspoloženje velikog broja ljudi može očitati iz objava na društvenim mrežama, može li ono biti povezano s kretanjem burzovnih indeksa? U svom radu analiziraju dnevne objave s Twittera i iz njih konstruiraju vremenske serije kolektivnog raspoloženja, koje potom uspoređuju s dnevnim promjenama indeksa Dow Jones Industrial Average (DJIA).
Za analizu teksta koriste dva različita alata. Prvi je OpinionFinder, koji mjeri opći polaritet poruka, odnosno odnos pozitivnog i negativnog sentimenta. Drugi je Google-Profile of Mood States (GPOMS), koji ne svodi raspoloženje samo na pozitivno i negativno, nego ga razlaže na šest dimenzija: Calm, Alert, Sure, Vital, Kind i Happy. Autori najprije provjeravaju imaju li tako dobivene mjere smisla tako da gledaju reagiraju li očekivano na poznate društvene događaje, poput predsjedničkih izbora i Dana zahvalnosti 2008. godine. Nakon toga ispituju prediktivnu vrijednost tih vremenskih nizova pomoću Grangerove kauzalnosti i modela Self-Organizing Fuzzy Neural Network, kako bi utvrdili poboljšava li uključivanje raspoloženja predviđanje dnevnog smjera kretanja DJIA-a.
Glavni nalaz rada jest da nisu sve dimenzije raspoloženja jednako korisne. Jednostavan pozitivno-negativan sentiment nije se pokazao osobito korisnim za predviđanje, ali su neke specifične dimenzije raspoloženja, osobito one povezane sa smirenošću, dale bolji doprinos modelu. Autori izvještavaju da se uključivanjem određenih dimenzija javnog raspoloženja može poboljšati točnost predviđanja smjera dnevnih promjena DJIA-a te smanjiti pogrešku predviđanja. Kao metodološka pouka, ova studija lijepo pokazuje da je u analizi teksta često korisnije promatrati više latentnih emocionalnih dimenzija nego samo grubi binarni sentiment, osobito kada tekst povezujemo s kompleksnim društvenim ili ekonomskim ishodima.
Unatoč jednostavnosti implementacije, analiza sentimenata suočava se s nizom metodoloških ograničenja.
Jedan od čestih problema je ironija i sarkazam. Rečenice poput „Baš odlično, opet je sustav pao” sadrže pozitivne riječi, ali stvarno značenje je negativno. Leksikonske metode često ne prepoznaju takve obrate značenja.
Drugi važan problem su negacije. Negacija može potpuno promijeniti polaritet izraza. Na primjer, riječ „dobro” ima pozitivan sentiment, ali izraz „nije dobro” ima negativno značenje.
Treći problem odnosi se na domenske pomake. Riječi mogu imati različito značenje u različitim kontekstima. Izraz „agresivan marketing” može imati pozitivnu konotaciju u poslovnom kontekstu, dok izraz „agresivno ponašanje” u društvenom kontekstu ima negativnu konotaciju. Slično, na primjer, riječ waste ima negativan predznak sentimenta, a često će se pojavljivati u temama o održivosti i načinima zbrinjavanja otpada i recikliranju - što zapravo predstavlja pozitivne aktivnosti.
Dodatni izazov predstavlja višejezičnost. Sentiment leksikoni najčešće su razvijeni za engleski jezik, dok za druge jezike često postoje ograničeni resursi. Razlike u morfologiji i sintaksi dodatno otežavaju analizu.
Rezultati analize sentimenata trebaju biti validirani kako bi se procijenila njihova pouzdanost.
Jedan od najjednostavnijih pristupa je ručna provjera uzorka tekstova. Analitičar nasumično odabire dio tekstova i uspoređuje automatsku procjenu sentimenta s vlastitom interpretacijom.
Drugi pristup je usporedba različitih sentiment leksikona. Ako različiti leksikoni daju slične rezultate, može se zaključiti da je procjena relativno stabilna.
Validacija je važan korak jer automatske metode analize teksta uvijek uključuju određenu razinu pogreške i aproksimacije.
Sentiment: Emocionalna/polarna ocjena teksta.
Polaritet: Smjer sentimenta (pozitivno/negativno).
Pozitivan sentiment: Tekst s prevladavajućim pozitivnim izrazima.
Negativan sentiment: Tekst s prevladavajućim negativnim izrazima.
Neutralan sentiment: Tekst bez jasne polarne orijentacije.
Leksikonski pristup: Sentiment iz rječnika označenih riječi.
Strojno učenje: Model uči sentiment iz označenih primjera.
Supervizirana analiza: Učenje na labeliranim podacima.
Nesupervizirana analiza: Učenje bez labela (npr. clustering) ili heuristike.
Sentiment score: Numerička mjera ukupnog sentimenta.
Agregacija: Sažimanje sentimenta po dokumentu/korisniku/vremenu.
Kontekstualni sentiment: Sentiment ovisi o kontekstu i domeni.
Ironija i sarkazam: Obrt značenja koji često ruši leksikonske metode.
Domensko prilagođavanje: Prilagodba rječnika/modela specifičnoj temi.
Višejezični sentiment: Analiza na više jezika; problem rječnika i morfologije.
Valencija: Intenzitet emocije/polariteta.
Emocije: Specifične kategorije (npr. radost, strah) umjesto samo polariteta.
Subjektivnost: Koliko tekst izražava osobni stav vs. činjenice.
Ograničenja leksikona: Nepokrivenost, polisemičnost, kontekst, negacije.
Interpretacija rezultata: Prevođenje metrika u zaključke uz oprez.
Sentiment u mrežama: Povezivanje sentimenta s odnosima.
Bollen, J., Mao, H., & Zeng, X. (2011). Twitter mood predicts the stock market. Journal of Computational Science, 2(1), 1-8. https://doi.org/10.1016/j.jocs.2010.12.007
Csárdi, G., & Nepusz, T. (2006). The igraph software package for complex network research. Semantic Scholar.
Grün, B., & Hornik, K. (2011). topicmodels: An R package for fitting topic models. Journal of Statistical Software, 40(13), 1–30.
Kassambara A. & Mundt F. (2026). factoextra: Extract and Visualize the Results of Multivariate Data Analyses. R package version 2.0.0.999. With contributions from Laszlo Erdey (Faculty of Economics and Business, University of Debrecen, Hungary), https://CRAN.R-project.org/package=factoextra.
Kostelić, K., & Gonan Božac, M. (2026). Corporate Self-Representation on Official Websites: Strategic Signifiers and Sentiment Profiles. Administrative Sciences, 16(3), 140. https://doi.org/10.3390/admsci16030140
Kwartler, T. (2017). Text mining in practice with R. Wiley.
Mohammad, S.M. and Turney, P.D. (2013), CROWDSOURCING A WORD–EMOTION ASSOCIATION LEXICON. Computational Intelligence, 29: 436-465. https://doi.org/10.1111/j.1467-8640.2012.00460.x
Nielsen, F. Å. (2011). A new ANEW: Evaluation of a word list for sentiment analysis in microblogs. Proceedings of the ESWC2011 Workshop on ‘Making Sense of Microposts’: Big things come in small packages, 93-98. https://doi.org/10.48550/arXiv.1103.2903
Pedersen, T. L. (2023). ggraph: An implementation of grammar of graphics for graphs and networks. https://CRAN.R-project.org/package=ggraph
Silge, J. & Robinson, D. (2016). tidytext: Text Mining and Analysis Using Tidy Data Principles in R. Journal of Open Source Software, 1(3), 37, doi:10.21105/joss.00037
Silge, J. & Robinson, D. (2025). Text Mining with R. https://www.tidytextmining.com/
Wickham et al. (2019). Welcome to the Tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer.
a
a, c, e
a
a
a
a, e
a
a
a
a
a, b
a
a
a
a
a
a
a, b
a
a, b