Skip to content

Archives

  • 2022 január
  • 2021 december
  • 2021 november
  • 2021 október
  • 2021 szeptember

Categories

  • Nincs kategória
Trend RepositoryArticles and guides
Articles

Bevezetés a hálózatelemzésbe az R segítségével

On november 4, 2021 by admin

A hálózatelemzés a legkülönbözőbb területeken egyre népszerűbb eszközzé vált a tudósok számára, hogy a legkülönbözőbb szereplők közötti kapcsolatok komplexitásával foglalkozzanak. A hálózatelemzés ígérete az, hogy a szereplők közötti kapcsolatokra helyezi a jelentőséget, ahelyett, hogy a szereplőket elszigetelt egységeknek tekintené. A komplexitás hangsúlyozása, valamint a hálózatok különböző aspektusainak mérésére szolgáló különféle algoritmusok létrehozása a hálózatelemzést a digitális bölcsészettudományok központi eszközévé teszi.1 Ez a bejegyzés bevezetést nyújt a hálózatokkal való munkába az R-ben, Daniel van der Meulen 1585-ös levelezésében szereplő városok hálózatának példáján keresztül.

Számos olyan alkalmazás létezik, amelyet hálózatelemzésre és hálózati gráfok létrehozására terveztek, mint például a gephi és a cytoscape. Bár nem kifejezetten erre tervezték, az R a hálózatelemzés hatékony eszközévé fejlődött. Az R erőssége az önálló hálózatelemző szoftverekkel szemben háromszoros. Először is, az R lehetővé teszi a reprodukálható kutatást, ami a GUI-alkalmazásokkal nem lehetséges. Másodszor, az R adatelemző képessége robusztus eszközöket biztosít az adatok manipulálásához, hogy előkészítse azokat a hálózatelemzésre. Végül pedig egyre több olyan csomag létezik, amelyek célja, hogy az R-t teljes körű hálózatelemző eszközzé tegyék. Az R jelentős hálózatelemző csomagjai közé tartozik a statnet csomagcsomag és a igraph. Emellett Thomas Lin Pedersen nemrégiben kiadta a tidygraph és ggraph csomagokat, amelyek a igraph teljesítményét a tidyverse munkafolyamatnak megfelelő módon használják ki. Az R segítségével interaktív hálózati gráfok is készíthetők a htmlwidgets keretrendszerrel, amely az R kódot JavaScriptre fordítja.

Ez a bejegyzés a hálózatelemzés alapszókincsének rövid bemutatásával kezdődik, majd az adatoknak a hálózatelemzéshez szükséges megfelelő struktúrába hozásának folyamatát tárgyalja. A hálózatelemző csomagok mindegyike saját objektumosztályokat valósított meg. Ebben a bejegyzésben bemutatom, hogyan lehet létrehozni a statnet csomagcsomagok specifikus objektumosztályait a network csomaggal, valamint a igraph és a tidygraph csomaghoz, amely a igraph implementáción alapul. Végül rátérek az interaktív gráfok létrehozására a vizNetwork és networkD3 csomagokkal.

  • Hálózatelemzés: Csomópontok és élek
  • Élom- és csomópontlisták létrehozása
  • Központlista
  • Szegmenslista
  • Hálózati objektumok létrehozása
  • network
  • igraph
  • tidygraph és ggraph
  • Interaktív hálózati grafikonok a visNetwork és networkD3 segítségével
  • visNetwork
  • networkD3
  • További olvasmányok a hálózatelemzésről

Hálózatelemzés: Csomópontok és élek

A hálózatok két elsődleges szempontja a különálló entitások sokasága és a köztük lévő kapcsolatok. A szókincs kissé technikai, sőt, a különböző tudományágak, csomagok és szoftverek között ellentmondásos is lehet. Az entitásokat a gráf csomópontjainak vagy csúcsainak, míg a kapcsolatokat éleknek vagy linkeknek nevezzük. Ebben a bejegyzésben főként a csomópontok és élek nómenklatúráját fogom használni, kivéve, amikor olyan csomagokat tárgyalok, amelyek más szókincset használnak.

A hálózatelemző csomagoknak szükségük van arra, hogy az adatok egy bizonyos formában legyenek, hogy létrehozzák az egyes csomagok által használt speciális típusú objektumokat. A network, igraph és tidygraph objektumosztályai mind szomszédsági mátrixokon, más néven szociomatrixokon alapulnak.2 Az szomszédsági mátrix egy négyzetmátrix, amelyben az oszlopok és sorok nevei a hálózat csomópontjai. A mátrixon belül az 1 azt jelzi, hogy a csomópontok között kapcsolat van, a 0 pedig azt, hogy nincs kapcsolat. A szomszédsági mátrixok az adatkeretektől nagyon eltérő adatszerkezetet valósítanak meg, és nem illeszkednek a korábbi bejegyzéseimben használt tidyverse munkafolyamatba. Segítségképpen a speciális hálózati objektumok létrehozhatók egy éllistás adatkeretből is, amelyek illeszkednek a tidyverse munkafolyamatba. Ebben a bejegyzésben ragaszkodom a tidyverse adatelemzési technikáihoz az éllisták létrehozásához, amelyeket aztán a network, igraph és tidygraph speciális objektumosztályokká alakítok át.

