Ishodi učenja:

  • Odabrati layout i vizualne kodove koji odgovaraju analitičkom cilju.

  • Izraditi čitljivu vizualizaciju mreže u ggraphu.

  • Kritički procijeniti “lijepo vs. informativno” i smanjiti vizualni šum.

  • Komunicirati nalaze kroz vizualnu priču (annotacije, legende, isticanje).


library(DBI)
library(RSQLite)
library(dplyr)
library(tidyr)
library(readr)
library(stringr)
library(igraph)
library(tidygraph)
library(ggplot2)
library(ggraph)
library(ggrepel)
library(grid)
library(visNetwork)
library(maps)


Vizualizacije

Vizualizacije, bilo da se radi o statističkim grafovima ili mrežnim dijagramima, predstavljaju spoj analitičke preciznosti i estetske kompozicije. One nisu samo tehnički prikazi podataka, nego oblik vizualnog argumenta: način na koji strukturiramo prostor, odaberemo boje, veličine i odnose izravno utječe na interpretaciju.

Iz tog spoja razvila se zasebna interdisciplinarna grana – informacijska vizualizacija (engl. information visualization) – koja povezuje statistiku, računarstvo, dizajn, kognitivnu psihologiju i teoriju percepcije. Informacijska vizualizacija ne bavi se samo pitanjem kako nacrtati graf, nego i pitanjem kako ljudi čitaju graf, što uočavaju prvo, kako prepoznaju obrasce i gdje nastaju pogrešne interpretacije.

*“Above all else show the data.”* 
(Tufte & Graves-Morris, 1983)

U duhu Tufteovih principa, cilj vizualizacije nije ukrasiti analizu, nego prenijeti što više informacija o podacima u što kraćem vremenu: “Above all else show the data”. Vizualni dizajn treba podržati brz uvid i jasnoću — dobra vizualizacija prenosi mnogo ideja brzo, uz minimalno vizualnog opterećenja. Posebno je važno čuvati proporcionalnost prikaza: ako graf pretjerano naglasi razlike (npr. kroz skale, veličine ili debljine), nastaje “lie factor”, tj. nesrazmjer između efekta na slici i efekta u podacima.

Mrežne vizualizacije čine specifičnu poddisciplinu prikaza kvantitativnih podataka. Za razliku od klasičnih statističkih grafova s jasno definiranim osima, mrežni dijagrami nemaju prirodnu prostornu referencu. Njihov izgled u potpunosti ovisi o algoritmu rasporeda (layoutu), što dodatno naglašava važnost metodološke odgovornosti u interpretaciji.

Kako ističe Ben Shneiderman, jedan od pionira vizualne analitike:

“Overview first, zoom and filter, then details-on-demand.”

Drugim riječima, dobra vizualizacija mora omogućiti pregled cjeline, selektivno fokusiranje i pristup detaljima — bez preopterećenja promatrača.

Vizualizacija, dakle, nije samo završni korak analize. Ona je dio analitičkog procesa. Ona strukturira način na koji mislimo o podacima.

Zašto vizualizacija mreža?

Mreža je matematički objekt koji opisujemo kao graf – skup čvorova i veza među njima. No, iako je graf formalna struktura definirana skupom vrhova i bridova, njegova interpretacija u praksi gotovo uvijek započinje vizualizacijom. Vizualni prikaz omogućuje nam da uočimo obrasce, nepravilnosti i strukturne karakteristike koje je teško odmah prepoznati iz same tablice podataka ili matrice susjedstva.

Vizualizacija mreže nije samo estetski dodatak analizi. Ona je analitički alat. Raspored čvorova, gustoća veza, postojanje izoliranih dijelova ili centralno pozicioniranih aktera često se mogu intuitivno prepoznati prije nego što ih formalno izmjerimo. Primjerice, mreža visoke gustoće vizualno će djelovati „zbijeno“, dok će rijetka mreža imati mnogo praznog prostora između čvorova. Slično tome, mreža s velikim dijametrom često će izgledati izduženije ili „razvučeno“, dok će mreža manjeg dijametra imati kompaktniju strukturu.

Važno je razumjeti da vizualni izgled mreže ne ovisi samo o njezinoj strukturi, već i o odabranom algoritmu rasporeda (layoutu). Ista mreža može izgledati potpuno drugačije ovisno o tome kako su čvorovi prostorno raspoređeni. Neki algoritmi pokušavaju minimizirati presijecanje bridova, neki simuliraju fizičke sile privlačenja i odbijanja, dok drugi čuvaju određene geometrijske pravilnosti. Zbog toga vizualizaciju uvijek treba interpretirati u kontekstu korištenog rasporeda.

Estetika u vizualizaciji mreža nije pitanje ljepote, nego jasnoće. Odabirom veličine čvorova, debljine i boje bridova, kao i pozadine i razmaka, možemo naglasiti određene karakteristike mreže. Primjerice, veličina čvora može se povezati s njegovim stupnjem (degree), čime vizualno ističemo aktere s većim brojem veza. Takav prikaz pomaže u povezivanju numeričkih mjera koje smo već izračunali s njihovom strukturnom interpretacijom.

Ovdje ćemo se fokusirati na osnovne principe vizualizacije mreža u R-u. Nećemo koristiti napredne mjere centralnosti niti metode detekcije zajednica. Umjesto toga, pokazat ćemo kako pomoću jednostavnih grafičkih postavki možemo bolje razumjeti strukturu mreže koristeći pojmove koje već poznajemo: stupanj čvora, gustoću mreže, dijametar i ekscentričnost.

Podaci i priprema

