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.
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.
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.
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.
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
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.
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-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.
Voit nähdä network()-funktion luoman objektin tyypin asettamalla routes_networkclass()-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).
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.