Az éllista olyan adatkeret, amely legalább két oszlopot tartalmaz, egy oszlopban a kapcsolat forrását jelentő csomópontokat, egy másik oszlopban pedig a kapcsolat célját jelentő csomópontokat. Az adatokban szereplő csomópontokat egyedi azonosítókkal azonosítjuk. Ha a forrás és a cél közötti különbségtétel értelmes, akkor a hálózat irányított. Ha a megkülönböztetés nem értelmes, a hálózat irányítatlan. A városok között küldött levelek példájánál a forrás és a cél között egyértelműen értelmes a különbségtétel, így a hálózat irányított. Az alábbi példákban a forrás oszlopot “from”-nak, a cél oszlopot pedig “to”-nak fogom nevezni. A csomópontok azonosítójaként eggyel kezdődő egész számokat fogok használni.3 Az élek listája tartalmazhat további oszlopokat is, amelyek az élek tulajdonságait írják le, például egy él nagyságrendi szempontját. Ha az éleknek van nagyság attribútuma, a gráf súlyozottnak tekinthető.

Az éllisták tartalmazzák a hálózati objektumok létrehozásához szükséges összes információt, de néha előnyösebb egy külön csomópontlistát is létrehozni. A legegyszerűbb esetben a csomópontlista egy adatkeret egyetlen oszloppal – amelyet “id”-ként fogok megjelölni -, amely az éllistában található csomópontok azonosítóit sorolja fel. A külön csomópontlista létrehozásának előnye, hogy az adatkerethez attribútumoszlopokat is hozzáadhatunk, például a csomópontok nevét vagy bármilyen csoportosítást. Az alábbiakban egy példát adok a tibble() függvénnyel létrehozott minimális él- és csomópontlistákra.

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

Ezt összehasonlítom egy szomszédsági mátrixszal, amely ugyanazokat az adatokat tartalmazza.

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

Élom- és csomópontlisták létrehozása

A Daniel van der Meulen által 1585-ben kapott levelek adatbázisából hálózati objektumok létrehozásához egy él- és egy csomópontlistát is készítek. Ehhez szükség lesz a dplyr csomag használatára, hogy a Danielnek küldött levelek adatkeretét manipuláljuk, és két adatkeretre vagy tibbletre osszuk fel az él- és csomópontlisták szerkezetével. Ebben az esetben a csomópontok azok a városok lesznek, ahonnan Dániel levelezőpartnerei leveleket küldtek neki, és azok a városok, ahonnan Dániel leveleket kapott. A csomópontlista tartalmazni fog egy “címke” oszlopot, amely a városok nevét tartalmazza. Az éllistának lesz egy attribútum oszlopa is, amely az egyes várospárok között küldött levelek mennyiségét mutatja. Az objektumok létrehozásának munkafolyamata hasonló lesz ahhoz, amit az R-be való rövid bevezetésemben és az R-rel való geokódolásban használtam. Ha szeretné követni, a GitHubon megtalálja az ebben a bejegyzésben használt adatokat és a felhasznált R-szkriptet.

Az első lépés az tidyverse könyvtár betöltése az adatok importálásához és manipulálásához. A letters adatkeret kinyomtatása azt mutatja, hogy négy oszlopot tartalmaz: “író”, “forrás”, “célállomás” és “dátum”. Ebben a példában csak a “forrás” és a “célállomás” oszlopokkal fogunk foglalkozni.

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

Központlista

A munkafolyamat a csomópontlista létrehozásához hasonló ahhoz, amit egy korábbi bejegyzésben a városok listájának megszerzéséhez használtam az adatok geokódolásához. Mind a “forrás”, mind a “cél” oszlopokból meg akarjuk szerezni a különböző városokat, majd az ezekből az oszlopokból származó információkat összekapcsoljuk. Az alábbi példában kissé megváltoztatom a parancsokat az előző bejegyzésben használtakhoz képest, hogy a városneveket tartalmazó oszlopok neve ugyanaz legyen mind a sources, mind a destinations adatkeret esetében, hogy egyszerűsítse a full_join() függvényt. A városneveket tartalmazó oszlopot “label”-nek nevezem át, hogy átvegyem a hálózatelemző csomagok által használt szókincset.

sources <- letters %>% distinct(source) %>% rename(label = source)destinations <- letters %>% distinct(destination) %>% rename(label = destination)

Az egyedi helyeket tartalmazó oszlopot tartalmazó egyetlen adatkeret létrehozásához teljes összekapcsolást kell használnunk, mivel mind a levelek forrásaiból, mind a célállomásokból az összes egyedi helyet fel akarjuk venni.

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

