Introduction to Network Analysis with R
On 4 marraskuun, 2021 by adminVerkostoanalyysistä on tullut yhä suositumpi työkalu, jonka avulla tutkijat voivat käsitellä erilaisten toimijoiden välisten suhteiden monimutkaisuutta monilla eri aloilla. Verkostoanalyysin lupaus on merkityksen antaminen toimijoiden välisille suhteille sen sijaan, että toimijat nähtäisiin erillisinä kokonaisuuksina. Monimutkaisuuden korostaminen sekä erilaisten algoritmien luominen verkostojen eri näkökohtien mittaamiseen tekevät verkostoanalyysistä keskeisen työkalun digitaalisille humanistisille tieteille.1 Tässä kirjoituksessa esitellään verkostojen kanssa työskentelyä R:ssä käyttäen esimerkkinä Daniel van der Meulenin kirjeenvaihdon kaupunkiverkostoa vuodelta 1585.
Verkostoanalyysiin ja verkostograafien luontiin on kehitetty useita sovelluksia, kuten gephi ja cytoscape. Vaikka R:ää ei olekaan erityisesti suunniteltu sitä varten, siitä on kehittynyt tehokas työkalu verkostoanalyysiin. R:n vahvuus verrattuna erillisiin verkkoanalyysiohjelmistoihin on kolminkertainen. Ensinnäkin R mahdollistaa toistettavan tutkimuksen, joka ei ole mahdollista GUI-sovelluksilla. Toiseksi R:n data-analyysin teho tarjoaa vankat työkalut datan käsittelyyn, jotta se voidaan valmistella verkostoanalyysiä varten. Lisäksi on olemassa jatkuvasti kasvava valikoima paketteja, jotka on suunniteltu tekemään R:stä täydellinen verkkoanalyysityökalu. Merkittäviä R:n verkkoanalyysipaketteja ovat statnet-pakettisarja ja igraph
. Lisäksi Thomas Lin Pedersen on hiljattain julkaissut tidygraph
– ja ggraph
-paketit, jotka hyödyntävät igraph
:n tehoa tidyverse-työnkulun mukaisella tavalla. R:ää voidaan käyttää myös interaktiivisten verkkograafien tekemiseen htmlwidgets-kehyksen avulla, joka kääntää R-koodin JavaScriptiksi.
Tämä viesti alkaa lyhyellä johdatuksella verkkoanalyysin perussanastoon, minkä jälkeen keskustellaan prosessista, jonka avulla data saadaan verkkoanalyysin kannalta oikeaan rakenteeseen. Verkkoanalyysipaketit ovat kaikki toteuttaneet omat objektiluokkansa. Tässä postauksessa näytän, miten luodaan erityiset objektiluokat statnet-pakettikokonaisuudelle network
-paketilla sekä igraph
– ja tidygraph
-paketille, joka perustuu igraph
-toteutukseen. Lopuksi siirryn interaktiivisten graafien luomiseen vizNetwork
– ja networkD3
-paketeilla.
Verkkoanalyysi: Solmut ja särmät
Verkkojen kaksi pääasiallista piirrettä ovat joukko erillisiä kokonaisuuksia ja niiden väliset yhteydet. Sanasto voi olla hieman teknistä ja jopa epäjohdonmukaista eri tieteenalojen, pakettien ja ohjelmistojen välillä. Entiteettejä kutsutaan graafin solmuiksi tai kärkipisteiksi, kun taas yhteyksiä kutsutaan reunoiksi tai linkeiksi. Tässä kirjoituksessa käytän pääasiassa solmujen ja reunojen nimikkeistöä, paitsi käsitellessäni paketteja, jotka käyttävät erilaista sanastoa.
Verkkoanalyysipaketit tarvitsevat dataa tietyssä muodossa, jotta ne voivat luoda kunkin paketin käyttämän erityyppisen objektin. network
:n, igraph
:n ja tidygraph
:n objektiluokat perustuvat kaikki vierekkäisyysmatriiseihin, joita kutsutaan myös sosiomatriiseiksi.2 Vierekkäisyysmatriisi on neliömatriisi, jossa sarakkeiden ja rivien nimet ovat verkon solmuja. Matriisin sisällä 1 tarkoittaa, että solmujen välillä on yhteys, ja 0 tarkoittaa, että yhteyttä ei ole. Adjacency-matriisit ovat hyvin erilainen tietorakenne kuin datakehykset, eivätkä ne sovi tidyverse-työnkulkuun, jota olen käyttänyt aiemmissa viesteissäni. Onneksi erikoistuneita verkko-objekteja voidaan luoda myös reunalistatietokehyksistä, jotka sopivat tidyverse-työnkulkuun. Tässä postauksessa pitäydyn tidyverse:n data-analyysitekniikoissa luodakseni reunalistoja, jotka sitten muunnetaan erityisiin objektiluokkiin network
, igraph
ja tidygraph
.
Edustelista on datakehys, joka sisältää vähintään kaksi saraketta, yhden sarakkeen solmuista, jotka ovat yhteyden lähde, ja toisen sarakkeen solmuista, jotka ovat yhteyden kohde. Aineiston solmut tunnistetaan yksilöllisillä tunnuksilla. Jos lähteen ja kohteen välinen ero on mielekäs, verkko on suunnattu. Jos erottelu ei ole mielekäs, verkko on suuntaamaton. Esimerkissä kaupunkien välillä lähetetyistä kirjeistä lähteen ja kohteen välinen ero on selvästi mielekäs, joten verkko on suunnattu. Seuraavissa esimerkeissä nimitän lähdesaraketta ”from” ja kohdesaraketta ”to”. Käytän solmujen tunnuksina ykkösellä alkavia kokonaislukuja.3 Reunaluettelo voi sisältää myös muita sarakkeita, jotka kuvaavat reunojen ominaisuuksia, kuten reunan suuruusluokka. Jos reunoilla on suuruusattribuutti, graafia pidetään painotettuna.
Särmäluettelot sisältävät kaiken tarvittavan tiedon verkko-objektien luomiseksi, mutta joskus on parempi luoda myös erillinen solmuluettelo. Yksinkertaisimmillaan solmuluettelo on datakehys, jossa on yksi sarake – jota nimitän nimellä ”id” – joka listaa reunaluettelosta löytyvät solmujen tunnukset. Erillisen solmuluettelon luomisen etuna on mahdollisuus lisätä datakehykseen attribuuttisarakkeita, kuten solmujen nimiä tai minkäänlaisia ryhmittelyjä. Alla annan esimerkin tibble()
-funktiolla luoduista minimaalisista reuna- ja solmuluetteloista.
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
Vertaa tätä vierekkäisyysmatriisiin, jossa on samat tiedot.
#> 1 2 3 4#> 1 0 1 0 0#> 2 0 0 1 1#> 3 0 1 0 0#> 4 1 0 0 0
Reuna- ja solmuluetteloiden luominen
Luoakseni verkko-objekteja tietokannasta, joka sisältää Daniel van der Meulenin vuonna 1585 vastaanottamat kirjeet, teen sekä reuna- että solmuluettelon. Tämä edellyttää dplyr-paketin käyttöä Danielille lähetettyjen kirjeiden datakehyksen käsittelemiseksi ja sen jakamiseksi kahdeksi datakehykseksi tai tibbletiksi, joilla on reuna- ja solmulistojen rakenne. Tässä tapauksessa solmut ovat kaupunkeja, joista Danielin kirjeenvaihtajat lähettivät hänelle kirjeitä, ja kaupunkeja, joissa hän vastaanotti niitä. Solmuluettelo sisältää sarakkeen ”label”, joka sisältää kaupunkien nimet. Reunaluettelossa on myös attribuuttisarake, josta käy ilmi kunkin kaupunkiparin välillä lähetettyjen kirjeiden määrä. Työnkulku näiden objektien luomiseksi on samanlainen kuin se, jota olen käyttänyt lyhyessä johdannossani R:ään ja geokoodauksessa R:llä. Jos haluat seurata mukana, löydät tässä postauksessa käytetyt tiedot ja käytetyn R-skriptin GitHubista.
Ensin ladataan tidyverse
-kirjasto tietojen tuontia ja käsittelyä varten. letters
-tietokehyksen tulostaminen osoittaa, että se sisältää neljä saraketta: ”kirjoittaja”, ”lähde”, ”kohde” ja ”päivämäärä”. Tässä esimerkissä käsittelemme vain ”source”- ja ”destination”-sarakkeita.
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
Solmuluettelo
Solmuluettelon luomiseen käytettävä työnkulku on samankaltainen kuin se, jota käytin saadakseni luettelon kaupungeista datan geokoodaamista varten aiemmassa viestissä. Haluamme saada erilliset kaupungit sekä ”lähde”- että ”kohde”-sarakkeista ja yhdistää sitten näiden sarakkeiden tiedot toisiinsa. Alla olevassa esimerkissä muutan hieman komentoja edellisessä viestissä käyttämistäni komennoista siten, että kaupunkien nimiä sisältävien sarakkeiden nimet ovat samat sekä sources
– että destinations
-tietokehyksissä full_join()
-funktion yksinkertaistamiseksi. Nimeän kaupunkien nimiä sisältävän sarakkeen uudelleen nimellä ”label” ottaakseni käyttöön verkkoanalyysipakettien käyttämän sanaston.
sources <- letters %>% distinct(source) %>% rename(label = source)destinations <- letters %>% distinct(destination) %>% rename(label = destination)
Luodaksemme yhden ainoan datakehyksen, jossa on sarake, jossa on yksilölliset sijainnit, meidän on käytettävä täyttä liitosta, koska haluamme sisällyttää kaikki yksilölliset sijainnit sekä kirjelähteistä että -kohteista.
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
Tuloksena on datakehys, jossa on yksi muuttuja. Tietokehyksen sisältämä muuttuja ei kuitenkaan ole oikeastaan se, mitä etsimme. Sarake ”label” sisältää solmujen nimet, mutta haluamme myös yksilölliset tunnukset jokaiselle kaupungille. Voimme tehdä tämän lisäämällä nodes
-tietokehykseen ”id”-sarakkeen, joka sisältää numeroita yhdestä siihen, mikä on tietokehyksen rivien kokonaismäärä. Tässä työvaiheessa hyödyllinen toiminto on rowid_to_column()
, joka lisää sarakkeen, jossa on rivien id-arvot, ja sijoittaa sarakkeen datakehyksen alkuun.4 Huomaa, että rowid_to_column()
on putkitettava komento, joten on mahdollista tehdä full_join()
ja lisätä ”id”-sarake yhdellä komennolla. Tuloksena on solmuluettelo, jossa on ID-sarake ja label-attribuutti.
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
REUNALUETTELO
Edgeluettelon luominen on samanlaista kuin edellä, mutta sitä mutkistaa tarve käsitellä kahta ID-saraketta yhden sijasta. Haluamme myös luoda painosarakkeen, johon merkitään kunkin solmupistejoukon välillä lähetettyjen kirjeiden määrä. Tämän toteuttamiseksi käytän samaa group_by()
– ja summarise()
-työnkulkua, jota olen käsitellyt aiemmissa viesteissä. Erona tässä on se, että haluamme ryhmitellä datakehyksen kahden sarakkeen – ”lähde” ja ”kohde” – mukaan yhden sarakkeen sijaan. Aiemmin olen nimennyt sarakkeen, joka laskee havaintojen määrän ryhmää kohti, ”count”, mutta tässä otan käyttöön verkostoanalyysin nimikkeistön ja kutsun sitä ”weight”. Viimeinen komento putkessa poistaa group_by()
-funktiolla käyttöön otetun datakehyksen ryhmittelyn. Tämä helpottaa tuloksena syntyvän per_route
-tietokehyksen esteetöntä käsittelyä. 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
Solmuluettelon tavoin per_route
on nyt haluamassamme perusmuodossa, mutta ongelmana on jälleen se, että ”lähde”- ja ”määränpää”-sarakkeet sisältävät merkintöjä eikä tunnuksia. Meidän on yhdistettävä nodes
:ssä määritetyt tunnukset kuhunkin paikkaan sekä ”lähde”- että ”kohde”-sarakkeissa. Tämä voidaan toteuttaa toisella join-funktiolla. Itse asiassa on tarpeen suorittaa kaksi join-funktiota, yksi ”lähde”-sarakkeelle ja yksi ”kohde”-sarakkeelle. Tässä tapauksessa käytän left_join()
:tä ja per_route
:tä vasemmanpuoleisena datakehyksenä, koska haluamme säilyttää per_route
:n rivien määrän. Kun teemme left_join
:n, haluamme myös nimetä uudelleen kaksi ”id”-saraketta, jotka tuodaan nodes
:sta. ”Lähde”-saraketta käyttävää liitosta varten nimeän sarakkeen uudelleen ”from”-sarakkeeksi. ”Kohde”-liitoksesta tuotu sarake nimetään uudelleen ”to”. Molemmat joinit olisi mahdollista tehdä yhdellä komennolla putken avulla. Selkeyden vuoksi suoritan kuitenkin yhdistämiset kahdessa erillisessä komennossa. Koska liitos tehdään kahdessa komennossa, huomaa, että putken alussa oleva datakehys muuttuu per_route
:stä edges
:ksi, joka on luotu ensimmäisellä komennolla.
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)
Nyt kun edges
:ssä on ”lähtöpaikka”- ja ”kohde”-sarakkeet, joissa on solmun ID:t, sarakkeet on järjestettävä uudelleen, jotta sarakkeet ”lähtöpaikka” ja ”kohde” saadaan datakehyksen vasempaan reunaan. Tällä hetkellä edges
-tietokehys sisältää edelleen ”lähde”- ja ”kohde”-sarakkeet, joissa on tunnuksia vastaavat kaupunkien nimet. Nämä tiedot ovat kuitenkin tarpeettomia, koska ne ovat jo nodes
:ssä. Siksi sisällytän vain ”lähtöpaikka”-, ”kohde”- ja ”paino”-sarakkeet select()
-funktioon.
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
edges
-tietokehys ei näytä kovin vaikuttavalta; se koostuu kolmesta kokonaislukusarakkeesta. Kuitenkin edges
yhdessä nodes
:n kanssa antaa meille kaikki tarvittavat tiedot verkko-objektien luomiseksi network
-, igraph
– ja tidygraph
-paketeilla.
Verkko-objektien luominen
Verkko-objektien luokat network
, igraph
ja tidygraph
liittyvät kaikki läheisesti yhteen. On mahdollista kääntää network
-objektin ja igraph
-objektin välillä. On kuitenkin parasta pitää nämä kaksi pakettia ja niiden objektit erillään. Itse asiassa network
:n ja igraph
:n ominaisuudet ovat siinä määrin päällekkäisiä, että paras käytäntö on, että vain toinen paketeista on ladattuna kerrallaan. Käyn ensin läpi network
-paketin ja siirryn sitten igraph
– ja tidygraph
-paketteihin.
verkko
library(network)
network
-objektin luomiseen käytettävä funktio on network()
. Komento ei ole erityisen suoraviivainen, mutta voit aina kirjoittaa konsoliin ?network()
, jos olet hämmentynyt. Ensimmäinen argumentti on – kuten dokumentaatiossa sanotaan – ”matriisi, joka antaa verkon rakenteen vierekkäisyys-, insidenssi- tai reunalistamuodossa”. Kieli osoittaa matriisien merkityksen verkkoanalyysissä, mutta matriisin sijasta meillä on reunaluettelo, joka täyttää saman roolin. Toinen argumentti on luettelo vertex-attribuuteista, joka vastaa solmuluetteloa. Huomaa, että network
-paketissa käytetään solmujen sijasta nimikkeistöä kärkipisteet. Sama pätee myös igraph
-pakettiin. Tämän jälkeen meidän on määritettävä kahteen ensimmäiseen argumenttiin syötetyn datan tyyppi määrittelemällä, että matrix.type
on "edgelist"
. Lopuksi asetamme ignore.eval
:n FALSE
:ksi, jotta verkkomme voi olla painotettu ja ottaa huomioon kunkin reitin varrella olevien kirjainten määrän.
routes_network <- network(edges, vertex.attr = nodes, matrix.type = "edgelist", ignore.eval = FALSE)
Funktion network()
luoman objektin tyypin näet sijoittamalla routes_network
funktioon class()
.
class(routes_network)#> "network"
Tulostamalla routes_network
konsoliin nähdään, että objektin rakenne on aivan erilainen kuin datakehystyylisten objektien kuten edges
ja nodes
. Tulostuskomento paljastaa tietoja, jotka on määritelty erityisesti verkkoanalyysiä varten. Se osoittaa, että routes_network
:ssa on 13 kärkeä tai solmua ja 15 reunaa. Nämä luvut vastaavat nodes
:n ja edges
:n rivien lukumäärää. Näemme myös, että sekä verteksit että reunat sisältävät attribuutteja, kuten label ja weight. Voit saada vielä enemmän tietoa, mukaan lukien aineiston sosiomatriisin, syöttämällä 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
Sen jälkeen on mahdollista saada alkeellinen, joskaan ei liian esteettisesti miellyttävä graafi kirjainverkostamme. Sekä network
– että igraph
-paketit käyttävät R:n perusplotteja. Perusplottien konventiot eroavat huomattavasti ggplot2:n konventioista – joita olen käsitellyt aiemmissa viesteissä – joten pitäydyn melko yksinkertaisissa ploteissa sen sijaan, että perehtyisin monimutkaisten plottien luomisen yksityiskohtiin R:n perusplotilla. Tässä tapauksessa ainoa muutos, jonka teen network
-paketin oletusarvona olevaan plot()
-funktioon, on se, että suurennan solmujen kokoa argumentilla vertex.cex
solmujen näkyvyyden parantamiseksi. Jo tällä hyvin yksinkertaisella kuvaajalla voimme oppia jotain datasta. Kuvaajasta käy selvästi ilmi, että datassa on kaksi pääryhmittymää tai klusteria, jotka vastaavat aikaa, jonka Daniel vietti Hollannissa vuoden 1585 kolmen ensimmäisen neljänneksen aikana ja sen jälkeen, kun hän muutti Bremeniin syyskuussa.
plot(routes_network, vertex.cex = 3)
plot()
-funktio, jossa on network
-olio network
-olion kanssa, käyttää Solmujen sijoittelun päättämiseen Fruchtermanin ja Reingoldin algoritmia.6 Voit vaihtaa asettelualgoritmia mode
-argumentilla. Alla asettelen solmut ympyrän muotoon. Tämä ei ole erityisen hyödyllinen asettelu tälle verkolle, mutta se antaa käsityksen joistakin käytettävissä olevista vaihtoehdoista.
plot(routes_network, vertex.cex = 3, mode = "circle")
igraph
Keskustellaan nyt igraph
-paketista. Ensin meidän on siivottava R:n ympäristö poistamalla network
-paketti, jotta se ei häiritse igraph
-komentoja. Voimme yhtä hyvin poistaa myös routes_network
, koska emme enää käytä sitä. network
-paketti voidaan poistaa detach()
-funktiolla, ja routes_network
poistetaan rm()
:llä.7 Tämän jälkeen voimme turvallisesti ladata igraph
:n.
detach(package:network)rm(routes_network)library(igraph)
Luodaaksemme igraph
-olion reunalistatietokehyksestä voimme käyttää graph_from_data_frame()
-funktiota, joka on hieman suoraviivaisempi kuin network()
. Funktiossa graph_from_data_frame()
on kolme argumenttia: d, vertices ja directed. Tässä d viittaa reunaluetteloon, vertices solmuluetteloon ja directed voi olla joko TRUE
tai FALSE
riippuen siitä, onko data suunnattua vai suuntaamatonta.
routes_igraph <- graph_from_data_frame(d = edges, vertices = nodes, directed = TRUE)
Tulostamalla graph_from_data_frame()
:llä luodun igraph
-olion konsoliin saadaan samanlaista tietoa kuin network
-oliosta, vaikkakin rakenne on salaperäisempi.
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
Objektin tärkeimmät tiedot ovat DNW- 13 15 --
:ssä. Tämä kertoo, että routes_igraph
on suunnattu verkko (D), jolla on nimiattribuutti (N) ja joka on painotettu (W). Viiva W:n jälkeen kertoo, että graafi ei ole kaksijakoinen. Seuraavat numerot kuvaavat graafin solmujen ja särmien lukumäärää. Seuraavaksi name (v/c), label (v/c), weight (e/n)
antaa tietoa graafin attribuuteista. Siinä on kaksi vertex-attribuuttia (v/c), jotka ovat nimi – eli tunnukset – ja merkinnät, sekä edge-attribuutti (e/n), joka on paino. Lopuksi on tuloste kaikista reunoista.
Aivan kuten network
-paketissa, voimme luoda kuvaajan igraph
-objektilla plot()
-funktion avulla. Ainoa muutos, jonka teen tässä oletukseen, on nuolien koon pienentäminen. Oletusarvoisesti igraph
merkitsee solmut label-sarakkeella, jos sellainen on, tai tunnuksilla.
plot(routes_igraph, edge.arrow.size = 0.2)
Kuten aiemminkin network
-kuvaajassa, igraph
-diagrammi ei ole oletusarvoisesti mitenkään erityisen esteettisesti miellyttävä, mutta kaikkia osa-alueitaan voi muokata. Tässä haluan vain muuttaa solmujen asettelun käyttämään Michael Schmuhlin luomaa graphopt-algoritmia. Tämä algoritmi helpottaa Haarlemin, Antwerpenin ja Delftin, jotka ovat kolme merkittävintä paikkaa kirjeenvaihtoverkossa, välisen suhteen hahmottamista levittämällä ne laajemmalle.
plot(routes_igraph, layout = layout_with_graphopt, edge.arrow.size = 0.2)
tidygraph ja ggraph
Tidygraph- ja ggraph-
paketit tidygraph
ja ggraph
ovat uusia tulokkaita verkkoanalyysin maisemassa, mutta yhdessä nämä kaksi pakettia tarjoavat todellisia etuja verrattuna paketteihin network
ja igraph
. tidygraph
ja ggraph
edustavat yritystä tuoda verkkoanalyysi osaksi tidyverse-työnkulkua. tidygraph
tarjoaa tavan luoda verkko-objekti, joka muistuttaa enemmän tibbleä tai datakehystä. Tämä mahdollistaa monien dplyr
-funktioiden käyttämisen verkkodatan käsittelyyn. ggraph
antaa tavan piirtää verkkokaavioita käyttäen ggplot2
:n konventioita ja tehoa. Toisin sanoen tidygraph
ja ggraph
mahdollistavat verkko-objektien käsittelyn tavalla, joka on johdonmukaisempi tibbeleiden ja datakehysten käsittelyyn käytettävien komentojen kanssa. tidygraph
:n ja ggraph
:n todellinen lupaus on kuitenkin se, että ne hyödyntävät igraph
:n tehoa. Tämä tarkoittaa, että uhraat vain vähän igraph
:n verkkoanalyysiominaisuuksista käyttämällä tidygraph
:tä ja ggraph
:tä.
Aloitetaan kuten aina lataamalla tarvittavat paketit.
library(tidygraph)library(ggraph)
Luotaan ensin tidygraph
:n avulla verkko-objekti, jota kutsutaan tbl_graph
:ksi. tbl_graph
koostuu kahdesta tibblestä: edge-tibblestä ja nodes-tibblestä. Kätevästi tbl_graph
-objektiluokka on kääre igraph
-objektin ympärillä, mikä tarkoittaa, että pohjimmiltaan tbl_graph
-objekti on pohjimmiltaan igraph
-objekti.8 tbl_graph
– ja igraph
-objektien läheinen yhteys johtaa siihen, että on kaksi pääasiallista tapaa luoda tbl_graph
-objekti. Ensimmäinen on käyttää reunaluetteloa ja solmuluetteloa käyttäen tbl_graph()
. Funktion argumentit ovat lähes identtiset graph_from_data_frame()
:n kanssa, vain argumenttien nimiä on hieman muutettu.
routes_tidy <- tbl_graph(nodes = nodes, edges = edges, directed = TRUE)
Toinen tapa luoda tbl_graph
-objekti on muuntaa igraph
– tai network
-objekti käyttäen as_tbl_graph()
. Voisimme siis muuntaa routes_igraph
:n tbl_graph
-objektiksi.
routes_igraph_tidy <- as_tbl_graph(routes_igraph)
Nyt kun olemme luoneet kaksi tbl_graph
-objektia, tarkastellaan niitä class()
-funktiolla. Tämä osoittaa, että routes_tidy
ja routes_igraph_tidy
ovat luokan "tbl_graph" "igraph"
objekteja, kun taas routes_igraph
on objektiluokan "igraph"
objekti.
class(routes_tidy)#> "tbl_graph" "igraph"class(routes_igraph_tidy)#> "tbl_graph" "igraph"class(routes_igraph)#> "igraph"
Tulostamalla tbl_graph
-objektin konsoliin saadaan huomattavasti erilainen tuloste kuin igraph
-objektin. Se on samanlainen tuloste kuin tavallisella tibblellä.
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
Tulostamalla routes_tidy
nähdään, että se on tbl_graph
-objekti, jossa on 13 solmua ja 15 reunaa. Komento tulostaa myös kuusi ensimmäistä riviä ”Node Data” ja kolme ensimmäistä riviä ”Edge Data”. Huomaa myös, että se ilmoittaa, että Node Data on aktiivinen. Aktiivisen solmun käsite tbl_graph
-objektissa mahdollistaa yhden solmun tietojen käsittelyn kerrallaan. Solmujen tibble on oletusarvoisesti aktivoitu, mutta voit muuttaa, mikä tibble on aktiivinen activate()
-funktiolla. Jos siis halusin järjestää reunojen tibblen rivit uudelleen siten, että ensin luetellaan ne, joilla on suurin ”painoarvo”, voisin käyttää activate()
– ja sitten arrange()
-toimintoa. Tässä yksinkertaisesti tulostan tuloksen sen sijaan, että tallentaisin sen.
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
Koska meidän ei tarvitse enää käsitellä routes_tidy
:tä, voimme piirtää kuvaajan ggraph
:llä. Kuten ggmap, myös ggraph
on ggplot2
:n laajennus, mikä helpottaa ggplot
:n perustaitojen siirtämistä verkkokuvausten luomiseen. Kuten kaikissa verkkograafeissa, ggraph
-plotissa on kolme pääasiallista näkökohtaa: solmut, reunat ja asettelut. ggraph-paketin vinjetit käsittelevät hyödyllisellä tavalla ggraph
-plottien perusasioita. ggraph
lisää ggplot
-geomien perusjoukkoon erityisiä geomeja, jotka on suunniteltu erityisesti verkostoja varten. Näin ollen on olemassa joukko geom_node
– ja geom_edge
-geomeja. Perusplottifunktio on ggraph()
, joka ottaa vastaan kuvaajassa käytettävän datan ja halutun asettelutyypin. Molemmat ggraph()
:n argumentit rakentuvat igraph
:n ympärille. Siksi ggraph()
voi käyttää joko igraph
-objektia tai tbl_graph
-objektia. Lisäksi käytettävissä olevat asettelualgoritmit johdetaan ensisijaisesti igraph
:sta. Lopuksi ggraph
esittelee erityisen ggplot
-teeman, joka tarjoaa paremmat oletusasetukset verkkograafeille kuin normaalit ggplot
-oletukset. ggraph
-teema voidaan asettaa sarjalle kuvaajia komennolla set_graph_style()
, joka suoritetaan ennen kuvaajien piirtämistä, tai käyttämällä theme_graph()
yksittäisissä kuvaajissa. Tässä käytän jälkimmäistä menetelmää.
Katsotaanpa, miltä perus ggraph
-plotti näyttää. Plotti alkaa ggraph()
:llä ja datalla. Tämän jälkeen lisään perusreuna- ja solmugeomit. Reuna- ja solmugeomien sisällä ei tarvita argumentteja, koska ne ottavat tiedot ggraph()
:ssä annetuista tiedoista.
ggraph(routes_tidy) + geom_edge_link() + geom_node_point() + theme_graph()
Kuten huomaatte, komennon rakenne on samanlainen kuin ggplot
:ssä, jossa on erilliset kerrokset lisätty +
-merkillä. ggraph
:n perusdiagrammi näyttää samankaltaiselta kuin network
:n ja igraph
:n, ellei jopa yksinkertaisemmalta, mutta voimme käyttää samankaltaisia komentoja kuin ggplot
:n luodaksemme informatiivisemman kuvaajan. Voimme näyttää reunojen ”painon” – tai kutakin reittiä pitkin lähetettyjen kirjeiden määrän – käyttämällä leveyttä geom_edge_link()
-funktiossa. Jotta viivan leveys muuttuisi weight-muuttujan mukaan, sijoitamme argumentin aes()
-funktion sisään. Reunojen enimmäis- ja vähimmäisleveyden hallitsemiseksi käytän scale_edge_width()
ja asetan range
. Valitsen minimileveydeksi suhteellisen pienen leveyden, koska reittejä pitkin lähetettävien kirjainten maksimi- ja minimimäärän välillä on huomattava ero. Voimme myös merkitä solmut paikkakuntien nimillä, koska solmuja on suhteellisen vähän. Kätevästi geom_node_text()
:n mukana tulee repel-argumentti, joka varmistaa, että tarrat eivät mene solmujen kanssa päällekkäin ggrepel-paketin tapaan. Lisään reunoihin hieman läpinäkyvyyttä argumentilla alpha. Käytän myös labs()
:a legendan ”Letters” uudelleenmerkitsemiseen.
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()
igraph
:n tarjoamien asetteluvaihtoehtojen lisäksi ggraph
toteuttaa myös omat asettelunsa. Voit esimerkiksi käyttää ggraph's
:n ympyrämäisyyden käsitettä kaarikaavioiden luomiseen. Tässä asettelen solmut vaakasuoraan riviin ja annan reunojen piirtyä kaarina. Toisin kuin edellisessä kuvaajassa, tässä kuvaajassa ilmoitetaan reunojen suuntaisuus.9 Vaakaviivan yläpuolella olevat reunat liikkuvat vasemmalta oikealle, kun taas viivan alapuolella olevat reunat liikkuvat oikealta vasemmalle. Sen sijaan, että olisin lisännyt pisteitä solmuja varten, lisään vain etikettien nimet. Käytän samaa leveyttä esteettisenä merkitsemään kunkin reunan painoeroa. Huomaa, että tässä kuvaajassa käytän kuvaajan datana igraph
-objektia, millä ei ole käytännön merkitystä.
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()
Interaktiiviset verkkograafit visNetworkin ja networkD3:n avulla
Htmlwidgets-pakettikokonaisuus mahdollistaa R:n käyttämisen vuorovaikutteisten JavaScripthavi-visualisointien luomiseen. Tässä näytän, miten grafiikoita tehdään visNetwork
– ja networkD3
-paketeilla. Nämä kaksi pakettia käyttävät eri JavaScript-kirjastoja graafiensa luomiseen. visNetwork
käyttää vis.js:ää, kun taas networkD3
käyttää suosittua d3-visualisointikirjastoa kuvaajiensa tekemiseen. Yksi vaikeus sekä visNetwork
:n että networkD3
:n kanssa työskentelyssä on se, että ne odottavat reunalistojen ja solmulistojen käyttävän tiettyä nimikkeistöä. Yllä oleva tietojen manipulointi vastaa visNetwork
:n perusrakennetta, mutta networkD3
:n osalta on tehtävä jonkin verran työtä. Tästä hankaluudesta huolimatta molemmilla paketeilla on laaja valikoima graafinmuodostusominaisuuksia, ja molemmat voivat työskennellä igraph
-objektien ja asettelujen kanssa.
library(visNetwork)library(networkD3)
visNetwork
Funktio visNetwork()
käyttää solmu- ja reunaluetteloa interaktiivisen graafin luomiseen. Solmujen luettelossa on oltava ”id”-sarake, ja reunaluettelossa on oltava ”from”- ja ”to”-sarakkeet. Funktio piirtää myös solmujen merkinnät käyttäen kaupunkien nimiä solmuluettelon ”label”-sarakkeesta. Tuloksena syntyvällä kuvaajalla on hauska leikkiä. Voit siirtää solmuja, ja graafi käyttää algoritmia pitääkseen solmut oikeassa etäisyydessä toisistaan. Voit myös suurentaa ja pienentää kuvaajaa ja siirtää sitä keskittääksesi sen uudelleen.
visNetwork(nodes, edges)
visNetwork
voi käyttää igraph
-asetteluja, mikä tarjoaa suuren valikoiman mahdollisia asetteluja. Lisäksi voit käyttää visIgraph()
:ää igraph
-objektin piirtämiseen suoraan. Tässä pitäydyn nodes
– ja edges
-työnkulussa ja käytän igraph
-asettelua kuvaajan mukauttamiseen. Lisään myös muuttujan reunan leveyden muuttamiseksi, kuten teimme ggraph
:n kanssa. visNetwork()
käyttää särmä- ja solmuluetteloiden sarakkeiden nimiä verkkoattribuuttien piirtämiseen funktiokutsussa olevien argumenttien sijasta. Tämä tarkoittaa, että on tehtävä jonkin verran datan manipulointia, jotta reunaluettelosta saadaan ”leveys”-sarake. visNetwork()
:n width-attribuutti ei skaalaa arvoja, joten tämä on tehtävä manuaalisesti. Molemmat toimenpiteet voidaan tehdä mutate()
-funktiolla ja yksinkertaisella aritmeettisella laskutoimituksella. Tässä luon uuden sarakkeen edges
ja skaalaan painoarvot jakamalla ne luvulla 5. Lisäämällä tulokseen 1 saadaan aikaan minimileveys.
edges <- mutate(edges, width = weight/5 + 1)
Kun tämä on tehty, voimme luoda kuvaajan, jossa reunojen leveydet vaihtelevat. Valitsen myös asettelualgoritmin igraph
ja lisään reunoihin nuolet sijoittamalla ne reunan keskelle.
visNetwork(nodes, edges) %>% visIgraphLayout(layout = "layout_with_fr") %>% visEdges(arrows = "middle")
networkD3
Hieman lisätyötä tarvitaan datan valmistelemiseksi networkD3
-graafin luomista varten. networkD3
-graafin tekeminen särmä- ja solmuluettelon avulla edellyttää, että ID:t ovat sarja numeerisia kokonaislukuja, jotka alkavat 0:lla. Tällä hetkellä aineistomme solmujen ID:t alkavat 1:llä, joten joudumme tekemään hieman datan manipulointia. Solmut on mahdollista numeroida uudelleen vähentämällä 1 ID-sarakkeista nodes
– ja edges
-tietokehyksissä. Tämäkin voidaan tehdä mutate()
-funktiolla. Tavoitteena on luoda nykyiset sarakkeet uudelleen vähentämällä 1 jokaisesta ID:stä. mutate()
-funktio toimii luomalla uuden sarakkeen, mutta voimme antaa sen korvata sarakkeen antamalla uudelle sarakkeelle saman nimen kuin vanhalle sarakkeelle. Tässä nimeän uudet tietoruudut d3-päätteellä, jotta ne erottuvat aiemmista nodes
– ja edges
-tietoruuduista.
nodes_d3 <- mutate(nodes, id = id - 1)edges_d3 <- mutate(edges, from = from - 1, to = to - 1)
Nyt on mahdollista piirtää networkD3
-kuvaaja. Toisin kuin visNetwork()
, forceNetwork()
-funktio käyttää sarjaa argumentteja kuvaajan ja piirrosverkon ominaisuuksien säätämiseen. ”Linkit”- ja ”Solmut”-argumentit antavat tiedot piirtoa varten reuna- ja solmuluetteloiden muodossa. Funktio vaatii myös ”NodeID”- ja ”Group”-argumentit. Tässä käytetyissä tiedoissa ei ole ryhmittelyjä, joten jokainen solmu on vain oma ryhmänsä, mikä käytännössä tarkoittaa, että kaikki solmut ovat erivärisiä. Lisäksi alla oleva kertoo funktiolle, että verkolla on ”Source”- ja ”Target”-kentät ja että se on siten suunnattu. Sisällytän tähän kuvaajaan ”Value”, joka skaalaa reunojen leveyden reunaluettelon ”weight”-sarakkeen mukaan. Lopuksi lisään joitakin esteettisiä parannuksia, jotta solmut olisivat läpinäkymättömiä ja jotta merkintöjen fonttikoko olisi suurempi luettavuuden parantamiseksi. Tulos on hyvin samankaltainen kuin ensimmäinen luomani visNetwork()
-diagrammi, mutta erilaisilla esteettisillä tyyleillä.
forceNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Group = "id", Value = "weight", opacity = 1, fontSize = 16, zoom = TRUE)
Yksi networkD3
:n tärkeimmistä eduista on, että se toteuttaa d3-tyylisen Sankey-diagrammin. Sankey-diagrammi sopii hyvin Danielille vuonna 1585 lähetettyihin kirjeisiin. Aineistossa ei ole liikaa solmuja, mikä helpottaa kirjeiden kulun visualisointia. Sankey-diagrammin luomisessa käytetään sankeyNetwork()
-funktiota, joka ottaa monia samoja argumentteja kuin forceNetwork()
. Tämä kaavio ei vaadi ryhmäargumenttia, ja ainoa muu muutos on ”yksikön” lisääminen. Tämä antaa merkinnän arvoille, jotka ponnahtavat esiin työkaluvihjeessä, kun kursori viedään kaavion elementin päälle. 10
sankeyNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Value = "weight", fontSize = 16, unit = "Letter(s)")
Lisälukemista verkostoanalyysistä
Tässä postauksessa on pyritty antamaan yleinen johdatus verkostotyyppisten objektien luontiin ja piirtämiseen R:ssä käyttäen network
-, igraph
-, tidygraph
-, tidygraph
– ja ggraph
-ohjelmistopaketteja staattisiin piirtokuvioihin, ja visNetwork
:tä sekä networkD3
-pakettia interaktiivisiin piirroksin. Olen esittänyt nämä tiedot verkostoteoriaan perehtymättömän henkilön näkökulmasta. Olen käsitellyt vain hyvin pienen osan R:n verkkoanalyysimahdollisuuksista. Erityisesti en ole käsitellyt verkkojen tilastollista analyysia. Onneksi verkkoanalyysistä yleensä ja erityisesti R:llä on tarjolla runsaasti resursseja.
Paras löytämäni johdatus verkkoihin aloittelemattomille on Katya Ognyanovan Network Visualization with R. Se tarjoaa sekä hyödyllisen johdannon verkkojen visuaalisiin näkökohtiin että syvällisemmän oppikirjan verkostokuvioiden luomisesta R:llä. Ognyanova käyttää ensisijaisesti mallia igraph
, mutta esittelee myös interaktiivisia verkostoja.
Springerin kustantamana on ilmestynyt kaksi verrattain hiljattain ilmestynyttä kirjaa verkkoanalyyseistä R:n avulla. Douglas A. Luke, A User’s Guide to Network Analysis in R (2015) on erittäin hyödyllinen johdatus verkkoanalyysiin R:llä. Luke käsittelee sekä statnet-pakettien pukua että igragh
. Sisältö on kauttaaltaan hyvin lähestyttävällä tasolla. Edistyneempi on Eric D. Kolaczykin ja Gábor Csárdin teos Statistical Analysis of Network Data with R (2014). Kolaczykin ja Csárdin kirjassa käytetään pääasiassa igraph
, sillä Csárdi on R:n igraph
-paketin ensisijainen ylläpitäjä. Tässä kirjassa päästään syvemmälle verkostojen tilastollisen analyysin edistyneisiin aiheisiin. Huolimatta hyvin teknisestä kielenkäytöstä neljä ensimmäistä lukua ovat yleisesti ottaen lähestyttäviä ei-asiantuntijan näkökulmasta.
François Briatten kuratoima lista on hyvä yleiskatsaus verkkoanalyysin resursseista yleensä. Myös Scott Weingartin julkaisema Networks Demystified -sarja on tutustumisen arvoinen.
-
Yksi esimerkki verkostoanalyysin herättämästä kiinnostuksesta digitaalisissa humanistisissa tieteissä on vastikään perustettu Journal of Historical Network Research. ︎
-
Hyvän kuvauksen
network
-objektiluokasta, mukaan lukien keskustelun sen suhteestaigraph
-objektiluokkaan, tarjoaa Carter Butts, ”network: A Package for Managing Relational Data in R”, Journal of Statistical Software, 24 (2008): 1-36 ︎ -
Tämä on
visNetwork
:n odottama erityisrakenne, joka kuitenkin vastaa muiden pakettien yleisiä odotuksia. ︎ -
Tämä on joidenkin jäljempänä käyttämieni verkkopakettien sarakkeiden odotettu järjestys. ︎
-
ungroup()
ei ole ehdottoman tarpeellinen tässä tapauksessa. Jos datakehystä ei kuitenkaan poisteta ryhmittelyä, ei ole mahdollista poistaa ”lähde”- ja ”kohde”-sarakkeita, kuten teen myöhemmin skriptissä. ︎ -
Thomas M. J. Fruchterman ja Edward M. Reingold, ”Graph Drawing by Force-Directed Placement,” Software: Practice and Experience, 21 (1991): 1129-1164. ︎
-
Funktio
rm()
on hyödyllinen, jos työympäristösi R:ssä menee sekaisin, mutta et halua tyhjentää koko ympäristöä ja aloittaa alusta. ︎ -
Objektien
tbl_graph
jaigraph
välinen suhde on samanlainen kuin objektientibble
jadata.frame
välinen suhde. ︎ -
On mahdollista saada
ggraph
piirtämään nuolia, mutta en ole näyttänyt sitä tässä. ︎ -
Työkaluvihjeen näkyminen voi kestää jonkin aikaa. ︎
Vastaa