Ishodi učenja:
Konstruirati mrežu ko-pojavljivanja pojmova i odabrati prozor konteksta.
Detektirati tematske zajednice i interpretirati “centralne” pojmove.
Usporediti mreže između korpusa (npr. prije/poslije događaja).
Integrirati sentiment i tekst-mrežu u koherentnu analitičku priču.
U prethodnim lekcijama mreže su bile konstruirane iz odnosa među osobama, organizacijama, državama ili drugim društvenim akterima. U ovoj lekciji fokus se pomiče na tekst. Umjesto ljudi ili organizacija, čvorovi su sada riječi, pojmovi ili izrazi, a veze među njima označavaju da se ti pojmovi pojavljuju zajedno u istom kontekstu.
Takav pristup polazi od jednostavne, ali analitički vrlo snažne ideje: značenje u tekstu ne proizlazi samo iz toga koliko se često neka riječ pojavljuje, nego i iz toga s kojim se drugim riječima pojavljuje. Drugim riječima, tekst se može promatrati kao relacijski sustav. Kada riječi pretvorimo u čvorove, a njihovu zajedničku pojavu u veze, dobivamo mrežu teksta.
Ovakav pristup dobro se uklapa u širu logiku mrežne analize, koja naglašava da se struktura sustava najbolje razumije kada promatramo odnose među elementima, a ne samo njihove pojedinačne karakteristike. Ono što pritom radimo naziva se konstrukcija semantičke mreže. U njoj blizina čvorova ne označava fizičku blizinu, već semantičku povezanost — riječi koje se često pojavljuju zajedno vjerojatno pripadaju istom misaonom konceptu. U mrežnoj analizi upravo vizualizacija i strukturni prikaz pomažu uočiti obrasce koji nisu vidljivi iz samih deskriptivnih pokazatelja. Isti princip vrijedi i za tekst: frekvencije riječi daju korisnu osnovu, ali tek mrežni prikaz pokazuje kako su teme organizirane, koji su pojmovi jezgreni, a koji periferni.
U osnovnoj analizi teksta često se polazi od modela bag-of-words, u kojem se dokument promatra kao skup riječi bez reda i bez eksplicitnih odnosa među njima. Takav pristup vrlo je koristan za frekvencije, dokument-term matrice, TF i TF-IDF, klasifikaciju i mnoge druge postupke. Međutim, on ne opisuje strukturu povezanosti pojmova.
Mreže teksta nadopunjuju taj pristup. Umjesto da se pitamo samo koje su riječi najčešće, pitamo se:
U praktičnom smislu, tekstualna analiza obično prolazi kroz nekoliko povezanih koraka: definiranje problema, prikupljanje i organizaciju tekstova, izdvajanje značajki, analizu i interpretaciju nalaza. Mreže teksta ulaze upravo u fazu u kojoj iz prethodno pripremljenog korpusa želimo izgraditi analitički prikaz odnosa među pojmovima.
Osnovni odnos u mrežama teksta jest ko-pojavljivanje. Dva pojma se ko-pojavljuju ako se nalaze u istom definiranom kontekstu. Taj kontekst mora biti unaprijed određen, a upravo je njegov odabir jedna od ključnih metodoloških odluka.
Najčešće mogućnosti su:
Ako je kontekst vrlo širok, primjerice cijeli dokument, mreža će biti gušća i općenitija, ali će se izgubiti dio lokalnog značenja. Ako je kontekst uzak, primjerice prozor od 5 riječi, mreža će bolje hvatati neposredne semantičke veze, ali može postati osjetljivija na šum i stil pisanja.
Drugim riječima, mreža teksta nije “prirodno” zadana u podacima, nego se konstruira na temelju istraživačke odluke. Zbog toga pri interpretaciji uvijek treba imati na umu da struktura mreže ovisi o:
Iako ćemo se u ovoj lekciji najviše baviti mrežama ko-pojavljivanja, važno je razlikovati nekoliko srodnih pristupa.
Semantičke mreže povezuju pojmove na temelju značenja ili kontekstualnih odnosa. U praksi se često operacionaliziraju upravo kao mreže ko-pojavljivanja.
Tematske mreže naglašavaju grupiranje pojmova u tematske cjeline. U njima je poseban interes usmjeren na zajednice, klastere i jezgrene pojmove.
Diskursne mreže šire pristup tako da uz pojmove uključuju i aktere, tvrdnje ili pozicije u javnom diskursu. One su osobito korisne u analizi politike, medija i javnih rasprava.
Bibliometrijske mreže ne povezuju riječi unutar rečenica ili dokumenata, nego primjerice autore, radove, ključne riječi, sažetke ili citate. Njih ćemo na kraju lekcije spomenuti kao posebnu primjenu mrežne logike na tekstualno-znanstvene podatke.
Mreže teksta vrlo brzo postaju nepregledne. To nije pogreška, nego prirodna posljedica bogatstva jezika. Stoga je redukcija kompleksnosti sastavni dio analize, a ne neprihvatljivo “uljepšavanje”.
Najčešće strategije redukcije su:
Koherentna interpretacija obično uključuje tri koraka.
Prvo se identificiraju strukturno važni pojmovi i tematske zajednice. Time dobivamo odgovor na pitanje kako je diskurs organiziran.
Zatim se promatra gdje je prisutan pozitivan ili negativan sentiment. Time dobivamo odgovor na pitanje kako je afektivno obojen određeni dio diskursa.
Na kraju se oba sloja spajaju u narativ. Primjerice, može se pokazati da se u razdoblju nakon određenog događaja diskurs pomiče s tehnološko-inovacijskog klastera prema regulatorno-rizičnom klasteru, uz istodobni rast negativno obojenih pojmova. Tada mreža ne služi samo za opis riječi, nego za opis promjene značenjske strukture i tona rasprave.
S obzirom da se radi o vrlo širokom području koje nije moguće prikazati u samo jednoj lekciji, ovdje ćemo slijediti četiri glavna koraka:
Cilj nije samo nacrtati “lijepu mrežu”, nego razviti koherentnu analitičku priču. To znači da vizualizacija mora biti povezana s istraživačkim pitanjem, a mrežne mjere moraju biti interpretirane u kontekstu teme koju proučavamo.
Za potrebe ove lekcije, istražit ćemo temu umjetne inteligencije u kontekstu tržišta rada i poslova prije i nakon javnog lansiranja ChatGPT-a. Pritom ćemo koristiti The Conversation, koji je za ovu lekciju vrlo prikladan jer se njegovi članci mogu besplatno dijeliti pod licencom Creative Commons Attribution–NoDerivatives, a ta licenca traži atribuciju i zabranjuje distribuciju izmijenjenog materijala. Creative Commons pritom izričito navodi da se pri CC BY-ND moraju navesti autor i licenca te da se, ako je materijal remiksan, transformiran ili nadograđen, izmijenjena verzija ne smije distribuirati. Sama analiza teksta ne krši CC BY-ND licencu, ali važno je razumjeti što točno znači “NoDerivatives” i što se smatra distribucijom izmijenjenog djela.
Licenca Creative Commons Attribution–NoDerivatives (BY-ND) dopušta:
kopiranje i redistribuciju djela
korištenje u istraživanju i obrazovanju
citiranje i analizu
ali zabranjuje distribuciju izmijenjene verzije originalnog djela.
U istraživačkoj metodologiji upotreba tekstova za ovu svrhu smatra se non-expressive use ili computational analysis. Tekst se koristi kao ulazni podatak, ali rezultat nije reprodukcija djela.
Pod tom licencom ne smije se javno objaviti:
skraćena verzija članka
prevedena verzija
prerađeni ili “remiksani” članak
članak kojem su dodani ili uklonjeni dijelovi.
Zbog toga se ovdje neće prikazivati dijelovi teksta, nego samo linkovi na originalne tekstove.
Pretražit ćemo isto izdavačko okruženje, što je važno jer se time smanjuje šum koji bi nastao miješanjem različitih medijskih stilova te kreirati dva korpusa:
Korpus A (prije genAI): The Conversation, 2019–2022, članci s pojmovima artificial intelligence
Korpus B (poslije genAI): The Conversation, 2023–2025, članci s pojmovima artificial intelligence.
Ta je granica metodološki smislena jer je javno lansiranje ChatGPT-a krajem studenoga 2022. praktična točka nakon koje se medijski diskurs o AI-u vidljivo mijenja (ili barem očekujemo to vidjeti) prema generativnoj umjetnoj inteligenciji, LLM-ovima, autorskim pravima, obrazovanju, regulaciji i radu.
library(rvest)
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()
## ✖ readr::guess_encoding() masks rvest::guess_encoding()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(xml2)
query <- "artificial intelligence work"
n_pages <- 50
search_urls <- tibble(
page_no = 1:n_pages,
url = paste0(
"https://theconversation.com/europe/search?page=",
page_no,
"&q=",
str_replace_all(query, " ", "+")
)
)
head(search_urls)
## # A tibble: 6 × 2
## page_no url
## <int> <chr>
## 1 1 https://theconversation.com/europe/search?page=1&q=artificial+intelli…
## 2 2 https://theconversation.com/europe/search?page=2&q=artificial+intelli…
## 3 3 https://theconversation.com/europe/search?page=3&q=artificial+intelli…
## 4 4 https://theconversation.com/europe/search?page=4&q=artificial+intelli…
## 5 5 https://theconversation.com/europe/search?page=5&q=artificial+intelli…
## 6 6 https://theconversation.com/europe/search?page=6&q=artificial+intelli…
Dohvat linkova sa svake stranice rezultata:
get_search_links <- function(url) {
page <- read_html(url)
links <- page |>
html_elements("a[href]") |>
html_attr("href") |>
unique()
# relativne pretvori u apsolutne
links <- url_absolute(links, "https://theconversation.com")
# zadrži samo linkove koji izgledaju kao članci:
# The Conversation članci gotovo uvijek završavaju s -broj
links <- links |>
str_subset("^https://theconversation\\.com/.+-\\d+$")
tibble(article_url = unique(links))
}
article_links <- search_urls |>
mutate(data = map(url, possibly(get_search_links, otherwise = tibble(article_url = character())))) |>
unnest(data) |>
distinct(article_url)
head(article_links)
## X
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
## article_url
## 1 https://theconversation.com/australias-official-plan-for-ai-safety-isnt-much-more-than-a-single-dot-point-will-it-be-enough-276962
## 2 https://theconversation.com/profiles/jose-miguel-bello-y-villarino-585048
## 3 https://theconversation.com/profiles/henry-fraser-1331184
## 4 https://theconversation.com/probably-doesnt-mean-the-same-thing-to-your-ai-as-it-does-to-you-275626
## 5 https://theconversation.com/profiles/mayank-kejriwal-1213029
## 6 https://theconversation.com/ai-and-work-an-expert-assesses-how-far-this-revolution-still-has-to-run-277650
nrow(article_links)
## [1] 443
Pristojni dohvat članaka:
library(polite)
library(rvest)
library(tidyverse)
library(stringr)
session <- bow(
"https://theconversation.com",
user_agent = "SNA-course-text-network-analysis (educational use)"
)
safe_text1 <- function(page, css) {
node <- html_element(page, css)
if (is.null(node)) return(NA_character_)
html_text2(node)
}
safe_attr1 <- function(page, css, attr) {
node <- html_element(page, css)
if (is.null(node)) return(NA_character_)
html_attr(node, attr)
}
get_article <- function(url, session) {
page <- nod(session, path = url) |> scrape()
title <- safe_text1(page, "h1")
text_nodes <- html_elements(page, "div[itemprop='articleBody'] p")
text <- if (length(text_nodes) == 0) {
NA_character_
} else {
text_nodes |>
html_text2() |>
paste(collapse = " ")
}
author_nodes <- html_elements(page, ".byline a, [rel='author']")
author <- if (length(author_nodes) == 0) {
NA_character_
} else {
author_nodes |>
html_text2() |>
first()
}
date <- safe_attr1(page, "time", "datetime")
tibble(
url = url,
title = title,
author = author,
date = date,
text = text
)
}
safe_get_article <- purrr::possibly(
get_article,
otherwise = tibble(
url = NA_character_,
title = NA_character_,
author = NA_character_,
date = NA_character_,
text = NA_character_
)
)
articles <- article_links |>
mutate(data = map(
article_url,
~{
Sys.sleep(2)
safe_get_article(.x, session)
}
)) |>
unnest(data)
articles_clean <- articles |>
filter(!is.na(url)) |>
filter(!is.na(title)) |>
filter(!is.na(text), text != "") |>
mutate(nchar_text = nchar(text)) |>
filter(nchar_text > 500)
str(articles_clean)
## 'data.frame': 200 obs. of 8 variables:
## $ X : int 1 2 3 4 5 6 7 8 9 10 ...
## $ article_url: chr "https://theconversation.com/australias-official-plan-for-ai-safety-isnt-much-more-than-a-single-dot-point-will-"| __truncated__ "https://theconversation.com/probably-doesnt-mean-the-same-thing-to-your-ai-as-it-does-to-you-275626" "https://theconversation.com/ai-and-work-an-expert-assesses-how-far-this-revolution-still-has-to-run-277650" "https://theconversation.com/time-to-retrain-how-to-future-proof-your-career-in-the-ai-age-276694" ...
## $ url : chr "https://theconversation.com/australias-official-plan-for-ai-safety-isnt-much-more-than-a-single-dot-point-will-"| __truncated__ "https://theconversation.com/probably-doesnt-mean-the-same-thing-to-your-ai-as-it-does-to-you-275626" "https://theconversation.com/ai-and-work-an-expert-assesses-how-far-this-revolution-still-has-to-run-277650" "https://theconversation.com/time-to-retrain-how-to-future-proof-your-career-in-the-ai-age-276694" ...
## $ title : chr "Australia<U+2019>s official plan for AI safety isn<U+2019>t much more than a single dot point. Will it be enough?" "<U+2018>Probably<U+2019> doesn<U+2019>t mean the same thing to your AI as it does to you" "AI and work: an expert assesses how far this revolution still has to run" "Time to retrain? How to future<U+2011>proof your career in the AI age" ...
## $ author : chr "Jos<U+00E9>-Miguel Bello y Villarino" "Mayank Kejriwal" "Vivek Soundararajan" "Kirk Chang" ...
## $ date : chr "2026-03-05T19:07:03Z" "2026-02-24T13:46:26Z" "2026-03-09T12:42:10Z" "2026-03-02T17:11:52Z" ...
## $ text : chr "Last week, one of Australia<U+2019>s leading artificial intelligence (AI) researchers, Toby Walsh, warned Austr"| __truncated__ "When a human says an event is <U+201C>probable<U+201D> or <U+201C>likely,<U+201D> people generally have a share"| __truncated__ "Every week brings fresh claims about AI transforming the workplace. A CEO declares a revolution. A think piece "| __truncated__ "These days, gen Z appears to be pivoting towards skilled trades, perhaps driven by a desire for <U+201C>AI-proo"| __truncated__ ...
## $ nchar_text : int 5868 3806 6007 5791 7353 4514 5816 6255 7891 5092 ...
Odvajamo članke po datumu:
docs <- articles_clean |>
mutate(
date = as.Date(substr(date, 1, 10)),
period = if_else(date <= as.Date("2022-11-30"), "prije", "poslije")
) |>
transmute(
doc_id = row_number(),
date,
period,
title,
url,
author,
text
)
docs |>
count(period)
## period n
## 1 poslije 122
## 2 prije 78
Provjera duplikata:
docs <- docs |>
distinct(url, .keep_all = TRUE) |>
distinct(title, .keep_all = TRUE)
docs |>
count(period)
## period n
## 1 poslije 122
## 2 prije 78
Čišćenje
library(stringi)
library(stringr)
docs_clean <- docs |>
mutate(
text = str_replace_all(text, "<U\\+[0-9A-F]{4}>", " "),
text = stri_replace_all_regex(text, "<U\\+([0-9A-Fa-f]+)>", "\\\\u$1"),
text = stri_trans_general(text, "latin-ascii"),
text = str_replace_all(text, "[0-9]", " "),
text = str_replace_all(text, "[[:punct:]]", " "),
text = str_squish(text)
)
docs_clean <- docs_clean |>
mutate(
text = str_to_lower(text)
)
docs_clean <- docs_clean |>
mutate(
text = str_replace_all(text, "[0-9]", " "),
text = str_replace_all(text, "[[:punct:]]", " ")
)
docs_clean$text |> str_subset("<U") # provjera
## character(0)
Imamo docs_clean sa stupcima:
doc_id — identifikator dokumentadate - datum objaveperiod — skupina ili razdoblje kojem dokument pripada
(Prije/ poslije)title - naslov dokumentaurl - poveznica dokumentaauthor - autor tekstatext — tekst dokumentaPretprocesiranje u tekstualnoj analizi nije tehnička formalnost, nego analitička odluka. Prejako čišćenje može ukloniti važne signale, a preslabo ostaviti previše šuma. U mrežama teksta najčešće se uklanjaju:
Za početak ćemo tekst rastaviti na tokene i ukloniti stop riječi.
library(tidytext)
tokens <- docs_clean |>
select(doc_id, period, text) |>
unnest_tokens(word, text)
library(tidytext)
data(stop_words)
stop_words <- stop_words %>%
filter(!word %in% c("no", "not", "nor", "never"))
tokens <- tokens %>%
anti_join(stop_words, by = "word")
Provjera najčešćih riječi
tokens |>
count(word, sort = TRUE) |>
slice_head(n = 10)
## word n
## 1 ai 2101
## 2 not 735
## 3 people 553
## 4 human 521
## 5 data 513
## 6 intelligence 395
## 7 technology 375
## 8 jobs 354
## 9 workers 349
## 10 research 326
Provjera po razdoblju
top_prije <- tokens |>
filter(period == "prije") |>
count(word, sort = TRUE) |>
slice_head(n = 20)
top_prije
## word n
## 1 ai 447
## 2 not 298
## 3 human 254
## 4 data 246
## 5 people 232
## 6 intelligence 230
## 7 jobs 210
## 8 google 202
## 9 artificial 172
## 10 world 168
## 11 learning 150
## 12 technology 145
## 13 future 144
## 14 workers 143
## 15 time 121
## 16 skills 117
## 17 systems 114
## 18 machine 111
## 19 research 110
## 20 job 108
top_poslije <- tokens |>
filter(period == "poslije") |>
count(word, sort = TRUE) |>
slice_head(n = 20)
top_poslije
## word n
## 1 ai 1654
## 2 not 437
## 3 people 321
## 4 data 267
## 5 human 267
## 6 technology 230
## 7 research 216
## 8 workers 206
## 9 time 203
## 10 tools 182
## 11 chatgpt 180
## 12 students 172
## 13 intelligence 165
## 14 learning 163
## 15 systems 162
## 16 artificial 148
## 17 tasks 147
## 18 jobs 144
## 19 future 130
## 20 found 124
Jedna od najvažnijih odluka jest kako definirati kontekst unutar kojeg dvije riječi smatramo povezanima. U ovoj lekciji koristit ćemo prozor od 10 tokena, što je dovoljno uzak kontekst da zadrži lokalne veze, ali i dovoljno širok da obuhvati tipične sintagmatske obrasce.
Ako dokument ima redoslijed tokena \(1, 2, 3, \dots, n\), tada prozor veličine \(w\) dijeli tekst na segmente unutar kojih promatramo zajedničku pojavu riječi. Ovdje je:
\[ w = 10 \]
gdje je:
Manji \(w\) daje lokalnije i strože veze, a veći \(w\) općenitije i gušće mreže.
Prvo ćemo svakom tokenu dodijeliti redni broj unutar dokumenta.
tokens <- tokens |>
group_by(doc_id) |>
mutate(token_id = row_number()) |>
ungroup()
Provjera:
tokens |>
filter(doc_id == 1) |>
slice_head(n = 15)
## # A tibble: 15 × 4
## doc_id period word token_id
## <int> <chr> <chr> <int>
## 1 1 poslije week 1
## 2 1 poslije australia 2
## 3 1 poslije leading 3
## 4 1 poslije artificial 4
## 5 1 poslije intelligence 5
## 6 1 poslije ai 6
## 7 1 poslije researchers 7
## 8 1 poslije toby 8
## 9 1 poslije walsh 9
## 10 1 poslije warned 10
## 11 1 poslije australia 11
## 12 1 poslije lack 12
## 13 1 poslije guardrails 13
## 14 1 poslije ai 14
## 15 1 poslije putting 15
window <- 10
pairs <- tokens |>
inner_join(tokens, by = "doc_id", suffix = c("_1", "_2"), relationship = "many-to-many") |>
filter(
token_id_1 < token_id_2,
token_id_2 - token_id_1 <= window
) |>
transmute(
period = period_1,
word_1 = word_1,
word_2 = word_2
)
Sada možemo izračunati koliko se puta svaka dva pojma pojavljuju u istom prozoru. Dobivena vrijednost postaje težina veze.
Rezultat sadrži tri ključna elementa:
item1item2n — broj zajedničkih pojavljivanjaTo znači da je težina veze:
\[ w_{ij} = n_{ij} \]
gdje je:
Što je \(w_{ij}\) veći, to je veza među pojmovima jača.
Prije crtanja mreže gotovo uvijek je potrebno filtrirati vrlo slabe veze, jer one povećavaju vizualni šum.
Često je dobro dodatno filtrirati i vrlo rijetke čvorove. To možemo učiniti na temelju ukupne frekvencije riječi.
No, prvo agregiramo veze:
edges <- pairs |>
count(period, word_1, word_2, sort = TRUE)
edges |>
count(period)
## # A tibble: 2 × 2
## period n
## <chr> <int>
## 1 poslije 399601
## 2 prije 302911
Filtriranje slabih veza:
edges <- edges |>
filter(n >= 10)
Provjera:
edges |>
count(period)
## # A tibble: 2 × 2
## period n
## <chr> <int>
## 1 poslije 1022
## 2 prije 394
library(dplyr)
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
edges_prije <- edges |>
filter(period == "prije")
edges_poslije <- edges |>
filter(period == "poslije")
library(igraph)
g_prije <- graph_from_data_frame(
edges_prije |>
select(from = word_1, to = word_2, weight = n),
directed = FALSE
)
g_prije <- igraph::simplify(g_prije, edge.attr.comb = "sum")
g_poslije <- graph_from_data_frame(
edges_poslije |>
select(from = word_1, to = word_2, weight = n),
directed = FALSE
)
g_poslije <- igraph::simplify(g_poslije, edge.attr.comb = "sum")
Budući da je riječ o mreži ko-pojavljivanja, mrežu najčešće promatramo kao neusmjerenu, jer zajednička pojava ne podrazumijeva smjer.
Mrežna vizualizacija nije sama sebi svrha. Njezina je uloga pomoći uočiti obrasce, grupiranja i jezgrene pojmove. U literaturi se naglašava da je vizualizacija posebno vrijedna zato što prikazuje odnose među elementima kao cjelinu, a ne samo pojedinačne sažetke ili frekvencije.
Najprije ćemo čvorovima pridružiti osnovne mrežne mjere.
V(g_prije)$degree <- degree(g_prije)
V(g_prije)$btw <- betweenness(g_prije)
cl_prije <- cluster_louvain(g_prije, weights = E(g_prije)$weight)
V(g_prije)$community <- membership(cl_prije)
V(g_poslije)$degree <- degree(g_poslije)
V(g_poslije)$btw <- betweenness(g_poslije)
cl_poslije <- cluster_louvain(g_poslije, weights = E(g_poslije)$weight)
V(g_poslije)$community <- membership(cl_poslije)
Prilagođavamo izgled (boju i veličinu) čvorova i bridova:
V(g_prije)$size <- scales::rescale(V(g_prije)$degree, to = c(1, 15))
V(g_prije)$color <- V(g_prije)$community
E(g_prije)$width <- scales::rescale(E(g_prije)$weight, to = c(0.5, 2))
E(g_prije)$color <- "grey75"
V(g_poslije)$size <- scales::rescale(V(g_poslije)$degree, to = c(1, 15))
V(g_poslije)$color <- V(g_poslije)$community
E(g_poslije)$width <- scales::rescale(E(g_poslije)$weight, to = c(0.5, 2))
E(g_poslije)$color <- "grey75"
Mreža prije ChatGPT plasmana
set.seed(123)
lay_prije <- layout_with_fr(g_prije)
plot(
g_prije,
layout = lay_prije,
vertex.label = V(g_prije)$name,
vertex.label.cex = 0.8,
vertex.label.color = "black",
main = "Mreza pojmova prije genAI"
)
Mreža nakon ChatGPT plasmana
set.seed(123)
lay_poslije <- layout_with_fr(g_poslije)
plot(
g_poslije,
layout = lay_poslije,
vertex.label = V(g_poslije)$name,
vertex.label.cex = 0.8,
vertex.label.color = "black",
main = "Mreza pojmova poslije genAI"
)
Ovo je prilično nečitko, pa ćemo postupak ponoviti na najvećoj komponenti u svakom od grafova.
comp_prije <- components(g_prije)
g_prije <- induced_subgraph(g_prije, which(comp_prije$membership == which.max(comp_prije$csize)))
comp_poslije <- components(g_poslije)
g_poslije <- induced_subgraph(g_poslije, which(comp_poslije$membership == which.max(comp_poslije$csize)))
Mreža prije ChatGPT plasmana
set.seed(135)
lay_prije <- layout_with_fr(g_prije)
plot(
g_prije,
layout = lay_prije,
vertex.label = V(g_prije)$name,
vertex.label.cex = 0.8,
vertex.label.color = "black",
main = "Mreza pojmova prije genAI"
)
Mreža nakon ChatGPT plasmana
set.seed(135)
lay_poslije <- layout_with_fr(g_poslije, grid = "grid")
plot(
g_poslije,
layout = lay_poslije,
vertex.label = V(g_poslije)$name,
vertex.label.cex = 0.8,
vertex.label.color = "black",
main = "Mreza pojmova poslije genAI"
)
Iako je ovo zanimljivo, nečitko je, pa ćemo označiti samo najvažnije čvorove:
lab_prije <- ifelse(V(g_prije)$degree >2, TRUE, FALSE)
V(g_prije)$label <- ifelse(lab_prije == TRUE, V(g_prije)$name, NA)
lab_poslije <- ifelse(V(g_poslije)$degree >2, TRUE, FALSE)
V(g_poslije)$label <- ifelse(lab_poslije == TRUE, V(g_poslije)$name, NA)
Mreža prije ChatGPT plasmana
set.seed(135)
lay_prije <- layout_with_fr(g_prije, grid = "grid")
plot(
g_prije,
layout = lay_prije,
vertex.label = V(g_prije)$label,
vertex.label.cex = 0.8,
vertex.label.color = "black",
main = "Mreza pojmova prije genAI"
)
Mreža nakon ChatGPT plasmana
set.seed(135)
lay_poslije <- layout_with_fr(g_poslije, grid = "grid")
plot(
g_poslije,
layout = lay_poslije,
vertex.label = V(g_poslije)$label,
vertex.label.cex = 0.8,
vertex.label.color = "black",
main = "Mreza pojmova poslije genAI"
)
U ovom prikazu:
Centralnost u mreži teksta ne znači isto što i “važnost” u općem smislu, ali često ukazuje na strukturno istaknute pojmove. Primjerice:
U ovoj lekciji centralne pojmove interpretiramo kao riječi koje sudjeluju u organizaciji diskursa.
library(dplyr)
deg_prije <- tibble(
word = V(g_prije)$name,
degree = V(g_prije)$degree,
label = V(g_prije)$label
) |>
filter(!is.na(label)) |>
arrange(desc(degree))
deg_prije
## # A tibble: 38 × 3
## word degree label
## <chr> <dbl> <chr>
## 1 ai 57 ai
## 2 jobs 27 jobs
## 3 data 23 data
## 4 intelligence 23 intelligence
## 5 not 22 not
## 6 human 21 human
## 7 people 18 people
## 8 google 14 google
## 9 workers 14 workers
## 10 artificial 13 artificial
## # ℹ 28 more rows
deg_poslije <- tibble(
word = V(g_poslije)$name,
degree = V(g_poslije)$degree,
label = V(g_poslije)$label
) |>
filter(!is.na(label)) |>
arrange(desc(degree))
deg_poslije
## # A tibble: 68 × 3
## word degree label
## <chr> <dbl> <chr>
## 1 ai 376 ai
## 2 not 37 not
## 3 people 24 people
## 4 human 20 human
## 5 research 19 research
## 6 data 19 data
## 7 time 17 time
## 8 chatgpt 17 chatgpt
## 9 students 16 students
## 10 workers 16 workers
## # ℹ 58 more rows
common_words <- deg_prije |>
select(word, degree_prije = degree) |>
inner_join(
deg_poslije |>
select(word, degree_poslije = degree),
by = "word"
) |>
arrange(desc(degree_prije + degree_poslije))
common_words
## # A tibble: 27 × 3
## word degree_prije degree_poslije
## <chr> <dbl> <dbl>
## 1 ai 57 376
## 2 not 22 37
## 3 data 23 19
## 4 people 18 24
## 5 human 21 20
## 6 jobs 27 10
## 7 intelligence 23 13
## 8 workers 14 16
## 9 learning 13 15
## 10 technology 8 15
## # ℹ 17 more rows
samo_prije <- deg_prije |>
select(word, degree) |>
anti_join(
deg_poslije |>
select(word),
by = "word"
) |>
arrange(desc(degree))
samo_prije
## # A tibble: 11 × 2
## word degree
## <chr> <dbl>
## 1 google 14
## 2 robots 10
## 3 computer 8
## 4 machines 7
## 5 siri 4
## 6 common 3
## 7 brain 3
## 8 skill 3
## 9 change 3
## 10 ties 3
## 11 revolution 3
samo_poslije <- deg_poslije |>
select(word, degree) |>
anti_join(
deg_prije |>
select(word),
by = "word"
) |>
arrange(desc(degree))
samo_poslije
## # A tibble: 41 × 2
## word degree
## <chr> <dbl>
## 1 chatgpt 17
## 2 students 16
## 3 tools 16
## 4 language 10
## 5 productivity 8
## 6 generative 7
## 7 health 7
## 8 digital 7
## 9 found 6
## 10 models 6
## # ℹ 31 more rows
Kod interpretacije je važno razlikovati dva slučaja. Neki su pojmovi centralni zato što su doista tematski nositelji rasprave. Drugi mogu postati centralni zato što su vrlo opći ili retorički česti. Zato se mrežne mjere uvijek interpretiraju zajedno s konkretnim riječima, kontekstom korpusa i vizualnim rasporedom čvorova.
Ovi rezultati zapravo vrlo lijepo ilustriraju evoluciju diskursa o umjetnoj inteligenciji između dva razdoblja. Budući da su analizirani samo pojmovi s (degree > 2), promjene ne odražavaju samo učestalost riječi nego i njihovu strukturnu ulogu u mreži teksta. U mreži teksta to znači da riječ s većim stupnjem povezuje više tematskih konteksta i često funkcionira kao semantički hub diskursa.
U zajedničkoj jezgri pojmova (common_words) vidljivo je
da su određeni koncepti stabilni kroz oba razdoblja: AI, data,
people, human, workers, learning, technology. To su temeljni
pojmovi rasprave o umjetnoj inteligenciji. Međutim, razlika u stupnju
jasno pokazuje promjenu intenziteta i širine diskursa. Pojam AI
dramatično raste (57 \(\rightarrow\)
376), što znači da se nakon pojave generativnih modela pojavljuje u
mnogo većem broju kontekstualnih kombinacija s drugim pojmovima. Sličan
rast vidimo kod riječi research, time ili
technology, što sugerira da diskurs prelazi iz relativno
specifičnih tehnoloških rasprava prema širem društvenom kontekstu.
Istodobno, neke teme gube strukturnu važnost. Primjerice, jobs (27 \(\rightarrow\) 10), intelligence (23 \(\rightarrow\) 13) ili artificial (13 \(\rightarrow\) 9) imaju manji stupanj u drugom razdoblju. To ne znači nužno da su nestale iz diskursa, nego da se pojavljuju u manjem broju različitih konteksta. Drugim riječima, diskurs se pomiče od općenitih rasprava o “umjetnoj inteligenciji i poslovima” prema konkretnijim temama i aplikacijama.
To potvrđuju i pojmovi koji se pojavljuju samo u prvom
razdoblju (samo_prije). Riječi poput robots,
machines, computer, siri i google ukazuju na diskurs u
kojem je umjetna inteligencija često predstavljena kroz
tehnološke artefakte ili kompanije. Također su prisutni
pojmovi poput brain ili revolution, koji odražavaju
raniji narativ o AI kao futurističkoj ili transformativnoj
tehnologiji.
Nasuprot tome, pojmovi koji se pojavljuju samo u drugom
razdoblju (samo_poslije) pokazuju jasnu promjenu
fokusa. Riječi poput chatgpt, generative, models, tools upućuju
na konkretne generativne sustave i njihove tehničke koncepte. Istodobno
se pojavljuju pojmovi povezani s društvenim i organizacijskim
kontekstom: students, companies, productivity, employees,
decision. Diskurs se, dakle, širi iz tehnološke sfere prema
obrazovanju, radu i svakodnevnim praksama.
Ova promjena može se interpretirati kao ekspanzija diskursa. U ranijem razdoblju AI je često predstavljen kroz tehnologiju i futurističke narative, dok se u kasnijem razdoblju rasprava fragmentira u više tematskih područja – od obrazovanja i produktivnosti do kreativnog rada i sigurnosti. U mrežnom smislu to znači da se povećava broj pojmova i konteksta povezanih s umjetnom inteligencijom, a središnji čvorovi poput AI povezuju sve širi semantički prostor.
Ova analiza pokazuje kako mreže teksta mogu otkriti strukturne promjene u javnom diskursu: neke teme postupno nestaju, nove se pojavljuju, a određeni pojmovi postaju sve centralniji jer povezuju veći broj tematskih područja. Takva promjena strukture mreže može se tumačiti kao indikator širenja i transformacije društvene rasprave o tehnologiji.
Ovdje smo naslutili kako možemo promatrati promjene u javnom diskursu kroz vrijeme. Sad ćemo to još detaljnije istražiti.
Za potrebe dubljeg uvida, promatramo mrežu teksta kroz godine.
library(dplyr)
library(lubridate)
docs_clean <- docs_clean |>
mutate(year = year(date))
tokens <- tokens |>
left_join(
docs_clean |>
select(doc_id, year),
by = "doc_id"
)
min(tokens$year)
## [1] 2013
max(tokens$year)
## [1] 2026
Najstariji zapisi datiraju iz 2013. godine, a najnoviji iz 2026. godine.
window <- 5
pairs_year <- tokens |>
inner_join(tokens, by = c("doc_id", "year"), suffix = c("_1", "_2"), relationship = "many-to-many") |>
filter(
token_id_1 < token_id_2,
token_id_2 - token_id_1 <= window
) |>
transmute(
year,
word_1 = word_1,
word_2 = word_2
)
edges_year <- pairs_year |>
count(year, word_1, word_2, sort = TRUE)
edges_year <- edges_year |>
filter(n >= 3)
library(igraph)
edges_all <- edges_year |>
count(word_1, word_2, wt = n, name = "weight")
g_all <- graph_from_data_frame(
edges_all |>
select(from = word_1, to = word_2, weight),
directed = FALSE
)
set.seed(123)
lay_all <- layout_with_fr(g_all, weights = E(g_all)$weight)
rownames(lay_all) <- V(g_all)$name
plot_year_graph <- function(y, edges_year, lay_all, degree_cut = 5) {
# edge lista za jednu godinu
e_y <- edges_year |>
filter(year == y) |>
select(from = word_1, to = word_2, weight = n)
if (nrow(e_y) == 0) {
plot.new()
title(main = paste("Godina", y, "- nema podataka"))
return(invisible(NULL))
}
# mreža
g <- graph_from_data_frame(e_y, directed = FALSE)
# najveća komponenta
comp <- components(g)
g <- induced_subgraph(g, which(comp$membership == which.max(comp$csize)))
# degree
V(g)$degree <- degree(g)
# zadrži dominantne pojmove
keep <- V(g)$degree > degree_cut
g <- induced_subgraph(g, vids = V(g)[keep])
# ako je nakon filtriranja mreža prazna ili premala
if (vcount(g) < 2) {
plot.new()
title(main = paste("Godina", y, "- premalo čvorova nakon filtriranja"))
return(invisible(NULL))
}
# ponovno najveća komponenta nakon filtriranja
comp2 <- components(g)
g <- induced_subgraph(g, which(comp2$membership == which.max(comp2$csize)))
g <- igraph::simplify(g, edge.attr.comb = "sum")
# zajednice za boju
cl <- cluster_louvain(g, weights = E(g)$weight)
V(g)$community <- membership(cl)
# izgled čvorova i veza
V(g)$label <- V(g)$name
V(g)$size <- scales::rescale(V(g)$degree, to = c(8, 20))
V(g)$color <- V(g)$community
E(g)$width <- scales::rescale(E(g)$weight, to = c(0.8, 4))
E(g)$color <- "grey75"
# koordinata iz globalnog layouta, ako postoje
common_nodes <- intersect(V(g)$name, rownames(lay_all))
if (length(common_nodes) >= 2) {
lay <- lay_all[V(g)$name, , drop = FALSE]
} else {
set.seed(123)
lay <- layout_with_fr(g, weights = E(g)$weight)
}
plot(
g,
layout = lay,
vertex.label = V(g)$label,
vertex.label.cex = 0.8,
vertex.label.family = "sans",
vertex.label.color = "black",
vertex.frame.color = NA,
main = paste("Godina", y)
)
}
years <- sort(unique(tokens$year))
par(mfrow = c(2,2), mar = c(1, 1, 2, 1))
for (y in years[2:5]) {
plot_year_graph(y, edges_year = edges_year, lay_all = lay_all, degree_cut = 5)
}
## Warning in min(x): no non-missing arguments to min; returning Inf
## Warning in max(x): no non-missing arguments to max; returning -Inf
par(mfrow = c(2,2), mar = c(1, 1, 2, 1))
for (y in years[6:9]) {
plot_year_graph(y, edges_year = edges_year, lay_all = lay_all, degree_cut = 5)
}
par(mfrow = c(1,2), mar = c(1, 1, 2, 1))
for (y in years[10:11]) {
plot_year_graph(y, edges_year = edges_year, lay_all = lay_all, degree_cut = 5)
}
par(mfrow = c(1,2), mar = c(1, 1, 2, 1))
for (y in years[12:13]) {
plot_year_graph(y, edges_year = edges_year, lay_all = lay_all, degree_cut = 10)
}
Iskoristimo odmah i predznanje o sentiment analizi kako bismo dobili uvide u promjenu sentimenata u diskursu o umjetnoj inteligenciji s obzirom na poslove.
library(tidytext)
library(dplyr)
library(tidyr)
sent_year <- tokens |>
inner_join(get_sentiments("bing"), by = "word") |>
count(year, sentiment) |>
pivot_wider(
names_from = sentiment,
values_from = n,
values_fill = 0
)
sent_long <- sent_year |>
pivot_longer(
cols = c(positive, negative),
names_to = "sentiment",
values_to = "count"
)
library(ggplot2)
ggplot(sent_long, aes(x = year, y = count, colour = sentiment)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2) +
scale_colour_manual(
values = c(
positive = "darkgreen",
negative = "firebrick"
)
) +
labs(
x = "Godina",
y = "Broj riječi",
colour = "Sentiment",
title = "Promjena sentimenta u diskursu o umjetnoj inteligenciji"
) +
theme_minimal()
Graf prikazuje apsolutan broj pozitivnih i negativnih riječi po godinama.
Drugim riječima, linije predstavljaju:
To znači da su vrijednosti pod utjecajem veličine korpusa. Ako je u nekoj godini objavljeno više tekstova, tada će ukupan broj riječi biti veći, pa će rasti i broj pozitivnih i negativnih riječi.
Zato nagli skok nakon 2022.–2023. vrlo vjerojatno odražava eksploziju medijskog interesa za generativnu AI (npr. nakon pojave ChatGPT-a), a ne nužno promjenu emocionalnog tona.
Iako se apsolutne vrijednosti mijenjaju, odnos između linija ostaje vrlo stabilan.
Pozitivni i negativni sentiment:
To sugerira da je diskurs o umjetnoj inteligenciji trajna kombinacija optimizma i zabrinutosti.
Drugim riječima, rasprava je ambivalentna:
pozitivni narativi vezani uz inovacije, produktivnost, istraživanje, nove mogućnosti…
negativni narativi vezani uz gubitak poslova, rizike, sigurnost, etiku…
Takva ambivalentnost je zapravo tipična za diskurse o transformativnim tehnologijama.
Primjetan je nagli rast nakon 2023.
To vrlo vjerojatno odražava:
To se zapravo poklapa s onim što smo već vidjeli u mreži pojmova:
Također, bitno je napomenuti da ovaj graf prikazuje frekvenciju sentimenta, ali ne i njegovu relativnu snagu.
Drugim riječima, ne pokazuje:
Za to nam treba drugi rječnik, recimo afinn. Ponavljamo
postupak radi kratke demonstracije intenziteta emotivnog diskursa.
library(tidytext)
library(dplyr)
afinn_year <- tokens |>
inner_join(get_sentiments("afinn"), by = "word")
sent_year <- afinn_year |>
mutate(
positive = ifelse(value > 0, value, 0),
negative = ifelse(value < 0, abs(value), 0)
) |>
group_by(year) |>
summarise(
positive = sum(positive),
negative = sum(negative),
.groups = "drop"
)
library(tidyr)
sent_long <- sent_year |>
pivot_longer(
cols = c(positive, negative),
names_to = "sentiment",
values_to = "value"
)
library(ggplot2)
ggplot(sent_long, aes(year, value, colour = sentiment)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2) +
scale_colour_manual(
values = c(
positive = "darkgreen",
negative = "firebrick"
)
) +
labs(
x = "Godina",
y = "Jačina sentimenta",
colour = "Sentiment",
title = "Promjena pozitivnog i negativnog sentimenta u diskursu o umjetnoj inteligenciji"
) +
theme_minimal()
Nakon što pripišemo intenzitet sentimenta, vidimo da i dalje dominira pozitivan sentiment (izuzev 2021. godine).
Jedna od najvećih prednosti mreža teksta jest mogućnost detekcije tematskih zajednica. To su skupine pojmova koje se češće pojavljuju međusobno nego s ostatkom mreže. U praksi takve skupine često odgovaraju:
U nastavku ćemo se posvetiti utvrđivanju zajednica tema u 2025. godini. U ranijem grafičkom prikazu mreže mogli smo uočiti najveći broj čvorova i disperziju rasprave prema brojnim temama. Također, to je posljednja završena godina (u ovom trenutku za 2026. godinu imamo tek nešto više od dva mjeseca dokumenata).
edges_2025 <- edges_year |>
filter(year == 2025) |>
select(from = word_1, to = word_2, weight = n)
library(igraph)
g_2025 <- graph_from_data_frame(edges_2025, directed = FALSE)
comp <- components(g_2025)
g_2025 <- induced_subgraph(
g_2025,
which(comp$membership == which.max(comp$csize)))
g_2025 <- igraph::simplify(g_2025, edge.attr.comb = "sum")
V(g_2025)$degree <- degree(g_2025, normalized = TRUE)
cl <- cluster_louvain(
g_2025,
weights = E(g_2025)$weight
)
V(g_2025)$community <- membership(cl)
length(unique(V(g_2025)$community))
## [1] 19
V(g_2025)$size <- scales::rescale(V(g_2025)$degree, to = c(8, 22))
V(g_2025)$color <- V(g_2025)$community
E(g_2025)$width <- scales::rescale(E(g_2025)$weight, to = c(0.5, 4))
E(g_2025)$color <- "grey75"
set.seed(12345)
lay <- layout_with_mds(g_2025)
plot(g_2025,
layout = lay,
vertex.size = V(g_2025)$degree*10,
vertex.label = V(g_2025)$name,
vertex.label.color = "black",
vertex.label.cex = 0.5,
vertex.color = V(g_2025)$color,
edge.width = E(g_2025)$width,
main = "Tematske zajednice u diskursu o umjetnoj inteligenciji (2025)"
)
Za interpretaciju je korisno pregledati koje riječi pripadaju kojoj zajednici.
communities_table <- tibble(
word = V(g_2025)$name,
degree = V(g_2025)$degree,
community = V(g_2025)$community
) |>
arrange(community, desc(degree))
top_words <- communities_table |>
group_by(community) |>
slice_max(degree, n = 20, with_ties = FALSE) |>
arrange(community, desc(degree))
top_words |>
summarise(words = paste(word, collapse = ", "))
## # A tibble: 19 × 2
## community words
## <membrshp> <chr>
## 1 1 ai, systems, employees, literacy, training, generative, job, crea…
## 2 2 future, time, intelligence, artificial, participants, world, trai…
## 3 3 students, tasks, writing, education, university, public, thinking…
## 4 4 people, not, workers, college, technology, cent, honey, knowledge…
## 5 5 research, review, makes, peer, consciousness, social, academic, r…
## 6 6 learning, bio, machine, signal, learn, skills, algorithm, relatio…
## 7 7 tools, found, design, chatgpt, digital, process, business, softwa…
## 8 8 data, human, quantum, collaboration, video, scale, chips, judgeme…
## 9 9 jobs, roles, entry, level, low, positions, replace, robots, incom…
## 10 10 language, models, text, produce, arapaho, word, native, australia…
## 11 11 information, google, company, million, prompt, users, active, con…
## 12 12 companies, copyright, publishers, authors, industry, local, tech,…
## 13 13 calls, complex, dee, chickadees, mountain, chick, call, simple, s…
## 14 14 health, monitoring, care
## 15 15 prompting, rhetorical, method
## 16 16 ability, belief, person
## 17 17 capacity, fitness
## 18 18 loss, mourned
## 19 19 reform, week, roundtable
Analitička vrijednost zajednica nije samo u tehničkom grupiranju riječi. Bitno je prevesti svaki klaster u smislenu tematsku oznaku. Na primjer, jedna zajednica može upućivati na regulatorna pitanja, druga na tehnološki razvoj, treća na korisničko iskustvo, a četvrta na etičke dileme.
U mrežnoj analizi ovakve zajednice obično odgovaraju temama ili diskurzivnim okvirima u korpusu. Budući da je algoritam (Louvain) grupirao čvorove tako da su veze unutar zajednice gušće nego između zajednica, svaka skupina riječi predstavlja relativno koherentno semantičko područje.
Za interpretaciju je najpraktičnije pogledati 3–5 najcentralnijih riječi u svakoj zajednici i pokušati prepoznati temu koju zajedno tvore.
Zajednica 1 – alati i organizacijska primjena AI
ai, tools, systems, tasks, employees, training, generative, process
Ova zajednica povezuje pojmove koji se odnose na primjenu AI alata u organizacijama i radu. Diskurs se ovdje fokusira na generativne alate, procese rada i njihovu integraciju u organizacijske sustave.
Tema: primjena generativne AI u radnim procesima.
Zajednica 2 – jezični modeli i ChatGPT
language, artificial, intelligence, models, chatgpt, openai, text
Ova skupina vrlo jasno upućuje na velike jezične modele (LLM) i tehnologiju koja stoji iza ChatGPT-a. Pojmovi poput language, models i text povezuju se s tehnološkim opisima modela.
Tema: tehnologija velikih jezičnih modela.
Zajednica 3 – obrazovanje i akademsko pisanje
students, writing, education, university, design, critical
Ovdje je fokus na upotrebi AI u obrazovanju i akademskom pisanju. Pojmovi sugeriraju raspravu o pisanju, učenju i kritičkom mišljenju u kontekstu AI alata.
Tema: AI u obrazovanju i akademskom radu.
Zajednica 4 – društvene reakcije i percepcija tehnologije
people, workers, technology, knowledge, challenge
Ova skupina sadrži pojmove koji se odnose na šire društvene reakcije na tehnologiju. Pojmovi poput people, workers i challenge upućuju na društvene i kulturne implikacije.
Tema: društvena percepcija i izazovi AI.
Zajednica 5 – filozofska i psihološka pitanja
research, consciousness, moral, psychology, reading
Ova zajednica uključuje pojmove koji sugeriraju teorijska i filozofska pitanja, primjerice o svijesti, moralu i psihologiji.
Tema: filozofske i psihološke implikacije AI.
Zajednica 6 – tehnički i znanstveni aspekti strojnog učenja
machine, learning, algorithm, model, signal, researchers
Ova skupina pripada tehničkom diskursu strojnog učenja i istraživačkog rada.
Tema: znanstveni i tehnički aspekti AI.
Zajednica 7 – podatci, etika i tehnologija
data, skills, ethical, collaboration, chips
Povezuje pojmove vezane uz tehnološku infrastrukturu, podatke i etiku.
Tema: podatci, infrastruktura i etička pitanja.
Zajednica 8 – tržište rada i automatizacija
jobs, roles, market, replace, robots, income
Ovdje je vrlo jasan fokus na utjecaju AI na tržište rada.
Tema: automatizacija i radna mjesta.
Zajednica 9 – rast korisnika i popularnost AI
million, users, active, monthly
Ova zajednica upućuje na statistike korištenja i popularnost platformi.
Tema: širenje i usvajanje AI tehnologija.
Zajednica 10 – akademsko izdavaštvo i recenziranje
review, survey, paper, literary, reviewer
Diskurs o znanstvenim radovima i evaluaciji AI istraživanja.
Tema: akademska rasprava o AI.
Zajednica 11 – autorska prava i industrija
copyright, publishers, authors, industry
Ova skupina pokazuje rasprave o autorskim pravima i regulaciji AI sadržaja.
Tema: pravni i industrijski aspekti.
Zajednica 12 – promptanje i interakcija s modelima
prompt, context, answer, results
Diskurs o načinu interakcije s AI sustavima.
Tema: promptanje i generiranje odgovora.
Zajednica 13 – poslovna i digitalna transformacija
business, microsoft, copilot, strategy
AI u poslovnom okruženju i digitalnoj transformaciji.
Manje zajednice (14–21) sadrže:
specifične teme pojedinih članaka
lokalne kontekste
ili rijetke pojmove.
Primjeri:
health monitoring – medicinske primjene AI
government / policy – regulatorne rasprave
prompting methods – metodologija promptanja.
Graf temeljen samo na top 10 riječi po zajednici:
top_words <- communities_table |>
group_by(community) |>
slice_max(degree, n = 10, with_ties = FALSE) |>
arrange(community, desc(degree))
top_nodes <- top_words$word
V(g_2025)$highlight <- V(g_2025)$name %in% top_nodes
V(g_2025)$plot_color <- ifelse(V(g_2025)$highlight, V(g_2025)$community, "grey85")
V(g_2025)$plot_size <- ifelse(V(g_2025)$highlight, scales::rescale(V(g_2025)$degree, to=c(3,15)), 0.1)
V(g_2025)$plot_label <- ifelse(V(g_2025)$highlight, V(g_2025)$name, NA)
E(g_2025)$plot_width <- scales::rescale(E(g_2025)$weight, to = c(0.1, 2))
E(g_2025)$plot_color <- "grey85"
set.seed(123)
lay <- layout_with_graphopt(g_2025)
plot(
g_2025,
layout = lay,
vertex.color = V(g_2025)$plot_color,
vertex.size = V(g_2025)$plot_size,
vertex.label = V(g_2025)$plot_label,
vertex.label.cex = V(g_2025)$plot_size/5,
vertex.label.color = "black",
vertex.frame.color = NA,
edge.color = E(g_2025)$plot_color,
edge.width = E(g_2025)$plot_width,
main = "Tematske zajednice u diskursu o umjetnoj inteligenciji (2025)"
)
Kako bi se sačuvala globalna struktura mreže, raspored čvorova izračunat je na punoj mreži za 2025. godinu, a zatim su vizualno naglašeni samo najvažniji pojmovi unutar svake tematske zajednice. Time se izbjegava problem da reducirani podgraf generira artificijelan raspored koji ne odražava stvarne odnose među temama.
Ako želimo sentiment povezati s mrežom, korisno je pridružiti riječima i pripadnost zajednici.
library(tidytext)
library(dplyr)
library(tidyr)
library(ggplot2)
# riječi i pripadnost zajednici iz mreže 2025
community_words <- tibble(
word = V(g_2025)$name,
community = V(g_2025)$community
)
# tokeni samo za 2025.
tokens_2025 <- tokens |>
filter(year == 2025)
# spajanje tokena sa zajednicama i sentimentom
sent_community <- tokens_2025 |>
inner_join(community_words, by = "word") |>
inner_join(get_sentiments("bing"), by = "word") |>
count(community, sentiment) |>
pivot_wider(
names_from = sentiment,
values_from = n,
values_fill = 0
) |>
mutate(
total = positive + negative,
positive_share = positive / total,
negative_share = negative / total,
net = positive - negative
) |>
arrange(desc(total))
sent_community
## # A tibble: 14 × 7
## community negative positive total positive_share negative_share net
## <membrshp> <int> <int> <int> <dbl> <dbl> <int>
## 1 1 119 257 376 0.684 0.316 138
## 2 2 41 63 104 0.606 0.394 22
## 3 3 23 40 63 0.635 0.365 17
## 4 4 23 37 60 0.617 0.383 14
## 5 9 24 13 37 0.351 0.649 -11
## 6 8 9 25 34 0.735 0.265 16
## 7 11 0 30 30 1 0 30
## 8 13 24 6 30 0.2 0.8 -18
## 9 5 11 11 22 0.5 0.5 0
## 10 18 16 0 16 0 1 -16
## 11 6 10 0 10 0 1 -10
## 12 12 10 0 10 0 1 -10
## 13 15 4 0 4 0 1 -4
## 14 19 0 4 4 1 0 4
sent_community_long <- sent_community |>
select(community, positive_share, negative_share) |>
pivot_longer(
cols = c(positive_share, negative_share),
names_to = "sentiment",
values_to = "share"
) |>
mutate(
sentiment = recode(
sentiment,
positive_share = "pozitivan",
negative_share = "negativan"
)
)
ggplot(sent_community_long, aes(x = factor(community), y = share, fill = sentiment)) +
geom_col(position = "dodge") +
scale_fill_manual(values = c("pozitivan" = "darkgreen", "negativan" = "firebrick")) +
labs(
x = "Tematska zajednica",
y = "Udio sentimenta",
fill = "Sentiment",
title = "Pozitivan i negativan sentiment po tematskim zajednicama (2025)"
) +
theme_minimal()
Ovdje je međutim nužan oprez. Sentiment leksikoni često pojednostavljuju značenje i ne prepoznaju ironiju, negaciju, kontekst ni domenski specifičnu upotrebu riječi. Zato sentiment treba koristiti kao indikator, a ne kao konačan sud o tonu cijelog diskursa.
Nadalje, na prvi pogled graf sugerira da se tematske zajednice dosta razlikuju po afektivnom tonu: neke su izrazito pozitivne, neke izrazito negativne, a neke uravnotežene. Međutim, ovdje je vrlo važno naglasiti da graf prikazuje udio pozitivnih i negativnih riječi unutar zajednice, a ne apsolutan broj sentimentom označenih riječi. Zato zajednice s vrlo malim brojem sentimentom prepoznatih riječi mogu izgledati “ekstremno” samo zato što imaju, primjerice, jednu ili dvije riječi jednog tipa. Zbog toga zajednice 2, 6, 11, 16, 19 ili 20, koje iskazuju samo jednu valenciju sentimenta, možda treba čitati kao male ili usko specijalizirane zajednice (osobito 16, 19 i 20), a ne kao dokaz da je tema inherentno potpuno pozitivna ili potpuno negativna. To je posebno važno jer smo i ranije zaključili da su manje zajednice često vezane uz vrlo specifične članke ili lokalne kontekste.
Sadržajno gledano, graf ipak nosi nekoliko korisnih poruka. Pozitivniji sentiment pojavljuje se ponajprije u temama koje govore o razvoju, primjeni i širenju umjetne inteligencije. U tim zajednicama diskurs naglašava mogućnosti tehnologije, produktivnost i nove oblike rada. Primjerice, zajednica 1 (alati i organizacijska primjena AI) okuplja pojmove poput ai, tools, systems, tasks, employees, training i upućuje na integraciju generativnih alata u organizacijske procese. Slično tome, zajednica 2 (jezični modeli i ChatGPT) i zajednica 9 (rast korisnika i popularnost AI) fokusiraju se na tehnološke karakteristike modela i njihovo brzo širenje među korisnicima. U tim kontekstima AI se prikazuje kao inovacija koja donosi nove mogućnosti, pa se u tekstovima češće pojavljuje pozitivno obojen vokabular.
Negativniji sentiment češće se pojavljuje u temama koje uključuju rizike, sukobe interesa ili društvene posljedice tehnologije. Najizraženiji primjer je zajednica 8 (tržište rada i automatizacija), u kojoj se pojavljuju pojmovi poput jobs, roles, replace, robots i income, što upućuje na rasprave o mogućem gubitku radnih mjesta i ekonomskim posljedicama automatizacije. Slično tome, zajednica 11 (autorska prava i industrija) povezuje pojmove poput copyright, publishers i authors, što odražava rasprave o pravnim sporovima i regulaciji generativnog sadržaja. U takvim temama diskurs je češće obilježen zabrinutošću, konfliktima ili upozorenjima na potencijalne negativne učinke tehnologije.
Između tih polova nalaze se teme s ambivalentnim sentimentom, u kojima se istodobno pojavljuju optimizam i zabrinutost. Takav obrazac vidljiv je u zajednicama poput 3 (obrazovanje i akademsko pisanje) i 4 (društvena percepcija tehnologije). Rasprave o AI u obrazovanju, primjerice, uključuju i potencijalne koristi za učenje i pisanje, ali i zabrinutosti oko plagiranja ili smanjenja kritičkog mišljenja. Slično tome, šire društvene rasprave o tehnologiji često kombiniraju fascinaciju tehnološkim napretkom s pitanjima o njegovim dugoročnim društvenim implikacijama. Ovi rezultati pokazuju da diskurs o umjetnoj inteligenciji nije jednoznačno pozitivan ili negativan, nego tematski diferenciran i emocionalno ambivalentan.
Najvažniji zaključak je da se afektivna obojanost razlikuje po tematskim poljima. To se lijepo nadovezuje na raniji nalaz da je ukupni diskurs o umjetnoj inteligenciji ambivalentan. Na razini cijelog korpusa pozitivni i negativni sentiment koegzistiraju, ali se u detaljnijoj analizi vidi da nisu ravnomjerno raspoređeni: određene teme privlače optimističniji vokabular, dok druge privlače vokabular zabrinutosti, prijetnje ili konflikta.
Kao i svaka druga metoda, mreže teksta imaju ograničenja. Prvo, rezultati snažno ovise o pretprocesiranju. Uklanjanje ili zadržavanje određenih riječi može promijeniti strukturu mreže.
Drugo, odabir prozora konteksta bitno utječe na značenje veze. Povezanost unutar iste rečenice nije isto što i povezanost unutar cijelog dokumenta.
Treće, centralnost pojma ne znači nužno njegovu teorijsku važnost. Pojam može biti strukturno centralan, ali sadržajno općenit.
Četvrto, zajednice nisu “prirodne” teme koje postoje same po sebi, nego rezultat algoritamske particije mreže. One su analitički korisne, ali zahtijevaju istraživačku interpretaciju.
Peto, sentiment leksikoni pojednostavljuju jezik. Posebno su problematični u kontekstima ironije, metafore, stručne terminologije i višeznačnosti.
Jedan od najpopularnijih modernih primjera mrežne analize teksta je rekonstrukcija socijalne mreže Westerosa koristeći isključivo tekst romana Georgea R.R. Martina. Istraživači (Beveridge i Shan, 2016) nisu ručno crtali veze. Umjesto toga, koristili su prozor konteksta od 15 riječi. Ako se imena dva lika pojave unutar tog razmaka, sustav bilježi vezu. Što se češće pojavljuju blizu, veza je snažnija.
Tyrion Lannister se pokazao kao najcentralniji lik u smislu betweenness centralnosti, djelujući kao most između udaljenih dijelova svijeta.
Prilikom analize mreža iz teksta, ključni izazov je prepoznavanje entiteta (Named Entity Recognition - NER).
Algoritmi za detekciju zajednica (poput onih koje ste učili u lekciji 7) savršeno su identificirali frakcije i obitelji bez ikakvog predznanja o radnji.
Mreža je otkrila da je narativna struktura knjige zapravo skup lokalnih “klika” povezanih tek s nekoliko ključnih putnika.
Analiza mreža pojmova (semantičkih mreža) često se koristi u politologiji kako bi se mapirale promjene u fokusima kandidata. Tipična studija slučaja analizira promjenu strukture govora prije i poslije izbora (Sudhar i sur., 2015).
Kako se mreža mijenja?
Korištenjem mjera poput gustoće (density) i modularnosti (modularity) može se kvantificirati koliko je politički diskurs postao polariziran. Ako su tematske zajednice u mreži pojmova previše razdvojene, to ukazuje na fragmentiranu politiku.
Ko-pojavljivanje: Pojava dvaju pojmova u istom kontekstu.
Mreža riječi: Graf gdje su čvorovi riječi, veze ko-pojavljivanja.
Čvorovi kao pojmovi: Pojmovi (tokeni/lemmi) tretirani kao čvorovi.
Veze kao ko-pojavljivanje: Težina veze = učestalost zajedničke pojave.
Prozor konteksta: Veličina segmenta (rečenica, n riječi) za ko-pojavljivanje.
Semantičke mreže: Mreže koje reprezentiraju značenjske odnose pojmova.
Tematske mreže: Mreže koje otkrivaju teme kroz grupiranje pojmova.
Diskursne mreže: Mreže koje povezuju aktere i pojmove u javnom diskursu.
Težine veza: Intenzitet ko-pojavljivanja ili sličnosti pojmova.
Filtriranje mreže: Uklanjanje slabih veza radi čitljivosti/stabilnosti.
Jezgre pojmova: Najpovezaniji/centralni pojmovi koji nose temu.
Tematske zajednice: Skupine pojmova koje se često pojavljuju zajedno.
Centralni pojmovi: Pojmovi s visokom centralnošću u mreži teksta.
Marginalni pojmovi: Pojmovi na rubu, rijetki ili slabo povezani.
Evolucija tema: Promjene tema i veza kroz vrijeme.
Dinamičke mreže teksta: Mreže ko-pojavljivanja kroz vremenske rezove.
Usporedba korpusa: Usporedba mreža/tema između skupova tekstova.
Sentiment-tekst mreže: Mreža gdje se sentiment veže uz pojmove ili aktere.
Vizualna interpretacija: Tumačenje strukture mreže kroz prikaz.
Redukcija kompleksnosti: Smanjenje čvorova/veza radi interpretacije.
Metodološka ograničenja: Ovisnost o prozoru, jeziku, filtrima i šumu.
Primjena u društvenim znanostima: Korištenje za analizu narativa, tema i diskursa.
library(DT)
library(dplyr)
sources_table <- docs_clean |>
select(author, date, title, url) |>
mutate(
link = paste0('<a href="', url, '" target="_blank">članak</a>')
) |>
select(author, date, title, link)
datatable(
sources_table,
escape = FALSE,
rownames = FALSE,
colnames = c("Autor", "Datum", "Naslov", "Link"),
options = list(
pageLength = 10,
autoWidth = TRUE
)
)
packages <- c(
"knitr",
"rvest",
"tidyverse",
"xml2",
"polite",
"stringr",
"stringi",
"tidytext",
"dplyr",
"tidyr",
"ggplot2",
"igraph",
"lubridate",
"DT",
"scales"
)
for (p in packages) {
cat("\n\n")
cat("## ", p, "\n", sep = "")
print(citation(p), bibtex = FALSE)
}
##
##
## ## knitr
## To cite package 'knitr' in publications use:
##
## Xie Y (2025). _knitr: A General-Purpose Package for Dynamic Report
## Generation in R_. R package version 1.51, <https://yihui.org/knitr/>.
##
## Yihui Xie (2015) Dynamic Documents with R and knitr. 2nd edition.
## Chapman and Hall/CRC. ISBN 978-1498716963
##
## Yihui Xie (2014) knitr: A Comprehensive Tool for Reproducible
## Research in R. In Victoria Stodden, Friedrich Leisch and Roger D.
## Peng, editors, Implementing Reproducible Computational Research.
## Chapman and Hall/CRC. ISBN 978-1466561595
##
##
## ## rvest
## To cite package 'rvest' in publications use:
##
## Wickham H (2025). _rvest: Easily Harvest (Scrape) Web Pages_. R
## package version 1.0.5, https://github.com/tidyverse/rvest,
## <https://rvest.tidyverse.org/>.
##
##
## ## tidyverse
## To cite package 'tidyverse' in publications use:
##
## Wickham H, Averick M, Bryan J, Chang W, McGowan LD, François R,
## Grolemund G, Hayes A, Henry L, Hester J, Kuhn M, Pedersen TL, Miller
## E, Bache SM, Müller K, Ooms J, Robinson D, Seidel DP, Spinu V,
## Takahashi K, Vaughan D, Wilke C, Woo K, Yutani H (2019). "Welcome to
## the tidyverse." _Journal of Open Source Software_, *4*(43), 1686.
## doi:10.21105/joss.01686 <https://doi.org/10.21105/joss.01686>.
##
##
## ## xml2
## To cite package 'xml2' in publications use:
##
## Wickham H, Hester J, Ooms J (2026). _xml2: Parse XML_. R package
## version 1.5.2, https://r-lib.r-universe.dev/xml2,
## <https://xml2.r-lib.org>.
##
##
## ## polite
## To cite package 'polite' in publications use:
##
## Perepolkin D (2023). _polite: Be Nice on the Web_. R package version
## 0.1.3, https://dmi3kno.github.io/polite/,
## <https://github.com/dmi3kno/polite>.
##
##
## ## stringr
## To cite package 'stringr' in publications use:
##
## Wickham H (2025). _stringr: Simple, Consistent Wrappers for Common
## String Operations_. R package version 1.6.0,
## https://github.com/tidyverse/stringr,
## <https://stringr.tidyverse.org>.
##
##
## ## stringi
## To cite stringi in publications, use:
##
## Gagolewski M (2022). "stringi: Fast and portable character string
## processing in R." _Journal of Statistical Software_, *103*(2), 1-59.
## doi:10.18637/jss.v103.i02 <https://doi.org/10.18637/jss.v103.i02>.
##
##
## ## tidytext
## To cite package 'tidytext' in publications use:
##
## Silge J, Robinson D (2016). "tidytext: Text Mining and Analysis Using
## Tidy Data Principles in R." _JOSS_, *1*(3). doi:10.21105/joss.00037
## <https://doi.org/10.21105/joss.00037>,
## <http://dx.doi.org/10.21105/joss.00037>.
##
##
## ## dplyr
## To cite package 'dplyr' in publications use:
##
## Wickham H, François R, Henry L, Müller K, Vaughan D (2026). _dplyr: A
## Grammar of Data Manipulation_. R package version 1.2.0,
## https://github.com/tidyverse/dplyr, <https://dplyr.tidyverse.org>.
##
##
## ## tidyr
## To cite package 'tidyr' in publications use:
##
## Wickham H, Vaughan D, Girlich M (2025). _tidyr: Tidy Messy Data_. R
## package version 1.3.2, https://github.com/tidyverse/tidyr,
## <https://tidyr.tidyverse.org>.
##
##
## ## ggplot2
## To cite ggplot2 in publications, please use
##
## H. Wickham. ggplot2: Elegant Graphics for Data Analysis.
## Springer-Verlag New York, 2016.
##
##
## ## igraph
## To cite igraph please use these three references.
##
## Csárdi G, Nepusz T (2006). "The igraph software package for complex
## network research." _InterJournal_, *Complex Systems*, 1695.
## <https://igraph.org>.
##
## Antonov M, Csárdi G, Horvát S, Müller K, Nepusz T, Noom D, Salmon M,
## Traag V, Welles BF, Zanini F (2023). "igraph enables fast and robust
## network analysis across programming languages." _arXiv preprint
## arXiv:2311.10260_. doi:10.48550/arXiv.2311.10260
## <https://doi.org/10.48550/arXiv.2311.10260>.
##
## Csárdi G, Nepusz T, Traag V, Horvát Sz, Zanini F, Noom D, Müller K,
## Schoch D, Salmon M (2026). _igraph: Network Analysis and
## Visualization in R_. doi:10.5281/zenodo.7682609
## <https://doi.org/10.5281/zenodo.7682609>, R package version 2.2.1,
## <https://CRAN.R-project.org/package=igraph>.
##
##
## ## lubridate
## To cite lubridate in publications use:
##
## Garrett Grolemund, Hadley Wickham (2011). Dates and Times Made Easy
## with lubridate. Journal of Statistical Software, 40(3), 1-25. URL
## https://www.jstatsoft.org/v40/i03/.
##
##
## ## DT
## To cite package 'DT' in publications use:
##
## Xie Y, Cheng J, Tan X, Aden-Buie G (2025). _DT: A Wrapper of the
## JavaScript Library 'DataTables'_. R package version 0.34.0,
## <https://github.com/rstudio/DT>.
##
##
## ## scales
## To cite package 'scales' in publications use:
##
## Wickham H, Pedersen T, Seidel D (2025). _scales: Scale Functions for
## Visualization_. R package version 1.4.0,
## https://github.com/r-lib/scales, <https://scales.r-lib.org>.
Beveridge, A., & Shan, J. (2016). Network of Thrones. Math Horizons, 23(4), 18-22. [DOI: 10.4169/mathhorizons.23.4.18]
Hu, M., & Liu, B. (2004). Mining and summarizing customer reviews. In Proceedings of the Tenth ACM SIGKDD International Conference on Knowledge Discovery and Data Mining (pp. 168–177). ACM. https://doi.org/10.1145/1014052.1014073
Kwartler, T. (2017). Text mining in practice with R. Wiley.
Nielsen, F. Å. (2011). A new ANEW: Evaluation of a word list for sentiment analysis in microblogs. In Proceedings of the ESWC2011 Workshop on “Making Sense of Microposts”: Big Things Come in Small Packages (pp. 93–98). CEUR Workshop Proceedings. https://ceur-ws.org/Vol-718/paper_16.pdf
Sudhahar, S., Veltri, G. A., & Cristianini, N. (2015). Automated analysis of the US presidential elections messages. Digital Scholarship in the Humanities, 30(1), 126-147. [DOI: 10.1093/llc/fqt054]
C
B
C
B
B
D
D
B
B
C
C
C
C
E
B
D
C
D
B
E