Ez egy egy változóval rendelkező adatkeretet eredményez. Az adatkeretben szereplő változó azonban nem igazán az, amit keresünk. A “címke” oszlop tartalmazza a csomópontok nevét, de szeretnénk, ha minden városhoz egyedi azonosítót is kapnánk. Ezt úgy érhetjük el, hogy a nodes adatkerethez hozzáadunk egy “id” oszlopot, amely számokat tartalmaz egytől addig a számig, amennyi az adatkeret összes sorának száma. Ehhez a munkafolyamathoz hasznos függvény a rowid_to_column(), amely egy oszlopot ad hozzá a sorok azonosítóinak értékeivel, és az oszlopot az adatkeret elejére helyezi.4 Vegye figyelembe, hogy a rowid_to_column() egy pipázható parancs, így a full_join() és az “id” oszlop hozzáadása egyetlen parancsban is elvégezhető. Az eredmény egy csomópontlista lesz egy azonosító oszloppal és egy címkeattribútummal.

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

Szegmenslista

A szegmenslista létrehozása hasonló a fentiekhez, de bonyolítja, hogy egy helyett két azonosító oszloppal kell foglalkoznunk. Szeretnénk létrehozni egy súlyoszlopot is, amely az egyes csomópontok között küldött levelek mennyiségét jegyzi. Ehhez ugyanazt a group_by() és summarise() munkafolyamatot fogom használni, amelyet a korábbi bejegyzésekben már tárgyaltam. A különbség itt az, hogy az adatkeretet nem egy, hanem két oszlop – “forrás” és “cél” – szerint akarjuk csoportosítani. Korábban a csoportonkénti megfigyelések számát számláló oszlopot “count”-nak neveztem el, itt azonban átveszem a hálózatelemzés nómenklatúráját, és “weight”-nek nevezem. A csővezeték utolsó parancsa eltávolítja a group_by() függvény által létrehozott adatkeret csoportosítását. Ez megkönnyíti a kapott per_route adatkeret akadálytalan manipulálását. 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

A csomópontlistához hasonlóan a per_route most már a kívánt alapformával rendelkezik, de ismét az a probléma, hogy a “forrás” és a “cél” oszlopok azonosítók helyett címkéket tartalmaznak. Azt kell tennünk, hogy a nodes-ben hozzárendelt azonosítókat összekapcsoljuk a “forrás” és a “cél” oszlopok egyes helyeihez. Ezt egy másik join függvénnyel érhetjük el. Valójában két egyesítést kell végrehajtani, egyet a “forrás” oszlophoz és egyet a “cél” oszlophoz. Ebben az esetben egy left_join()-et fogok használni, amelynek bal oldali adatkerete per_route lesz, mert meg akarjuk tartani a per_route-ben lévő sorok számát. A left_join elkészítése közben a nodes-ből áthozott két “id” oszlopot is át akarjuk nevezni. A “forrás” oszlopot használó joinhoz átnevezem az oszlopot “from”-ra. A “cél” joinból áthozott oszlopot átnevezzük “to”-ra. Lehetséges lenne mindkét join egyetlen parancsban elvégezni a pipe használatával. Az egyértelműség kedvéért azonban két külön parancsban fogom végrehajtani az egyesítéseket. Mivel az egyesítés két parancsban történik, vegye észre, hogy a csővezeték elején lévő adatkeret per_route-ről edges-re változik, amelyet az első parancs hoz létre.

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)

Most, hogy a edges rendelkezik “from” és “to” oszlopokkal a csomópontok azonosítóival, át kell rendeznünk az oszlopokat, hogy a “from” és “to” az adatkeret bal oldalán legyen. Jelenleg a edges adatkeret még mindig tartalmazza a “forrás” és a “cél” oszlopokat az azonosítóknak megfelelő városok neveivel. Ezek az adatok azonban feleslegesek, mivel a nodes-ben már szerepelnek. Ezért a select() függvényben csak a “honnan”, “hová” és “súly” oszlopokat veszem fel.

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

A edges adatkeret nem tűnik túl látványosnak; három oszlopnyi egész számot tartalmaz. A edges azonban a nodes-vel kombinálva minden szükséges információt megad ahhoz, hogy a network, igraph és tidygraph csomagokkal hálózati objektumokat hozzunk létre.

Hálózati objektumok létrehozása

A network, igraph és tidygraph csomagok hálózati objektumosztályai szorosan kapcsolódnak egymáshoz. Lehetőség van egy network objektum és egy igraph objektum közötti fordításra. A legjobb azonban, ha a két csomagot és objektumaikat külön tartjuk. Valójában a network és a igraph képességei olyan mértékben átfedik egymást, hogy a legjobb gyakorlat, ha egyszerre csak az egyik csomag van betöltve. Először a network csomagot tekintem át, majd áttérek a igraph és tidygraph csomagokra.

network

library(network)

A network objektum létrehozásához használt függvény a network(). A parancs nem különösebben egyszerű, de bármikor beírhatod a konzolba a ?network(), ha összezavarodnál. Az első argumentum – a dokumentáció szerint – “egy mátrix, amely a hálózat szerkezetét adja meg szomszédsági, incidencia vagy éllista formájában”. A nyelv demonstrálja a mátrixok jelentőségét a hálózatelemzésben, de mátrix helyett éllistát kapunk, ami ugyanazt a szerepet tölti be. A második argumentum a csúcsok attribútumainak listája, amely megfelel a csomópontok listájának. Vegyük észre, hogy a network csomag a csomópontok helyett a csúcsok nomenklatúráját használja. Ugyanez igaz a igraph csomagra is. Ezután meg kell adnunk az első két argumentumba beírt adatok típusát azzal, hogy megadjuk, hogy a matrix.type egy "edgelist". Végül a ignore.eval-öt FALSE-re állítjuk, hogy a hálózatunk súlyozott legyen, és figyelembe vegye az egyes útvonalak mentén található betűk számát.

