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.

Tekst kao mreža

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.

Od bag-of-words pristupa do relacijske analize

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:

  • koje se riječi pojavljuju zajedno
  • koje skupine pojmova čine tematske cjeline
  • koji pojmovi povezuju različite teme
  • kako se struktura diskursa mijenja između razdoblja ili korpusa

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.

Što je ko-pojavljivanje?

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:

  • ista rečenica
  • isti odlomak
  • isti dokument
  • prozor od \(n\) riječi

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:

  • jeziku i stilu korpusa
  • pretprocesiranju
  • odabiru prozora konteksta
  • filtriranju rijetkih riječi i slabih veza

Vrste mreža teksta

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.

Redukcija kompleksnosti

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:

  • uklanjanje vrlo rijetkih riječi
  • uklanjanje slabih veza
  • prikaz samo najveće komponente
  • prikaz samo najcentralnijih čvorova
  • agregacija po zajednicama

Kako povezati mrežu i sentiment u interpretaciju?

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.

Logika analize u ovoj lekciji

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:

  • priprema i tokenizacija korpusa
  • konstrukcija mreže ko-pojavljivanja
  • identifikacija centralnih pojmova i tematskih zajednica
  • usporedba mreža između korpusa i integracija sentimenta

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.

Priprema podataka

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.

Dohvat podataka

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 ...

Pretprocesiranje

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 dokumenta
  • date - datum objave
  • period — skupina ili razdoblje kojem dokument pripada (Prije/ poslije)
  • title - naslov dokumenta
  • url - poveznica dokumenta
  • author - autor teksta
  • text — tekst dokumenta

Pretprocesiranje 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:

  • interpunkcija i brojke
  • stop riječi
  • vrlo rijetki tokeni
  • ponekad i vlastita imena ili tehnički artefakti, ovisno o istraživačkom pitanju

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



Konstrukcija mreže ko-pojavljivanja

Odabir prozora konteksta

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:

  • \(w\) — veličina prozora konteksta
  • vrijednost 10 znači da se riječi promatraju unutar blokova od 10 uzastopnih tokena

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:

  • item1
  • item2
  • n — broj zajedničkih pojavljivanja

To znači da je težina veze:

\[ w_{ij} = n_{ij} \]

gdje je:

  • \(w_{ij}\) — težina veze između pojmova \(i\) i \(j\)
  • \(n_{ij}\) — broj prozora u kojima se pojmovi \(i\) i \(j\) pojavljuju zajedno

Š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

Pretvaranje u mrežni objekt

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.

Vizualizacija mreže

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:

  • veće točke označavaju pojmove s većim stupnjem
  • boja označava tematsku zajednicu
  • debljina veze odražava jačinu ko-pojavljivanja

Centralni pojmovi

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:

  • degree centrality upućuje na pojmove povezane s mnogo drugih pojmova
  • betweenness centrality može ukazivati na pojmove koji povezuju različite tematske cjeline

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.

Evolucija diskursa

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)
}

Evolucija sentimenta

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:

  • broj tokena koji pripadaju pozitivnom sentimentu
  • broj tokena koji pripadaju negativnom sentimentu

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:

  • rastu gotovo paralelno
  • pozitivni je uglavnom nešto viši
  • ali razlika nije velika.

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:

  • pojavu generativne AI u javnom prostoru
  • nagli rast broja članaka
  • širenje rasprave iz tehnološkog u društveni kontekst.

To se zapravo poklapa s onim što smo već vidjeli u mreži pojmova:

  • pojavljuju se novi pojmovi (chatgpt, generative, tools, students, productivity)
  • diskurs se širi na nove domene.

Također, bitno je napomenuti da ovaj graf prikazuje frekvenciju sentimenta, ali ne i njegovu relativnu snagu.

Drugim riječima, ne pokazuje:

  • je li diskurs postao pozitivniji ili negativniji
  • nego samo koliko se takvih riječi pojavljuje.

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).

Tematske zajednice

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:

  • podtemama
  • argumentacijskim linijama
  • diskurzivnim poljima
  • narativnim jezgrama

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.

Sentiment po tematskim zajednicama

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.

Metodološka ograničenja

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.




Studija slučaja 1: Igra prijestolja

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).

    • U knjigama: Istraživači su morali povezati različita imena za istu osobu (npr. Tyrion, the Imp, Halfman) u jedan čvor kako bi mreža bila točna. Da su radili analizu serije (npr. titlova), čvorovi bi mogli biti glumci, ali studija Beveridgea i Shana fokusirala se isključivo na tekst treće knjige (A Storm of Swords). -Zašto knjige, a ne serija? Tekst knjiga je bogatiji opisima interakcija, dok se serija oslanja na vizualni prikaz. U tekstu, svako spominjanje imena u blizini drugog imena gradi “semantičku blizinu” koju algoritam može izmjeriti.
  • 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.