Ova tema je već obuhvaćena u materijalima Prikupljanje i priprema podataka za SNA. Odatle ćemo koristiti dva primjera mreža za koje je već prikazan postupak učitavanja, transformacije i pohrane u igraph objekt. U sekciji “Učitavanje podataka iz baze” korišteni su podaci chinook mreže izvođača ( https://github.com/lerocha/chinook-database/raw/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite), koja je u posljednjem koraku spremljena kao g3. Nadalje, u sekciji “Otvoreni repozitoriji s mrežnim podacima” prikazan je primjer SNAP Bitcoin OTC trust network https://snap.stanford.edu/data/soc-sign-bitcoinotc.csv.gz, čiji je podgraf spremljen u objekt tg_small.

Pozabavimo se prvo Chinook mrežom izvođača.

download.file(
  "https://github.com/lerocha/chinook-database/raw/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite",
  destfile = "data/chinook.sqlite",
  mode = "wb"
)

con <- dbConnect(RSQLite::SQLite(), "data/chinook.sqlite")

dbListTables(con)
##  [1] "Album"         "Artist"        "Customer"      "Employee"     
##  [5] "Genre"         "Invoice"       "InvoiceLine"   "MediaType"    
##  [9] "Playlist"      "PlaylistTrack" "Track"

Kreirajmo mrežu suradnje izvođača:

library(dplyr)
edges_db <- dbGetQuery(con, "
SELECT DISTINCT a1.Name AS from_id,
                a2.Name AS to_id
FROM Artist a1
JOIN Album al1 ON a1.ArtistId = al1.ArtistId
JOIN Track t1 ON al1.AlbumId = t1.AlbumId
JOIN Genre g1 ON t1.GenreId = g1.GenreId
JOIN Track t2 ON g1.GenreId = t2.GenreId
JOIN Album al2 ON t2.AlbumId = al2.AlbumId
JOIN Artist a2 ON al2.ArtistId = a2.ArtistId
WHERE a1.ArtistId < a2.ArtistId
")

glimpse(edges_db)
## Rows: 4,087
## Columns: 2
## $ from_id <chr> "AC/DC", "AC/DC", "AC/DC", "AC/DC", "AC/DC", "AC/DC", "AC/DC",…
## $ to_id   <chr> "Accept", "Aerosmith", "Alanis Morissette", "Alice In Chains",…
library(dplyr)
library(stringr)
edges_db <- as_tibble(edges_db)
edges_db <- edges_db %>% 
  mutate(across(c(from_id,to_id), \(x) str_squish(str_replace_all(x, "\u00A0", " ")))) %>%
  mutate(across(c(from_id,to_id), ~ str_to_lower(str_squish(.x))))
edges_db
## # A tibble: 4,087 × 2
##    from_id to_id                          
##    <chr>   <chr>                          
##  1 ac/dc   accept                         
##  2 ac/dc   aerosmith                      
##  3 ac/dc   alanis morissette              
##  4 ac/dc   alice in chains                
##  5 ac/dc   audioslave                     
##  6 ac/dc   led zeppelin                   
##  7 ac/dc   frank zappa & captain beefheart
##  8 ac/dc   queen                          
##  9 ac/dc   kiss                           
## 10 ac/dc   david coverdale                
## # ℹ 4,077 more rows

Primjer: SNAP Bitcoin OTC trust network

# Preuzimanje podataka
dir.create("data", showWarnings = FALSE)

url <- "https://snap.stanford.edu/data/soc-sign-bitcoinotc.csv.gz"
f   <- file.path("data", basename(url))

if (!file.exists(f)) download.file(url, f, mode = "wb")

# Učitavanje
raw <- read_csv(f, col_names = FALSE, show_col_types = FALSE) |>
  setNames(c("source", "target", "rating", "time_unix"))

glimpse(raw)
## Rows: 35,592
## Columns: 4
## $ source    <dbl> 6, 6, 1, 4, 13, 13, 7, 2, 2, 21, 21, 21, 21, 21, 17, 17, 10,…
## $ target    <dbl> 2, 5, 15, 3, 16, 10, 5, 21, 20, 2, 1, 10, 8, 3, 3, 23, 1, 6,…
## $ rating    <dbl> 4, 2, 1, 7, 8, 8, 1, 5, 5, 5, 8, 8, 9, 7, 5, 1, 8, 7, 8, 1, …
## $ time_unix <dbl> 1289241912, 1289241942, 1289243140, 1289245277, 1289254254, …

Trebamo prilagoditi tipove podataka.

df <- raw |>
  transmute(
    source = as.character(source),
    target = as.character(target),
    rating = as.integer(rating),
    time   = as.POSIXct(time_unix, origin = "1970-01-01", tz = "UTC")
  )

glimpse(df)
## Rows: 35,592
## Columns: 4
## $ source <chr> "6", "6", "1", "4", "13", "13", "7", "2", "2", "21", "21", "21"…
## $ target <chr> "2", "5", "15", "3", "16", "10", "5", "21", "20", "2", "1", "10…
## $ rating <int> 4, 2, 1, 7, 8, 8, 1, 5, 5, 5, 8, 8, 9, 7, 5, 1, 8, 7, 8, 1, 10,…
## $ time   <dttm> 2010-11-08 18:45:11, 2010-11-08 18:45:41, 2010-11-08 19:05:40,…

Brzinska provjera konzistentnosti skupa podataka

# Provjera missing vrijednosti
colSums(is.na(df))
## source target rating   time 
##      0      0      0      0
# Provjera raspona ratinga
range(df$rating, na.rm = TRUE)
## [1] -10  10
# Self-loopovi
sum(df$source == df$target)
## [1] 0
# Duplikati (isti source-target-time-rating)
sum(duplicated(df))
## [1] 0

S obzirom da je ova mreža ogromna, izdvojit ćemo samo pozitivne veze (povjerenje).

edges_pos <- df |>
  filter(rating > 0) |>
  select(from = source, to = target, weight = rating)

g_pos <- graph_from_data_frame(edges_pos, directed = TRUE)
g_pos
## IGRAPH 543b60f DNW- 5573 32029 -- 
## + attr: name (v/c), weight (e/n)
## + edges from 543b60f (vertex names):
##  [1] 6 ->2  6 ->5  1 ->15 4 ->3  13->16 13->10 7 ->5  2 ->21 2 ->20 21->2 
## [11] 21->1  21->10 21->8  21->3  17->3  17->23 10->1  10->6  10->21 10->8 
## [21] 10->25 10->2  10->3  4 ->26 26->4  5 ->1  5 ->6  5 ->7  1 ->5  6 ->4 
## [31] 4 ->6  2 ->4  17->28 17->13 13->17 13->29 29->13 17->20 4 ->31 31->4 
## [41] 32->6  13->1  7 ->34 34->7  32->1  1 ->32 1 ->34 34->1  34->13 13->34
## [51] 6 ->7  7 ->6  1 ->17 1 ->31 31->1  35->6  1 ->13 36->37 37->36 35->1 
## [61] 17->1  8 ->1  7 ->29 1 ->20 37->44 44->37 39->45 39->7  39->44 44->39
## [71] 23->17 23->19 36->46 46->36 47->1  13->7  7 ->13 29->51 51->29 29->52
## + ... omitted several edges

Najjednostavniji graf u igraphu

Chinook mreža izvođača:

library(igraph)

g3 <- graph_from_data_frame(edges_db, directed = FALSE)

plot(g3)

Bitcoin OTC mreža povjerenja

plot(g_pos)

Malo jasniji graf dobivamo ako uklonimo oznake čvorova. I to nas zapravo već uvodi u sljedeću temu.

Chinook mreža izvođača:

plot(g3, vertex.label = NA)

Bitcoin OTC mreža povjerenja

plot(g_pos, vertex.label = NA)

Unatoč potencijalnim očekivanjima, nismo dobili ni lijepe, ni naročito informativne grafove. Najjednostavniji prikaz će dobro funkcionirati za vrlo male grafove, no za veće grafove koji se često pojavljuju u SNA, potrebno je koristiti grafičke elemente i layoute.

Grafički elementi

Kod mrežnih dijagrama uvijek kombiniramo dva sloja: strukturu (tko je s kim povezan) i vizualno kodiranje (kako tu strukturu prikažemo). Struktura je zadana grafom, ali vizualna interpretacija ovisi o parametrima prikaza: veličini čvorova, boji, debljini bridova, prikazu oznaka i odabranom layoutu. Cilj nije “najljepši graf”, nego graf iz kojeg se može nešto jasno pročitati.

U ovoj lekciji koristimo samo jednostavne informacije koje već poznajemo. Najvažnija je ideja da veličina čvora može biti povezana sa stupnjem čvora (degree), jer time vizualno naglašavamo izvođače s više veza. Međutim, čak i kad imamo “informativno” kodiranje, lako proizvedemo vizualni šum: previše oznaka, preveliki čvorovi, preguste veze ili neprikladan layout. Zato ćemo kroz nekoliko koraka graditi čitljiv prikaz.

Vizualizacija mreže sastoji se od dva dijela:

  1. raspored (layout) koji određuje položaje čvorova u prostoru i

  2. grafičko kodiranje koje određuje kako su čvorovi i veze nacrtani (veličina, boja, debljina, labeli…).

U paketu igraph graf se najčešće crta funkcijom plot() koja za igraph objekt koristi funkciju plot.igraph(). Ta funkcija ima mnogo argumenata, ali većina ih se prirodno grupira u argumente za čvorove (vertex.*), veze (edge.*) i tekst (vertex.label.*,edge.label.*), uz nekoliko “općih” argumenata za platno (margine, omjeri, rescale).

U ggplot2 pristup je drugačiji: graf ne nastaje jednim pozivom funkcije, nego se gradi slojevito (estetike + geometrije + skale + tema). Za mreže je najprirodnije koristiti ggraph (koji je “ggplot za grafove”), ali ista logika vrijedi: prvo odlučimo što mapiramo u aes(), zatim biramo geometrije (geom_*), a zatim kontroliramo izgled skalama i temom.

Svi argumentiplot.igraph() (popis iz R-a)

# svi argumenti koje plot() prihvaća kad je objekt igraph
names(formals(igraph::plot.igraph))
##  [1] "x"           "axes"        "add"         "xlim"        "ylim"       
##  [6] "mark.groups" "mark.shape"  "mark.col"    "mark.border" "mark.expand"
## [11] "mark.lwd"    "loop.size"   "..."

Napomena: lista je duga. Zato ih u nastavku prolazimo po skupinama koje se najčešće koriste.

plot.igraph: najvažnije skupine argumenata

  1. Argumenti za čvorove (vertex.*)

Najčešće:

  • vertex.size

  • vertex.color

  • vertex.frame.color

  • vertex.shape

  • vertex.label (ili NA za uklanjanje)

  • vertex.label.cex, vertex.label.color,vertex.label.family, vertex.label.font

  • vertex.label.dist, vertex.label.degree (pomak naziva oko čvora)

Primjer (na g3):

set.seed(1)
plot(g3,
     layout = layout_with_fr(g3),
     vertex.size = 10,
     vertex.color = "grey80",
     vertex.frame.color = "grey40",
     vertex.shape = "circle",
     vertex.label = NA)

  1. Argumenti za veze (edge.*)

Najčešće:

  • edge.color

  • edge.width

  • edge.lty

  • edge.curved (korisno kad se veze preklapaju)

  • edge.arrow.size, edge.arrow.width (za usmjerene grafove)

  • edge.label + edge.label.cex, edge.label.color (rijetko u praksi jer stvara šum)

Primjer:

plot(g3,
     layout = layout_with_fr(g3),
     vertex.size = 5,
     vertex.label = NA,
     edge.color = "grey70",
     edge.width = 1,
     edge.curved = 0.1)

  1. Opći argumenti “platna” i skaliranja

Često korisno:

  • main (naslov)

  • asp (omjer osi; asp = 0 često pomaže da graf ne izgleda “stisnuto”)

  • margin (margine oko crteža)

  • rescale, xlim, ylim

Primjer:

plot(g3,
     layout = layout_with_fr(g3),
     vertex.size = 5,
     vertex.label = NA,
     edge.color = "grey75",
     main = "Graf",
     margin = 0.05,
     asp = 0)

  1. Kako sustavno postaviti stil preko atributa grafa

Umjesto da sve pišemo u plot(), možemo postaviti atribute u grafu g pa onda samo plot(g):

V(g3)$deg  <- degree(g3)
V(g3)$size <- 2 + 10 * V(g3)$deg / max(V(g3)$deg)

E(g3)$color <- "grey75"
E(g3)$width <- 1

plot(g3,
     layout = layout_with_fr(g3),
     vertex.label = NA,
     asp = 0,
     margin = 0.05)

ggplot2 (i ggraph)

U ggplot2 ne postoji jedna funkcija s “cijelim popisom argumenata” kao u plot.igraph(). Umjesto toga, sve se slaže u 4 koraka:

  1. Podaci + mapiranje: aes(x=..., y=..., size=..., colour=...)

  2. Geometrije: geom_point(), geom_text(), geom_edge_link()

  3. Skale: scale_size_*, scale_colour_*, scale_edge_width()

  4. Tema: theme() ili theme_graph()

Za mreže to radimo kroz ggraph, gdje su najvažniji “grafički elementi”:

  • čvorovi: geom_node_point(), geom_node_text()

  • veze: geom_edge_link(), geom_edge_fan(), geom_edge_arc() (ovisno o potrebi)

  • layout: ggraph(graph, layout = "...")

Kako vidjeti argumente ggplot geometrija

Primjer za geom_point() (čvorovi):

names(formals(ggplot2::geom_point))
## [1] "mapping"     "data"        "stat"        "position"    "..."        
## [6] "na.rm"       "show.legend" "inherit.aes"

A za ggraph geometrije (npr. bridovi):

names(formals(ggraph::geom_edge_link))
##  [1] "mapping"       "data"          "position"      "arrow"        
##  [5] "n"             "lineend"       "linejoin"      "linemitre"    
##  [9] "label_colour"  "label_alpha"   "label_parse"   "check_overlap"
## [13] "angle_calc"    "force_flip"    "label_dodge"   "label_push"   
## [17] "show.legend"   "..."

Primjer:

library(tidygraph)
library(ggraph)
library(ggplot2)

tg_demo <- as_tbl_graph(g3) %>%
  activate(nodes) %>%
  mutate(deg = degree(g3))   # koristi igraph::degree

ggraph(tg_demo, layout = "fr") +
  geom_edge_link(alpha = 0.4) +
  geom_node_point(aes(size = deg), alpha = 0.9) +
  theme_graph()

Napomena: U ovom poglavlju koristimo podgraf samo radi čitljivosti i usporedbe layouta, ne zato što ‘mijenjamo’ mrežu. U kasnijem poglavlju podgraf koristimo kao analitičku odluku (što prikazati i zašto).

Ako želite selektivne oznake (npr. top 10 po degreeu, jer za veliki graf ne možemo čitko prikazati sve oznake) i nijansirane čvorove prema stupnju čvora:

library(tidygraph)
library(ggraph)
library(ggplot2)

tg_demo <- as_tbl_graph(g3) %>%
  activate(nodes) %>%
  mutate(deg = degree(g3))  # koristimo igraph::degree

nodes_tbl <- tg_demo %>% 
  activate(nodes) %>% 
  as_tibble()

top10 <- nodes_tbl %>% 
  arrange(desc(deg)) %>% 
  slice(1:10) %>% 
  pull(name)

tg_demo2 <- tg_demo %>%
  activate(nodes) %>%
  mutate(label = ifelse(name %in% top10, name, NA))

ggraph(tg_demo2, layout = "fr") +
  geom_edge_link(alpha = 0.35, colour = "grey70") +
  geom_node_point(aes(colour = deg), alpha = 0.9) +
  geom_node_text(aes(label = label), repel = TRUE, na.rm = TRUE, size = 2) +
  scale_colour_gradient(
    low  = "#c6dbef",   # svijetlo plava
    high = "#08306b",   # tamno plava
    name = "Degree"
  ) +
  scale_size_continuous(range = c(1, 5)) +
  theme_graph()

Primjer s Bitcoin OTC mrežom povjerenja

library(tidygraph)
library(ggraph)
library(ggplot2)

tg_demo_pos <- as_tbl_graph(g_pos) %>%
  activate(nodes) %>%
  mutate(deg_pos = degree(g_pos))  # koristimo igraph::degree

nodes_tbl_pos <- tg_demo_pos %>% 
  activate(nodes) %>% 
  as_tibble()

top10_pos <- nodes_tbl_pos %>% 
  arrange(desc(deg_pos)) %>% 
  slice(1:10) %>% 
  pull(name)

# Ne možemo istovremeno imati veliku mrežu, puno oznaka i čitljiv graf

tg_demo_pos2 <- tg_demo_pos %>%
  activate(nodes) %>%
  mutate(label = ifelse(name %in% top10_pos, name, NA))  # nazivi za top 10 čvorova

ggraph(tg_demo_pos2, layout = "fr") +
  geom_edge_link(alpha = 0.35, colour = "grey70") +
  geom_node_point(aes(colour = deg_pos), alpha = 0.9) +
  geom_node_text(aes(label = label), repel = TRUE, na.rm = TRUE, size = 5) +
  scale_colour_gradient(
    low  = "#c6dbef",   # svijetlo plava
    high = "#08306b",   # tamno plava
    name = "Degree"
  ) +
  scale_size_continuous(range = c(1, 5)) +
  theme_graph(base_family = "Arial")

Layout

Layout je algoritam koji određuje položaje čvorova u prostoru. Isti graf može izgledati vrlo različito ovisno o layoutu, pa layout ne biramo zato što je “lijep”, nego zato što pomaže vidjeti strukturu koja nas zanima. Kod većih mreža layout je posebno važan jer gustoća informacija brzo preraste u vizualni šum. Zbog toga ćemo prvo nacrtati veliku mrežu g3 samo informativno, a za usporedbu layouta koristit ćemo manji podgraf g_demo od 10 čvorova.

Treba naglasiti da različiti algoritmi mogu različito naglasiti dijelove mreže, pa izbor layouta utječe na to što gledatelj prvo uoči.

# Mini Chinook mreža - top 10 prema stupnju čvora
deg <- degree(g3)
top <- names(sort(deg, decreasing = TRUE))[1:10]
g_demo <- induced_subgraph(g3, vids = top) # vids može biti indeks čvora ili ime

Slijedi 15 najčešće korištenih layouta.

layout_nicely

layout_nicely() je “pametni” zadani odabir - igraph interno pokušava odabrati layout koji ima smisla za dani graf bez da korisnik mora znati što točno želi. U praksi se često svodi na neki force-directed raspored (ovisno o verziji i tipu grafa), pa mu je glavni cilj brzo proizvesti čitljiv prikaz s relativno malo presijecanja bridova i bez ekstremnih preklapanja. Ne oslanja se na jednu formulu, nego na heuristiku: “što je razumno za ovaj graf”.

Primjeren je kad tek počinjemo istraživati mrežu i želimo prvu sliku koja nije potpuno slučajna. Dobar je i kad želimo provjeriti radi li plot, ali uz napomenu da default nije nužno optimalan za naše analitičko pitanje. Tipično ga koristimo kao početnu točku, a zatim prelazimo na specifičniji layout kad znamo što želimo istaknuti (npr. udaljenosti, hijerarhiju, ili usporedbe).

plot(g3,
     layout = layout_nicely(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): osnovni prikaz u igraphu")

layout_with_fr

Fruchterman–Reingold je klasični force-directed layout: čvorovi se ponašaju kao nabijene čestice koje se međusobno odbijaju, dok bridovi djeluju kao opruge koje povezuju čvorove i “vuku” ih zajedno. Cilj je postići stanje ravnoteže u kojem povezani čvorovi budu bliže, nepovezani dovoljno razmaknuti, a presijecanje bridova (posredno) smanjeno. U igraph implementaciji važni parametri su broj iteracija (niter), “temperatura” hlađenja (start.temp) i, ovisno o varijanti, težine bridova ako ih graf ima (težina može jače “povući” čvorove).

Ovaj layout je odličan kao opći prikaz “strukture” mreže: često vizualno izvuče jezgru i periferiju te zbijenije dijelove grafa. Primjeren je kad želimo dobiti intuitivnu sliku povezanosti, ali treba paziti na dvije stvari: (1) rezultati ovise o slučajnoj inicijalizaciji (dobro je postaviti set.seed() radi ponovljivosti) i (2) na velikim mrežama može postati spor ili dati “kuglu špageta”. Za učenje i srednje mreže je gotovo uvijek dobar prvi “ozbiljni” layout.

plot(g3,
     layout = layout_with_fr(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Fruchterman–Reingold prikaz u igraphu")

layout_with_kk

Kamada–Kawai layout polazi od ideje da graf ima smislen “idealni” razmak između svakog para čvorova, koji je proporcionalan njihovom najkraćem putu. Algoritam potom pokušava smjestiti čvorove u ravninu tako da Euklidske udaljenosti na slici što bolje odgovaraju tim idealnim udaljenostima. To se radi minimizacijom energije: parovi čvorova s malom udaljenošću trebali bi biti blizu, a udaljeni parovi daleko. U praksi, KK često daje vrlo čitljive, “geometrijski smislenije” crteže, pogotovo kad je graf povezan i nije prevelik.

Primjeren je kad želimo naglasiti ideju udaljenosti u grafu (što se lijepo veže uz dijametar i ekscentričnost) i kad nam je čitljivost važnija od brzine. Na većim grafovima može biti znatno sporiji od FR-a, jer u pozadini koristi informacije o udaljenostima između mnogih parova čvorova. Također, ako graf ima više komponenti ili je jako nepravilan, može “razvući” prikaz ili naglasiti udaljene dijelove na način koji djeluje dramatičnije nego što bismo htjeli.

plot(g3,
     layout = layout_with_kk(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Kamada–Kawai prikaz u igraphu")

layout_with_sugiyama

Sugiyama layout je hijerarhijski algoritam razvijen za prikaz usmjerenih grafova, osobito onih koji imaju strukturu toka ili razina (npr. organizacijske strukture, ovisnosti, Directed Acyclic Graph (DAG)). Osnovna ideja je rasporediti čvorove u horizontalne ili vertikalne slojeve (razine) tako da većina bridova ide u jednom smjeru (npr. odozgo prema dolje). Algoritam najprije pokušava ukloniti cikluse (ako postoje), zatim određuje razine čvorova, a potom optimizira raspored unutar razina kako bi smanjio presijecanje bridova. U igraph funkciji layout_with_sugiyama() moguće je specificirati korijenski čvor ili koristiti postojeću usmjerenost grafa kao osnovu za određivanje razina.

Sugiyama je primjeren kad želimo naglasiti smjer i hijerarhiju, a ne opću povezanost. Za razliku od force-directed layouta, on ne pokušava postići “fizičku ravnotežu”, nego strukturirani, slojeviti prikaz. Posebno je koristan za DAG-ove i analize toka (npr. tko inicira interakcije prema kome). Međutim, na gusto povezanim mrežama s mnogo ciklusa može proizvesti vrlo zbijen ili vizualno složen prikaz, pa je često najprimjereniji za manje ili prethodno filtrirane grafove. Kao i kod ostalih layouta, Sugiyama ne “dokazuje” hijerarhiju, nego je vizualno nameće na temelju smjera bridova — zato je važno jasno navesti da je korišten hijerarhijski raspored.

plot(g3,
     layout = layout_with_sugiyama(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Sugiyama prikaz u igraphu")

plot(g_pos,
     layout = layout_with_sugiyama(g_pos),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g_pos (Bitcoin OTCmreža povjerenja): \n Sugiyama prikaz u igraphu")

layout_with_drl

DrL (Distributed Recursive Layout) je algoritam dizajniran da relativno dobro skalira veće mreže i da pritom zadrži globalnu strukturu. Intuicija je slična force-directed pristupu, ali DrL koristi više razina (multi-level) i rekurzivno gradi raspored: prvo dobije “grubu” sliku na sažetoj verziji grafa, a zatim tu sliku rafinira dok ne dođe do pune mreže. Time može postići da graf ne završi kao potpuno zbijena masa, nego da se globalni oblici (npr. više “krakova” ili jezgri) bolje očuvaju.

Primjeren je kad je graf dovoljno velik da FR/KK postanu spori ili previše zbijeni. Dobar je i kad želimo “makro” strukturu (kako se mreža širi i gdje su veće gustoće), a ne savršenu lokalnu estetiku.

plot(g3,
     layout = layout_with_drl(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Distributed Recursive Layout prikaz u igraphu")

layout_with_lgl

LGL (Large Graph Layout) je posebno orijentiran na velike, rijetke (sparse) mreže. Umjesto da jednako tretira sve čvorove, često gradi prikaz oko “jezgre” i širi ga prema periferiji, pri čemu nastoji izbjeći preveliku zbijenost i omogućiti da se veliki graf uopće može vizualno “rasporediti”. U pozadini koristi heuristike koje su praktične za velike grafove, gdje je savršena optimizacija energije prespora ili nepotrebna.

Primjeren je kad imamo puno čvorova, relativno malo veza po čvoru i želimo dobiti preglednu sliku bez ekstremnog vremena izvođenja. Kod manjih grafova može djelovati “previše specifično” (npr. nepotrebno naglasiti periferiju) ili dati prikaz koji nije estetski bolji od FR-a. U praksi ga koristimo kad već znamo da graf “ne stane” u standardne force-directed pokušaje i trebamo nešto robusno za velike ulaze.

plot(g3,
     layout = layout_with_lgl(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Large Graph Layout prikaz u igraphu")

layout_in_circle

layout_in_circle() je deterministički raspored: čvorovi se postave na kružnicu u jednakim razmacima, bez pokušaja optimizacije presijecanja ili udaljenosti. Ne koristi “parametre” u smislu iteracija; njegova logika je geometrijska i vrlo jednostavna. Upravo zbog toga je odličan kao kontrolni prikaz: kad želimo usporediti dvije mreže ili kad želimo izbjeći da layout “sugerira” strukturu koju graf možda nema.

Primjeren je za usporedbe i za situacije kad redoslijed čvorova ima smisla (npr. ako ih sortiramo po degreeu ili abecedno prije crtanja). Također je koristan ako želimo naglasiti da je mreža vrlo gusta (tada će krug brzo postati “špageti”) ili ako želimo izbjeći lažni dojam klastera koji force-directed layout ponekad stvori. Nije primjeren kad nam treba čitljiv prikaz strukture, nego više kao baseline i za didaktiku.

plot(g3,
     layout = layout_in_circle(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Kružni prikaz u igraphu")

layout_on_grid

layout_on_grid() postavlja čvorove na pravilnu mrežu (grid) — kao tablicu koordinata. Ideja nije prikazati strukturu grafa kroz položaj, nego povećati čitljivost i smanjiti preklapanje čvorova, osobito kad želimo jasno vidjeti oznake ili kada graf koristimo kao “nosač” za prikaz atributa (npr. boja/veličina čvora). Ne optimizira presijecanja bridova, pa bridovi često izgledaju kaotično, ali čvorovi su uredno raspoređeni.

Primjeren je kad su čvorovi primarni, a veze sekundarne, ili kad želimo stabilan, ponovljiv raspored koji nije osjetljiv na slučajnu inicijalizaciju. Dobar je i za brznu provjeru prikazuju li se svi čvorovi koji bi se trebali prikazati. Nije najbolji kad želimo da sam položaj sugerira strukturu povezivanja, jer grid položaj to ne radi.

plot(g3,
     layout = layout_on_grid(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Rešetkasti prikaz u igraphu")

layout_randomly

layout_randomly() čvorove smješta slučajno u prostor. Ne pokušava postići ravnotežu, smanjiti presijecanja ili naglasiti bilo kakvu strukturu. Parametri su minimalni (uglavnom se oslanja na generator slučajnih brojeva), pa je rezultat jako ovisan o set.seed(). Ovaj layout je “namjerno loš” za čitljivost — i upravo zato je koristan.

Primjeren je kao baseline za demonstraciju: pokazuje koliko je layout bitan i koliko se dojam grafa mijenja kad se prijeđe sa slučajnog rasporeda na optimizirani. U analitičkom smislu rijetko ga koristimo za konačan prikaz, osim ako želimo neutralan raspored koji ne sugerira klastere. Najčešće služi kao didaktički kontrast.

plot(g3,
     layout = layout_randomly(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Nasumični prikaz u igraphu")

layout_as_star

layout_as_star() raspoređuje čvorove tako da jedan čvor (ili više njih) bude u središtu, a ostali oko njega. Inicijalno je zamišljen za korištenje na ego - mrežama. To nije “otkrivena” struktura iz grafa, nego namjerno nametnut prikaz. Ovisno o tome kako definiramo središte (u igraphu postoje opcije/parametri poput centra), možemo vizualno staviti fokus na odabrani čvor i promatrati njegove veze prema ostalima. Također, ovdje vidimo kako izbor prikaza može naglasiti jedan element.

Primjeren je kad želimo objasniti ideju “hub-a” ili kad želimo prikazati ego-mrežu oko nekog čvora (tko je povezan s kim u odnosu na jednog aktera). Međutim, nije primjeren za prikaz opće strukture mreže jer može sugerirati da je mreža zvjezdasta čak i kad nije. Koristan je kao “vizualna lupa” za jedan čvor, a ne kao univerzalni layout.

plot(g3,
     layout = layout_as_star(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Zvijezda - prikaz u igraphu")

layout_as_tree

layout_as_tree() pretpostavlja da graf ima (ili da želimo nametnuti) hijerarhijsku strukturu: čvorovi su raspoređeni u razine, a veze uglavnom idu “od gore prema dolje” (ili obratno). Parametri često uključuju izbor korijena (root) te smjer (ovisno o tome je li graf usmjeren). Kod općih mreža koje nisu stabla, često se prvo radi spanning tree (npr. BFS stablo) pa se onda na njega primijeni tree layout. Cilj je dobiti čitljiv prikaz tokova, razina ili hijerarhije.

Primjeren je kad struktura doista jest hijerarhijska (organizacijska struktura, ovisnosti, stablo pretraživanja, DAG), ili kad želimo iz opće mreže izvući stablo radi objašnjenja “kako se mreža širi” od nekog korijena. Najčešći problem je preklapanje oznaka i zbijanje na istim razinama — zato je tree layout idealan za pokazivanje ručnih dorada: širenje koordinata, selektivne oznake i (u ggraphu) repel za tekst. U konačnici, tree layout nije “najtočniji” za opće mreže, ali je primjeren u situacijama u kojima se želi intuitivno/vizualno prenijeti priča o hijerarhiji.

plot(g3,
     layout = layout_as_tree(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Stablo - prikaz u igraphu")

layout_with_dh

Davidson–Harel (DH) layout je optimizacijski algoritam koji pokušava minimizirati funkciju koja uključuje više ciljeva: smanjenje presijecanja bridova, ravnomjernu raspodjelu čvorova i održavanje razumnog razmaka između povezanih i nepovezanih čvorova. Za razliku od jednostavnijih force-directed metoda, DH koristi simulirano kaljenje (simulated annealing), što znači da postupno “hladi” sustav i traži globalno bolje rješenje.

Ovaj layout može dati vrlo uredne i estetski uravnotežene prikaze, ali je računski sporiji od FR-a ili KK-a, osobito kod većih grafova. Primjeren je kad želimo kvalitetan statički prikaz srednje velikog grafa i spremni smo uložiti više vremena u računanje rasporeda. U praksi se rjeđe koristi u svakodnevnoj analizi, ali je koristan kad želimo pokazati da postoje napredniji optimizacijski pristupi rasporedu mreže.

plot(g3,
     layout = layout_with_dh(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Davidson–Harel prikaz u igraphu")

layout_on_sphere

layout_on_sphere() raspoređuje čvorove na površinu kugle u trodimenzionalnom prostoru. Ideja je izbjeći preveliku zbijenost u ravnini i omogućiti ravnomjerniju raspodjelu čvorova, osobito kod većih mreža. U dvodimenzionalnom prikazu to može izgledati kao projekcija kugle na ravninu, pa čvorovi djeluju raspoređeni po kružnom ili sfernom obrascu.

Ovaj layout je primjeren kad želimo naglasiti globalnu strukturu bez centralne dominacije ili kada radimo s 3D prikazima. U standardnom 2D prikazu njegova prednost nije uvijek očita, ali može pomoći u izbjegavanju preklapanja kod gušćih mreža. Nije posebno analitički orijentiran (ne čuva udaljenosti kao MDS niti hijerarhiju kao Sugiyama), ali pokazuje da raspored može biti definiran i u višedimenzionalnom prostoru.

plot(g3,
     layout = layout_on_sphere(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Sferični prikaz u igraphu")

layout_with_graphopt

layout_with_graphopt() temelji se na fizikalnoj analogiji: čvorovi se međusobno odbijaju, dok bridovi djeluju kao opruge koje ih privlače. Za razliku od Fruchterman–Reingolda, Graphopt koristi nešto drugačiji model sila i parametara (npr. jačinu opruge, jačinu odbijanja i broj iteracija), čime omogućuje fleksibilniju kontrolu nad rasporedom. Algoritam iterativno prilagođava pozicije čvorova dok se ne postigne ravnoteža između privlačnih i odbojnih sila.

Graphopt je primjeren za srednje velike mreže kada želimo klasičan “organski” prikaz, ali s nešto drukčijim naglascima nego kod FR-a. U nekim slučajevima može proizvesti kompaktniji ili stabilniji raspored, osobito ako pažljivo prilagodimo parametre (npr. niter, charge, spring.length). Međutim, kao i ostali force-directed layouti, može sugerirati grupiranja koja nisu formalno analizirana, pa ga treba interpretirati oprezno. Dobar je izbor kada želimo alternativu FR-u bez prelaska na hijerarhijske ili udaljenosne metode.

plot(g3,
     layout = layout_with_graphopt(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): Graphopt prikaz u igraphu")

layout_with_mds

MDS (Multidimensional Scaling) layout temelji se na ideji da se čvorovi rasporede u prostoru tako da udaljenosti na slici približno odgovaraju udaljenostima u grafu (najčešće najkraćim putovima). Algoritam najprije izračuna matricu udaljenosti između čvorova (npr. duljine najkraćih putova), a zatim pokušava pronaći koordinate u dvodimenzionalnom prostoru koje najbolje odražavaju te udaljenosti. Rezultat je raspored u kojem su čvorovi koji su “bliski” u mreži blizu i na slici, a udaljeni čvorovi prostorno razmaknuti.

MDS layout je primjeren kad želimo naglasiti globalnu strukturu i relacije udaljenosti — primjerice kad analiziramo dijametar ili ekscentričnost. Za razliku od force-directed layouta, MDS ima jasnu matematičku podlogu u metodama redukcije dimenzionalnosti. Međutim, na velikim grafovima može biti računski zahtjevan jer uključuje računanje matrice udaljenosti. Također, raspored može izgledati “geometrijski” ili pomalo apstraktno, što nije uvijek intuitivno za početnike.

plot(g3,
     layout = layout_with_mds(g3),
     vertex.size = 2,
     vertex.label = NA,
     edge.color = "grey80",
     main = "g3 (Chinook): MDS prikaz u igraphu")

Prikaz layouta na Chinook podgrafu od 10 čvorova:

layouts_igraph <- list(
  nicely = layout_nicely,
  fr     = layout_with_fr,
  kk     = layout_with_kk,
  sg     = layout_with_sugiyama,
  drl    = layout_with_drl,
  lgl    = layout_with_lgl,
  circle = layout_in_circle,
  grid   = layout_on_grid,
  random = layout_randomly,
  star   = layout_as_star,
  tree   = layout_as_tree,
  dh     = layout_with_dh,
  sphere = layout_on_sphere,
  opt    = layout_with_graphopt,
  mds    = layout_with_mds
)

par(mfrow = c(3, 5), mar = c(1, 1, 2, 1))

for (nm in names(layouts_igraph)) {
  lay_fun <- layouts_igraph[[nm]]
  plot(g_demo,
       layout = lay_fun(g_demo),
       vertex.size = 4,
       vertex.label = NA,
       edge.color = "grey75",
       main = paste0("layout_", nm))
}

Prikaz layouta na Bitcoin OTC mreži povjerenja izgledao bi ovako (isprobajte sami).

layouts_igraph <- list(
  nicely = layout_nicely,
  fr     = layout_with_fr,
  kk     = layout_with_kk,
  sg     = layout_with_sugiyama,
  drl    = layout_with_drl,
  lgl    = layout_with_lgl,
  circle = layout_in_circle,
  grid   = layout_on_grid,
  random = layout_randomly,
  star   = layout_as_star,
  tree   = layout_as_tree,
  dh     = layout_with_dh,
  sphere = layout_on_sphere,
  opt    = layout_with_graphopt,
  mds    = layout_with_mds
)

par(mfrow = c(3, 5), mar = c(1, 1, 2, 1))

for (nm in names(layouts_igraph)) {
  lay_fun <- layouts_igraph[[nm]]
  plot(g_pos,
       layout = lay_fun(g_pos),
       vertex.size = 4,
       vertex.label = NA,
       edge.color = "grey75",
       main = paste0("layout_", nm))
}

Ovdje nisu prikazani svi layouti, ali ih možete isprobati samostalno.

Napomena: ggraph layout-e naziva malo drugačije, ali logika je ista.

library(tidygraph)
library(ggraph)
library(ggplot2)

tg3 <- as_tbl_graph(g3)

ggraph(tg3, layout = "fr") +
  geom_edge_link(alpha = 0.15, colour = "grey75") +
  geom_node_point(size = 1, alpha = 0.8) +
  theme_graph() +
  ggtitle("g3 (Chinook): osnovni prikaz u ggraphu (FR)")

library(tidygraph)
library(ggraph)
library(ggplot2)

tg3 <- as_tbl_graph(g3)

ggraph(tg3, layout = "kk") +
  geom_edge_link(alpha = 0.15, colour = "grey75") +
  geom_node_point(size = 1, alpha = 0.8) +
  theme_graph() +
  ggtitle("g3 (Chinook): osnovni prikaz u ggraphu (KK)")

Prikazi layouta na podgrafu:

tg_demo <- as_tbl_graph(g_demo)

layouts_ggraph <- c(
  "nicely",
  "fr",
  "kk",
  "drl",
  "lgl",
  "circle",
  "grid",
  "randomly",
  "stress",   # često jako čitljiv 
  "tree",
  "star",
  "dh",
  "graphopt",
  "sphere",
  "mds"
  
)

for (lay in layouts_ggraph) {
  print(
    ggraph(tg_demo, layout = lay) +
      geom_edge_link(alpha = 0.35, colour = "grey70") +
      geom_node_point(size = 2, alpha = 0.9) +
      theme_graph() +
      ggtitle(paste("g_demo:", lay))
  )
}

Ručno zadani layout

Tree prikazi često imaju preklapanja oznaka i “zbijanje” na istim razinama, pa je ovo najbolji tip grafa za prikaz ručnog (ili poluručnog) podešavanja koordinata.

Ideja: iz g_demo napravimo BFS stablo (spanning tree) iz čvora s najvećim degreeom, pa ga prikažemo kao hijerarhiju.

  1. Izrada tree grafa (BFS spanning tree)
root <- names(sort(degree(g_demo), decreasing = TRUE))[1]
tree_g <- bfs(g_demo, root = root, father = TRUE)

# iz father vektora složimo bridove stabla
father <- tree_g$father
nodes  <- V(g_demo)$name

edges_tree <- data.frame(
  from = nodes[father[!is.na(father)]],
  to   = nodes[!is.na(father)]
)

g_tree <- graph_from_data_frame(edges_tree, directed = TRUE)
  1. Ručni layout u igraphu: layout_as_tree + ručno “širenje” i margine
lay_tree <- layout_as_tree(g_tree, root = root)

# malo raširimo x da smanji preklapanja
lay_tree[,1] <- lay_tree[,1] * 1.6
lay_tree[,2] <- lay_tree[,2] * 1.2

# selektivno labeliranje (top 5 po out-degree u stablu)
deg_out <- degree(g_tree, mode = "out")
top_lab <- names(sort(deg_out, decreasing = TRUE))[1:5]
V(g_tree)$label <- ifelse(V(g_tree)$name %in% top_lab, V(g_tree)$name, NA)

plot(g_tree,
     layout = lay_tree,
     vertex.size = 4,
     vertex.label = V(g_tree)$label,
     vertex.label.cex = 0.7,
     edge.arrow.size = 0.3,
     edge.color = "grey70",
     margin = 0.08,
     asp = 0,
     main = "Tree: layout_as_tree + ručno širenje (igraph)")

  1. Ručni layout u ggraphu: isti layout kao “manual” koordinate
tg_tree <- as_tbl_graph(g_tree)

ggraph(tg_tree, layout = "manual", x = lay_tree[,1], y = lay_tree[,2]) +
  geom_edge_link(alpha = 0.4, colour = "grey70",
                 arrow = grid::arrow(length = grid::unit(3, "mm"))) +
  geom_node_point(size = 2.2, alpha = 0.9) +
  geom_node_text(aes(label = ifelse(name %in% top_lab, name, NA)),
                 repel = TRUE, size = 3) +
  theme_graph() +
  ggtitle("Tree: manual layout (ggraph)")

Geografski layout

Za razliku od većine layout algoritama (npr. Fruchterman–Reingold ili Kamada–Kawai), geografski layout ne izračunava položaje čvorova. Umjesto toga, položaji su unaprijed zadani stvarnim koordinatama u prostoru — najčešće zemljopisnom širinom (latitude) i dužinom (longitude).

To znači da za geografski layout moramo imati:

  • tablicu čvorova s koordinatama (lon, lat)

  • tablicu veza (npr. letovi između aerodroma)

  • konzistentne identifikatore (ID čvora u obje tablice)

Drugim riječima, ovdje layout nije analitički algoritam, nego preslikavanje realnog prostora u grafički prostor.

Važno je razumjeti:

  • udaljenosti na slici ovdje imaju stvarno značenje (geografska udaljenost),

  • struktura mreže može biti vizualno “razvučena” jer je uvjetovana geografijom,

  • layout ne sugerira klastere — nego ih može uvjetovati prostor.

Zbog toga je geografski layout posebno primjeren za:

  • prometne mreže (zrakoplovne, željezničke),

  • logističke tokove,

  • epidemiološke mreže,

  • migracijske tokove.

Primjer: OpenFlights – koristit ćemo javno dostupne podatke projekta OpenFlights (mreža letova)

Skup podataka sadrži:

  • airports.dat – aerodromi s koordinatama
  • routes.dat – rute između aerodroma
# Učitavanje
dir.create("data", showWarnings = FALSE)

# Preuzimanje podataka
download.file(
  "https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat",
  destfile = "data/airports.dat",
  mode = "wb"
)

download.file(
  "https://raw.githubusercontent.com/jpatokal/openflights/master/data/routes.dat",
  destfile = "data/routes.dat",
  mode = "wb"
)

# Priprema podataka
# aerodromi, tj. čvorovi
airports <- read_csv("data/airports.dat",
                     col_names = FALSE,
                     show_col_types = FALSE)

colnames(airports) <- c(
  "airport_id", "name", "city", "country",
  "iata", "icao", "lat", "lon",
  "altitude", "timezone", "dst",
  "tz_database", "type", "source"
)

airports <- airports %>%
  filter(!is.na(lat), !is.na(lon),
         iata != "\\N")

glimpse(airports)
## Rows: 6,072
## Columns: 14
## $ airport_id  <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,…
## $ name        <chr> "Goroka Airport", "Madang Airport", "Mount Hagen Kagamuga …
## $ city        <chr> "Goroka", "Madang", "Mount Hagen", "Nadzab", "Port Moresby…
## $ country     <chr> "Papua New Guinea", "Papua New Guinea", "Papua New Guinea"…
## $ iata        <chr> "GKA", "MAG", "HGU", "LAE", "POM", "WWK", "UAK", "GOH", "S…
## $ icao        <chr> "AYGA", "AYMD", "AYMH", "AYNZ", "AYPY", "AYWK", "BGBW", "B…
## $ lat         <dbl> -6.081690, -5.207080, -5.826790, -6.569803, -9.443380, -3.…
## $ lon         <dbl> 145.3920, 145.7890, 144.2960, 146.7260, 147.2200, 143.6690…
## $ altitude    <dbl> 5282, 20, 5388, 239, 146, 19, 112, 283, 165, 251, 6, 76, 2…
## $ timezone    <chr> "10", "10", "10", "10", "10", "10", "-3", "-3", "-3", "-4"…
## $ dst         <chr> "U", "U", "U", "U", "U", "U", "E", "E", "E", "E", "N", "N"…
## $ tz_database <chr> "Pacific/Port_Moresby", "Pacific/Port_Moresby", "Pacific/P…
## $ type        <chr> "airport", "airport", "airport", "airport", "airport", "ai…
## $ source      <chr> "OurAirports", "OurAirports", "OurAirports", "OurAirports"…
# rute, tj. lukovi
routes <- read_csv("data/routes.dat",
                   col_names = FALSE,
                   show_col_types = FALSE)

colnames(routes) <- c(
  "airline", "airline_id",
  "source_iata", "source_id",
  "dest_iata", "dest_id",
  "codeshare", "stops", "equipment"
)

routes <- routes %>%
  filter(source_iata != "\\N",
         dest_iata != "\\N")

glimpse(routes)
## Rows: 67,663
## Columns: 9
## $ airline     <chr> "2B", "2B", "2B", "2B", "2B", "2B", "2B", "2B", "2B", "2B"…
## $ airline_id  <chr> "410", "410", "410", "410", "410", "410", "410", "410", "4…
## $ source_iata <chr> "AER", "ASF", "ASF", "CEK", "CEK", "DME", "DME", "DME", "D…
## $ source_id   <chr> "2965", "2966", "2966", "2968", "2968", "4029", "4029", "4…
## $ dest_iata   <chr> "KZN", "KZN", "MRV", "KZN", "OVB", "KZN", "NBC", "TGK", "U…
## $ dest_id     <chr> "2990", "2990", "2962", "2990", "4078", "2990", "6969", "\…
## $ codeshare   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ stops       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ equipment   <chr> "CR2", "CR2", "CR2", "CR2", "CR2", "CR2", "CR2", "CR2", "C…
# Spajanje ruta i koordinata
airports_small <- airports %>%
  select(iata, name, country, lat, lon)

routes_geo <- routes %>%
  inner_join(airports_small, by = c("source_iata" = "iata")) %>%
  rename(from_lat = lat,
         from_lon = lon,
         from_name = name) %>%
  inner_join(airports_small, by = c("dest_iata" = "iata")) %>%
  rename(to_lat = lat,
         to_lon = lon,
         to_name = name)

edges_geo <- routes_geo %>% select(from = source_iata, to = dest_iata)

# graf objekt
g_air <- graph_from_data_frame(edges_geo, directed = TRUE)

# Koordinate iz airports
coords <- airports %>%
  select(iata, lat, lon) %>%
  distinct() %>%
  filter(iata %in% V(g_air)$name) %>%
  mutate(
    name = as.character(iata),
    lat  = as.numeric(lat),
    lon  = as.numeric(lon)
  )

# Poredamo koordinate točno redoslijedom čvorova u grafu
coords_ig <- coords[match(V(g_air)$name, coords$name), ]

# Layout matrica (x = lon, y = lat)
lay_geo <- as.matrix(coords_ig[, c("lon", "lat")])

# Skaliranje degreea (graf je OGROMAN, moramo biti umjereni)
deg_air <- degree(g_air, mode = "all")
v_size  <- scales::rescale(deg_air, to = c(0.5, 3))

# Smanjimo "špageti efekt"
E(g_air)$color <- adjustcolor("grey40", alpha.f = 0.02)
E(g_air)$width <- 0.2


# Izrada igraph objekta
map("world",
    col = "grey92",
    fill = TRUE,
    border = "grey80",
    lty = 0)

plot(g_air,
     layout = lay_geo,
     add = TRUE,
     rescale = FALSE,   # Ovo je jako važno! Bez ove postavke, može skalirati dane koordinate
     vertex.size = v_size,
     vertex.label = NA,
     vertex.color = "blue",
     vertex.frame.color = NA,
     edge.arrow.size = 0.1,
     asp = 0)
title("OpenFlights: mreža letova na karti svijeta (igraph)")

# geografski layout u ggraphu
# Karta svijeta
world <- map_data("world")

# Pretvorimo graf u tbl_graph i dodamo koordinate čvorovima
tg_air <- as_tbl_graph(g_air) %>%
  activate(nodes) %>%
  left_join(
    airports %>%
      transmute(name = as.character(iata),
                lon  = as.numeric(lon),
                lat  = as.numeric(lat)) %>%
      distinct(),
    by = "name"
  ) %>%
  # sigurnosno: izbaci čvorove bez koordinata (u praksi ih bude malo)
  filter(!is.na(lon), !is.na(lat)) %>%
  mutate(deg = igraph::degree(as.igraph(.), mode = "all"))

# Crtanje
ggraph(tg_air, layout = "manual", x = lon, y = lat) +
  geom_polygon(data = world,
               aes(x = long, y = lat, group = group),
               fill = "grey92", colour = "grey80", linewidth = 0.2) +
  geom_edge_link(alpha = 0.03, colour = "grey40",
                 arrow = grid::arrow(length = unit(1.5, "mm")),
                 end_cap = circle(0.6, "mm")) +
  geom_node_point(aes(size = deg), colour = "darkred", alpha = 0.7) +
  scale_size_continuous(range = c(0.3, 2.5), guide = "none") +
  coord_quickmap() +
  theme_void() +
  ggtitle("OpenFlights: mreža letova na karti svijeta (ggraph)")

Graf kao informacija

Koja se priča želi ispričati grafom? Promatramo li aktere s velikim brojem veza? Zanima li nas koliko je mreža “rastegnuta” i tko je na njezinu rubu? Ili želimo usporediti dvije mreže po gustoći i rasponu? Vizualizacija nije neutralna: način na koji nacrtamo graf određuje što će promatrač prvo uočiti.

Vizualni signali – veličina, boja, položaj – imaju smisla samo ako su povezani s analitičkim ciljem. U ovoj fazi koristimo mjere koje već poznajemo: stupanj čvora (degree), gustoću (density), dijametar i ekscentričnost (kasnije ćemo na sličan način koristiti i druge metrike). Te mjere možemo pretvoriti u grafičke argumente i time dobiti prikaz koji nije samo “lijep”, nego i informativan.

Međutim, postoji temeljno ograničenje: ne postoji način da istodobno dobijemo lijep i informativan graf, ako je mreža prevelika. Kod velikih mreža prvo ćemo žrtvovati oznake čvorova. Time već gubimo dio informacije. Sljedeće nestaju oznake bridova. I dalje će se čvorovi i bridovi preklapati, pa i najbolji layout više ne pomaže. U takvim situacijama problem nije u funkciji plot(), nego u veličini mreže. Rješenje je odabrati podgraf, a način odabira ovisi o cilju analize.

U nastavku ćemo prikazati nekoliko različitih strategija odabira podgrafa koristeći samo mjere koje poznajemo.

Podgraf prema stupnju čvora (najpovezaniji akteri)

Ako nas zanima tko ima najviše veza, prirodno je izdvojiti čvorove s najvećim degree-om.

# Chinook

deg_all <- degree(g3)

top30 <- names(sort(deg_all, decreasing = TRUE))[1:30]

g_top_deg <- induced_subgraph(g3, vids = top30)

g_top_deg
## IGRAPH 53fdb54 UN-- 30 435 -- 
## + attr: name (v/c), deg (v/n), size (v/n), color (e/c), width (e/n)
## + edges from 53fdb54 (vertex names):
## [1] nicolaus esterhazy sinfonia           --alberto turco & nova schola gregoriana                  
## [2] nicolaus esterhazy sinfonia           --richard marlow & the choir of trinity college, cambridge
## [3] alberto turco & nova schola gregoriana--richard marlow & the choir of trinity college, cambridge
## [4] nicolaus esterhazy sinfonia           --english concert & trevor pinnock                        
## [5] alberto turco & nova schola gregoriana--english concert & trevor pinnock                        
## + ... omitted several edges
# Bitcoin OTC mreža povjerenja

tg_pos <- as_tbl_graph(g_pos) %>%
  activate(nodes) %>%
  mutate(indeg = centrality_degree(mode = "in"))

top_nodes <- tg_pos %>%
  activate(nodes) %>%
  as_tibble() %>%
  arrange(desc(indeg)) %>%
  slice(1:50) %>%
  pull(name)

g_small <- induced_subgraph(as.igraph(tg_pos), vids = top_nodes)

tg_small <- as_tbl_graph(g_small) |>
  activate(nodes) |>
  mutate(indeg = centrality_degree(mode = "in"))
str(tg_small)
## Classes 'tbl_graph', 'igraph'  hidden list of 10
##  $ : num 50
##  $ : logi TRUE
##  $ : num [1:958] 0 0 0 0 0 0 0 0 0 0 ...
##  $ : num [1:958] 1 2 3 4 5 6 7 8 10 13 ...
##  $ : NULL
##  $ : NULL
##  $ : NULL
##  $ : NULL
##  $ :List of 4
##   ..$ : num [1:3] 1 0 1
##   ..$ : Named list()
##   ..$ :List of 2
##   .. ..$ name : chr [1:50] "1" "13" "7" "35" ...
##   .. ..$ indeg: Named num [1:50] 26 20 14 21 19 25 11 18 26 23 ...
##   .. .. ..- attr(*, "names")= chr [1:50] "1" "13" "7" "35" ...
##   ..$ :List of 1
##   .. ..$ weight: int [1:958] 3 9 4 8 6 1 3 5 1 1 ...
##  $ :<environment: 0x000001ca69d6da58> 
##  - attr(*, "active")= chr "nodes"

Vizualizacija:

V(g_top_deg)$deg <- degree(g_top_deg)

ggraph(as_tbl_graph(g_top_deg), layout = "fr") +
  geom_edge_link(alpha = 0.4, colour = "grey70") +
  geom_node_point(aes(size = deg, colour = deg), alpha = 0.9) +
  scale_colour_gradient(low = "#c6dbef", high = "#08306b") +
  scale_size_continuous(range = c(2, 8)) +
  theme_graph() +
  ggtitle("Podgraf Chinook mreže: \n 30 čvorova s najvećim stupnjem")

V(tg_small)$deg <- degree(tg_small)

ggraph(as_tbl_graph(tg_small), layout = "fr") +
  geom_edge_link(alpha = 0.4, colour = "grey70") +
  geom_node_point(aes(size = deg, colour = deg), alpha = 0.9) +
  scale_colour_gradient(low = "#c6dbef", high = "#08306b") +
  scale_size_continuous(range = c(2, 8)) +
  theme_graph() +
  ggtitle("Podgraf Bitcoin OTC mreže povjerenja: \n 50 čvorova s najvećim stupnjem")

Što ovdje komuniciramo? Naglašavamo “jezgru” mreže – aktere s velikim brojem veza. Gubimo periferiju, ali dobivamo čitljiv prikaz strukture među najpovezanijima.

Npr., Chinook podgraf daje regularnu mrežu, jer svi čvorovi imaju jednak stupanj (svi su povezani sa svim opstalima u ovom odgrafu). U podgrafu Bitcoin OTC mreže povjerenja stupnjevi čvorova kreću se od 10 do preko 60, ukazujući na veću asimetriju u povezivanju u ovom podgrafu.

Podgraf prema ekscentričnosti (rub mreže)

Ako nas zanima tko je “na rubu” mreže, koristimo ekscentričnost. Čvorovi s većom ekscentričnošću udaljeniji su od ostatka mreže.

ecc_all <- eccentricity(g3)

# uzmimo 30 čvorova s najvećom ekscentričnošću
top30_ecc <- names(sort(ecc_all, decreasing = TRUE))[1:30]

g_top_ecc <- induced_subgraph(g3, vids = top30_ecc)
ecc_all_pos <- eccentricity(g_pos)

# uzmimo 30 čvorova s najvećom ekscentričnošću
top30_pos_ecc <- names(sort(ecc_all_pos, decreasing = TRUE))[1:30]

g_pos_top_ecc <- induced_subgraph(g_pos, vids = top30_pos_ecc)

Vizualizacija:

V(g_top_ecc)$ecc <- eccentricity(g_top_ecc)

ggraph(as_tbl_graph(g_top_ecc), layout = "kk") +
  geom_edge_link(alpha = 0.4, colour = "grey70") +
  geom_node_point(aes(colour = ecc), size = 4) +
  scale_colour_gradient(low = "#deebf7", high = "#08519c") +
  theme_graph() +
  ggtitle("Podgraf Chinook mreže: \n 30 čvorova s najvećom ekscentričnošću")

V(g_pos_top_ecc)$ecc <- eccentricity(g_pos_top_ecc)

ggraph(as_tbl_graph(g_pos_top_ecc), layout = "kk") +
  geom_edge_link(alpha = 0.4, colour = "grey70") +
  geom_node_point(aes(colour = ecc), size = 4) +
  scale_colour_gradient(low = "#deebf7", high = "#08519c") +
  theme_graph() +
  ggtitle("Podgraf Bitcoin OTC mreže povjerenja: \n 30 čvorova s najvećom ekscentričnošću")

Što ovdje komuniciramo? Ne prikazujemo “najpovezanije”, nego one koji su najudaljeniji. Ovaj podgraf daje drugačiju priču: fokus nije na jezgri, nego na periferiji.

U Chinook mreži suradnje izvođača, najmanje povezani su još uvijek više povezani od najmanje povezanih u Bitcoin mreži povjerenja.

Podgraf oko jednog čvora (ego pristup)

Ako je cilj analiza jednog aktera, možemo prikazati njegovu lokalnu okolinu.

deg_all <- degree(g3)
root <- names(sort(deg_all, decreasing = TRUE))[1]

# root kao vertex sequence
root_v <- V(g3)[name == root]

# susjedi su već vertex sequence
neighbors_root <- neighbors(g3, root_v)

# induced_subgraph prima vertex sequence bez problema
g_ego <- induced_subgraph(g3, vids = c(root_v, neighbors_root))
deg_pos_all <- degree(g_pos)
root_pos <- names(sort(deg_pos_all, decreasing = TRUE))[1]

# root kao vertex sequence
root_pos_v <- V(g_pos)[name == root_pos]

# susjedi su već vertex sequence
neighbors_pos_root <- neighbors(g_pos, root_pos_v)

# induced_subgraph prima vertex sequence bez problema
g_pos_ego <- induced_subgraph(g_pos, vids = c(root_pos_v, neighbors_pos_root))

Vizualizacija:

V(g_ego)$color <- ifelse(V(g_ego)$name == root,
                         "#08519c",
                         "#9ecae1")

plot(g_ego,
     layout = layout_as_star(g_ego),
     vertex.size = 6,
     vertex.label.cex = 0.3,
     edge.color = "grey70",
     main = "Ego-podgraf Chinocco mreže: \n lokalna mreža čvora s najvećim stupnjem")

V(g_pos_ego)$color <- ifelse(V(g_pos_ego)$name == root,
                         "#08519c",
                         "#9ecae1")

plot(g_pos_ego,
     layout = layout_as_star(g_pos_ego),
     vertex.size = 6,
     vertex.label.cex = 0.3,
     edge.color = "grey70",
     main = "Ego-podgraf Bitcoin OTC mreže povjerenja: \n lokalna mreža čvora s najvećim stupnjem")

Što ovdje komuniciramo? Fokus je na lokalnoj strukturi – tko je povezan s odabranim akterom. Ovo je informativno ako analiziramo pojedinačne slučajeve.

Ako usporedimo ove dvije mreže, vidimo da najpovezaniji akter u Bitcoin OTC mreži povjerenja ima puno više veza nego najpovezaniji ekter u Chinook mreži. Između ostalog, to signalizira i veličinu mreže, ali i potencijalni napor potreban za održavanje vodeće pozicije u takvoj mreži.

Kompromis: srednje veliki podgraf + selektivne oznake

Ako želimo zadržati više strukture, ali i čitljivost, možemo:

  • ograničiti broj čvorova (npr. 30)
  • veličinom i bojom kodirati degree
  • označiti samo top 5–10 čvorova
top30 <- names(sort(deg_all, decreasing = TRUE))[1:30]
g_mid <- induced_subgraph(g3, vids = top30)

tg_mid <- as_tbl_graph(g_mid) %>%
  activate(nodes) %>%
  mutate(deg = igraph::degree(g_mid)) %>%  # <- eksplicitno dodaj deg ovdje
  mutate(label = ifelse(name %in% names(sort(deg, decreasing = TRUE))[1:10],
                        name, NA))

ggraph(tg_mid, layout = "fr") +
  geom_edge_link(alpha = 0.35, colour = "grey75") +
  geom_node_point(aes(size = deg, colour = deg), alpha = 0.9) +
  geom_node_text(aes(label = label),
                 repel = TRUE,
                 size = 3,
                 na.rm = TRUE,
                 max.overlaps = Inf) +
  scale_colour_gradient(low = "#c6dbef", high = "#08306b") +
  scale_size_continuous(range = c(2, 8)) +
  theme_graph() +
  ggtitle("Kompromis: 30 čvorova + selektivne oznake\nChinook mreža")

deg_pos_in <- degree(g_pos, mode = "in")

top50_pos <- names(sort(deg_pos_in, decreasing = TRUE))[1:50]
g_pos_mid <- induced_subgraph(g_pos, vids = top50_pos)

tg_pos_mid <- as_tbl_graph(g_pos_mid) %>%
  activate(nodes) %>%
  mutate(deg_in = degree(g_pos_mid, mode = "in")) %>% 
  mutate(label = ifelse(name %in% names(sort(deg_in, decreasing = TRUE))[1:10],
                        name, NA))

ggraph(tg_pos_mid, layout = "fr") +
  geom_edge_link(aes(width = weight),
                 alpha = 0.25,
                 colour = "grey75") +
  geom_node_point(aes(size = deg_in, colour = deg_in),
                  alpha = 0.9) +
  geom_node_text(aes(label = label),
                 repel = TRUE,
                 size = 3,
                 na.rm = TRUE,
                 max.overlaps = Inf) +
  scale_colour_gradient(low = "#c6dbef", high = "#08306b", name = "In-degree") +
  scale_size_continuous(range = c(2, 8)) +
  scale_edge_width(range = c(0.2, 1), guide = "none") +
  theme_graph(base_family = "Arial") +
  ggtitle("Kompromis: 50 čvorova + selektivne oznake\nBitcoin OTC mreža povjerenja (pozitivne veze)")

Što ovdje komuniciramo? Zadržavamo širu strukturu, ali svjesno biramo koje informacije istaknuti. Ostatak je prisutan, ali nenametljiv.

Ključna poruka: Velika mreža ne može biti istovremeno:

  • potpuno označena,
  • potpuno čitljiva,
  • i potpuno informativna.

Zato se uvijek postavlja pitanje: Što želim da gledatelj vidi u prvih 5 sekundi?

  • Ako je odgovor “najpovezanije aktere”, biramo podgraf po degree-u.
  • Ako je odgovor “rub mreže”, biramo ekscentričnost.
  • Ako je odgovor “lokalna struktura”, biramo ego-podgraf.

Vizualizacija nije tehnički zadatak, nego metodološka odluka.

Interaktivna vizualizacija

Interaktivna vizualizacija je odlično rješenje problema prevelike mreže: umjesto da sve oznake stoje stalno, informacije se prikažu na hover, a korisnik može zoomirati dijelove mreže koje želi bolje pregledati.

U R-u je za to najjednostavnije (i stabilno za RMarkdown/HTML) koristiti visNetwork (radi direktno iz edge/node tablica, hover tooltip je prirodan, zoom i navigacija su ugrađeni). Možemo je napraviti tako da:

  • na hover pokaže ime čvora + degree + ekscentričnost

  • možemo pomicati čvorove za dodatne uvide

  • debljina/boja bridova može (opcionalno) koristiti weight kod Bitcoin mreže

  • za jako velike grafove ipak se preporučuje podgraf npr. top 500 po degreeu (jer browser zna usporiti)

Ovakv grafovi su u duhu Shneidermanovih (2003) uputa za vizualizacije.

Interaktivna mreža: Chinook (g3) – preporuka: podgraf, da browser ne pati

Za Chinook je g3 dosta velik (stotine čvorova, tisuće veza). Interaktivno je često bolje prikazati npr. top 100 po degreeu.

# Podmreža
deg_all <- degree(g3)
top100 <- names(sort(deg_all, decreasing = TRUE))[1:100]
g3_int <- induced_subgraph(g3, vids = top100)

# 1. Degree i ekscentričnost
deg_all <- degree(g3_int)
ecc_all <- eccentricity(g3_int)
# 2. Node tablica
nodes <- data.frame(
  id    = V(g3_int)$name,
  label = V(g3_int)$name,
  value = deg_all,                    # veličina čvora
  title = paste0(                     # tekst koji se prikazuje na hover
    "<b>", V(g3_int)$name, "</b><br>",
    "Degree: ", deg_all, "<br>",
    "Ekscentričnost: ", ecc_all
  ),
  stringsAsFactors = FALSE
)

# 3. Edge tablica
edges <- as_data_frame(g3_int, what = "edges")

# 4. Interaktivni graf
visNetwork(nodes, edges, height = "650px", width = "100%") %>%
  visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) %>%
  visInteraction(navigationButtons = TRUE, zoomView = TRUE) %>%    # olakšavaju rad s većim mrežama
  visPhysics(stabilization = TRUE)   

Ovo je jednostavna inačica. No, možete primijetiti da je malo nezgodno “uhvatiti” čvor kako bismo pročitali oznaku. Drugi način je layout definirati unutar postavki grafa (u tablici čvorova).

# ostali elementi su definirani u prethodnom chunku
lay <- layout_with_kk(g3_int)

nodes <- data.frame(
  id    = V(g3_int)$name,
  label = V(g3_int)$name,
  value = deg_all,
  title = paste0(
    "<b>", V(g3_int)$name, "</b><br>",
    "Degree: ", deg_all, "<br>",
    "Ekscentričnost: ", ecc_all
  ),
  x = lay[,1] * 100,   # skaliramo jer visNetwork koristi piksele
  y = lay[,2] * 100,
  stringsAsFactors = FALSE
)

edges <- as_data_frame(g3_int, what = "edges")

visNetwork(nodes, edges, height = "650px", width = "100%") %>%
  visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) %>%
  visInteraction(navigationButtons = TRUE, zoomView = TRUE) %>%
  visPhysics(enabled = FALSE)     # onemogućujemo korištenje visPhysics

Interaktivna mreža: Bitcoin OTC (g_pos)

Za g_pos (tisuće čvorova) obavezno radimo podgraf, top 300 po in-degree-u iz podgrafa pozitivnih odnosa.

# Podmreža
deg_pos_all <- degree(g_pos, mode = "in")
top300_pos <- names(sort(deg_pos_all, decreasing = TRUE))[1:300]
g_pos_int <- induced_subgraph(g_pos, vids = top300_pos)

# 1. Degree i ekscentričnost
deg_pos_int <- degree(g_pos_int, mode = "in")
deg_pos_int_out <- degree(g_pos_int, mode = "out")
ecc_pos_int <- eccentricity(g_pos_int)

# 2. Layout
lay <- layout_with_kk(g_pos_int)
lay <- lay*3

# 2. Node tablica
size_scaled <- scales::rescale(deg_pos_int, to = c(10, 40)) # za veličinu čvora, skaliramo na raspon

nodes <- data.frame(
  id    = V(g_pos_int)$name,
  label = V(g_pos_int)$name,
  value = size_scaled,
  title = paste0(
    "<b>", V(g_pos_int)$name, "</b><br>",
    "In-degree: ", deg_pos_int, "<br>",
    "Out-degree: ", deg_pos_int_out, "<br>",
    "Ekscentričnost: ", ecc_pos_int
  ),
  x = lay[,1] * 100,   # skaliramo jer visNetwork koristi piksele
  y = lay[,2] * 100,
  stringsAsFactors = FALSE
)

# 3. Nijansiranje čvora prema degree-u
pal <- colorRampPalette(c("#e8b679", "#7f2704")) # ColorBrewer “YlOrBr” skala
deg_bins <- cut(deg_pos_int,
                breaks = 10,
                include.lowest = TRUE)
nodes$color <- pal(10)[as.integer(deg_bins)]

# 4. Edge tablica
edges_df <- as_data_frame(g_pos_int, what = "edges")

# 5. Debljina lukova s obzirom na weight
edges <- data.frame(
  from  = edges_df$from,
  to    = edges_df$to,
  width = scales::rescale(edges_df$weight, to = c(1, 6)),  # debljina prema weight
  title = paste0("Weight: ", edges_df$weight),
  arrows = "to",
  stringsAsFactors = FALSE
)

# 6. Interaktivni graf
visNetwork(nodes, edges, height = "650px", width = "100%") %>%
  visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE) %>%
  visEdges(smooth = FALSE, color = list(color = "#e6d8c3", highlight = "darkred")) %>%
  visInteraction(navigationButtons = TRUE, zoomView = TRUE) %>%
  visPhysics(enabled = FALSE)     # onemogućujemo korištenje visPhysics   

Vizualni integritet

“To describe the visible world in images we need a developed system of schemata.” Ben Shneiderman

Vizualizacija mreže nije samo tehnički postupak, nego interpretativni čin. Svaki odabir — podgraf, layout, boja, veličina čvora — utječe na percepciju promatrača. Zbog toga je vizualni integritet ključan dio analize mreža.

Cilj vizualizacije nije impresionirati, nego jasno prenijeti informaciju.

Selekcija podgrafa i rizik manipulacije

U ovoj lekciji više puta smo koristili podgrafove:

  • top N po degree-u

  • čvorovi s najvećom ekscentričnošću

  • ego-mreža oko jednog čvora

Takav odabir je legitiman — ali mora biti transparentan.

Ako prikažemo samo top 30 čvorova, graf više ne predstavlja cijelu mrežu. On predstavlja jezgru mreže prema određenom kriteriju i to moramo jasno navesti.

Potencijalna manipulacija

Prikaz samo najpovezanijih čvorova može stvoriti dojam da je mreža vrlo gusta. Prikaz periferije može sugerirati fragmentiranost. Izostavljanje bridova niske težine može “očistiti” mrežu i učiniti je koherentnijom nego što jest.

Kako izbjeći manipulaciju?

Uvijek jasno navesti:

  • Koliko čvorova ima izvorna mreža?

  • Koliko ih je prikazano?

  • Prema kojem kriteriju su odabrani?

Primjer dobre prakse:

“Prikazan je podgraf od 50 čvorova s najvećim in-degree-om (od ukupno 5573 čvorova).”

Layout može sugerirati strukturu koja ne postoji

Force-directed layout (npr. FR) često “grupira” čvorove. Promatrač može zaključiti da postoje klasteri — iako nismo proveli nikakvu analizu zajednica.

Layout ne dokazuje postojanje strukture. On samo vizualno sugerira prostorne blizine.

Kako očuvati integritet?

  • Ne tvrditi postojanje “grupa” ako nisu formalno analizirane.

  • U tekstu navesti koji algoritam je korišten za layout.

Po potrebi usporediti više layouta.

Boja i veličina kao implicitna poruka

Veći čvorovi i tamnije boje automatski sugeriraju “važnost”. Ako koristimo degree kao veličinu čvora, implicitno tvrdimo da je degree relevantna mjera. To je legitimno — ali mora biti svjesno.

Potencijalni problemi

  • Dvostruko kodiranje iste mjere (veličina + boja) može pojačati dojam važnosti.

  • Prevelik raspon veličina može dramatično naglasiti razlike.

Dobra praksa

  • Skalirati vrijednosti umjereno.

  • U legendi jasno navesti što veličina i boja predstavljaju.

  • Ne koristiti dramatične palete bez potrebe.

Izostavljanje podataka

Često:

  • izbacujemo negativne weight-e

  • filtriramo veze ispod određenog praga

  • prikazujemo samo jednu komponentu

To može biti analitički opravdano — ali mora biti dokumentirano.

Minimalna transparentnost

Graf bi trebao biti popraćen informacijom:

  • Broj čvorova

  • Broj veza

  • Je li graf usmjeren

  • Jesu li weight-i filtrirani

Vizualna dramatizacija

Manipulacija može nastati i nenamjerno:

  • ekstremno tamna paleta → dojam “intenziteta”

  • jako debeli bridovi → dojam snažne povezanosti

  • layout s velikim prazninama → dojam fragmentacije

Vizualna estetika ne smije nadjačati informaciju.

Interaktivne mreže i selektivna percepcija

Interaktivni grafovi omogućuju zoom i filtriranje, ali:

  • korisnik može fokusirati samo dio mreže

  • može zanemariti globalnu strukturu

Zato interaktivni graf ne zamjenjuje analitičke mjere — on ih nadopunjuje.

Kako primjereno izvijestiti o grafu?

Dobar opis uz graf treba sadržavati:

  • Što graf prikazuje (koja mreža).

  • Koliki je uzorak (broj čvorova i veza).

  • Koji je kriterij odabira (ako je podgraf).

  • Koji layout je korišten.

  • Što veličina/boja predstavljaju.

Primjer:

“Prikazana je interaktivna podmreža Bitcoin OTC mreže povjerenja (500 čvorova s najvećim in-degree-om). Čvorovi su veličinom i bojom kodirani prema in-degree-u, a debljina brida odražava težinu (weight, jačinu povjerenja). Layout je izračunat algoritmom Fruchterman–Reingold.”

Takav opis osigurava interpretacijsku transparentnost.

Vizualizacija mreže nije neutralna slika stvarnosti. Ona je model — pojednostavljenje — i interpretacija.

Vizualni integritet znači:

  • jasno komunicirati ograničenja,

  • ne sugerirati više nego što je analizirano,

  • i dokumentirati svaki analitički izbor.

U znanstvenom i profesionalnom kontekstu, to je jednako važno kao i sama analiza.

Uobičajeni problemi i debug

RStudio “crasha” ili se smrzne kod velikih mreža

Velike mreže (tisuće čvorova i desetci tisuća veza) mogu:

  • zamrznuti RStudio
  • uzrokovati poruku o nedostatku memorije
  • generirati graf koji se crta nekoliko minuta
  • preopteretiti preglednik kod interaktivne vizualizacije

Zašto se to događa?

  • Layout algoritmi (posebno FR i KK) računaju pozicije iterativno.
  • Kod velikih mreža broj izračuna raste vrlo brzo.

Interaktivni grafovi dodatno opterećuju memoriju jer:

  • čuvaju sve koordinate
  • imaju physics engine
  • renderiraju HTML/JS objekt

Rješenja:

  • Ne crtati cijelu mrežu — koristiti podgraf. Ako broj čvorova prelazi nekoliko tisuća, gotovo uvijek je bolje:

    • uzeti top N po degree-u, ili

    • filtrirati po weight-u, ili

    • analizirati komponentu po komponentu

  • Smanjiti broj iteracija layouta, npr.

layout_with_fr(g3, niter = 500)

Manje iteracija = brže izvođenje (uz nešto manje “optimalan” raspored).

  • Isključiti physics u visNetwork
visPhysics(enabled = FALSE)
  • Upravljanje memorijom

Ako radimo s više velikih objekata:

rm(g_large)  # ukloniti objekt kad nam više ne treba (osobito ako renderiramo rmd u html)
gc()
##            used  (Mb) gc trigger   (Mb)  max used   (Mb)
## Ncells  2244778 119.9   50405157 2692.0  76595634 4090.7
## Vcells 12541357  95.7  475317421 3626.4 742683459 5666.3

gc() pokreće garbage collection i oslobađa memoriju.

  • Paralelne jezgre

    • igraph layout funkcije u osnovnoj verziji ne koriste paralelizaciju, ali kod većih analiza (npr. simulacije, ponovljeni layout) može pomoći:
parallel::detectCores()
## [1] 16
  • I planirati rad u više jezgri kod drugih paketa (npr. simulacije), ali za osnovnu vizualizaciju to obično nije nužno.

Provjera tipova podataka

Jedan od najčešćih problema je pogrešan tip podataka.

Tipični problemi:

  • ID čvorova su numerički, a trebali bi biti character

  • weight je character, pa se ne može skalirati

  • postoji NA u atributima

Primjer provjere:

str(edges_pos)
str(V(g3)$name)

Za visNetwork:

  • id mora biti character

  • from i to moraju odgovarati id

  • weight mora biti numeric

Ako nešto ne radi, prvo provjeriti:

any(is.na(nodes))
any(is.na(edges))

Argumenti na pogrešnom mjestu

U igraph-u:

plot(g3,
     vertex.size = 5,
     edge.width = 2)

Ako napišemo:

plot(g3,
     size = 5)

argument se ignorira — jer size nije argument plot.igraph().

U ggraph-u:

  • veličina mora biti u aes(size = ...)

  • boja u aes(colour = ...)

  • skala se kontrolira kroz scale_*

Ako napišemo: geom_node_point(size = deg) to neće mapirati podatke, nego postaviti fiksnu veličinu.

Razlika između: geom_node_point(aes(size = deg)) i geom_node_point(size = 5) je temeljna.

Upravljanje veličinom čvora i brida

Jedna od najčešćih pogrešaka je direktno korištenje sirovih vrijednosti.

Ako degree ide od 1 do 200:

vertex.size = degree(g3)

dobit ćemo ogromne razlike.

Kada skalirati?

  • Kad je raspon velik
  • Kad želimo usporediv vizualni raspon
  • Kad želimo izbjeći dominaciju nekoliko čvorova

Kako skalirati?

U ggraph-u:

scale_size_continuous(range = c(2, 8))

U visNetwork-u:

scales::rescale(deg, to = c(10, 40))

Za weight bridova:

edges$width <- scales::rescale(edges$weight, to = c(1, 6))

Skaliranje je gotovo uvijek bolja opcija od direktne mape.

Palete boja i odabir boja

Boja nije samo estetika — ona komunicira informaciju.

Kada koristiti kontinuiranu paletu?

  • Za numeričke mjere (degree, weight, ecc)

  • Kad postoji prirodan redoslijed

Primjer:

colorRampPalette(c("#f4c38b", "#7f2704"))
## function (n) 
## {
##     x <- ramp(seq.int(0, 1, length.out = n))
##     if (ncol(x) == 4L) 
##         rgb(x[, 1L], x[, 2L], x[, 3L], x[, 4L], maxColorValue = 255)
##     else rgb(x[, 1L], x[, 2L], x[, 3L], maxColorValue = 255)
## }
## <bytecode: 0x000001ca3b237158>
## <environment: 0x000001ca352181f0>

Kada koristiti diskretnu paletu?

  • Za kategorije

  • Za male brojčane grupe

Što izbjegavati?

  • Prejake kontraste

  • Hladne i tople boje u istom kontinuiranom rasponu

  • Palete koje su nečitljive za daltonizam

Vizualna hijerarhija u dobro dizajniranom grafu:

  • Najvažniji čvorovi privlače pogled

  • Naglašeni bridovi su jasni

  • Ostali elementi su neutralni

Ako su svi elementi jednako intenzivni — graf je vizualno kaotičan.

Vizualni šum

Previše:

  • oznaka

  • debelih bridova

  • jarkih boja

  • zasićenih čvorova

→ smanjuje čitljivost.

Ponekad je manje informacija — informativnije.

Zaključna napomena

Većina problema u vizualizaciji mreža nije tehničke prirode, nego metodološke:

  • Koji dio mreže prikazujemo?

  • Koju mjeru naglašavamo?

  • Koju poruku želimo prenijeti?

Vizualizacija je interpretacija — a ne samo crtanje.




Memento

Mrežni dijagram: Grafički prikaz čvorova i veza.

Layout algoritmi: Pravila rasporeda čvorova u prostoru.

Force-directed layout: Raspored temeljen na “silama” privlačenja/odbijanja.

Fruchterman–Reingold: Popularan force-directed layout za opće mreže.

Kamada–Kawai: Layout temeljen na udaljenostima; dobro za čitljivost.

Sugiyama layout: Hijerarhijski layout za usmjerene grafove, posebno DAG-ove.

Kružni layout: Čvorovi raspoređeni u krug (usporedbe, baseline).

Grid layout: Čvorovi na rešetki (stabilan raspored, čitljivost čvorova).

Random layout: Nasumični raspored (baseline za usporedbu).

Tree layout: Raspored stabla / hijerarhije (razine, tokovi).

Fiksne koordinate: Layout zadan koordinatama (npr. geografski ili “manual”).

Podgraf: Dio mreže odabran prema kriteriju (npr. top N po degree-u).

Inducirani podgraf: Podgraf koji zadržava sve veze među odabranim čvorovima.

Ego-mreža: Čvor i njegovi susjedi (lokalni kontekst jednog aktera).

Degree (stupanj čvora): Broj veza čvora; često kodiran veličinom/bojom.

Ekscentričnost: Najveća udaljenost čvora do drugih čvorova (rub mreže).

Težina brida (weight): Atribut veze koji kodira jačinu/ocjenu/povjerenje.

Veličina čvora: Vizualno kodiranje mjere (npr. degree) uz skaliranje.

Boja čvora: Kodiranje mjere ili kategorije (uz pažljiv odabir palete).

Debljina brida: Kodiranje jačine veze (npr. weight) uz skaliranje.

Smjer veze: Strelice koje označuju usmjerenost.

Označavanje čvorova: Nazivi čvorova (label, često selektivno za top-k).

Selektivne oznake: Oznake samo za odabrane čvorove radi čitljivosti.

Skaliranje (rescale): Pretvaranje sirovih vrijednosti u čitljiv raspon.

Vizualni šum: Elementi koji smanjuju čitljivost bez dodane informacije.

Preklapanje: Čvorovi/labeli se preklapaju → potreban podgraf ili selekcija.

Interaktivna vizualizacija: Zoom/hover/filter; informacije se prikazuju na tooltip.

Physics/stabilizacija: Gibanje čvorova u interaktivnom prikazu; često se gasi.

Statička vizualizacija: Fiksna slika (PDF/PNG) za izvještaje.

Vizualni integritet: Transparentno navesti što je prikazano, kako i zašto.

Estetika vs. informativnost: Balans ljepote i analitičke jasnoće.

Vizualna hijerarhija: Namjerno isticanje važnih elemenata.




Pitanja za ponavljanje

1) Koje tvrdnje najbolje opisuju ulogu vizualizacije mreže u SNA?

Odaberite sve točne odgovore.

  1. Vizualizacija je analitički alat za uočavanje obrazaca u strukturi.
  2. Vizualizacija je dekoracija koja se dodaje tek nakon mjera i testova.
  3. Vizualizacija pomaže prepoznati izolirane dijelove i gusto povezane zone.
  4. Vizualizacija zamjenjuje izračun mjera gustoće i dijametra mreže.

2) Što je layout u kontekstu vizualizacije mreža?

Odaberite sve točne odgovore.

  1. Algoritam koji određuje položaje čvorova u prostoru.
  2. Skup argumenata koji određuje debljinu bridova na grafu.
  3. Postupak koji može promijeniti dojam strukture iste mreže.
  4. Metoda koja automatski računa centralnosti i zajednice.

3) Koje su ispravne tvrdnje o plot.igraph() argumentima?

Odaberite sve točne odgovore.

  1. Argumenti vertex.* primarno kontroliraju izgled čvorova.
  2. Argumenti edge.* primarno kontroliraju izgled bridova.
  3. Argument size je standardni argument za veličinu čvorova.
  4. Argument vertex.label = NA uklanja prikaz oznaka čvorova.

4) Što je tipičan problem kod crtanja velikih mreža?

Odaberite sve točne odgovore.

  1. Čvorovi i oznake se preklapaju pa graf postaje nečitljiv.
  2. plot() automatski filtrira mrežu i odabire najbolji podgraf.
  3. Layout algoritmi mogu postati spori na velikim grafovima.
  4. Velike mreže uvijek daju jednako čitljive prikaze uz FR layout.

5) Koje su dobre prakse za smanjenje vizualnog šuma?

Odaberite sve točne odgovore.

  1. Ukloniti većinu oznaka čvorova i označiti samo najvažnije.
  2. Dodati oznake bridova za sve veze radi potpunosti prikaza.
  3. Smanjiti debljinu i kontrast bridova kako bi bili “pozadina”.
  4. Povećati broj boja i zasićenost kako bi sve bilo upadljivije.

6) Koje tvrdnje vrijede za ggraph pristup vizualizaciji?

Odaberite sve točne odgovore.

  1. Graf se gradi slojevito: geometrije + skale + tema.
  2. Vizualna mapiranja se tipično pišu unutar aes(...).
  3. ggraph ignorira layout i koristi uvijek kružni prikaz.
  4. theme_graph() pomaže ukloniti osi i pozadinski “šum”.

7) Kada je smisleno kodirati veličinu čvora prema degree-u?

Odaberite sve točne odgovore.

  1. Kada želimo naglasiti čvorove s većim brojem veza.
  2. Kada želimo prikazati geografske koordinate na karti.
  3. Kada želimo vizualno povezati mjeru i strukturu grafa.
  4. Kada želimo dokazati postojanje zajednica u mreži.

8) Što je razumna strategija kad je mreža prevelika za čitljiv statički graf?

Odaberite sve točne odgovore.

  1. Izabrati podgraf prema analitičkom cilju (npr. top N po degree).
  2. Prikazati sve čvorove i sve oznake, ali koristiti manji font.
  3. Ukloniti oznake bridova i većinu oznaka čvorova radi čitljivosti.
  4. Povećati veličinu platna bez ikakvog filtriranja podataka.

9) Što znači ekscentričnost čvora (u kontekstu ove lekcije)?

Odaberite sve točne odgovore.

  1. Najveća udaljenost tog čvora do bilo kojeg drugog čvora u komponenti.
  2. Broj bridova koji ulaze u čvor u usmjerenoj mreži.
  3. Mjera koja pomaže prepoznati čvorove “na rubu” mreže.
  4. Mjera koja direktno otkriva zajednice i modularnost.

10) Koje tvrdnje o layout_with_fr() su točne?

Odaberite sve točne odgovore.

  1. To je force-directed layout koji koristi privlačenje i odbijanje.
  2. Rezultat može ovisiti o inicijalizaciji pa je koristan set.seed().
  3. Uvijek daje identičan raspored bez obzira na slučajni početak.
  4. Na velikim grafovima može dati “kuglu špageta” bez filtriranja.

11) Koje tvrdnje o layout_with_kk() su točne?

Odaberite sve točne odgovore.

  1. Pokušava očuvati grafovske udaljenosti u euklidskim udaljenostima.
  2. Često je čitljiv za manje/srednje povezane grafove.
  3. Uvijek je brži od FR-a jer koristi manje računanja.
  4. Može biti sporiji na većim grafovima zbog računanja udaljenosti.

12) Koje su značajke layout_in_circle()?

Odaberite sve točne odgovore.

  1. Deterministički postavlja čvorove na kružnicu bez optimizacije.
  2. Služi kao dobar baseline za usporedbe prikaza.
  3. Minimizira presijecanja bridova pomoću iterativne optimizacije.
  4. Dobar je za neutralan prikaz kada redoslijed čvorova ima smisla.

13) Koje tvrdnje o layout_on_grid() su točne?

Odaberite sve točne odgovore.

  1. Cilj je uredan raspored čvorova, ne nužno prikaz strukture kroz položaj.
  2. Često smanjuje preklapanje čvorova jer ih stavlja u pravilnu mrežu.
  3. Tipično optimizira presijecanje bridova bolje od FR-a.
  4. Može biti koristan kad su atributi čvorova važniji od geometrije veza.

14) Što je dobra interpretacijska opreznost kod force-directed layouta?

Odaberite sve točne odgovore.

  1. Vizualna “grupiranja” ne znače nužno formalne zajednice.
  2. Layout dokazuje postojanje klastera i modularnosti bez analize.
  3. Dobro je navesti koji je layout korišten i zašto.
  4. Dobro je usporediti više layouta ako želimo provjeriti stabilnost dojma.

15) Koje tvrdnje o “kompromisnom” prikazu (srednje velik podgraf + selektivne oznake) su točne?

Odaberite sve točne odgovore.

  1. Dobivamo više strukture nego kod ego-grafa, ali zadržavamo čitljivost.
  2. Uvijek možemo prikazati sve oznake ako imamo repel tekst.
  3. Selektivno labeliranje smanjuje šum i ističe ključne čvorove.
  4. Ostatak čvorova može biti prisutan kao pozadina bez oznaka.

16) Koje su važne stavke vizualnog integriteta kod podgrafova?

Odaberite sve točne odgovore.

  1. Jasno navesti kriterij odabira (npr. top N po in-degree-u).
  2. Implicitno pretpostaviti da podgraf predstavlja cijelu mrežu.
  3. Navesti koliko čvorova/veza ima originalna mreža i podgraf.
  4. Opisati što kodiraju boja i veličina čvorova (i eventualno bridova).

17) Koje tvrdnje vrijede za interaktivne mreže u visNetwork?

Odaberite sve točne odgovore.

  1. Hover “tooltip” može prikazati informacije bez stalnih oznaka čvorova.
  2. Zoom i navigacija pomažu kad je čvorova više nego u statičkom grafu.
  3. Interaktivni graf automatski rješava problem prevelike mreže bez filtriranja.
  4. Isključivanje physicsa može stabilizirati prikaz i olakšati hover.

18) Što znači “skaliranje” veličina čvorova/bridova i zašto je važno?

Odaberite sve točne odgovore.

  1. Pretvaranje sirovih vrijednosti u umjeren raspon veličina radi čitljivosti.
  2. Način da nekoliko ekstremnih čvorova ne dominira cijelim prikazom.
  3. Postupak kojim se mreža pretvara iz usmjerene u neusmjerenu.
  4. Postupak kojim se uklanjaju duplikati u tablici bridova.

19) Koje tvrdnje o odabiru boja su točne (u kontekstu ove lekcije)?

Odaberite sve točne odgovore.

  1. Kontinuirane palete su prikladne za numeričke mjere poput degree-a.
  2. Prejaki kontrasti i zasićene boje mogu povećati vizualni šum.
  3. Boja je samo estetika i ne nosi interpretacijsku poruku.
  4. Dobro je osigurati da su manje važni elementi vizualno “tiši”.

20) Koje tvrdnje najbolje opisuju razliku igraph plot() vs. ggraph?

Odaberite sve točne odgovore.

  1. plot.igraph() je jedan poziv s mnogo argumenata za izgled grafa.
  2. ggraph se slaže slojevito kao ggplot: geometrije, skale i tema.
  3. U ggraph-u se mapiranje podataka u izgled tipično radi preko aes().
  4. U plot.igraph() se mapiranje podataka radi isključivo preko aes().



Korištena literatura

Almende B.V., Thieurmel, B., & Johnson, R. T. (2025). visNetwork. Network Visualization using the ‘vis.js’ Library. Available from, CRAN.

Battista, G. D., Eades, P., Tamassia, R., & Tollis, I. G. (1998). Graph drawing: algorithms for the visualization of graphs. Prentice Hall PTR.

Becker, R. A., Wilks, A. R., & Brownrigg, R. (2025). maps. Draw Geographical Maps. Available from, CRAN.

Csárdi, G., & Nepusz, T. (2025). igraph. Network Analysis and Visualization. Available from, CRAN.

Di Battista, G., Eades, P., Tamassia, R., & Tollis, I. G. (1994). Algorithms for drawing graphs: an annotated bibliography. Computational geometry, 4(5), 235-282.

Müller, K., Wickham, H., James, D. A., & Falcon, S. (2025). RSQLite. SQLite Interface for R. Available from, CRAN.

Pedersen, T. L. (2025). ggraph. An Implementation of Grammar of Graphics for Graphs and Networks. Available from, CRAN.

Pedersen, T. L. (2025). tidygraph. A Tidy API for Graph Manipulation. Available from, CRAN.

R Core Team (2025). DBI. Database Interface Definition for R. Available from, CRAN.

R Core Team (2025). grid. The Grid Graphics Package. Available from, CRAN.

Rawlings, C. M., Smith, J. A., McFarland, D. A., & Moody, J. (2023). Network analysis: integrating social network theory, method, and application with R (Vol. 52). Cambridge University Press.

Shneiderman, B. (2003). The eyes have it: A task by data type taxonomy for information visualizations. In The craft of information visualization (pp. 364-371). Morgan Kaufmann.

Slowikowski, K. (2025). ggrepel. Automatically Position Non-Overlapping Text Labels with ‘ggplot2’. Available from, CRAN.

Tufte, E. R., & Graves-Morris, P. R. (1983). The visual display of quantitative information (Vol. 2, No. 9). Cheshire, CT: Graphics press.

Wasserman, S. (1994). Social network analysis: Methods and applications.

Wickham, H. (2025). ggplot2. Elegant Graphics for Data Analysis. Available from, CRAN.

Wickham, H., François, R., Henry, L., Müller, K., & Vaughan, D. (2026). dplyr. A Grammar of Data Manipulation. Available from, CRAN.

Wickham, H., Hester, J., & Bryan, J. (2025). readr. Read Rectangular Text Data. Available from, CRAN.

Wickham, H., Vaughan, D., & Girlich, M. (2026). tidyr. Tidy Messy Data. Available from, CRAN.

Wickham, H., & Wickham, M. H. (2019). Package ‘stringr’. Available from CRAN.




Ključ odgovora

  1. a, c
  2. a, c
  3. a, b, d
  4. a, c
  5. a, c
  6. a, b, d
  7. a, c
  8. a, c
  9. a, c
  10. a, b, d
  11. a, b, d
  12. a, b, d
  13. a, b, d
  14. a, c, d
  15. a, c, d
  16. a, c, d
  17. a, b, d
  18. a, b
  19. a, b, d
  20. a, b, c