routes_network <- network(edges, vertex.attr = nodes, matrix.type = "edgelist", ignore.eval = FALSE)

A network() függvény által létrehozott objektum típusát a routes_network class() függvénybe helyezésével láthatjuk.

class(routes_network)#> "network"

A routes_network kiírása a konzolra megmutatja, hogy az objektum szerkezete egészen más, mint az olyan adatkeret stílusú objektumoké, mint a edges és a nodes. A nyomtatási parancs olyan információkat tár fel, amelyek kifejezetten a hálózatelemzéshez vannak meghatározva. Azt mutatja, hogy a routes_network-ben 13 csúcs vagy csomópont és 15 él van. Ezek a számok megfelelnek a nodes és a edges sorainak. Azt is láthatjuk, hogy a csúcsok és élek egyaránt tartalmaznak olyan attribútumokat, mint a címke és a súly. Még több információt kaphatunk, beleértve az adatok szociomátrixát is, ha beírjuk a 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

A betűhálózatunk kezdetleges, ha nem is túlságosan esztétikus grafikonját. Mind a network, mind a igraph csomag az R alapplotrendszerét használja. Az alapplotok konvenciói jelentősen eltérnek a ggplot2 konvencióitól – amelyeket korábbi bejegyzésekben már tárgyaltam -, ezért maradok a meglehetősen egyszerű plotoknál, ahelyett, hogy belemennék az összetett plotok létrehozásának részleteibe az alap R-rel. Ebben az esetben az egyetlen változtatás, amit a network csomag alapértelmezett plot() függvényén végrehajtok, hogy a vertex.cex argumentummal növelem a csomópontok méretét, hogy a csomópontok jobban láthatóak legyenek. Még ezzel a nagyon egyszerű grafikonnal is megtudhatunk már valamit az adatokról. A grafikon egyértelművé teszi, hogy az adatoknak két fő csoportosulása vagy klasztere van, amelyek megfelelnek annak az időnek, amelyet Daniel 1585 első háromnegyedévében Hollandiában töltött, illetve a szeptemberi Brémába költözése után.

plot(routes_network, vertex.cex = 3)

A plot() függvény a network objektummal a Fruchterman és Reingold algoritmus segítségével dönt a csomópontok elhelyezéséről.6 Az mode argumentummal megváltoztathatja az elrendezési algoritmust. Az alábbiakban a csomópontokat kör alakban rendezem el. Ez nem egy különösebben hasznos elrendezés ehhez a hálózathoz, de képet ad néhány lehetőségről.

plot(routes_network, vertex.cex = 3, mode = "circle")

igraph

Kezdjük most a igraph csomag tárgyalását. Először is meg kell tisztítanunk a környezetet az R-ben a network csomag eltávolításával, hogy ne zavarja a igraph parancsokat. Akár a routes_network csomagot is eltávolíthatjuk, mivel a továbbiakban nem fogjuk használni. A network csomagot a detach() függvénnyel, a routes_network csomagot pedig a rm() függvénnyel távolíthatjuk el.7 Ezek után nyugodtan betölthetjük a igraph csomagot.

detach(package:network)rm(routes_network)library(igraph)

Az éllista adatkeretből egy igraph objektum létrehozásához használhatjuk a graph_from_data_frame() függvényt, ami egy kicsit egyszerűbb, mint a network(). A graph_from_data_frame() függvénynek három argumentuma van: d, csúcsok és irányított. Itt a d az éllistára, a vertices a csomópontlistára utal, a directed pedig TRUE vagy FALSE lehet attól függően, hogy az adat irányított vagy irányítatlan.

routes_igraph <- graph_from_data_frame(d = edges, vertices = nodes, directed = TRUE)

A graph_from_data_frame() által létrehozott igraph objektumot a konzolon kinyomtatva hasonló információkat kapunk, mint egy network objektumról, bár a struktúra rejtélyesebb.

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

Az objektumra vonatkozó legfontosabb információkat a DNW- 13 15 -- tartalmazza. Ebből kiderül, hogy a routes_igraph egy irányított hálózat (D), amely rendelkezik névattribútummal (N) és súlyozott (W). A W utáni kötőjel azt mondja, hogy a gráf nem kétrészes. Az ezt követő számok a gráf csomópontjainak, illetve éleinek számát írják le. Ezután a name (v/c), label (v/c), weight (e/n) a gráf attribútumairól ad információt. Van két vertex attribútum (v/c) a név – azaz az azonosítók – és a címkék, valamint egy él attribútum (e/n) a súly. Végül az összes él kiírása következik.

Az network csomaghoz hasonlóan a plot() függvény segítségével egy igraph objektummal is létrehozhatunk egy gráfot. Az egyetlen változtatás, amit itt az alapértelmezetthez képest végzek, az a nyilak méretének csökkentése. Alapértelmezés szerint a igraph a csomópontokat a címkeoszloppal címkézi, ha van, vagy az azonosítókkal.