Studija slučaja 2: Politički diskurs

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?

  • Predizborna faza: Mreže su obično “labave” i fokusirane na ključne ideološke pojmove (npr. budućnost, promjena, nada). Ovi pojmovi djeluju kao hubovi koji povezuju različite tematske cjeline.
  • Postizborna faza: Mreža postaje gušća i tehnička. Centralni čvorovi postaju pojmovi vezani uz proračun, zakone i administraciju.

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.




Memento

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.




Pitanja za ponavljanje

1. Koja je glavna svrha tokenizacije u analizi teksta?

  1. Grupirati dokumente prema tematskoj sličnosti u korpusu
  2. Izračunati emocionalni ton svakog dokumenta u korpusu
  3. Pretvoriti tekst u niz riječi ili jedinica pogodnih za analizu
  4. Pretvoriti riječi u numeričke varijable za regresijsku analizu
  5. Odrediti semantičke odnose između pojmova u mreži

2. Zašto se u analizi tekstualnih mreža često uklanjaju stop-riječi?

  1. Zato što predstavljaju vlastita imena i nazive organizacija
  2. Zato što su česte funkcionalne riječi koje ne nose tematsko značenje
  3. Zato što mijenjaju gramatičku strukturu rečenice u tekstu
  4. Zato što stvaraju probleme pri lematizaciji i stemmingu
  5. Zato što se rijetko pojavljuju i otežavaju izračun frekvencija

3. Što predstavlja brid u mreži ko-pojavljivanja riječi?

  1. Gramatičku ovisnost između subjekta i predikata
  2. Razliku u frekvenciji između dviju riječi u korpusu
  3. Povezanost dviju riječi koje se pojavljuju u istom kontekstu
  4. Redoslijed pojavljivanja riječi unutar jedne rečenice
  5. Semantičku sličnost procijenjenu pomoću jezičnog modela

4. Zašto se u analizi mreže često filtriraju čvorovi s vrlo malim stupnjem (degree)?

  1. Kako bi se spriječila pojava izoliranih bridova u mreži
  2. Kako bi se uklonile rijetke riječi koje stvaraju vizualni šum u mreži
  3. Kako bi se povećala ukupna gustoća mreže
  4. Kako bi se osigurala ravnomjerna raspodjela riječi po zajednicama
  5. Kako bi se povećala statistička značajnost rezultata analize

5. Koja je svrha korištenja istog layouta za mreže različitih godina?

  1. Spriječiti pojavu novih tematskih zajednica u mreži
  2. Omogućiti usporedbu promjena strukture mreže kroz vrijeme
  3. Osigurati da se broj čvorova u svakoj mreži smanji
  4. Izjednačiti frekvencije riječi između različitih korpusa
  5. Povećati modularnost mreže tijekom analize

6. Što znači koristiti prozor konteksta pri izgradnji mreže riječi?

  1. Odrediti koliko dokumenata ulazi u jednu tematsku zajednicu
  2. Odrediti koliko riječi ulazi u pojedini dokument
  3. Odrediti koliko puta riječ mora biti ponovljena u tekstu
  4. Odrediti koliko riječi može biti udaljeno da bi se smatralo povezanim
  5. Odrediti koliko riječi treba ukloniti iz analize

7. Zašto se u analizi sentimenta koriste leksikoni poput Bing ili AFINN?

  1. Kako bi se procijenila sintaktička struktura rečenica
  2. Kako bi se generirali novi tekstovi iz postojećeg korpusa
  3. Kako bi se odredila gramatička kategorija svake riječi
  4. Kako bi se riječima pridružile unaprijed definirane emocionalne vrijednosti
  5. Kako bi se automatski identificirale tematske zajednice u mreži

8. Zašto je korisno analizirati mreže teksta po godinama?

  1. Zato što povećava ukupnu veličinu analiziranog korpusa
  2. Zato što omogućuje praćenje promjena diskursa kroz vrijeme
  3. Zato što smanjuje broj riječi u pojedinom dokumentu
  4. Zato što uklanja utjecaj rijetkih riječi iz analize
  5. Zato što osigurava ravnomjernu raspodjelu tema u tekstu

9. Koja je svrha algoritama za detekciju zajednica u mreži?

  1. Procijeniti emocionalni ton pojedinih dokumenata
  2. Identificirati skupine čvorova koji su međusobno jače povezani
  3. Izračunati prosječnu frekvenciju riječi u korpusu
  4. Odrediti kronološki redoslijed tekstova u analizi
  5. Izračunati gramatičku složenost rečenica

