Verkostoanalyysistä on tullut yhä suositumpi apuväline tutkijoiden keskuudessa, kun he pyrkivät käsittelemään erilaisten toimijoiden välisten suhteiden monimutkaisuutta. 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ä postauksessa 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 verkkoanalyysiin. 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 oikeaan rakenteeseen verkkoanalyysia varten. 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. Objektiluokat network, igraph ja tidygraph 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ä nimetään lähdesarakkeeksi ”from” ja kohdesarakkeeksi ”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” – jossa luetellaan 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

Vertaile tätä samoilla tiedoilla olevaan vierekkäisyysmatriisiin.

#> 1 2 3 4#> 1 0 1 0 0#> 2 0 0 1 1#> 3 0 1 0 0#> 4 1 0 0 0

Särmä- ja solmuluetteloiden luominen

Luodakseni verkko-objekteja tietokannasta, joka sisältää Daniel van der Meulenin vuonna 1585 saamia kirjeitä, 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äytetyn datan ja käytetyn R-skriptin GitHubista.

Ensin ladataan tidyverse-kirjasto datan 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

Työnkulku solmuluettelon luomiseksi 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 edellisessä viestissä käyttämiäni komentoja 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 full_join() ja ”id”-sarakkeen lisääminen on mahdollista tehdä 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

Särmäluettelo

Särmäluettelon luominen on samankaltaista kuin edellä, mutta sitä mutkistaa tarve käsitellä kahta ID-saraketta yhden sijaan. 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:aa 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:sta 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 ”from”- ja ”to”-sarakkeet, joissa on solmun ID:t, sarakkeet on järjestettävä uudelleen, jotta sarakkeet ”from” (lähtöpaikka) ja ”to” (määräpaikka) 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 select()-funktioon vain ”lähtöpaikka”-, ”määräpaikka”- ja ”paino”-sarakkeet.

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 network, igraph ja tidygraph luokat 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)

Funktio, jota käytetään network-objektin luomiseen, 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 reunaluettelomuodossa”. 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 tiedon tyyppi määrittelemällä, että matrix.type on "edgelist". Lopuksi asetamme ignore.eval:n arvoksi FALSE, 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)

Voit nähdä network()-funktion luoman objektin tyypin asettamalla routes_network class()-funktioon.

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:ssä 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 perusplottien avulla. Tässä tapauksessa ainoa muutos, jonka teen oletusarvona olevaan plot()-funktioon paketissa network, on se, että suurennan solmupisteiden kokoa vertex.cex-argumentin avulla, jotta solmupisteistä tulisi paremmin näkyviä. Jopa tämän hyvin yksinkertaisen kuvaajan avulla voimme jo oppia jotain datasta. Kuvaajasta käy selvästi ilmi, että aineistossa 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)

Funktio plot(), jossa on network-objekti, käyttää Fruchtermanin ja Reingoldin algoritmia päättääkseen solmujen sijoittelusta.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 reunaluettelon datakehikosta 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 --:ssa. 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-diagrammi, igraph-diagrammin oletusarvoinen kuvaaja ei ole erityisen esteettisesti miellyttävä, mutta kaikkia piirroksen piirroksen osa-alueita voidaan 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

Tidygraph ja ggraph

Tidygraph ja ggraph

Paketit tidygraph ja ggraph ovat tulokkaita verkkoanalyysin maisemassa, mutta yhdessä nämä kaksi pakettia tuovat mukanaan todellista etua 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 kanssa työskentelyyn 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:a ja ggraph:a.

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 tbl_graph():n avulla. 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 tuloste, joka eroaa huomattavasti igraph-objektin tulosteesta. 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. tbl_graph-olion sisällä olevan aktiivisen tibbelin käsite mahdollistaa yhden tibbelin 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ä luetellaan ensin ne, joilla on suurin ”painoarvo”, voisin käyttää activate() ja sitten arrange(). Tässä tapauksessa tulostan yksinkertaisesti 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, 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 perustuvat ensisijaisesti igraph:ään. 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ää. Piirros 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 samankaltainen kuin komennossa ggplot, jossa erilliset kerrokset on lisätty +-merkillä. ggraph:n perusdiagrammi näyttää samanlaiselta kuin network:n ja igraph:n, ellei jopa yksinkertaisemmalta, mutta voimme käyttää ggplot:n kaltaisia komentoja luodaksemme informatiivisemman kuvaajan. Voimme näyttää reunojen ”painon” – tai kutakin reittiä pitkin lähetettyjen kirjeiden määrän – käyttämällä width-funktiota geom_edge_link(). 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():tä 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()