plot(routes_igraph, edge.arrow.size = 0.2)

Az előző network grafikonhoz hasonlóan a igraph plot alapértelmezése sem különösebben esztétikus, de a plotok minden aspektusa manipulálható. Itt csak a csomópontok elrendezését szeretném megváltoztatni, hogy a Michael Schmuhl által létrehozott graphopt algoritmust használjam. Ez az algoritmus megkönnyíti a Haarlem, Antwerpen és Delft – a levelezési hálózat három legjelentősebb helyszíne – közötti kapcsolat áttekintését azáltal, hogy jobban széthúzza őket.

plot(routes_igraph, layout = layout_with_graphopt, edge.arrow.size = 0.2)

tidygraph és ggraph

A tidygraph és ggraph csomagok újoncok a hálózatelemzésben, de a két csomag együtt valódi előnyöket nyújt a network és igraph csomagokkal szemben. A tidygraph és a ggraph egy kísérletet jelentenek arra, hogy a hálózatelemzés bekerüljön a tidyverse munkafolyamatba. A tidygraph egy olyan hálózati objektum létrehozásának módját biztosítja, amely jobban hasonlít egy tibble-re vagy adatkeretre. Ez lehetővé teszi a dplyr számos függvényének használatát a hálózati adatok manipulálására. A ggraph módot ad a hálózati grafikonok ábrázolására a ggplot2 konvencióinak és teljesítményének felhasználásával. Más szóval, a tidygraph és a ggraph lehetővé teszi, hogy a hálózati objektumokat olyan módon kezelje, amely jobban összhangban van a tibbletekkel és adatkeretekkel való munkához használt parancsokkal. A tidygraph és ggraph igazi ígérete azonban az, hogy kihasználják a igraph erejét. Ez azt jelenti, hogy a tidygraph és ggraph használatával keveset áldozunk fel a igraph hálózatelemzési lehetőségeiből.

Azzal kell kezdenünk, mint mindig, hogy betöltjük a szükséges csomagokat.

library(tidygraph)library(ggraph)

Először hozzunk létre egy hálózati objektumot a tidygraph segítségével, amelyet tbl_graph-nek hívunk. Egy tbl_graph két tibble-ből áll: egy élek tibble-ből és egy csomópontok tibble-ből. Kényelmes módon a tbl_graph objektumosztály egy igraph objektum körüli burkolat, ami azt jelenti, hogy az alapját tekintve egy tbl_graph objektum lényegében egy igraph objektum.8 A tbl_graph és a igraph objektumok közötti szoros kapcsolat két fő módot eredményez egy tbl_graph objektum létrehozására. Az első az él- és csomópontlista használata, a tbl_graph() használatával. A függvény argumentumai majdnem azonosak a graph_from_data_frame()-éval, csak az argumentumok nevei változnak kissé.

routes_tidy <- tbl_graph(nodes = nodes, edges = edges, directed = TRUE)

A tbl_graph objektum létrehozásának második módja egy igraph vagy network objektum átalakítása a as_tbl_graph() használatával. Így a routes_igraph objektumot átalakíthatjuk tbl_graph objektummá.

routes_igraph_tidy <- as_tbl_graph(routes_igraph)

Most, hogy létrehoztunk két tbl_graph objektumot, vizsgáljuk meg őket a class() függvénnyel. Ebből kiderül, hogy routes_tidy és routes_igraph_tidy a "tbl_graph" "igraph" osztályba tartozó objektumok, míg routes_igraph a "igraph" objektumosztályba tartozik.

class(routes_tidy)#> "tbl_graph" "igraph"class(routes_igraph_tidy)#> "tbl_graph" "igraph"class(routes_igraph)#> "igraph"

A tbl_graph objektumot a konzolra kiírva drasztikusan más eredményt kapunk, mint egy igraph objektum esetében. A kimenet egy normál tibble-hez hasonló.

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

A routes_tidy kinyomtatása azt mutatja, hogy ez egy tbl_graph objektum 13 csomóponttal és 15 éllel. A parancs kiírja a “Node Data” első hat sorát és az “Edge Data” első három sorát is. Vegyük észre azt is, hogy kijelzi, hogy a Node Data aktív. Az aktív tibble fogalma egy tbl_graph objektumon belül lehetővé teszi, hogy az adatokat egyszerre egy tibble-ben manipuláljuk. A csomópont tibble alapértelmezés szerint aktív, de a activate() függvénnyel megváltoztathatjuk, hogy melyik tibble legyen aktív. Így ha át akarom rendezni az élek tibble sorait, hogy először a legnagyobb “súllyal” rendelkező sorokat soroljam fel, akkor a activate(), majd a arrange() funkciót használhatom. Itt egyszerűen csak kiírom az eredményt ahelyett, hogy elmenteném.

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