10. Zašto se u vizualizaciji mreža često skalira veličina čvorova prema degree-u?

  1. Kako bi se izjednačila frekvencija svih riječi u korpusu
  2. Kako bi se povećala gustoća mreže tijekom analize
  3. Kako bi se naglasila važnost riječi koje su centralne u mreži
  4. Kako bi se spriječilo stvaranje izoliranih komponenti
  5. Kako bi se povećao broj detektiranih zajednica

11. Ako riječ ima vrlo visok degree u mreži ko-pojavljivanja, što to najčešće znači?

  1. Riječ ima vrlo negativan emocionalni ton u tekstu
  2. Riječ se pojavljuje samo u jednom dokumentu
  3. Riječ se pojavljuje u mnogim kontekstima i povezuje više tema
  4. Riječ pripada isključivo jednoj tematskoj zajednici
  5. Riječ ima vrlo nisku frekvenciju u korpusu

12. Ako nova riječ postaje centralna u mreži nakon određenog razdoblja, to može značiti da

  1. su svi tekstovi napisani od istog autora
  2. je frekvencija svih drugih riječi značajno smanjena
  3. je nova tema postala važna u javnom diskursu
  4. su svi dokumenti u korpusu napisani u isto vrijeme
  5. su algoritmi za detekciju zajednica pogrešno primijenjeni

13. Ako se dvije riječi često pojavljuju zajedno u mreži, to obično znači da

  1. imaju identičan emocionalni ton
  2. pripadaju istoj vrsti riječi u rječniku
  3. se pojavljuju u sličnim kontekstima ili temama
  4. imaju jednak broj pojavljivanja u korpusu
  5. imaju identičnu gramatičku funkciju u rečenici

14. Ako se u mreži pojavi nova tematska zajednica vezana uz “ChatGPT”, to može značiti da

  1. su uklonjene sve stop-riječi iz analize
  2. su svi tekstovi analizirani istim algoritmom
  3. su svi dokumenti napisani nakon iste godine
  4. su sve riječi dobile isti degree u mreži
  5. se pojavila nova tehnološka tema u diskursu

15. Ako sentiment analiza pokazuje istodobno mnogo pozitivnih i negativnih riječi, to može značiti da

  1. su svi autori koristili identičan stil pisanja
  2. je rasprava o temi ambivalentna i uključuje različite stavove
  3. su svi tekstovi napisani u istom vremenskom razdoblju
  4. su svi dokumenti analizirani istim algoritmom
  5. su sve riječi iz leksikona pravilno klasificirane

16. Ako zajednica sadrži riječi poput “jobs”, “market” i “automation”, najvjerojatnije se odnosi na

  1. raspravu o metodama strojnog učenja
  2. raspravu o akademskom izdavaštvu
  3. raspravu o filozofskim pitanjima svijesti
  4. raspravu o utjecaju tehnologije na tržište rada
  5. raspravu o gramatičkoj strukturi jezika

17. Ako mreža pokazuje više tematskih zajednica, to obično znači da

  1. svi algoritmi daju iste rezultate analize
  2. svi dokumenti imaju identičnu duljinu teksta
  3. diskurs uključuje više međusobno povezanih tema
  4. svi tekstovi dolaze iz istog izvora podataka
  5. sve riječi imaju jednaku frekvenciju pojavljivanja

18. Ako određena zajednica sadrži riječi poput “education”, “students” i “writing”, to sugerira

  1. raspravu o tržištu rada i zaposlenju
  2. raspravu o računalnoj infrastrukturi
  3. raspravu o statističkim metodama analize
  4. raspravu o primjeni AI u obrazovanju i učenju
  5. raspravu o tehničkim parametrima modela

19. Ako se veličina najveće komponente mreže povećava kroz godine, to može značiti da

  1. svi dokumenti koriste identičan vokabular
  2. diskurs postaje bogatiji i uključuje više međusobno povezanih pojmova
  3. algoritam za detekciju zajednica prestaje raditi
  4. broj dokumenata u korpusu se smanjuje kroz vrijeme
  5. sve riječi dobivaju isti stupanj povezanosti

20. Ako određena tema ima izrazito negativan sentiment, to najčešće znači da

  1. su sve riječi u toj temi vrlo rijetke u korpusu
  2. se riječ pojavljuje samo u jednoj tematskoj zajednici
  3. su sve riječi gramatički iste vrste
  4. su svi dokumenti napisani u istoj godini
  5. se u diskursu često spominje u kontekstu zabrinutosti ili kritike



Korištena literatura

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]




Ključ odgovora

  1. C

  2. B

  3. C

  4. B

  5. B

  6. D

  7. D

  8. B

  9. B

  10. C

  11. C

  12. C

  13. C

  14. E

  15. B

  16. D

  17. C

  18. D

  19. B

  20. E