Introducere în analiza rețelelor cu R
On noiembrie 4, 2021 by adminÎntr-o gamă largă de domenii, analiza rețelelor a devenit un instrument din ce în ce mai popular pentru cercetători pentru a aborda complexitatea interrelațiilor dintre actorii de toate felurile. Promisiunea analizei rețelelor este plasarea semnificației pe relațiile dintre actori, mai degrabă decât considerarea actorilor ca entități izolate. Accentul pus pe complexitate, împreună cu crearea unei varietăți de algoritmi pentru a măsura diferite aspecte ale rețelelor, face ca analiza rețelelor să fie un instrument central pentru științele umaniste digitale.1 Această postare va oferi o introducere în lucrul cu rețelele în R, folosind exemplul rețelei de orașe din corespondența lui Daniel van der Meulen din 1585.
Există o serie de aplicații concepute pentru analiza rețelelor și crearea de grafice de rețea, cum ar fi gephi și cytoscape. Deși nu a fost conceput special pentru aceasta, R s-a dezvoltat într-un instrument puternic pentru analiza rețelelor. Punctul forte al R în comparație cu software-ul de analiză a rețelelor de sine stătător este triplu. În primul rând, R permite o cercetare reproductibilă care nu este posibilă cu aplicațiile GUI. În al doilea rând, puterea de analiză a datelor din R oferă instrumente robuste de manipulare a datelor pentru a le pregăti pentru analiza rețelelor. În cele din urmă, există o gamă tot mai largă de pachete concepute pentru a face din R un instrument complet de analiză a rețelelor. Printre pachetele semnificative de analiză a rețelelor pentru R se numără suita de pachete statnet și igraph
. În plus, Thomas Lin Pedersen a lansat recent pachetele tidygraph
și ggraph
care valorifică puterea igraph
într-o manieră coerentă cu fluxul de lucru tidyverse. R poate fi, de asemenea, utilizat pentru a realiza grafice de rețea interactive cu ajutorul cadrului htmlwidgets care traduce codul R în JavaScript.
Acest post începe cu o scurtă introducere în vocabularul de bază al analizei de rețea, urmată de o discuție despre procesul de obținere a datelor în structura adecvată pentru analiza de rețea. Pachetele de analiză a rețelelor și-au implementat toate propriile clase de obiecte. În această postare, voi arăta cum se creează clasele de obiecte specifice pentru suita de pachete statnet cu pachetul network
, precum și pentru igraph
și tidygraph
, care se bazează pe implementarea igraph
. În final, voi trece la crearea de grafice interactive cu pachetele vizNetwork
și networkD3
.
Analiză de rețele: Noduri și muchii
Cele două aspecte principale ale rețelelor sunt o multitudine de entități separate și conexiunile dintre ele. Vocabularul poate fi un pic tehnic și chiar inconsecvent între diferite discipline, pachete și software. Entitățile sunt denumite noduri sau vârfuri ale unui graf, în timp ce conexiunile sunt muchii sau legături. În această postare voi folosi în principal nomenclatura de noduri și muchii, cu excepția cazului în care voi discuta despre pachetele care folosesc un vocabular diferit.
Pachetele de analiză a rețelelor au nevoie ca datele să fie într-o anumită formă pentru a crea tipul special de obiect folosit de fiecare pachet. Clasele de obiecte pentru network
, igraph
și tidygraph
se bazează toate pe matrici de adiacență, cunoscute și sub numele de sociomatrice.2 O matrice de adiacență este o matrice pătrată în care numele coloanelor și al rândurilor reprezintă nodurile rețelei. În cadrul matricei, un 1 indică faptul că există o conexiune între noduri, iar un 0 indică absența unei conexiuni. Matricile de adiacență implementează o structură de date foarte diferită de cea a cadrelor de date și nu se încadrează în fluxul de lucru tidyverse pe care l-am folosit în postările mele anterioare. În mod util, obiectele de rețea specializate pot fi create, de asemenea, dintr-un cadru de date de tip edge-list, care se încadrează în fluxul de lucru tidyverse. În această postare mă voi rezuma la tehnicile de analiză a datelor din tidyverse pentru a crea liste de muchii, care vor fi apoi convertite în clasele de obiecte specifice pentru network
, igraph
și tidygraph
.
O listă de muchii este un cadru de date care conține cel puțin două coloane, o coloană de noduri care sunt sursa unei conexiuni și o altă coloană de noduri care sunt ținta conexiunii. Nodurile din date sunt identificate prin ID-uri unice. În cazul în care distincția dintre sursă și țintă este semnificativă, rețeaua este direcționată. În cazul în care distincția nu este semnificativă, rețeaua este nedirijată. În exemplul scrisorilor trimise între orașe, distincția dintre sursă și țintă este în mod clar semnificativă și, prin urmare, rețeaua este direcționată. Pentru exemplele de mai jos, voi numi coloana sursă „de la” și coloana țintă „la”. O listă de muchii poate conține, de asemenea, coloane suplimentare care descriu atribute ale muchiei, cum ar fi un aspect de mărime pentru o muchie. Dacă marginile au un atribut de magnitudine, graful este considerat ponderat.
Listele de muchii conțin toate informațiile necesare pentru a crea obiecte de rețea, dar uneori este preferabil să se creeze și o listă separată de noduri. La modul cel mai simplu, o listă de noduri este un cadru de date cu o singură coloană – pe care o voi numi „id” – care enumeră ID-urile nodurilor găsite în lista de muchii. Avantajul creării unei liste separate de noduri este posibilitatea de a adăuga coloane de atribute la cadrul de date, cum ar fi numele nodurilor sau orice fel de grupări. Mai jos dau un exemplu de liste minime de muchii și noduri create cu funcția 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
Comparați acest lucru cu o matrice de adiacență cu aceleași date.
#> 1 2 3 4#> 1 0 1 0 0#> 2 0 0 1 1#> 3 0 1 0 0#> 4 1 0 0 0
Crearea listelor de muchii și noduri
Pentru a crea obiecte de rețea din baza de date a scrisorilor primite de Daniel van der Meulen în 1585 voi realiza atât o listă de muchii, cât și o listă de noduri. Acest lucru va necesita utilizarea pachetului dplyr pentru a manipula cadrul de date al scrisorilor trimise lui Daniel și a-l împărți în două cadre de date sau tibbles cu structura listelor de muchii și de noduri. În acest caz, nodurile vor fi orașele din care corespondenții lui Daniel i-au trimis scrisori și orașele în care acesta le-a primit. Lista de noduri va conține o coloană „etichetă”, care va conține numele orașelor. Lista de muchii va avea, de asemenea, o coloană de atribute care va arăta cantitatea de scrisori trimise între fiecare pereche de orașe. Fluxul de lucru pentru a crea aceste obiecte va fi similar cu cel pe care l-am folosit în scurta mea introducere în R și în geocodarea cu R. Dacă doriți să urmăriți acest lucru, puteți găsi datele folosite în această postare și scriptul R folosit pe GitHub.
Primul pas este să încărcați biblioteca tidyverse
pentru a importa și manipula datele. Tipărirea cadrului de date letters
arată că acesta conține patru coloane: „writer”, „source”, „destination” și „date”. În acest exemplu, ne vom ocupa doar de coloanele „sursă” și „destinație”.
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
Listă de noduri
Fluxul de lucru pentru a crea o listă de noduri este similar cu cel pe care l-am folosit pentru a obține lista de orașe în vederea geocodificării datelor într-o postare anterioară. Dorim să obținem orașele distincte atât din coloana „sursă”, cât și din coloana „destinație” și apoi să unim informațiile din aceste coloane. În exemplul de mai jos, am modificat ușor comenzile față de cele pe care le-am folosit în postarea anterioară pentru ca numele coloanelor cu numele orașelor să fie același atât pentru cadrele de date sources
, cât și pentru destinations
, pentru a simplifica funcția full_join()
. Redenumesc coloana cu numele orașelor în „label” pentru a adopta vocabularul folosit de pachetele de analiză a rețelelor.
sources <- letters %>% distinct(source) %>% rename(label = source)destinations <- letters %>% distinct(destination) %>% rename(label = destination)
Pentru a crea un singur cadru de date cu o coloană cu locațiile unice trebuie să folosim o îmbinare completă, deoarece dorim să includem toate locațiile unice atât de la sursele de scrisori, cât și de la destinații.
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
Aceasta duce la un cadru de date cu o singură variabilă. Cu toate acestea, variabila conținută în cadrul de date nu este chiar ceea ce căutăm. Coloana „label” conține numele nodurilor, dar dorim, de asemenea, să avem ID-uri unice pentru fiecare oraș. Putem face acest lucru adăugând o coloană „id” la cadrul de date nodes
care să conțină numere de la unu la oricare ar fi numărul total de rânduri din cadrul de date. O funcție utilă pentru acest flux de lucru este rowid_to_column()
, care adaugă o coloană cu valorile din id-urile rândurilor și plasează coloana la începutul cadrului de date.4 Rețineți că rowid_to_column()
este o comandă care poate fi transmisă prin pipe și, astfel, este posibil să efectuați full_join()
și să adăugați coloana „id” într-o singură comandă. Rezultatul este o listă de noduri cu o coloană „ID” și un atribut „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
Listă de muchii
Crearea unei liste de muchii este similară cu cea de mai sus, dar este complicată de necesitatea de a avea de-a face cu două coloane „ID” în loc de una. De asemenea, dorim să creăm o coloană de greutate care va nota cantitatea de litere trimise între fiecare set de noduri. Pentru a realiza acest lucru, voi folosi același flux de lucru group_by()
și summarise()
pe care l-am discutat în postările anterioare. Diferența aici este că dorim să grupăm cadrul de date în funcție de două coloane – „sursă” și „destinație” – în loc de una singură. Anterior, am numit coloana care numără numărul de observații pe grup „count” (număr), dar aici adopt nomenclatura analizei de rețea și o numesc „weight” (greutate). Ultima comandă din pipeline elimină gruparea pentru cadrul de date instituită de funcția group_by()
. Acest lucru facilitează manipularea nestingherită a cadrului de date per_route
rezultat. 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
Ca și lista de noduri, per_route
are acum forma de bază pe care o dorim, dar avem din nou problema că coloanele „sursă” și „destinație” conțin mai degrabă etichete decât ID-uri. Ceea ce trebuie să facem este să legăm ID-urile care au fost atribuite în nodes
la fiecare locație din ambele coloane „sursă” și „destinație”. Acest lucru poate fi realizat cu o altă funcție de îmbinare. De fapt, este necesar să efectuăm două îmbinări, una pentru coloana „sursă” și una pentru „destinație”. În acest caz, voi utiliza un left_join()
cu per_route
ca și cadru de date din stânga, deoarece dorim să menținem numărul de rânduri din per_route
. În timp ce facem left_join
, dorim, de asemenea, să redenumim cele două coloane „id” care sunt aduse din nodes
. Pentru join-ul care utilizează coloana „source”, voi redenumi coloana „from”. Coloana adusă din join-ul „destinație” este redenumită „la”. Ar fi posibil să se facă ambele îmbinări într-o singură comandă cu ajutorul pipe-ului. Cu toate acestea, pentru claritate, voi efectua îmbinările în două comenzi separate. Deoarece îmbinarea se face în două comenzi, observați că cadrul de date de la începutul pipetei se schimbă de la per_route
la edges
, care este creat de prima comandă.
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)
Acum că edges
are coloanele „de la” și „către” cu ID-uri de nod, trebuie să reordonăm coloanele pentru a aduce „de la” și „către” în partea stângă a cadrului de date. În prezent, cadrul de date edges
conține încă coloanele „sursă” și „destinație” cu numele orașelor care corespund ID-urilor. Totuși, aceste date sunt de prisos, deoarece sunt deja prezente în nodes
. Prin urmare, voi include doar coloanele „from”, „to” și „weight” în funcția select()
.
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
Cadrul de date edges
nu pare foarte impresionant; este format din trei coloane de numere întregi. Cu toate acestea, edges
combinat cu nodes
ne oferă toate informațiile necesare pentru a crea obiecte de rețea cu pachetele network
, igraph
și tidygraph
.
Crearea obiectelor de rețea
Classele de obiecte de rețea pentru network
, igraph
și tidygraph
sunt toate strâns legate între ele. Este posibilă conversia între un obiect network
și un obiect igraph
. Cu toate acestea, este mai bine să se păstreze cele două pachete și obiectele lor separate. De fapt, capacitățile network
și igraph
se suprapun într-o asemenea măsură încât cea mai bună practică este de a avea doar unul dintre pachete încărcat la un moment dat. Voi începe prin a trece în revistă pachetul network
și apoi voi trece la pachetele igraph
și tidygraph
.
network
library(network)
Funcția utilizată pentru a crea un obiect network
este network()
. Comanda nu este deosebit de simplă, dar puteți oricând să introduceți ?network()
în consolă dacă vă încurcați. Primul argument este – așa cum se precizează în documentație – „o matrice care oferă structura rețelei sub formă de adiacență, incidență sau edgelist”. Limbajul demonstrează semnificația matricelor în analiza rețelelor, dar în loc de o matrice, avem o listă de muchii, care îndeplinește același rol. Al doilea argument este o listă de atribute ale vertexurilor, care corespunde listei de noduri. Observați că pachetul network
utilizează nomenclatura de vârfuri în loc de noduri. Același lucru este valabil și pentru igraph
. Trebuie apoi să specificăm tipul de date care a fost introdus în primele două argumente, precizând că matrix.type
este un "edgelist"
. În cele din urmă, setăm ignore.eval
la FALSE
, astfel încât rețeaua noastră să poată fi ponderată și să ia în considerare numărul de litere de-a lungul fiecărui traseu.
routes_network <- network(edges, vertex.attr = nodes, matrix.type = "edgelist", ignore.eval = FALSE)
Puteți vedea tipul de obiect creat de funcția network()
prin plasarea lui routes_network
în funcția class()
.
class(routes_network)#> "network"
Imprimarea lui routes_network
pe consolă arată că structura obiectului este destul de diferită de cea a obiectelor de tip data-frame, cum ar fi edges
și nodes
. Comanda de imprimare dezvăluie informații care sunt definite în mod specific pentru analiza rețelei. Aceasta arată că există 13 vârfuri sau noduri și 15 muchii în routes_network
. Aceste numere corespund numărului de rânduri din nodes
și, respectiv, edges
. De asemenea, putem observa că atât vârfurile, cât și marginile conțin atribute precum eticheta și greutatea. Puteți obține și mai multe informații, inclusiv o sociomatrice a datelor, introducând 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
Este acum posibil să obținem un grafic rudimentar, chiar dacă nu prea plăcut din punct de vedere estetic, al rețelei noastre de litere. Atât pachetul network
, cât și pachetul igraph
utilizează sistemul de reprezentare grafică de bază din R. Convențiile pentru graficele de bază sunt semnificativ diferite de cele ale ggplot2 – despre care am discutat în postările anterioare – și, prin urmare, mă voi limita la grafice destul de simple în loc să intru în detaliile creării de grafice complexe cu R de bază. În acest caz, singura modificare pe care o fac la funcția implicită plot()
pentru pachetul network
este de a mări dimensiunea nodurilor cu argumentul vertex.cex
pentru a face nodurile mai vizibile. Chiar și cu acest grafic foarte simplu, putem deja să învățăm ceva despre date. Graficul arată clar că există două grupări sau clustere principale ale datelor, care corespund timpului pe care Daniel l-a petrecut în Olanda în primele trei sferturi ale anului 1585 și după mutarea sa la Bremen în septembrie.
plot(routes_network, vertex.cex = 3)
Funcția plot()
cu un obiect network
utilizează algoritmul Fruchterman și Reingold pentru a decide asupra plasării nodurilor.6 Puteți modifica algoritmul de dispunere cu ajutorul argumentului mode
. Mai jos, am dispus nodurile într-un cerc. Aceasta nu este o dispunere deosebit de utilă pentru această rețea, dar oferă o idee despre unele dintre opțiunile disponibile.
plot(routes_network, vertex.cex = 3, mode = "circle")
igraph
Să trecem acum la discutarea pachetului igraph
. În primul rând, trebuie să curățăm mediul din R prin eliminarea pachetului network
, astfel încât acesta să nu interfereze cu comenzile igraph
. Am putea la fel de bine să eliminăm și routes_network
, deoarece nu îl vom mai folosi. Pachetul network
poate fi eliminat cu funcția detach()
, iar routes_network
se elimină cu rm()
.7 După aceasta, putem încărca în siguranță igraph
.
detach(package:network)rm(routes_network)library(igraph)
Pentru a crea un obiect igraph
dintr-un cadru de date de tip edge-list putem folosi funcția graph_from_data_frame()
, care este un pic mai simplă decât network()
. Există trei argumente în funcția graph_from_data_frame()
: d, vertices și directed. Aici, d se referă la lista de muchii, vertices la lista de noduri, iar directed poate fi fie TRUE
, fie FALSE
, în funcție de faptul dacă datele sunt direcționate sau nedirecționate.
routes_igraph <- graph_from_data_frame(d = edges, vertices = nodes, directed = TRUE)
Imprimarea pe consolă a obiectului igraph
creat de graph_from_data_frame()
dezvăluie informații similare cu cele de la un obiect network
, deși structura este mai criptică.
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
Informațiile principale despre obiect sunt conținute în DNW- 13 15 --
. Acesta spune că routes_igraph
este o rețea dirijată (D) care are un atribut nume (N) și este ponderată (W). Traducerea după W ne spune că graful nu este bipartit. Numerele care urmează descriu numărul de noduri și, respectiv, de muchii din graf. În continuare, name (v/c), label (v/c), weight (e/n)
oferă informații despre atributele grafului. Există două atribute de vertex (v/c) de nume – care sunt ID-urile – și etichete și un atribut de muchie (e/n) de greutate. În cele din urmă, există o imprimare a tuturor marginilor.
La fel ca în cazul pachetului network
, putem crea un grafic cu un obiect igraph
prin intermediul funcției plot()
. Singura modificare pe care o fac aici față de cea implicită este de a micșora dimensiunea săgeților. În mod implicit, igraph
etichetează nodurile cu coloana de etichete, dacă există una, sau cu ID-urile.
plot(routes_igraph, edge.arrow.size = 0.2)
Ca și în cazul graficului network
anterior, valoarea implicită a unui grafic igraph
nu este deosebit de plăcută din punct de vedere estetic, dar toate aspectele graficelor pot fi manipulate. Aici, vreau doar să schimb dispunerea nodurilor pentru a utiliza algoritmul graphopt creat de Michael Schmuhl. Acest algoritm permite să se vadă mai ușor relația dintre Haarlem, Antwerp și Delft, care sunt trei dintre cele mai semnificative locații din rețeaua de corespondență, prin răspândirea lor mai mare.
plot(routes_igraph, layout = layout_with_graphopt, edge.arrow.size = 0.2)
tidygraph și ggraph
Pachetele tidygraph
și ggraph
sunt nou-veniți în peisajul analizei rețelelor, dar împreună cele două pachete oferă avantaje reale față de pachetele network
și igraph
. tidygraph
și ggraph
reprezintă o încercare de a aduce analiza rețelelor în fluxul de lucru tidyverse. tidygraph
oferă o modalitate de a crea un obiect de rețea care seamănă mai mult cu un tibble sau un cadru de date. Acest lucru face posibilă utilizarea multora dintre funcțiile dplyr
pentru a manipula datele de rețea. ggraph
oferă o modalitate de a trasa grafice de rețea folosind convențiile și puterea lui ggplot2
. Cu alte cuvinte, tidygraph
și ggraph
vă permit să manipulați obiectele de rețea într-o manieră mai coerentă cu comenzile utilizate pentru lucrul cu tibbles și cadre de date. Cu toate acestea, adevărata promisiune a tidygraph
și ggraph
este că acestea valorifică puterea lui igraph
. Aceasta înseamnă că sacrificați puține dintre capacitățile de analiză a rețelelor din igraph
folosind tidygraph
și ggraph
.
Trebuie să începem ca întotdeauna prin a încărca pachetele necesare.
library(tidygraph)library(ggraph)
În primul rând, să creăm un obiect de rețea folosind tidygraph
, care se numește tbl_graph
. Un tbl_graph
este format din două tibble: un tibble de muchii și un tibble de noduri. În mod convenabil, clasa de obiecte tbl_graph
este un înveliș în jurul unui obiect igraph
, ceea ce înseamnă că, la baza sa, un obiect tbl_graph
este, în esență, un obiect igraph
.8 Legătura strânsă dintre obiectele tbl_graph
și igraph
are ca rezultat două moduri principale de a crea un obiect tbl_graph
. Primul constă în utilizarea unei liste de muchii și a unei liste de noduri, folosind tbl_graph()
. Argumentele funcției sunt aproape identice cu cele ale funcției graph_from_data_frame()
, cu doar o ușoară modificare a numelor argumentelor.
routes_tidy <- tbl_graph(nodes = nodes, edges = edges, directed = TRUE)
Cel de-al doilea mod de a crea un obiect tbl_graph
este de a converti un obiect igraph
sau network
folosind as_tbl_graph()
. Astfel, am putea converti routes_igraph
într-un obiect tbl_graph
.
routes_igraph_tidy <- as_tbl_graph(routes_igraph)
Acum că am creat două obiecte tbl_graph
, haideți să le inspectăm cu ajutorul funcției class()
. Aceasta arată că routes_tidy
și routes_igraph_tidy
sunt obiecte din clasa "tbl_graph" "igraph"
, în timp ce routes_igraph
este un obiect din clasa "igraph"
.
class(routes_tidy)#> "tbl_graph" "igraph"class(routes_igraph_tidy)#> "tbl_graph" "igraph"class(routes_igraph)#> "igraph"
Imprimarea unui obiect tbl_graph
pe consolă are ca rezultat o ieșire drastic diferită de cea a unui obiect igraph
. Este o ieșire similară cu cea a unui tibble normal.
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
Imprimarea lui routes_tidy
arată că este un obiect tbl_graph
cu 13 noduri și 15 muchii. Comanda tipărește, de asemenea, primele șase rânduri din „Node Data” și primele trei din „Edge Data”. Observați, de asemenea, că se precizează că „Node Data” este activă. Noțiunea de „tibble activ” în cadrul unui obiect tbl_graph
face posibilă manipularea datelor dintr-un tibble la un moment dat. Tibbul nodurilor este activat în mod implicit, dar puteți schimba tibbul care este activ cu ajutorul funcției activate()
. Astfel, dacă aș dori să rearanjez rândurile din tibble-ul marginilor pentru a le lista mai întâi pe cele cu cea mai mare „greutate”, aș putea folosi activate()
și apoi arrange()
. Aici pur și simplu tipăresc rezultatul în loc să îl salvez.
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
Din moment ce nu trebuie să mai manipulăm routes_tidy
, putem trasa graficul cu ggraph
. La fel ca ggmap, ggraph
este o extensie a lui ggplot2
, ceea ce facilitează preluarea abilităților de bază din ggplot
pentru crearea de grafice de rețea. La fel ca în cazul tuturor grafurilor de rețea, există trei aspecte principale ale unei diagrame ggraph
: noduri, muchii și dispuneri. Vinietele pentru pachetul ggraph acoperă în mod util aspectele fundamentale ale graficelor ggraph
. ggraph
adaugă geomere speciale la setul de bază de geomere ggplot
care sunt special concepute pentru rețele. Astfel, există un set de geomere geom_node
și geom_edge
. Funcția de bază de trasare este ggraph()
, care preia datele care urmează să fie utilizate pentru grafic și tipul de aspect dorit. Ambele argumente pentru ggraph()
sunt construite în jurul lui igraph
. Prin urmare, ggraph()
poate utiliza fie un obiect igraph
, fie un obiect tbl_graph
. În plus, algoritmii de dispunere disponibili derivă în principal din igraph
. În cele din urmă, ggraph
introduce o temă specială ggplot
care oferă valori implicite mai bune pentru grafurile de rețea decât valorile implicite normale din ggplot
. Tema ggraph
poate fi setată pentru o serie de grafice cu ajutorul comenzii set_graph_style()
executată înainte ca graficele să fie trasate sau prin utilizarea theme_graph()
în graficele individuale. Aici, voi folosi cea din urmă metodă.
Să vedem cum arată un grafic ggraph
de bază. Graficul începe cu ggraph()
și cu datele. Apoi adaug geomorfii de bază pentru muchii și noduri. Nu sunt necesare argumente în cadrul geomurilor de muchie și nod, deoarece acestea preiau informațiile din datele furnizate în ggraph()
.
ggraph(routes_tidy) + geom_edge_link() + geom_node_point() + theme_graph()
După cum puteți vedea, structura comenzii este similară cu cea din ggplot
, cu straturile separate adăugate cu semnul +
. Graficul de bază ggraph
arată similar cu cele din network
și igraph
, dacă nu chiar mai simplu, dar putem folosi comenzi similare cu ggplot
pentru a crea un grafic mai informativ. Putem arăta „greutatea” marginilor – sau cantitatea de scrisori trimise de-a lungul fiecărui traseu – folosind width în funcția geom_edge_link()
. Pentru ca lățimea liniei să se schimbe în funcție de variabila weight, plasăm argumentul în cadrul unei funcții aes()
. Pentru a controla lățimea maximă și minimă a marginilor, folosesc scale_edge_width()
și setez o funcție range
. Aleg o lățime relativ mică pentru minim, deoarece există o diferență semnificativă între numărul maxim și minim de litere trimise de-a lungul traseelor. Putem, de asemenea, să etichetăm nodurile cu numele localităților, deoarece există relativ puține noduri. În mod convenabil, geom_node_text()
vine cu un argument repel care asigură că etichetele nu se suprapun cu nodurile într-un mod similar cu pachetul ggrepel. Adaug un pic de transparență la margini cu ajutorul argumentului alpha. De asemenea, folosesc labs()
pentru a reeticheta legenda „Letters”.
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()
În plus față de opțiunile de aspect oferite de igraph
, ggraph
implementează, de asemenea, propriile sale aspecte. De exemplu, puteți utiliza conceptul ggraph's
de circularitate pentru a crea diagrame în arc de cerc. Aici, am dispus nodurile pe o linie orizontală și am trasat marginile sub formă de arce. Spre deosebire de diagrama anterioară, acest grafic indică direcționalitatea marginilor.9 Marginile de deasupra liniei orizontale se deplasează de la stânga la dreapta, în timp ce marginile de sub linie se deplasează de la dreapta la stânga. În loc să adaug puncte pentru noduri, am inclus doar numele etichetelor. Folosesc aceeași lățime estetică pentru a indica diferența de greutate a fiecărei muchii. Rețineți că în acest grafic folosesc un obiect igraph
ca date pentru grafic, ceea ce nu face nicio diferență practică.
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()
Grafice de rețea interactive cu visNetwork și networkD3
Setul de pachete htmlwidgets face posibilă utilizarea R pentru a crea vizualizări interactive în JavaScript. Aici, voi arăta cum se realizează grafice cu pachetele visNetwork
și networkD3
. Aceste două pachete folosesc biblioteci JavaScript diferite pentru a crea graficele lor. visNetwork
utilizează vis.js, în timp ce networkD3
utilizează populara bibliotecă de vizualizare d3 pentru a-și realiza graficele. O dificultate în lucrul atât cu visNetwork
, cât și cu networkD3
este că acestea se așteaptă ca listele de muchii și listele de noduri să utilizeze o nomenclatură specifică. Manipularea datelor de mai sus este conformă cu structura de bază pentru visNetwork
, dar va trebui să se lucreze puțin pentru networkD3
. În ciuda acestui inconvenient, ambele pachete posedă o gamă largă de capacități de creare de grafice și ambele pot lucra cu obiecte și layout-uri igraph
.
library(visNetwork)library(networkD3)
visNetwork
Funcția visNetwork()
utilizează o listă de noduri și o listă de muchii pentru a crea un grafic interactiv. Lista de noduri trebuie să includă o coloană „id”, iar lista de muchii trebuie să aibă coloanele „from” și „to”. De asemenea, funcția trasează etichetele pentru noduri, folosind numele orașelor din coloana „label” din lista de noduri. Graficul rezultat este distractiv de utilizat. Puteți muta nodurile, iar graficul va utiliza un algoritm pentru a menține nodurile spațiate corespunzător. De asemenea, puteți mări și micșora graficul și îl puteți deplasa pentru a-l recentra.
visNetwork(nodes, edges)
visNetwork
poate utiliza machetele igraph
, oferind o mare varietate de machete posibile. În plus, puteți utiliza visIgraph()
pentru a trasa direct un obiect igraph
. Aici, voi rămâne la fluxul de lucru nodes
și edges
și voi utiliza un aspect igraph
pentru a personaliza graficul. De asemenea, voi adăuga o variabilă pentru a modifica lățimea marginii, așa cum am făcut cu ggraph
. visNetwork()
utilizează numele coloanelor din listele de muchii și noduri pentru a trasa atributele rețelei în loc de argumente în cadrul apelului de funcție. Acest lucru înseamnă că este necesar să se facă unele manipulări de date pentru a obține o coloană „width” în lista de muchii. Atributul de lățime pentru visNetwork()
nu scalează valorile, așa că trebuie să facem acest lucru manual. Ambele acțiuni pot fi realizate cu ajutorul funcției mutate()
și a unor calcule aritmetice simple. Aici, creez o nouă coloană în edges
și scalez valorile ponderilor prin împărțirea la 5. Adăugarea lui 1 la rezultat oferă o modalitate de a crea o lățime minimă.
edges <- mutate(edges, width = weight/5 + 1)
După ce acest lucru este făcut, putem crea un grafic cu lățimi variabile ale marginilor. Aleg, de asemenea, un algoritm de dispunere din igraph
și adaug săgeți pe muchii, plasându-le în mijlocul muchiei.
visNetwork(nodes, edges) %>% visIgraphLayout(layout = "layout_with_fr") %>% visEdges(arrows = "middle")
networkD3
Este necesară încă puțină muncă pentru a pregăti datele pentru a crea un graf networkD3
. Pentru a realiza un graf networkD3
cu o listă de muchii și noduri este necesar ca ID-urile să fie o serie de numere întregi care încep cu 0. În prezent, ID-urile nodurilor pentru datele noastre încep cu 1 și, prin urmare, trebuie să facem o mică manipulare a datelor. Este posibil să renumerotați nodurile scăzând 1 din coloanele ID din cadrele de date nodes
și edges
. Încă o dată, acest lucru se poate face cu ajutorul funcției mutate()
. Scopul este de a recrea coloanele actuale, scăzând în același timp 1 din fiecare ID. Funcția mutate()
funcționează prin crearea unei noi coloane, dar putem face ca aceasta să înlocuiască o coloană dându-i noii coloane același nume ca și vechea coloană. Aici, numesc noile cadre de date cu un sufix d3 pentru a le distinge de cadrele de date anterioare nodes
și edges
.
nodes_d3 <- mutate(nodes, id = id - 1)edges_d3 <- mutate(edges, from = from - 1, to = to - 1)
Acum este posibil să se traseze un grafic networkD3
. Spre deosebire de visNetwork()
, funcția forceNetwork()
utilizează o serie de argumente pentru a ajusta graficul și a trasa atributele rețelei. Argumentele „Links” și „Nodes” furnizează datele pentru trasare sub forma unor liste de muchii și noduri. Funcția necesită, de asemenea, argumentele „NodeID” și „Group”. Datele utilizate în acest caz nu au nicio grupare, astfel încât fiecare nod este propriul său grup, ceea ce, în practică, înseamnă că toate nodurile vor avea culori diferite. În plus, cele de mai jos îi spun funcției că rețeaua are câmpurile „Source” și „Target” și că, prin urmare, este direcționată. Am inclus în acest grafic o „Valoare”, care scalează lățimea marginilor în funcție de coloana „greutate” din lista de margini. În cele din urmă, am adăugat câteva modificări estetice pentru a face nodurile opace și pentru a mări dimensiunea fontului etichetelor pentru a îmbunătăți lizibilitatea. Rezultatul este foarte asemănător cu prima diagramă visNetwork()
pe care am creat-o, dar cu stiluri estetice diferite.
forceNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Group = "id", Value = "weight", opacity = 1, fontSize = 16, zoom = TRUE)
Unul dintre principalele beneficii ale networkD3
este că implementează o diagramă Sankey în stil d3. O diagramă Sankey se potrivește bine pentru scrisorile trimise lui Daniel în 1585. Nu există prea multe noduri în date, ceea ce face mai ușor de vizualizat fluxul de scrisori. Crearea unei diagrame Sankey utilizează funcția sankeyNetwork()
, care primește multe dintre aceleași argumente ca și forceNetwork()
. Acest grafic nu necesită un argument de grup, iar singura altă modificare este adăugarea unei „unități”. Aceasta oferă o etichetă pentru valorile care apar într-un tool tip atunci când cursorul trece pe deasupra unui element al diagramei. 10
sankeyNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Value = "weight", fontSize = 16, unit = "Letter(s)")
Lectura suplimentară privind analiza rețelelor
Acest articol a încercat să ofere o introducere generală în crearea și reprezentarea grafică a obiectelor de tip rețea în R, folosind pachetele network
, igraph
, tidygraph
și ggraph
pentru grafice statice și visNetwork
și networkD3
pentru grafice interactive. Am prezentat aceste informații din poziția unui nespecialist în teoria rețelelor. Am acoperit doar un procent foarte mic din capacitățile de analiză a rețelelor din R. În special, nu am discutat despre analiza statistică a rețelelor. Din fericire, există o multitudine de resurse privind analiza rețelelor în general și în R în special.
Cea mai bună introducere în rețele pe care am găsit-o pentru neinițiațiat este lucrarea Network Visualization with R a Katyei Ognyanova. Aceasta prezintă atât o introducere utilă în aspectele vizuale ale rețelelor, cât și un tutorial mai aprofundat privind crearea de diagrame de rețea în R. Ognyanova folosește în principal igraph
, dar introduce și rețelele interactive.
Există două cărți relativ recente publicate de Springer privind analiza rețelelor cu R. Douglas A. Luke, A User’s Guide to Network Analysis in R (2015) este o introducere foarte utilă în analiza rețelelor cu R. Luke acoperă atât suita de pachete statnet, cât și igragh
. Conținutul este la un nivel foarte accesibil pe tot parcursul lucrării. Mai avansată este cartea lui Eric D. Kolaczyk și Gábor Csárdi, Statistical Analysis of Network Data with R (2014). Cartea lui Kolaczyk și Csárdi utilizează în principal igraph
, deoarece Csárdi este principalul susținător al pachetului igraph
pentru R. Această carte intră mai mult în subiecte avansate privind analiza statistică a rețelelor. În ciuda utilizării unui limbaj foarte tehnic, primele patru capitole sunt, în general, abordabile din punctul de vedere al nespecialiștilor.
Lista curatoriată de François Briatte este o bună trecere în revistă a resurselor privind analiza rețelelor în general. Seria de postări Networks Demystified de Scott Weingart merită, de asemenea, să fie parcursă.
-
Un exemplu al interesului pentru analiza rețelelor în cadrul științelor umaniste digitale este recent lansatul Journal of Historical Network Research. ︎
-
Pentru o bună descriere a clasei de obiecte
network
, inclusiv o discuție despre relația sa cu clasa de obiecteigraph
, a se vedea Carter Butts, „network: A Package for Managing Relational Data in R”, Journal of Statistical Software, 24 (2008): 1-36 ︎ -
Aceasta este structura specifică așteptată de
visNetwork
, fiind în același timp conformă cu așteptările generale ale celorlalte pachete. ︎ -
Aceasta este ordinea așteptată pentru coloane pentru unele dintre pachetele de rețea pe care le voi folosi mai jos. ︎
-
ungroup()
nu este strict necesar în acest caz. Cu toate acestea, dacă nu degrupați cadrul de date, nu este posibil să renunțați la coloanele „sursă” și „destinație”, așa cum fac eu mai târziu în script. ︎ -
Thomas M. J. Fruchterman și Edward M. Reingold, „Graph Drawing by Force-Directed Placement,” Software: Practice and Experience, 21 (1991): 1129-1164. ︎
-
Funcția
rm()
este utilă dacă mediul dumneavoastră de lucru în R se dezorganizează, dar nu doriți să ștergeți întregul mediu și să o luați de la capăt. ︎ -
Relația dintre obiectele
tbl_graph
șiigraph
este similară cu cea dintre obiecteletibble
șidata.frame
. ︎ -
Este posibil ca
ggraph
să deseneze săgeți, dar nu am arătat acest lucru aici. ︎ -
Poate dura puțin timp pentru ca vârful instrumentului să apară. ︎
Lasă un răspuns