Mivel nem kell tovább manipulálnunk a routes_tidy-t, a ggraph segítségével ábrázolhatjuk a grafikont. A ggmap-hoz hasonlóan a ggraph is a ggplot2 kiterjesztése, ami megkönnyíti az alapvető ggplot ismeretek átvitelét a hálózati ábrák készítésére. Mint minden hálózati gráf esetében, a ggraph plotnak is három fő aspektusa van: csomópontok, élek és elrendezések. A ggraph csomag vignettái segítőkészen tárgyalják a ggraph-plotok alapvető aspektusait. A ggraph speciális geomokat ad a ggplot geomok alapkészletéhez, amelyeket kifejezetten hálózatokhoz terveztek. Így létezik egy geom_node és egy geom_edge geomkészlet. Az alapvető ábrázoló funkció a ggraph(), amely a grafikonhoz használandó adatokat és a kívánt elrendezés típusát veszi fel. A ggraph() mindkét argumentuma a igraph köré épül. Ezért a ggraph() használhat akár egy igraph objektumot, akár egy tbl_graph objektumot. Ezenkívül a rendelkezésre álló elrendezési algoritmusok elsősorban a igraph-ból származnak. Végül a ggraph bevezet egy speciális ggplot témát, amely jobb alapértelmezéseket biztosít a hálózati gráfok számára, mint a normál ggplot alapértelmezések. A ggraph téma beállítható egy plotsorozathoz a set_graph_style() paranccsal, amelyet a grafikonok kirajzolása előtt futtatunk, vagy a theme_graph() használatával az egyes plotokban. Itt az utóbbi módszert fogom használni.

Lássuk, hogyan néz ki egy alapvető ggraph plot. A plot a ggraph() és az adatokkal kezdődik. Ezután hozzáadom az alapvető él- és csomópontgeomákat. Az él- és csomópontgeomokon belül nincs szükség argumentumokra, mert azok a ggraph()-ben megadott adatokból veszik az információt.

ggraph(routes_tidy) + geom_edge_link() + geom_node_point() + theme_graph()

Amint látható, a parancs felépítése hasonló a ggplot-hoz, a + jellel hozzáadott külön rétegekkel. Az alapvető ggraph grafikon hasonlóan néz ki, mint a network és a igraph, ha nem még egyszerűbben, de a ggplot-hoz hasonló parancsokkal még informatívabb grafikont készíthetünk. Az élek “súlyát” – vagyis az egyes útvonalakon küldött levelek mennyiségét – a geom_edge_link() függvényben a width használatával mutathatjuk meg. Ahhoz, hogy a vonal szélessége a súly változónak megfelelően változzon, az argumentumot egy aes() függvényen belül helyezzük el. Az élek maximális és minimális szélességének szabályozásához a scale_edge_width() funkciót használom, és egy range-et állítok be. A minimumnak viszonylag kis szélességet választok, mert az útvonalak mentén küldött betűk maximális és minimális száma között jelentős különbség van. A csomópontokat a helyszínek nevével is felcímkézhetjük, mivel viszonylag kevés csomópont van. Kényelmes módon a geom_node_text()-hez tartozik egy repel argumentum, amely a ggrepel csomaghoz hasonlóan biztosítja, hogy a címkék ne fedjék át a csomópontokat. Az élekhez egy kis átlátszóságot adok az alpha argumentummal. A labs()-t arra is használom, hogy a “Levelek” legendát átcímkézzem.

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()

A igraph által biztosított elrendezési lehetőségek mellett a ggraph saját elrendezéseket is megvalósít. Például a ggraph's körkörösség koncepcióját használhatja ívdiagramok létrehozásához. Itt a csomópontokat vízszintes vonalban elrendezem, és az éleket ívként rajzoltatom. Az előző ábrázolással ellentétben ez a grafikon jelzi az élek irányultságát.9 A vízszintes vonal feletti élek balról jobbra, míg a vonal alatti élek jobbról balra mozognak. Ahelyett, hogy pontokat adnék a csomópontokhoz, csak a címkék nevét adom meg. Az egyes élek súlykülönbségének jelölésére azonos szélességű esztétikumot használok. Vegyük észre, hogy ebben a grafikonban egy igraph objektumot használok a grafikon adataként, ami gyakorlatilag nem jelent különbséget.

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()

Interaktív hálózati grafikonok a visNetwork és networkD3 segítségével

A htmlwidgets csomagkészlet lehetővé teszi, hogy az R segítségével interaktív JavaScript vizualizációkat hozzunk létre. Itt bemutatom, hogyan készíthetünk grafikonokat a visNetwork és networkD3 csomagokkal. Ez a két csomag különböző JavaScript-könyvtárakat használ a grafikonok létrehozásához. A visNetwork a vis.js-t használja, míg a networkD3 a népszerű d3 vizualizációs könyvtárat használja a grafikonok elkészítéséhez. A visNetwork és a networkD3 csomagokkal való munka egyik nehézsége, hogy mindkettő elvárja, hogy az él- és csomópontlisták sajátos nómenklatúrát használjanak. A fenti adatmanipuláció a visNetwork esetében megfelel az alapstruktúrának, de a networkD3 esetében némi munkát kell végezni. E kellemetlenség ellenére mindkét csomag széles körű grafikus képességekkel rendelkezik, és mindkettő képes igraph objektumokkal és elrendezésekkel dolgozni.

library(visNetwork)library(networkD3)

visNetwork