labs():n tarjoamien asetteluvaihtoehtojen lisäksi ggraph:ssä on toteutettu myös omia asetteluja. 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 eroa.

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 verkostograafit visNetworkilla ja networkD3:lla

Htmlwidgets-pakettikokonaisuus mahdollistaa R:n käyttämisen vuorovaikutteisten JavaScriptha-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öä. Edellä esitetty tietojen käsittely noudattaa visNetwork:n perusrakennetta, mutta networkD3:n osalta on tehtävä jonkin verran työtä. Tästä hankaluudesta huolimatta molemmilla paketeilla on monenlaisia 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. Solmuluettelossa 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ä ympäriinsä 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 tarpeen tehdä jonkin verran datan manipulointia saadaksemme ”leveys”-sarakkeen reunaluetteloon. 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 on muuttuvat reunojen leveydet. Valitsen myös asettelualgoritmin igraph:stä 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 reuna- 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 edellisistä nodes– ja edges-tietoruuduista.

nodes_d3 <- mutate(nodes, id = id - 1)edges_d3 <- mutate(edges, from = from - 1, to = to - 1)

Se on nyt 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– ja ggraph-paketteja staattisiin piirtokuvioihin sekä pakettipaketteja visNetwork ja networkD3 interaktiivisiin piirtokuviin. 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:ssä on tarjolla runsaasti resursseja.

Paras löytämäni johdatus verkkoihin aloittelemattomille on Katya Ognyanovan Network Visualization with R. Se on sekä hyödyllinen johdatus verkkojen visuaalisiin näkökohtiin että syvällisempi opetusohjelma verkkoesimerkkien luomisesta R:ssä. Ognyanova käyttää ensisijaisesti mallia igraph, mutta hän esittelee myös vuorovaikutteisia verkkoja.

Springer on julkaissut kaksi suhteellisen uutta kirjaa verkkoanalyysistä R:llä. Douglas A. Luke, A User’s Guide to Network Analysis in R (2015) on erittäin hyödyllinen johdatus verkostoanalyysiin 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, 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.

  1. Yksi esimerkki verkostoanalyysin herättämästä kiinnostuksesta digitaalisissa humanistisissa tieteissä on hiljattain perustettu Journal of Historical Network Research. ︎

  2. Hyvän kuvauksen network-objektiluokasta, mukaan lukien keskustelun sen suhteesta igraph-objektiluokkaan, tarjoaa Carter Butts, ”network: A Package for Managing Relational Data in R”, Journal of Statistical Software, 24 (2008): 1-36 ︎

  3. Tämä on visNetwork:n odottama erityisrakenne, joka kuitenkin vastaa muiden pakettien yleisiä odotuksia. ︎

  4. Tämä on joidenkin jäljempänä käyttämieni verkkopakettien sarakkeiden odotettu järjestys. ︎

  5. 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ä. ︎

  6. Thomas M. J. Fruchterman ja Edward M. Reingold, ”Graph Drawing by Force-Directed Placement,” Software: Practice and Experience, 21 (1991): 1129-1164. ︎

  7. Funktio rm() on hyödyllinen, jos työympäristösi R:ssä menee sekaisin, mutta et halua tyhjentää koko ympäristöä ja aloittaa alusta. ︎

  8. Objektien tbl_graph ja igraph välinen suhde on samanlainen kuin objektien tibble ja data.frame välinen suhde. ︎

  9. On mahdollista saada ggraph piirtämään nuolia, mutta en ole näyttänyt sitä tässä. ︎

  10. Työkaluvihjeen näkyminen voi kestää jonkin aikaa. ︎

r

Vastaa

Sähköpostiosoitettasi ei julkaista.