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, 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.
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.
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
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.
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:
raspored (layout) koji određuje položaje čvorova u prostoru i
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 argumenatavertex.*)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)
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)
Č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)
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:
Podaci + mapiranje:
aes(x=..., y=..., size=..., colour=...)
Geometrije:
geom_point(), geom_text(), geom_edge_link()…
Skale:
scale_size_*, scale_colour_*, scale_edge_width()…
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 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_nicelylayout_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_frFruchterman–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_kkKamada–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_sugiyamaSugiyama 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_drlDrL (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_lglLGL (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_circlelayout_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_gridlayout_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_randomlylayout_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_starlayout_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_treelayout_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_dhDavidson–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_spherelayout_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_graphoptlayout_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_mdsMDS (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))
)
}
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.
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)
igraphu: layout_as_tree +
ručno “širenje” i marginelay_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)")
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)")
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:
# 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)")
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:
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:
Zato se uvijek postavlja pitanje: Što želim da gledatelj vidi u prvih 5 sekundi?
Vizualizacija nije tehnički zadatak, nego metodološka odluka.
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
“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.
Velike mreže (tisuće čvorova i desetci tisuća veza) mogu:
Zašto se to događa?
Interaktivni grafovi dodatno opterećuju memoriju jer:
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).
visPhysics(enabled = FALSE)
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
parallel::detectCores()
## [1] 16
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))
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.
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?
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.
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.
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.
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.
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.