A visNetwork() függvény egy csomópontlistát és egy éllistát használ egy interaktív gráf létrehozásához. A csomópontlistának tartalmaznia kell egy “id” oszlopot, az éllistának pedig “from” és “to” oszlopokat. A függvény a csomópontok címkéit is kirajzolja, a csomópontlista “label” oszlopában szereplő városnevek felhasználásával. Az eredményül kapott gráffal jó szórakozást nyújt a játék. A csomópontokat mozgathatja, és a gráf egy algoritmust használ a csomópontok megfelelő távolságtartására. A grafikonra is rá- és ráközelíthet, és mozgathatja azt, hogy újra középre állítsa.

visNetwork(nodes, edges)

visNetwork használhatja a igraph elrendezéseket, ami a lehetséges elrendezések nagy választékát biztosítja. Ezenkívül a visIgraph() segítségével közvetlenül egy igraph objektumot is kirajzolhat. Itt maradok a nodes és edges munkafolyamatnál, és egy igraph elrendezést használok a grafikon testreszabásához. Egy változót is hozzáadok az él szélességének megváltoztatásához, ahogyan azt a ggraph esetében is tettük. A visNetwork() az élek és csomópontok listáiból származó oszlopneveket használja a hálózati attribútumok ábrázolásához a függvényhíváson belüli argumentumok helyett. Ez azt jelenti, hogy némi adatmanipulációt kell végezni ahhoz, hogy az éllistában legyen egy “szélesség” oszlop. A visNetwork() szélesség attribútuma nem skálázza az értékeket, ezért ezt manuálisan kell elvégeznünk. Mindkét művelet elvégezhető a mutate() függvénnyel és némi egyszerű aritmetikával. Itt létrehozok egy új oszlopot a edges-ben, és a súlyértékeket úgy skálázom, hogy elosztom 5-tel. Ha az eredményhez hozzáadok 1-et, akkor egy minimális szélességet hozhatunk létre.

edges <- mutate(edges, width = weight/5 + 1)

Mihelyt ez megtörtént, létrehozhatunk egy változó élszélességű gráfot. Én is választok egy elrendezési algoritmust a igraph-ból, és nyilakat adok az élekhez, az él közepére helyezve őket.

visNetwork(nodes, edges) %>% visIgraphLayout(layout = "layout_with_fr") %>% visEdges(arrows = "middle")

networkD3

Az adatok előkészítéséhez még egy kis munkára van szükség, hogy egy networkD3 gráfot hozzunk létre. Egy networkD3 gráf elkészítéséhez egy él- és csomópontlistával az szükséges, hogy az azonosítók numerikus egész számok sorozatai legyenek, amelyek 0-val kezdődnek. Jelenleg az adataink csomópont-azonosítói 1-gyel kezdődnek, ezért egy kis adatmanipulációt kell végeznünk. A csomópontok átszámozása úgy lehetséges, hogy a nodes és edges adatkeretek ID oszlopaiból kivonjuk az 1-et. Ezt ismét a mutate() függvénnyel lehet elvégezni. A cél az aktuális oszlopok újrateremtése, miközben minden ID-ből kivonunk 1-et. A mutate() függvény úgy működik, hogy új oszlopot hoz létre, de lecserélhetünk vele egy oszlopot, ha az új oszlopnak ugyanazt a nevet adjuk, mint a régi oszlopnak. Itt az új adatkereteket d3 utótaggal nevezem el, hogy megkülönböztessem őket a korábbi nodes és edges adatkeretektől.

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

Az networkD3 grafikon ábrázolása most már lehetséges. A visNetwork() függvénytől eltérően a forceNetwork() függvény egy sor argumentumot használ a grafikon beállításához és a hálózat attribútumainak ábrázolásához. A “Linkek” és a “Csomópontok” argumentumok élek és csomópontok listái formájában szolgáltatják az adatokat a grafikonhoz. A függvénynek szüksége van a “NodeID” és a “Group” argumentumokra is. Az itt használt adatok nem rendelkeznek csoportosítással, ezért minden csomópontnak csak saját csoportja van, ami a gyakorlatban azt jelenti, hogy a csomópontok mind különböző színűek lesznek. Ezenkívül az alábbiakban közöljük a függvénnyel, hogy a hálózatnak vannak “Source” és “Target” mezői, tehát irányított. Ebbe a gráfba beleveszek egy “Value” értéket, amely az élek szélességét az éllista “weight” oszlopának megfelelően skálázza. Végül néhány esztétikai finomítást adok hozzá, hogy a csomópontok átláthatatlanok legyenek, és növelem a címkék betűméretét az olvashatóság javítása érdekében. Az eredmény nagyon hasonlít az általam készített első visNetwork() diagramhoz, de más esztétikai stílusban.

forceNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Group = "id", Value = "weight", opacity = 1, fontSize = 16, zoom = TRUE)

A networkD3 egyik fő előnye, hogy egy d3-stílusú Sankey-diagramot valósít meg. A Sankey-diagram jól illeszkedik a Dánielnek 1585-ben küldött levelekhez. Az adatokban nincs túl sok csomópont, ami megkönnyíti a levelek áramlásának vizualizálását. A Sankey-diagram létrehozása a sankeyNetwork() függvényt használja, amely sokszor ugyanazokat az argumentumokat veszi fel, mint a forceNetwork(). Ez a diagram nem igényel csoport argumentumot, és az egyetlen további változás az “egység” hozzáadása. Ez egy címkét ad az értékeknek, amelyek felugranak az eszköztippben, amikor a kurzor egy diagramelem fölé kerül.10

