4、图转换到其他R数据结构
图是对实体关系的表达,在igraph中,图可以转换为三种数据结构。
4-1 图转邻接矩阵:as_adjacency_matrix | as_adj
,结果是矩阵
邻接矩阵又分为有向图邻接矩阵和无向图邻接矩阵,但本函数使用无向图,有向图忽略边的方向,矩阵可以是上三角、下三角或一般矩阵,这通过参数type
设置;
百度百科:
邻接矩阵(Adjacency Matrix)是表示顶点之间相邻关系的矩阵。设G=(V,E)是一个图,其中V={v1,v2,…,vn}。G的邻接矩阵是一个具有下列性质的n阶方阵:
①对无向图而言,邻接矩阵一定是对称的,而且主对角线一定为零(在此仅讨论无向简单图),副对角线不一定为0,有向图则不一定如此。
②在无向图中,任一顶点i的度为第i列(或第i行)所有非零元素的个数,在有向图中顶点i的出度为第i行所有非零元素的个数,而入度为第i列所有非零元素的个数。
③用邻接矩阵法表示图共需要n^2个空间,由于无向图的邻接矩阵一定具有对称关系,所以扣除对角线为零外,仅需要存储上三角形或下三角形的数据即可,因此仅需要n(n-1)/2个空间。
> g <- sample_gnp(10, 2 / 10)
> as_adjacency_matrix(g)
10 x 10 sparse Matrix of class "dgCMatrix"
[1,] . . . . . . 1 . . .
[2,] . . . . 1 1 . . . .
[3,] . . . 1 . . . 1 . .
[4,] . . 1 . . . . . . .
[5,] . 1 . . . . . 1 1 .
[6,] . 1 . . . . . . . .
[7,] 1 . . . . . . . . 1
[8,] . . 1 . 1 . . . . .
[9,] . . . . 1 . . . . .
[10,] . . . . . . 1 . . .
可见,邻接矩阵用行位置和列位置来匹配边的起始顶点和终止顶点,如果两个顶点间存在一条边,就在交接处填入数字1。如果想找一条边起点,需要看该表在矩阵中的行号,终点需要看列号。
MIT 的Gilbert Strang教授在线性代数视频中,也用矩阵表示图,但他在起始顶点的位置填-1
,在终点位置填1
,他的表示法和igraph不同,矩阵所有元素之和是0,似乎有特殊的数学意义。
邻接矩阵,igraph默认填入数字1,如果想将边的属性值(比如边的权值)填入,可以通过设置参数attr = "weight
实现:
> E(g)$weight <- 2
> as_adjacency_matrix(g, attr = "weight")
10 x 10 sparse Matrix of class "dgCMatrix"
[[ suppressing 10 column names ‘a’, ‘b’, ‘c’ ... ]]
a . . . . . . 2 . . .
b . . . . 2 2 . . . .
c . . . 2 . . . 2 . .
d . . 2 . . . . . . .
e . 2 . . . . . 2 2 .
f . 2 . . . . . . . .
g 2 . . . . . . . . 2
h . . 2 . 2 . . . . .
i . . . . 2 . . . . .
j . . . . . . 2 . . .
igraph默认用顶点的ID号对应邻接矩阵的行号和列号,但如果设置了顶点的name属性,igraph会自动用顶点名替换邻接矩阵的行号
> V(g)$name <- letters[1:vcount(g)]
> as_adjacency_matrix(g)
10 x 10 sparse Matrix of class "dgCMatrix"
[[ suppressing 10 column names ‘a’, ‘b’, ‘c’ ... ]]
a . . . . . . 1 . . .
b . . . . 1 1 . . . .
c . . . 1 . . . 1 . .
d . . 1 . . . . . . .
e . 1 . . . . . 1 1 .
f . 1 . . . . . . . .
g 1 . . . . . . . . 1
h . . 1 . 1 . . . . .
i . . . . 1 . . . . .
j . . . . . . 1 . . .
4-2 专用函数:二部邻接矩阵as_biadjacency_matrix
据百度百科:二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(U,V),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in U,j in V),则称图G为一个二分图。
g <- make_bipartite_graph(c(0, 1, 0, 1, 0, 0), c(1, 2, 2, 3, 3, 4))
as_biadjacency_matrix(g)
2 4
1 1 0
3 1 1
5 0 0
6 0 0
plot(g,layout=-layout_as_bipartite(g))
4-3 图转邻接表:as_adj_list | as_adj_edge_list
,结果是列表
igraph的as_adj_list和网页一些邻接列表的介绍不同,网上很多文章形容邻接列表是“对图的每个顶点都创建一个单链表,每个单链表都存储与该顶点直接相连的顶点。”
igraph不是这样,它用list数据结构,先列出一个顶点,随后列出这个顶点的邻接顶点
> g <- make_ring(10)
> as_adj_list(g)
[[1]]
+ 2/10 vertices, from 74ab1d3:
[1] 2 10
[[2]]
+ 2/10 vertices, from 74ab1d3:
[1] 1 3
[[3]]
+ 2/10 vertices, from 74ab1d3:
[1] 2 4
[[4]]
+ 2/10 vertices, from 74ab1d3:
[1] 3 5
igraph默认用顶点ID显示,但如果图的顶点设置了name属性,则用Name,但label属性不行
> g <- make_ring(10) %>% set_vertex_attr('name',value = letters[1:10])
> as_adj_list(g)
$a
+ 2/10 vertices, named, from 600b734:
[1] b j
$b
+ 2/10 vertices, named, from 600b734:
[1] a c
$c
+ 2/10 vertices, named, from 600b734:
[1] b d
> g <- make_ring(10) %>% set_vertex_attr('label',value = letters[1:10])
> as_adj_list(g)
[[1]]
+ 2/10 vertices, from a6a7d0d:
[1] 2 10
[[2]]
+ 2/10 vertices, from a6a7d0d:
[1] 1 3
[[3]]
+ 2/10 vertices, from a6a7d0d:
[1] 2 4
如果想将结果用边的方式展示,可以用as_adj_edge_list
函数,用法相同,只是显示形式不同:
> as_adj_edge_list(g)
[[1]]
+ 2/10 edges from a6a7d0d:
[1] 1-- 2 1--10
[[2]]
+ 2/10 edges from a6a7d0d:
[1] 1--2 2--3
[[3]]
+ 2/10 edges from a6a7d0d:
[1] 2--3 3--4
因为函数返回的结果是列表,可以向普通列表一样检索它:
> as_adj_edge_list(g)[1:3]
[[1]]
+ 2/10 edges from a6a7d0d:
[1] 1-- 2 1--10
[[2]]
+ 2/10 edges from a6a7d0d:
[1] 1--2 2--3
[[3]]
+ 2/10 edges from a6a7d0d:
[1] 2--3 3--4
> as_adj_edge_list(g)[[1]]
+ 2/10 edges from a6a7d0d:
[1] 1-- 2 1--10
4-4 图转边列表:as_edgelist
函数,结果是矩阵或数组
边列表类似于电子表格的结构,igraph把边列表看作图的标准表示。as_edgelist
用每行用起止两个顶点来表示存在一条边外,并不显示图的其他属性(顶点的name属性除外,如果顶点设置的name属性,则用name属性取代顶点 ID)
> g <- sample_gnp(10, 2 / 10)
> as_edgelist(g)
[,1] [,2]
[1,] 3 4
[2,] 2 5
[3,] 2 6
[4,] 1 7
> V(g)$name <- LETTERS[seq_len(gorder(g))]
> as_edgelist(g)
[,1] [,2]
[1,] "C" "D"
[2,] "B" "E"
[3,] "B" "F"
[4,] "A" "G"
> g <- sample_gnp(10, 2 / 10)
> V(g)$label <- letters[seq_len(gorder(g))]
> as_edgelist(g)
[,1] [,2]
[1,] 1 3
[2,] 1 4
[3,] 1 5
[4,] 2 5
5、图转换为其他数据格式
5-1 与Pajek互通的.net
Pajek是开源的大型复杂网络分析工具,是用于研究目前所存在的各种复杂非线性网络的有力工具,用于带上千乃至数百万个结点大型网络的分析和可视化操作。
igraph可以直接输出Pajek支持的.net
文件格式
write_graph(g,'g.net',format = 'pajek')`
*Vertices 10
*Edges
1 3
1 4
1 5
2 5
5 7
输出的结果采用的是边列表的结构,但没有顶点名信息。
目前,我在igraph函数中没有找到解决生成的.net
文件中没有顶点名的问题,便自己写了一个函数data.frame(v=paste(1:gorder(g),V(g)$name))
手动复制的.net
文件中
附录:pajek读入.net
中文乱码解决办法
使用pajek官网推荐的BabelPad软件(下载地址:BabelStone : BabelPad (Unicode Text Editor for Windows))
运行该软件,打开.net
文件,文件–>另存,Engcoding
下拉菜单中选择 ”GB18030"即可。
5-2 与graph包互通的graphNel:as_graphnel
g <- make_ring(10)
V(g)$name <- letters[1:10]
GNEL <- as_graphnel(g)
g2 <- graph_from_graphnel(GNEL)
5-3 与电子表格互通的data.frame:as_data_frame
简版
把电子表格转换为图将放在图的创建部分,本节只谈把图转换为data.farme
转换时可通过设置what参数,决定转换顶点、边、还是两者都转换
本命令只输出构成表的两个顶点的ID或name,不显示边或顶点的其他属性,如需显示其他属性,用5-4的命令。
> igraph::as_data_frame(g,what = 'vertices')
name
a a
b
c c
d d
> igraph::as_data_frame(g,what = 'edges')
from to
1 b c
2 a d
3 d e
4 a f
> igraph::as_data_frame(g,what = 'both')
$vertices
name
a a
b b
c c
d d
$edges
from to
1 b c
2 a d
3 d e
4 a f
注意,R语言中多个包都有as_data_frame函数,比如tibble包、dplyr包、igraph包,所以如果执行上面示例提示错误的话,请在函数前面家包名igraph::as_data_frame()
5-4 与电子表格互通的data.frame:as_long_data_frame
显示属性版
as_long_data_frame
包含关于图的顶点和边的所有元数据。它为每条边包含一行,关于该边及其关联顶点的所有元数据都包含在该行中。包含偶发顶点元数据的列的名称以from_和to_为前缀。前两列始终命名为from和to,它们包含入射顶点的数字id。这些行按数字顶点ID的顺序列出。
> g <- make_(ring(10),
+ with_vertex_(name= letters[1:10],color='green'),
+ with_edge_(weight=1:10,color='red'))
> as_long_data_frame(g)
from to weight color from_name from_color to_name to_color
1 1 2 1 red a green b green
2 2 3 2 red b green c green
3 3 4 3 red c green d green
4 4 5 4 red d green e green
5-5 转换为普通向量:as_ids
本函数将顶点序列或边序列转换为普通向量。
如果图的顶点没有设置name属性,则返回数字向量,如果顶点设置了name属性,则返回字符向量。
对边序列的操作有所不同,如果顶点没有name属性,直接返回边ID序列,比如有7条边,直接返回“1 2 3 4 5 6 7”。但如果顶点设置了name属性,则用a|b
的格式显示每条边构成的序列:
> g <- sample_gnp(10, 2 / 10)
> as_ids(V(g))
[1] 1 2 3 4 5 6 7 8 9 10
> V(g)$name <- letters[1:10]
> as_ids(V(g))
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
> g <- sample_gnp(10, 2 / 10)
> as_ids(E(g))
[1] 1 2 3 4 5 6 7
> V(g)$name <- letters[1:10]
> as_ids(E(g))
[1] "b|c" "d|g" "e|g" "c|h" "d|h" "e|i" "a|j"