Úvod do síťové analýzy s R
On 4 listopadu, 2021 by adminV širokém spektru oborů se síťová analýza stává stále oblíbenějším nástrojem vědců, kteří se zabývají složitostí vzájemných vztahů mezi aktéry všeho druhu. Příslibem síťové analýzy je přikládání významu vztahům mezi aktéry, spíše než vnímání aktérů jako izolovaných entit. Důraz na komplexnost spolu s vytvořením řady algoritmů pro měření různých aspektů sítí činí ze síťové analýzy ústřední nástroj digitálních humanitních věd.1 Tento příspěvek poskytne úvod do práce se sítěmi v jazyce R na příkladu sítě měst v korespondenci Daniela van der Meulena z roku 1585.
Existuje řada aplikací určených pro síťovou analýzu a vytváření síťových grafů, například gephi a cytoscape. Ačkoli k tomu nebyl speciálně navržen, program R se vyvinul ve výkonný nástroj pro analýzu sítí. Síla R ve srovnání se samostatným softwarem pro analýzu sítí je trojnásobná. V první řadě R umožňuje reprodukovatelný výzkum, který není možný u aplikací s grafickým uživatelským rozhraním. Za druhé, síla analýzy dat v R poskytuje robustní nástroje pro manipulaci s daty a jejich přípravu pro analýzu sítí. V neposlední řadě existuje stále rostoucí počet balíčků určených k tomu, aby se R stal kompletním nástrojem pro analýzu sítí. Mezi významné balíčky pro analýzu sítí v R patří sada balíčků statnet a igraph
. Thomas Lin Pedersen navíc nedávno vydal balíčky tidygraph
a ggraph
, které využívají sílu balíčku igraph
způsobem konzistentním s pracovním postupem tidyverse. R lze také použít k vytváření interaktivních síťových grafů pomocí frameworku htmlwidgets, který překládá kód R do jazyka JavaScript.
Tento příspěvek začíná krátkým úvodem do základní slovní zásoby síťové analýzy, po kterém následuje diskuse o postupu, jak dostat data do správné struktury pro síťovou analýzu. Všechny balíčky síťové analýzy mají implementovány vlastní třídy objektů. V tomto příspěvku ukážu, jak vytvořit specifické objektové třídy pro sadu balíčků statnet s balíčkem network
a také pro igraph
a tidygraph
, který je založen na implementaci igraph
. Nakonec se zaměřím na tvorbu interaktivních grafů pomocí balíků vizNetwork
a networkD3
.
Síťová analýza: Uzly a hrany
Dvěma základními aspekty sítí jsou množství samostatných entit a spojení mezi nimi. Slovník může být poněkud technický a dokonce nejednotný mezi různými obory, balíčky a softwarem. Entity se označují jako uzly nebo vrcholy grafu, zatímco spojení jsou hrany nebo spoje. V tomto příspěvku budu používat hlavně názvosloví uzlů a hran s výjimkou případů, kdy budu diskutovat o balíčcích, které používají jiný slovník.
Balíčky pro síťovou analýzu potřebují, aby data byla v určité podobě, aby bylo možné vytvořit speciální typ objektu používaný jednotlivými balíčky. Třídy objektů pro network
, igraph
a tidygraph
jsou všechny založeny na adjacenčních maticích, známých také jako sociomatrice.2 Adjacenční matice je čtvercová matice, v níž názvy sloupců a řádků představují uzly sítě. V matici značí 1, že mezi uzly existuje spojení, a 0 značí, že žádné spojení neexistuje. Matice adjacencí implementují zcela jinou strukturu dat než datové rámce a nehodí se do pracovního postupu tidyverse, který jsem použil ve svých předchozích příspěvcích. Nápomocné je, že specializované síťové objekty lze vytvořit také z datového rámce se seznamem hran, který do pracovního postupu tidyverse zapadá. V tomto příspěvku se budu držet technik analýzy dat tidyverse a vytvořím seznamy hran, které pak převedu na specifické třídy objektů pro network
, igraph
a tidygraph
.
Seznam hran je datový rámec, který obsahuje minimálně dva sloupce, jeden sloupec uzlů, které jsou zdrojem spojení, a druhý sloupec uzlů, které jsou cílem spojení. Uzly v datech jsou identifikovány jedinečnými ID. Pokud je rozlišení mezi zdrojem a cílem smysluplné, je síť směrovaná. Pokud rozlišení není smysluplné, je síť neusměrněná. U příkladu dopisů posílaných mezi městy je rozlišení mezi zdrojem a cílem jednoznačně smysluplné, a síť je tedy směrovaná. V níže uvedených příkladech budu sloupec zdroje označovat jako „od“ a sloupec cíle jako „do“. Jako ID uzlů budu používat celá čísla začínající jedničkou.3 Seznam hran může obsahovat i další sloupce, které popisují atributy hran, například aspekt velikosti hrany. Pokud mají hrany atribut magnituda, je graf považován za vážený.
Seznamy hran obsahují všechny informace potřebné k vytvoření síťových objektů, ale někdy je vhodnější vytvořit také samostatný seznam uzlů. V nejjednodušším případě je seznam uzlů datový rámec s jediným sloupcem – který označím jako „id“ – který obsahuje seznam ID uzlů nalezených v seznamu hran. Výhodou vytvoření samostatného seznamu uzlů je možnost přidat do datového rámce sloupce atributů, jako jsou názvy uzlů nebo jakékoliv seskupení. Níže uvádím příklad minimálních seznamů hran a uzlů vytvořených pomocí funkce tibble()
.
library(tidyverse)edge_list <- tibble(from = c(1, 2, 2, 3, 4), to = c(2, 3, 4, 2, 1))node_list <- tibble(id = 1:4)edge_list#> # A tibble: 5 x 2#> from to#> <dbl> <dbl>#> 1 1 2#> 2 2 3#> 3 2 4#> 4 3 2#> 5 4 1node_list#> # A tibble: 4 x 1#> id#> <int>#> 1 1#> 2 2#> 3 3#> 4 4
Srovnejte to s maticí adjacencí se stejnými daty.
#> 1 2 3 4#> 1 0 1 0 0#> 2 0 0 1 1#> 3 0 1 0 0#> 4 1 0 0 0
Vytvoření seznamů hran a uzlů
Pro vytvoření síťových objektů z databáze dopisů přijatých Danielem van der Meulenem v roce 1585 vytvořím seznam hran i seznam uzlů. To si vyžádá použití balíčku dplyr pro manipulaci s datovým rámcem dopisů zaslaných Danielovi a jeho rozdělení na dva datové rámce neboli tibbles se strukturou seznamů hran a uzlů. V tomto případě budou uzly města, ze kterých Danielovi korespondenti posílali dopisy, a města, ve kterých je přijímal. Seznam uzlů bude obsahovat sloupec „štítek“, který bude obsahovat názvy měst. Seznam hran bude obsahovat také sloupec „atribut“, ve kterém bude uvedeno množství dopisů odeslaných mezi jednotlivými dvojicemi měst. Pracovní postup pro vytvoření těchto objektů bude podobný tomu, který jsem použil ve svém stručném úvodu do R a v geokódování pomocí R. Pokud byste se jím chtěli řídit, data použitá v tomto příspěvku a použitý skript R najdete na GitHubu.
Prvním krokem je načtení knihovny tidyverse
pro import a manipulaci s daty. Při vypisování datového rámce letters
zjistíte, že obsahuje čtyři sloupce: „writer“, „source“, „destination“ a „date“. V tomto příkladu se budeme zabývat pouze sloupci „zdroj“ a „cíl“.
library(tidyverse)letters <- read_csv("data/correspondence-data-1585.csv")letters#> # A tibble: 114 x 4#> writer source destination date#> <chr> <chr> <chr> <date>#> 1 Meulen, Andries van der Antwerp Delft 1585-01-03#> 2 Meulen, Andries van der Antwerp Haarlem 1585-01-09#> 3 Meulen, Andries van der Antwerp Haarlem 1585-01-11#> 4 Meulen, Andries van der Antwerp Delft 1585-01-12#> 5 Meulen, Andries van der Antwerp Haarlem 1585-01-12#> 6 Meulen, Andries van der Antwerp Delft 1585-01-17#> 7 Meulen, Andries van der Antwerp Delft 1585-01-22#> 8 Meulen, Andries van der Antwerp Delft 1585-01-23#> 9 Della Faille, Marten Antwerp Haarlem 1585-01-24#> 10 Meulen, Andries van der Antwerp Delft 1585-01-28#> # ... with 104 more rows
Seznam uzlů
Pracovní postup pro vytvoření seznamu uzlů je podobný tomu, který jsem použil pro získání seznamu měst za účelem geokódování dat v předchozím příspěvku. Chceme získat různá města ze sloupců „zdroj“ i „cíl“ a poté spojit informace z těchto sloupců dohromady. V příkladu níže mírně měním příkazy oproti těm, které jsem použil v předchozím příspěvku, aby název sloupců s názvy měst byl stejný pro oba datové rámce sources
a destinations
, abych zjednodušil funkci full_join()
. Sloupec s názvy měst přejmenuji na „label“, abych převzal slovník používaný balíky pro síťovou analýzu.
sources <- letters %>% distinct(source) %>% rename(label = source)destinations <- letters %>% distinct(destination) %>% rename(label = destination)
Pro vytvoření jediného datového rámce se sloupcem s unikátními místy musíme použít úplné spojení, protože chceme zahrnout všechna unikátní místa jak ze zdrojů dopisů, tak z cílů.
nodes <- full_join(sources, destinations, by = "label")nodes#> # A tibble: 13 x 1#> label#> <chr>#> 1 Antwerp#> 2 Haarlem#> 3 Dordrecht#> 4 Venice#> 5 Lisse#> 6 Het Vlie#> 7 Hamburg#> 8 Emden#> 9 Amsterdam#> 10 Delft#> 11 The Hague#> 12 Middelburg#> 13 Bremen
Výsledkem je datový rámec s jednou proměnnou. Proměnná obsažená v datovém rámci však ve skutečnosti není tím, co hledáme. Sloupec „label“ obsahuje názvy uzlů, ale my chceme mít pro každé město také jedinečné ID. Toho můžeme dosáhnout přidáním sloupce „id“ do datového rámce nodes
, který obsahuje čísla od jedné do libovolného celkového počtu řádků v datovém rámci. Užitečnou funkcí pro tento pracovní postup je rowid_to_column()
, která přidá sloupec s hodnotami z id řádků a umístí sloupec na začátek datového rámce.4 Všimněte si, že rowid_to_column()
je rourový příkaz, a tak je možné provést full_join()
a přidat sloupec „id“ v jediném příkazu. Výsledkem je seznam uzlů se sloupcem ID a atributem label.
nodes <- nodes %>% rowid_to_column("id")nodes#> # A tibble: 13 x 2#> id label#> <int> <chr>#> 1 1 Antwerp#> 2 2 Haarlem#> 3 3 Dordrecht#> 4 4 Venice#> 5 5 Lisse#> 6 6 Het Vlie#> 7 7 Hamburg#> 8 8 Emden#> 9 9 Amsterdam#> 10 10 Delft#> 11 11 The Hague#> 12 12 Middelburg#> 13 13 Bremen
Seznam hran
Vytvoření seznamu hran je podobné výše uvedenému, ale je komplikováno nutností pracovat se dvěma sloupci ID místo jednoho. Chceme také vytvořit sloupec váhy, který bude zaznamenávat množství písmen posílaných mezi jednotlivými sadami uzlů. K tomu použiji stejný pracovní postup group_by()
a summarise()
, který jsem probíral v předchozích příspěvcích. Rozdíl je v tom, že zde chceme seskupit datový rámec podle dvou sloupců – „zdroj“ a „cíl“ – namísto jednoho. Dříve jsem sloupec, který počítá počet pozorování ve skupině, pojmenoval „count“, zde však přejímám názvosloví síťové analýzy a nazývám jej „weight“. Poslední příkaz v pipeline odstraní seskupení pro datový rámec zavedené funkcí group_by()
. To usnadňuje nerušenou manipulaci s výsledným datovým rámcem per_route
. 5
per_route <- letters %>% group_by(source, destination) %>% summarise(weight = n()) %>% ungroup()per_route#> # A tibble: 15 x 3#> source destination weight#> <chr> <chr> <int>#> 1 Amsterdam Bremen 1#> 2 Antwerp Delft 68#> 3 Antwerp Haarlem 5#> 4 Antwerp Middelburg 1#> 5 Antwerp The Hague 2#> 6 Dordrecht Haarlem 1#> 7 Emden Bremen 1#> 8 Haarlem Bremen 2#> 9 Haarlem Delft 26#> 10 Haarlem Middelburg 1#> 11 Haarlem The Hague 1#> 12 Hamburg Bremen 1#> 13 Het Vlie Bremen 1#> 14 Lisse Delft 1#> 15 Venice Haarlem 2
Stejně jako seznam uzlů má nyní per_route
základní tvar, který chceme, ale opět se potýkáme s problémem, že sloupce „zdroj“ a „cíl“ obsahují spíše štítky než ID. Potřebujeme tedy propojit ID, která byla přiřazena v nodes
, s jednotlivými místy ve sloupcích „zdroj“ i „cíl“. Toho lze dosáhnout pomocí další funkce join. Ve skutečnosti je nutné provést dvě spojení, jedno pro sloupec „zdroj“ a druhé pro sloupec „cíl“. V tomto případě použiji left_join()
s per_route
jako levým datovým rámcem, protože chceme zachovat počet řádků v per_route
. Při provádění left_join
chceme také přejmenovat dva sloupce „id“, které jsou přeneseny z nodes
. Pro spojení pomocí sloupce „source“ přejmenuji sloupec na „from“. Sloupec přenesený z „cílového“ spojení přejmenuji na „do“. Bylo by možné provést obě spojení v jednom příkazu s použitím roury. Pro přehlednost však provedu spojení ve dvou samostatných příkazech. Protože se spojení provádí přes dva příkazy, všimněte si, že datový rámec na začátku potrubí se změní z per_route
na edges
, který je vytvořen prvním příkazem.
edges <- per_route %>% left_join(nodes, by = c("source" = "label")) %>% rename(from = id)edges <- edges %>% left_join(nodes, by = c("destination" = "label")) %>% rename(to = id)
Teď, když má edges
sloupce „od“ a „do“ s ID uzlů, musíme změnit pořadí sloupců, aby se „od“ a „do“ dostaly do levé části datového rámce. V současné době datový rámec edges
stále obsahuje sloupce „zdroj“ a „cíl“ s názvy měst, které odpovídají ID. Tyto údaje jsou však nadbytečné, protože jsou již obsaženy v nodes
. Proto do funkce select()
zahrnu pouze sloupce „od“, „do“ a „váha“.
edges <- select(edges, from, to, weight)edges#> # A tibble: 15 x 3#> from to weight#> <int> <int> <int>#> 1 9 13 1#> 2 1 10 68#> 3 1 2 5#> 4 1 12 1#> 5 1 11 2#> 6 3 2 1#> 7 8 13 1#> 8 2 13 2#> 9 2 10 26#> 10 2 12 1#> 11 2 11 1#> 12 7 13 1#> 13 6 13 1#> 14 5 10 1#> 15 4 2 2
Datový rámec edges
nevypadá příliš působivě; jsou to tři sloupce celých čísel. Nicméně edges
v kombinaci s nodes
nám poskytuje všechny informace potřebné k vytvoření síťových objektů pomocí balíčků network
, igraph
a tidygraph
.
Vytváření síťových objektů
Třídy síťových objektů pro network
, igraph
a tidygraph
spolu úzce souvisejí. Je možné překládat mezi objektem network
a objektem igraph
. Nejlepší je však tyto dva balíky a jejich objekty oddělit. Ve skutečnosti se schopnosti network
a igraph
překrývají do té míry, že je nejlepší mít v daném okamžiku načtený pouze jeden z balíčků. Nejprve projdu balíček network
a poté přejdu k balíčkům igraph
a tidygraph
.
síť
library(network)
Funkce používaná k vytvoření objektu network
je network()
. Příkaz není nijak zvlášť přímočarý, ale kdybyste byli zmateni, vždy můžete do konzoly zadat ?network()
. Prvním argumentem je – jak je uvedeno v dokumentaci – „matice udávající strukturu sítě ve formě adjacencí, incidence nebo edgelistů“. Jazyk demonstruje význam matic v síťové analýze, ale místo matice máme k dispozici seznam hran, který plní stejnou úlohu. Druhým argumentem je seznam atributů vrcholů, který odpovídá seznamu uzlů. Všimněte si, že balíček network
používá názvosloví vrcholů místo uzlů. Totéž platí i pro igraph
. Poté musíme určit typ dat, která byla zadána do prvních dvou argumentů, a to tak, že uvedeme, že matrix.type
je "edgelist"
. Nakonec nastavíme ignore.eval
na FALSE
, aby naše síť mohla být vážená a zohledňovala počet písmen na každé trase.
routes_network <- network(edges, vertex.attr = nodes, matrix.type = "edgelist", ignore.eval = FALSE)
Typ objektu vytvořeného funkcí network()
zjistíte vložením routes_network
do funkce class()
.
class(routes_network)#> "network"
Vypsání routes_network
do konzoly ukazuje, že struktura objektu je zcela odlišná od objektů ve stylu datového rámce, jako jsou edges
a nodes
. Příkaz pro tisk odhalí informace, které jsou specificky definovány pro síťovou analýzu. Ukazuje, že v routes_network
je 13 vrcholů neboli uzlů a 15 hran. Tato čísla odpovídají počtu řádků v nodes
, respektive edges
. Vidíme také, že vrcholy i hrany obsahují atributy, jako je značka a váha. Ještě více informací, včetně sociomatrix dat, získáte zadáním summary(routes_network)
.
routes_network#> Network attributes:#> vertices = 13 #> directed = TRUE #> hyper = FALSE #> loops = FALSE #> multiple = FALSE #> bipartite = FALSE #> total edges= 15 #> missing edges= 0 #> non-missing edges= 15 #> #> Vertex attribute names: #> id label vertex.names #> #> Edge attribute names: #> weight
Nyní je možné získat základní, i když ne příliš estetický graf naší sítě písmen. Oba balíky network
a igraph
používají základní vykreslovací systém R. Konvence pro základní grafy se výrazně liší od konvencí ggplot2 – o kterém jsem hovořil v předchozích příspěvcích – a proto zůstanu u poměrně jednoduchých grafů, místo abych se zabýval podrobnostmi vytváření složitých grafů pomocí základního R. V tomto případě jedinou změnou, kterou provedu ve výchozí funkci plot()
pro balík network
, je zvětšení velikosti uzlů pomocí argumentu vertex.cex
, aby byly uzly lépe viditelné. I z tohoto velmi jednoduchého grafu se již můžeme něco dozvědět o datech. Z grafu je zřejmé, že existují dvě hlavní seskupení nebo shluky dat, které odpovídají době, kterou Daniel strávil v Holandsku v prvních třech čtvrtinách roku 1585, a po jeho přesunu do Brém v září.
plot(routes_network, vertex.cex = 3)
Funkce plot()
s objektem network
používá k rozhodnutí o umístění uzlů Fruchtermanův a Reingoldův algoritmus.6 Algoritmus rozmístění můžete změnit pomocí argumentu mode
. Níže uvádím rozložení uzlů do kruhu. Toto uspořádání není pro tuto síť nijak zvlášť užitečné, ale dává představu o některých dostupných možnostech.
plot(routes_network, vertex.cex = 3, mode = "circle")
igraph
Přejděme nyní k diskusi o balíčku igraph
. Nejprve musíme vyčistit prostředí v R odstraněním balíčku network
, aby nezasahoval do příkazů igraph
. Rovněž bychom mohli odstranit routes_network
, protože jej již nebudeme používat. Balík network
odstraníme pomocí funkce detach()
a routes_network
odstraníme pomocí rm()
.7 Poté můžeme bez obav načíst igraph
.
detach(package:network)rm(routes_network)library(igraph)
Pro vytvoření objektu igraph
z datového rámce edge-list můžeme použít funkci graph_from_data_frame()
, která je o něco jednodušší než network()
. Funkce graph_from_data_frame()
má tři argumenty: d, vrcholy a directed. Zde d odkazuje na seznam hran, vrcholy na seznam uzlů a directed může být buď TRUE
nebo FALSE
podle toho, zda jsou data směrovaná nebo neusměrněná.
routes_igraph <- graph_from_data_frame(d = edges, vertices = nodes, directed = TRUE)
Vytištění objektu igraph
vytvořeného pomocí graph_from_data_frame()
na konzolu odhalí podobné informace jako u objektu network
, i když struktura je záhadnější.
routes_igraph#> IGRAPH f84c784 DNW- 13 15 -- #> + attr: name (v/c), label (v/c), weight (e/n)#> + edges from f84c784 (vertex names):#> 9->13 1->10 1->2 1->12 1->11 3->2 8->13 2->13 2->10 2->12 2->11#> 7->13 6->13 5->10 4->2
Hlavní informace o objektu jsou obsaženy v DNW- 13 15 --
. Ta říká, že routes_igraph
je směrovaná síť (D), která má atribut jméno (N) a je vážená (W). Pomlčka za W nám říká, že graf není bipartitní. Čísla, která následují, popisují počet uzlů, respektive hran v grafu. Dále name (v/c), label (v/c), weight (e/n)
uvádí informace o atributech grafu. Jsou zde dva atributy vrcholů (v/c) jména – což jsou ID – a značky a atribut hran (e/n) váhy. Nakonec je zde výpis všech hran.
Stejně jako u balíčku network
můžeme pomocí funkce plot()
vytvořit graf s objektem igraph
. Jedinou změnou, kterou zde provedu oproti výchozímu nastavení, je zmenšení velikosti šipek. Ve výchozím nastavení igraph
jsou uzly označeny sloupcem štítků, pokud je k dispozici, nebo pomocí ID.
plot(routes_igraph, edge.arrow.size = 0.2)
Stejně jako u předchozího grafu network
není výchozí graf igraph
nijak zvlášť estetický, ale se všemi aspekty grafů lze manipulovat. Zde chci pouze změnit rozložení uzlů tak, aby používaly algoritmus graphopt, který vytvořil Michael Schmuhl. Tento algoritmus usnadňuje zobrazení vztahu mezi Haarlemem, Antverpami a Delftem, což jsou tři nejvýznamnější místa v korespondenční síti, tím, že je dále rozprostře.
plot(routes_igraph, layout = layout_with_graphopt, edge.arrow.size = 0.2)
tidygraph a ggraph
Balíčky tidygraph
a ggraph
jsou na poli síťové analýzy nováčky, ale společně tyto dva balíčky poskytují skutečné výhody oproti balíčkům network
a igraph
. Balíky tidygraph
a ggraph
představují pokus o začlenění síťové analýzy do pracovního postupu tidyverse. Balík tidygraph
poskytuje způsob, jak vytvořit síťový objekt, který se více podobá tibble nebo datovému rámci. To umožňuje používat mnoho funkcí dplyr
pro manipulaci se síťovými daty. ggraph
poskytuje způsob vykreslování síťových grafů s využitím konvencí a možností ggplot2
. Jinými slovy, tidygraph
a ggraph
umožňují pracovat se síťovými objekty způsobem, který je více konzistentní s příkazy používanými pro práci s tibbles a datovými rámci. Skutečným příslibem tidygraph
a ggraph
je však to, že využívají sílu igraph
. To znamená, že použitím tidygraph
a ggraph
obětujete jen málo z možností síťové analýzy igraph
.
Na začátek musíme jako vždy načíst potřebné balíčky.
library(tidygraph)library(ggraph)
Nejprve vytvoříme síťový objekt pomocí tidygraph
, který se nazývá tbl_graph
. Síť tbl_graph
se skládá ze dvou tibble: tibble hran a tibble uzlů. Je příhodné, že třída objektů tbl_graph
je obalem kolem objektu igraph
, což znamená, že ve svém základu je objekt tbl_graph
v podstatě objektem igraph
.8 Úzká vazba mezi objekty tbl_graph
a igraph
vede ke dvěma hlavním způsobům vytvoření objektu tbl_graph
. Prvním je použití seznamu hran a seznamu uzlů pomocí tbl_graph()
. Argumenty funkce jsou téměř totožné s argumenty funkce graph_from_data_frame()
, pouze s malou změnou názvů argumentů.
routes_tidy <- tbl_graph(nodes = nodes, edges = edges, directed = TRUE)
Druhým způsobem vytvoření objektu tbl_graph
je konverze objektu igraph
nebo network
pomocí as_tbl_graph()
. Mohli bychom tedy převést routes_igraph
na objekt tbl_graph
.
routes_igraph_tidy <- as_tbl_graph(routes_igraph)
Když jsme nyní vytvořili dva objekty tbl_graph
, zkontrolujme je pomocí funkce class()
. Z toho vyplývá, že routes_tidy
a routes_igraph_tidy
jsou objekty třídy "tbl_graph" "igraph"
, zatímco routes_igraph
je objekt třídy "igraph"
.
class(routes_tidy)#> "tbl_graph" "igraph"class(routes_igraph_tidy)#> "tbl_graph" "igraph"class(routes_igraph)#> "igraph"
Výpis objektu tbl_graph
na konzolu vede k drasticky odlišnému výstupu než u objektu igraph
. Je to výstup podobný výstupu normálního tibblu.
routes_tidy#> # A tbl_graph: 13 nodes and 15 edges#> ##> # A directed acyclic simple graph with 1 component#> ##> # Node Data: 13 x 2 (active)#> id label#> <int> <chr>#> 1 1 Antwerp#> 2 2 Haarlem#> 3 3 Dordrecht#> 4 4 Venice#> 5 5 Lisse#> 6 6 Het Vlie#> # ... with 7 more rows#> ##> # Edge Data: 15 x 3#> from to weight#> <int> <int> <int>#> 1 9 13 1#> 2 1 10 68#> 3 1 2 5#> # ... with 12 more rows
Vytisknutí routes_tidy
ukazuje, že jde o objekt tbl_graph
s 13 uzly a 15 hranami. Příkaz také vytiskne prvních šest řádků „Údaje o uzlech“ a první tři řádky „Údaje o hranách“. Všimněte si také, že uvádí, že „Data uzlu“ jsou aktivní. Pojem aktivní uzel v rámci objektu tbl_graph
umožňuje manipulovat s daty v jednom uzlu najednou. Ve výchozím nastavení je aktivován tibble uzlů, ale pomocí funkce activate()
můžete změnit, který tibble je aktivní. Pokud bych tedy chtěl změnit uspořádání řádků v tibblu hran tak, aby byly nejprve uvedeny ty s nejvyšší „váhou“, mohl bych použít activate()
a poté arrange()
. Zde výsledek místo ukládání jednoduše vypíšu.
routes_tidy %>% activate(edges) %>% arrange(desc(weight))#> # A tbl_graph: 13 nodes and 15 edges#> ##> # A directed acyclic simple graph with 1 component#> ##> # Edge Data: 15 x 3 (active)#> from to weight#> <int> <int> <int>#> 1 1 10 68#> 2 2 10 26#> 3 1 2 5#> 4 1 11 2#> 5 2 13 2#> 6 4 2 2#> # ... with 9 more rows#> ##> # Node Data: 13 x 2#> id label#> <int> <chr>#> 1 1 Antwerp#> 2 2 Haarlem#> 3 3 Dordrecht#> # ... with 10 more rows
Protože s routes_tidy
nemusíme dále manipulovat, můžeme graf vykreslit pomocí ggraph
. Stejně jako ggmap je ggraph
rozšířením ggplot2
, což usnadňuje přenesení základních dovedností ggplot
do tvorby síťových grafů. Stejně jako u všech síťových grafů existují i u grafu ggraph
tři hlavní aspekty: uzly, hrany a rozložení. Vignety pro balík ggraph užitečně pokrývají základní aspekty grafů ggraph
. ggraph
přidává k základní sadě ggplot
geomů speciální geomy, které jsou určeny speciálně pro sítě. Existuje tedy sada geom_node
a geom_edge
geomů. Základní vykreslovací funkcí je ggraph()
, která přebírá data, která mají být použita pro graf, a požadovaný typ rozložení. Oba argumenty pro ggraph()
jsou postaveny kolem igraph
. Proto může ggraph()
použít buď objekt igraph
, nebo objekt tbl_graph
. Kromě toho jsou dostupné algoritmy rozvržení primárně odvozeny od igraph
. A konečně, ggraph
zavádí speciální téma ggplot
, které poskytuje lepší výchozí nastavení pro síťové grafy než běžné výchozí nastavení ggplot
. Téma ggraph
lze nastavit pro řadu grafů příkazem set_graph_style()
spuštěným před vykreslením grafů nebo pomocí theme_graph()
v jednotlivých grafech. Zde použiji druhý způsob.
Podívejme se, jak vypadá základní graf ggraph
. Výkres začíná znakem ggraph()
a daty. Poté přidám základní geomy hran a uzlů. V rámci hranových a uzlových geomů nejsou nutné žádné argumenty, protože přebírají informace z dat uvedených v ggraph()
.
ggraph(routes_tidy) + geom_edge_link() + geom_node_point() + theme_graph()
Jak vidíte, struktura příkazu je podobná jako u ggplot
s tím, že samostatné vrstvy jsou přidány pomocí znaku +
. Základní graf ggraph
vypadá podobně jako grafy network
a igraph
, ne-li ještě jednodušeji, ale pomocí podobných příkazů jako ggplot
můžeme vytvořit informativnější graf. Pomocí šířky ve funkci geom_edge_link()
můžeme zobrazit „váhu“ hran – neboli množství písmen odeslaných po jednotlivých trasách. Aby se šířka čáry měnila v závislosti na proměnné váha, umístíme argument do funkce aes()
. Abych mohl řídit maximální a minimální šířku hran, použiji scale_edge_width()
a nastavím range
. Pro minimální šířku volím poměrně malou šířku, protože mezi maximálním a minimálním počtem písmen posílaných po trasách je značný rozdíl. Uzly můžeme také označit názvy míst, protože uzlů je relativně málo. Je výhodné, že geom_node_text()
obsahuje argument repel, který zajišťuje, aby se popisky nepřekrývaly s uzly podobně jako v balíčku ggrepel. Pomocí argumentu alpha přidávám hranám trochu průhlednosti. Používám také labs()
k přeznačení legendy „Písmena“.
ggraph(routes_tidy, layout = "graphopt") + geom_node_point() + geom_edge_link(aes(width = weight), alpha = 0.8) + scale_edge_width(range = c(0.2, 2)) + geom_node_text(aes(label = label), repel = TRUE) + labs(edge_width = "Letters") + theme_graph()
Kromě možností rozložení, které poskytuje igraph
, implementuje ggraph
také vlastní rozložení. Například při vytváření obloukových diagramů můžete použít koncept kružnice ggraph's
. Zde jsem rozložil uzly do vodorovné linie a hrany nechal vykreslit jako oblouky. Na rozdíl od předchozího grafu je v tomto grafu vyznačena směrovost hran.9 Hrany nad vodorovnou čarou se pohybují zleva doprava, zatímco hrany pod čarou se pohybují zprava doleva. Místo přidávání bodů pro uzly uvádím pouze názvy štítků. Pro označení rozdílu ve váze jednotlivých hran používám stejnou estetickou šířku. Všimněte si, že v tomto grafu používám jako data pro graf objekt igraph
, což prakticky nic nemění.
ggraph(routes_igraph, layout = "linear") + geom_edge_arc(aes(width = weight), alpha = 0.8) + scale_edge_width(range = c(0.2, 2)) + geom_node_text(aes(label = label)) + labs(edge_width = "Letters") + theme_graph()
Interaktivní síťové grafy s visNetwork a networkD3
Sada balíčků htmlwidgets umožňuje používat R k vytváření interaktivních vizualizací v jazyce JavaScript. Zde ukážu, jak vytvořit grafy pomocí balíčků visNetwork
a networkD3
. Tyto dva balíčky používají k vytváření grafů různé knihovny jazyka JavaScript. visNetwork
používá vis.js, zatímco networkD3
používá k tvorbě grafů populární vizualizační knihovnu d3. Jedním z problémů při práci s balíky visNetwork
i networkD3
je, že očekávají, že seznamy hran a seznamy uzlů budou používat specifické názvosloví. Výše uvedená manipulace s daty je v souladu se základní strukturou pro visNetwork
, ale pro networkD3
bude třeba udělat nějakou práci. I přes tuto nepříjemnost disponují oba balíky širokou škálou grafických schopností a oba umí pracovat s objekty a rozvržením igraph
.
library(visNetwork)library(networkD3)
visNetwork
Funkce visNetwork()
používá k vytvoření interaktivního grafu seznam uzlů a seznam hran. Seznam uzlů musí obsahovat sloupec „id“ a seznam hran musí mít sloupce „od“ a „do“. Funkce také vykreslí popisky uzlů pomocí názvů měst ze sloupce „label“ v seznamu uzlů. S výsledným grafem je zábavné si hrát. Uzly můžete přesouvat a graf použije algoritmus, který udržuje správné rozestupy uzlů. Graf můžete také zvětšovat a zmenšovat a přesouvat jej, abyste jej znovu vycentrovali.
visNetwork(nodes, edges)
visNetwork
může používat igraph
rozložení, což poskytuje velké množství možných rozložení. Kromě toho můžete pomocí visIgraph()
vykreslit objekt igraph
přímo. Zde se budu držet pracovního postupu nodes
a edges
a k přizpůsobení grafu použiji rozložení igraph
. Přidám také proměnnou pro změnu šířky hrany, stejně jako jsme to udělali u ggraph
. visNetwork()
používá názvy sloupců ze seznamů hran a uzlů pro vykreslení atributů sítě místo argumentů v rámci volání funkce. To znamená, že je nutné provést určitou manipulaci s daty, abychom získali sloupec „šířka“ v seznamu hran. Atribut šířky pro visNetwork()
neškáluje hodnoty, takže to musíme udělat ručně. Obě tyto činnosti lze provést pomocí funkce mutate()
a několika jednoduchých aritmetických úkonů. Zde vytvořím nový sloupec v edges
a hodnoty vah škáluji dělením 5. Přičtením 1 k výsledku získáme způsob, jak vytvořit minimální šířku.
edges <- mutate(edges, width = weight/5 + 1)
Po provedení této operace můžeme vytvořit graf s proměnlivou šířkou hran. Zvolím také algoritmus rozvržení z igraph
a přidám k hranám šipky, které umístím do středu hrany.
visNetwork(nodes, edges) %>% visIgraphLayout(layout = "layout_with_fr") %>% visEdges(arrows = "middle")
networkD3
Pro přípravu dat k vytvoření networkD3
grafu je třeba ještě trochu práce. Vytvoření grafu networkD3
se seznamem hran a uzlů vyžaduje, aby ID byla řada celých čísel, která začínají číslem 0. V současné době začínají ID uzlů našich dat číslem 1, a proto musíme provést trochu manipulace s daty. Uzly je možné přečíslovat odečtením 1 od sloupců ID v datových rámcích nodes
a edges
. To lze opět provést pomocí funkce mutate()
. Cílem je znovu vytvořit aktuální sloupce, přičemž se od každého ID odečte 1. Funkce mutate()
pracuje tak, že vytvoří nový sloupec, ale můžeme ji nechat nahradit sloupec tak, že novému sloupci dáme stejné jméno jako starému sloupci. Zde pojmenovávám nové datové rámce příponou d3, abych je odlišil od předchozích datových rámců nodes
a edges
.
nodes_d3 <- mutate(nodes, id = id - 1)edges_d3 <- mutate(edges, from = from - 1, to = to - 1)
Nyní je možné vykreslit graf networkD3
. Na rozdíl od funkce visNetwork()
používá funkce forceNetwork()
řadu argumentů pro úpravu grafu a vykreslení atributů sítě. Argumenty „Links“ a „Nodes“ poskytují data pro vykreslení ve formě seznamů hran a uzlů. Funkce také vyžaduje argumenty „NodeID“ a „Group“. Data, která jsou zde použita, nemají žádné seskupení, a tak mám pouze každý uzel svou vlastní skupinu, což v praxi znamená, že všechny uzly budou mít různé barvy. Níže je navíc funkci sděleno, že síť má pole „Source“ a „Target“, a je tedy směrovaná. Do tohoto grafu zahrnuji „Hodnotu“, která škáluje šířku hran podle sloupce „Váha“ v seznamu hran. Nakonec přidávám několik estetických úprav, aby uzly byly neprůhledné, a zvětšuji velikost písma popisků pro zlepšení čitelnosti. Výsledek je velmi podobný prvnímu grafu visNetwork()
, který jsem vytvořil, ale s jinou estetickou stylizací.
forceNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Group = "id", Value = "weight", opacity = 1, fontSize = 16, zoom = TRUE)
Jednou z hlavních výhod networkD3
je, že implementuje Sankeyho diagram ve stylu d3. Sankeyho diagram se dobře hodí pro dopisy zaslané Danielovi v roce 1585. V datech není příliš mnoho uzlů, což usnadňuje vizualizaci toku dopisů. Při vytváření Sankeyova diagramu se používá funkce sankeyNetwork()
, která přijímá mnoho stejných argumentů jako funkce forceNetwork()
. Tento graf nevyžaduje argument skupiny a jedinou další změnou je přidání „jednotky“. Ta poskytuje označení hodnot, které se objeví v tipu nástroje, když kurzor najede na prvek diagramu.10
sankeyNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Value = "weight", fontSize = 16, unit = "Letter(s)")
Další čtení o síťové analýze
Tento příspěvek se pokusil poskytnout obecný úvod do vytváření a vykreslování objektů síťového typu v R pomocí balíčků network
, igraph
, tidygraph
a ggraph
pro statické grafy a visNetwork
a networkD3
pro interaktivní grafy. Tyto informace jsem prezentoval z pozice nespecialisty na teorii sítí. Pokryl jsem jen velmi malé procento možností síťové analýzy v R. Zejména jsem se nezabýval statistickou analýzou sítí. Naštěstí existuje nepřeberné množství zdrojů o analýze sítí obecně a zejména v R.
Nejlepším úvodem do sítí, který jsem pro nezasvěcené našel, je kniha Katyi Ognyanové Network Visualization with R. Ta představuje jak užitečný úvod do vizuálních aspektů sítí, tak podrobnější výuku vytváření síťových grafů v R. Ognyanová používá především igraph
, ale představuje také interaktivní sítě.
Vydavatelství Springer vydalo dvě relativně nedávné knihy o analýze sítí v R. V nich se můžete seznámit se základními principy analýzy sítí. Douglas A. Luke, A User’s Guide to Network Analysis in R (2015) je velmi užitečný úvod do síťové analýzy v R. Luke se zabývá jak balíčky statnet, tak igragh
. Obsah je po celou dobu na velmi přístupné úrovni. Pokročilejší je kniha Eric D. Kolaczyk a Gábor Csárdi, Statistical Analysis of Network Data with R (2014). Kniha Kolaczyka a Csárdiho využívá především igraph
, protože Csárdi je hlavním správcem balíčku igraph
pro R. Tato kniha se dále dostává k pokročilým tématům statistické analýzy sítí. I přes použití velmi odborného jazyka jsou první čtyři kapitoly obecně přístupné i z pohledu neodborníka.
Seznam, jehož kurátorem je François Briatte, je dobrým přehledem zdrojů o analýze sítí obecně. Za prostudování stojí také série příspěvků Networks Demystified Scotta Weingarta.
-
Jedním z příkladů zájmu o síťovou analýzu v rámci digitálních humanitních věd je nově založený časopis Journal of Historical Network Research. ︎
-
Dobrý popis třídy objektů
network
, včetně diskuse o jejím vztahu ke třídě objektůigraph
, viz Carter Butts, „network: A Package for Managing Relational Data in R“, Journal of Statistical Software, 24 (2008): 1-36 ︎ -
Tato specifická struktura se očekává od třídy
visNetwork
a zároveň odpovídá obecným očekáváním ostatních balíků. ︎ -
Toto je očekávané pořadí sloupců pro některé síťové balíčky, které budu používat níže. ︎
-
ungroup()
není v tomto případě nezbytně nutné. Pokud však datový rámec nezeskupíte, není možné vypustit sloupce „zdroj“ a „cíl“, jak to udělám později ve skriptu. ︎ -
Thomas M. J. Fruchterman a Edward M. Reingold, „Graph Drawing by Force-Directed Placement“, Software: Practice and Experience, 21 (1991): 1129-1164. ︎
-
Funkce
rm()
je užitečná, pokud se vám pracovní prostředí v R rozsype, ale nechcete celé prostředí vymazat a začít znovu. ︎ -
Vztah mezi objekty
tbl_graph
aigraph
je podobný vztahu mezi objektytibble
adata.frame
. ︎ -
Je možné, aby
ggraph
kreslil šipky, ale to jsem zde neukazoval. ︎ -
Může chvíli trvat, než se zobrazí tip nástroje. ︎
Napsat komentář