sankeyNetwork(Links = edges_d3, Nodes = nodes_d3, Source = "from", Target = "to", NodeID = "label", Value = "weight", fontSize = 16, unit = "Letter(s)")

További olvasmányok a hálózatelemzésről

Ez a bejegyzés megkísérelt egy általános bevezetést adni a hálózati típusú objektumok létrehozásához és ábrázolásához az R-ben a network, igraph, tidygraph és ggraph csomagok segítségével statikus ábrákhoz, valamint a visNetwork és networkD3 csomagok segítségével interaktív ábrákhoz. Ezt az információt a hálózatelméletben nem jártas szakember pozíciójából mutattam be. Az R hálózatelemzési képességeinek csak egy nagyon kis százalékát fedtem le. Különösen a hálózatok statisztikai elemzését nem tárgyaltam. Szerencsére rengeteg forrás áll rendelkezésre a hálózatelemzésről általában és különösen az R-ben.

A legjobb bevezetés a hálózatokba, amit a beavatatlanok számára találtam, Katya Ognyanova Network Visualization with R című könyve. Ez egyrészt hasznos bevezetést nyújt a hálózatok vizuális aspektusaiba, másrészt mélyebb útmutatást nyújt a hálózati ábrák létrehozásához az R-ben. Ognyanova elsősorban a igraph-t használja, de bemutatja az interaktív hálózatokat is.

A Springer kiadó két viszonylag friss könyvet is megjelentetett a hálózatelemzésről az R-rel. Douglas A. Luke, A User’s Guide to Network Analysis in R (2015) egy nagyon hasznos bevezetés a hálózatelemzésbe az R-rel. Luke foglalkozik mind a statnet csomagokkal, mind a igragh csomagokkal. A tartalom végig nagyon megközelíthető szinten van. Haladóbb Eric D. Kolaczyk és Csárdi Gábor: Statistical Analysis of Network Data with R (2014) című könyve. Kolaczyk és Csárdi könyve elsősorban a igraph-t használja, mivel Csárdi az R számára a igraph csomag elsődleges karbantartója. Ez a könyv mélyebben belemegy a hálózatok statisztikai elemzésének haladó témáiba. A nagyon technikai nyelvezet használata ellenére az első négy fejezet általában megközelíthető egy nem szakember szemszögéből is.

A François Briatte által gondozott lista jó áttekintést nyújt az általános hálózatelemzéssel kapcsolatos forrásokról. Scott Weingart Networks Demystified című bejegyzéssorozatát is érdemes áttanulmányozni.

  1. A digitális bölcsészettudományokon belül a hálózatelemzés iránti érdeklődés egyik példája az újonnan indított Journal of Historical Network Research. ︎

  2. A network objektumosztály jó leírását, beleértve a igraph objektumosztályhoz való viszonyának tárgyalását, lásd Carter Butts, “network: A Package for Managing Relational Data in R”, Journal of Statistical Software, 24 (2008): 1-36 ︎

  3. Ez a visNetwork által elvárt speciális struktúra, miközben megfelel a többi csomag általános elvárásainak is. ︎

  4. Ez az oszlopok elvárt sorrendje néhány hálózati csomag esetében, amelyeket az alábbiakban használni fogok. ︎

  5. ungroup() ebben az esetben nem feltétlenül szükséges. Ha azonban nem szünteti meg az adatkeret csoportosítását, akkor nem lehetséges a “forrás” és a “cél” oszlopok elhagyása, ahogyan azt a szkriptben később megteszem. ︎

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

  7. A rm() funkció hasznos, ha az R-ben a munkakörnyezetünk rendezetlenné válik, de nem akarjuk az egész környezetet kitakarítani és újrakezdeni. ︎

  8. A tbl_graph és igraph objektumok közötti kapcsolat hasonló a tibble és data.frame objektumok közötti kapcsolathoz. ︎

  9. Lehetséges, hogy a ggraph nyilakat rajzoljon, de ezt itt nem mutattam be. ︎

  10. Egy kis időbe telhet, amíg az eszköztipp megjelenik. ︎

r

Vélemény, hozzászólás? Kilépés a válaszból

Az e-mail-címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

Archívum

  • 2022 január
  • 2021 december
  • 2021 november
  • 2021 október
  • 2021 szeptember

Meta

  • Bejelentkezés
  • Bejegyzések hírcsatorna
  • Hozzászólások hírcsatorna
  • WordPress Magyarország
  • DeutschDeutsch
  • NederlandsNederlands
  • SvenskaSvenska
  • DanskDansk
  • EspañolEspañol
  • FrançaisFrançais
  • PortuguêsPortuguês
  • ItalianoItaliano
  • RomânăRomână
  • PolskiPolski
  • ČeštinaČeština
  • MagyarMagyar
  • SuomiSuomi
  • 日本語日本語

Copyright Trend Repository 2022 | Theme by ThemeinProgress | Proudly powered by WordPress