前言
将网络图结构与我们平常绘制的数据图进行对比,我们可以发现,图的布局就相当于每个数据点的坐标,而节点就相当于数据点,边就是连接数据点的几何图形,可以是直线、曲线或带箭头的线等。
所以,不同的节点形状和不同的边形状进行组合,就绘制出了上节我们展示的各式各样的图形。
下面,我们要介绍节点与边的相关操作,图结构如下
edges <- read.csv('~/Downloads/p53_signaling_pathway.csv')
colnames(edges) <- c("from", "to")
edges <- edges %>%
mutate(corr = sample(-1:1, size = n(), replace = TRUE))
nodes <- data.frame(
name = unique(union(edges$from, edges$to))
)
nodes$type <- sample(c("up", "down"), size = length(nodes$name), replace = TRUE)
g <- tbl_graph(nodes = nodes, edges = edges)
节点
节点就像是一个数据点,我们也可以用 ggplot2
的方式来绘制所有的节点
ggraph(g, layout = 'kk') +
geom_point(aes(x, y)) +
theme_graph()
因为对图进行布局时,会返回一个数据框
> head(create_layout(g, layout = 'kk'))
x y name friends .ggraph.orig_index circular .ggraph.index
1 0.44575339 -2.14390304 MDM4 few 1 FALSE 1
2 -0.93225120 0.59792670 CHEK1 few 2 FALSE 2
3 -0.07559781 0.92801986 ATR few 3 FALSE 3
4 -0.62547450 0.89958214 CHEK2 few 4 FALSE 4
5 -1.14085626 0.24507223 ATM few 5 FALSE 5
6 2.63609895 -0.06153547 IGFBP3 few 6 FALSE 6
我们实际使用的是这个数据框来绘制的,因此,我们可以在 aes()
中使用布局算法返回的数据框中的变量
虽然可以使用 ggplot2
所提供的几何图形,但是 ggraph
在 ggplot2
的基础上定义了自己的几何图形 geom_node_*()
,让我们的代码更加清晰、简洁。
ggraph
包含的节点几何图形有:
geom_node_point
将节点表示为点,其参数类似于 geom_point
ggraph(g, layout = 'kk') +
geom_node_point(aes(colour = type, shape = type), size = 4) +
theme_graph()
所有的 geom_node_*()
函数都有一个额外的参数 filter
可以用于筛选节点
ggraph(g, layout = 'kk') +
geom_node_point(aes(filter = type == "down"),
shape = 23, size = 4,
colour = "blue", fill = "red") +
theme_graph()
-
geom_node_text
和geom_node_label
这两个函数用于将节点绘制成纯文本或带矩形框的文本,可以类比于 geom_text
和 geom_label
,少了一个 group
参数,其他参数都一样
ggraph(g, layout = 'stress') +
geom_node_text(aes(label = name, colour = type),
angle = 45, repel = TRUE) +
theme_graph()
ggraph(g, layout = 'stress') +
geom_node_label(aes(label = name, fill = type),
colour = "white", fontface = "bold",
repel = TRUE) +
theme_graph()
使用 repel
尽可能避免文本的重叠
geom_node_tile
在 treemap
布局中,可以绘制矩形树状图。参数类似于 geom_tile()
我们先构建一个树结构
flareGraph <- tbl_graph(flare$vertices, flare$edges) %>%
mutate(
class = map_bfs_chr(node_is_root(), .f = function(node, dist, path, ...) {
if (dist <= 1) {
return(shortName[node])
}
path$result[[nrow(path)]]
})
)
绘制矩形树状图
ggraph(flareGraph, 'treemap', weight = size) +
geom_node_tile(aes(fill = class, filter = leaf, alpha = depth), colour = NA) +
geom_node_tile(aes(size = depth), colour = 'white') +
scale_alpha(range = c(1, 0.5), guide = 'none') +
scale_size(range = c(4, 0.2), guide = 'none')
在 partition
布局中,可以绘制冰柱图
ggraph(flareGraph, 'partition') +
geom_node_tile(aes(y = -y, fill = class)) +
theme_graph()
在这里,我们设置了 y = -y
将冰柱图倒置
geom_node_voronoi
维诺图,将节点绘制成类似细胞一样的形状,根据节点将空间进行分割,可以避免节点之间的重叠
ggraph(g, layout = 'stress') +
geom_node_voronoi(aes(fill = type, colour = type), alpha = 0.3) +
geom_node_point() +
geom_edge_link() +
theme_graph()
可以设置 max.radius
参数的值,让节点形状看起来更像细胞
ggraph(g, layout = 'stress') +
geom_node_voronoi(aes(fill = type, colour = type),
alpha = 0.3, max.radius = 1) +
geom_node_point() +
geom_edge_link() +
theme_graph()
geom_node_circle
绘制圆形节点,必须与 coord_fixed()
搭配使用才能绘制圆形。
ggraph(g, layout = 'stress') +
geom_node_circle(aes(fill = type,
colour = type, r = 0.2)) +
geom_edge_link() +
coord_fixed() +
theme_graph()
必须在 aes
中设置 r
的值
与 circlepack
布局搭配使用,可以绘制圆堆积图.
ggraph(flareGraph, 'circlepack') +
geom_node_circle(aes(fill = factor(depth))) +
coord_fixed() +
theme_graph()
那为什么不用设置 r
的值呢?因为该布局返回值中包含了 r
> head(create_layout(flareGraph, 'circlepack'))
x y r circular leaf depth name size shortName class .ggraph.orig_index
1 0.000000 0.0000000 16.1255485 FALSE FALSE 0 flare 0 flare flare 252
2 -7.859692 9.2774060 3.6877643 FALSE FALSE 1 flare.animate 0 animate animate 224
3 -4.679497 3.6143179 2.8071727 FALSE FALSE 1 flare.data 0 data data 227
4 -2.911247 7.5871414 1.5413945 FALSE FALSE 1 flare.display 0 display display 228
5 -2.001902 1.5657652 0.5641896 FALSE FALSE 1 flare.flex 0 flex flex 229
6 -2.878393 -0.9334722 2.0842862 FALSE FALSE 1 flare.physics 0 physics physics 230
.ggraph.index
1 1
2 2
3 3
4 4
5 5
6 6
geom_node_arc_bar
根据内外半径弧线绘制图形,主要与圆形 partition
布局搭配,绘制 sunburst
图
ggraph(flareGraph, 'partition', circular = TRUE, weight = size) +
geom_node_arc_bar(aes(fill = class)) +
coord_fixed() +
theme_graph()
geom_node_range
主要与 fabric
分布一起使用,将节点作为水平线
ggraph(g, layout = 'fabric') +
geom_node_range(aes(color = type, linetype = type),
size = 0.8) +
geom_edge_span() +
theme_graph()
边
我们可以将节点作为数据点,那边就是 geom_segment()
了吗?可以这么理解,但是 ggraph
提供了更多的内容。
直线只是边的其中一种表现形式,有时候甚至没有绘制,而是以一种容量或位置的形式表现,如 treemap
、circle packing
和 partition
布局。但大多数时候都是以某种线条的形式来表现的。
边的几何形状都是用 geom_edge_*()
函数来设置的,几乎每个几何形状都有三个不同水平的函数,即 geom_edge_*0()
和 geom_edge_*2()
。
其中,常规版本,即不带数字后缀的函数,会沿着边将其分割为一系列的点,并且每个点都有一个数字值 index
,该值与点的位置相关。例如 colour = stat(index)
会沿着边的方向设置渐变颜色。
2
后缀的函数为长边模式,在边的起始和终止节点插入节点参数,通常性能更低。只在需要的时候再用。0
后缀的函数是最高性能的版本,会忽略掉很多设置,追求极致的性能,没有 index
计算变量
通常这些函数都有一些共同的参数用于对边进行设置:
-
edge_colour
,也可以使用colour
参数,会自动进行转换 edge_width
edge_linetype
edge_alpha
filter
geom_edge_*()
和 geom_edge_*2()
函数的参数还可以设置边的标签等:
start_cap
end_cap
label
label_pos
label_size
angle
hjust
vjust
family
fontface
lineheight
1. link
使用直线连接节点
ggraph(g, layout = 'stress') +
geom_edge_link0(aes(colour = factor(corr))) +
theme_graph()
使用 index
变量来绘制渐变色
ggraph(g, layout = 'stress') +
geom_edge_link(aes(colour = factor(corr), alpha = stat(index))) +
theme_graph()
使用节点的类型为边上色,记住不能直接使用 type
,类型不同的节点之间的边会有两种颜色
ggraph(g, layout = 'stress') +
geom_edge_link2(aes(colour = node.type)) +
theme_graph()
2. fan
如果两个节点之间的边不只一条,那直接绘制直线的方式是不行的。对于存在平行边的图,可以使用 geom_edge_fan()
来绘制,不同的平行边,会绘制成不同曲率的圆弧,而没有平行边的还是绘制成直线。例如
gr <- create_notable('bull') %>%
convert(to_directed) %>%
bind_edges(data.frame(from = c(1, 2, 2, 3), to = c(2, 1, 3, 2))) %E>%
mutate(class = sample(letters[1:3], 9, TRUE)) %N>%
mutate(class = sample(c('x', 'y'), 5, TRUE))
ggraph(gr, 'stress') +
geom_edge_fan2(aes(colour = node.class)) +
theme_graph()
其他设置与 link
相同
上面的代码中 %E>%
管道表示对边数据框进行操作,%N>%
表示对节点数据框进行操作
3. parallel
与 fan
类似,将平行边绘制成平行线
ggraph(gr, 'stress') +
geom_edge_parallel0(aes(colour = class)) +
theme_graph()
4. loops
如果图存在自循环,正常使用上面的方法是不会显示的,因为这种边是没有长度的,可以使用 geom_edge_loop
ggraph(gr, 'stress') +
geom_edge_loop(aes(colour = stat(index))) +
geom_edge_fan(aes(colour = stat(index))) +
theme_graph()
自循环没有 geom_edge_loop2()
函数
5. density
添加密度阴影
ggraph(g, layout = 'stress') +
geom_edge_density(aes(fill = factor(corr))) +
geom_edge_link(alpha = 0.25) +
theme_graph()
6. arcs
曲线边,通常与 linear
和 circular
布局一起使用。
ggraph(g, 'linear') +
geom_edge_arc2(aes(colour = node.type), strength = 0.6) +
theme_graph()
圆形布局
ggraph(g, 'linear', circular = TRUE) +
geom_edge_arc2(aes(colour = node.type), strength = 0.6) +
theme_graph()
7. elbow
用于绘制树状图,用直角边连接两个节点
irisDen <- hclust(dist(iris[1:4], method = 'euclidean'), method = 'ward.D2') %>%
as_tbl_graph() %>%
mutate(class = sample(letters[1:3], n(), TRUE)) %>%
activate(edges) %>%
mutate(class = sample(letters[1:3], n(), TRUE))
ggraph(irisDen, 'dendrogram') +
geom_edge_elbow2(aes(colour = node.class)) +
theme_graph()
上面的代码中,activate()
函数用于激活边或节点数据框,即 activate(edges)
表示后面的管道操作都是针对边数据框来进行的
圆形布局
ggraph(irisDen, 'dendrogram', circular = TRUE) +
geom_edge_elbow(aes(colour = stat(index))) +
coord_fixed() +
theme_graph()
8. diagonals
将边绘制成对角贝塞尔曲线,也是一种树状图
ggraph(irisDen, 'dendrogram') +
geom_edge_diagonal0(aes(colour = class)) +
theme_graph()
9. bends
也是绘制树状图,是 diagonals
的一种替代方案,相当于二次贝塞尔曲线,圆角连接线
ggraph(irisDen, 'dendrogram') +
geom_edge_bend2(aes(colour = node.class)) +
theme_graph()
10. hive
该几何图形只能与 hive
布局一起使用,将边绘制为贝塞尔曲线
g <- g %>%
mutate(friends = ifelse(
centrality_degree(mode = 'all') < 3, "few",
ifelse(centrality_degree(mode = 'all') > 3, "many", "medium")
))
ggraph(g, 'hive', axis = friends) +
geom_edge_hive(aes(colour = factor(corr))) +
geom_axis_hive(aes(colour = friends), size = 2, label = FALSE) +
coord_fixed() +
theme_graph()
11. span
将边绘制为竖直线,用于连接水平线表示的节点,只能在 fabric
布局中使用
ggraph(g, 'fabric', sort.by = node_rank_fabric()) +
geom_node_range(aes(colour = type)) +
geom_edge_span(aes(colour = factor(corr)), end_shape = 'circle') +
scale_edge_colour_brewer(palette = "Set1") +
theme_graph()
12. point and tile
对于 matrix
布局,x
轴表示起始节点的位置,y
轴表示终止节点的位置,表可以表示为对应于 (x, y)
的图形
对于下面的图
gr <- create_notable('zachary') %>%
mutate(group = group_infomap()) %>%
morph(to_split, group) %>%
activate(edges) %>%
mutate(edge_group = as.character(.N()$group[1])) %>%
unmorph()
绘制不同形状的点来表示边
ggraph(gr, 'matrix', sort.by = node_rank_hclust()) +
geom_edge_point(aes(colour = edge_group, edge_shape = edge_group),
mirror = TRUE, edge_size = 3) +
scale_y_reverse() +
coord_fixed() +
theme_graph()
或者使用矩形来表示
ggraph(gr, 'matrix', sort.by = node_rank_hclust()) +
geom_edge_tile(aes(fill = edge_group),
mirror = TRUE, edge_size = 3) +
scale_y_reverse() +
coord_fixed() +
theme_graph()
边样式
1. strength
许多边几何图形都有一个 strength
参数,用于表示它们与直线的偏离程度,strength = 0
将会变成直线,默认样式对应于 strength = 1
。
例如
small_tree <- create_tree(10, 2)
ggraph(small_tree, 'dendrogram') +
geom_edge_elbow(strength = 0.75) +
theme_graph()
ggraph(small_tree, 'dendrogram') +
geom_edge_diagonal(strength = 0.5) +
theme_graph()
2. 边的修饰
边不仅仅只是一条线,它还可以添加标签和箭头
2.1 箭头
给边添加箭头的方式与 ggplot2
一样,例如
ggraph(g, layout = 'graphopt') +
geom_edge_link(arrow = arrow(length = unit(4, 'mm'))) +
geom_node_point(size = 5) +
theme_graph()
但是这种不好看,箭头的顶点都延伸到节点的中心了,所以,我们需要设置边与两端节点之间的间隔
ggraph(g, layout = 'graphopt') +
geom_edge_link(arrow = arrow(length = unit(4, 'mm')),
end_cap = circle(3, 'mm')) +
geom_node_point(size = 5) +
theme_graph()
可以看到,箭头已经与节点分开了。
可以使用 circle()
、square()
、ellipsis()
和 rectangle()
函数来设置不同类型的间隔
ggraph(g, layout = 'stress') +
geom_edge_arc(aes(colour = corr),
arrow = arrow(length = unit(4, 'mm')),
start_cap = square(3, 'mm'),
end_cap = circle(3, 'mm')) +
geom_node_point(aes(colour = type), size = 5) +
theme_graph()
对于纯文本的节点,可以计算标签的矩形范围来控制间隔
ggraph(g, layout = 'graphopt') +
geom_edge_link(aes(colour = corr,
start_cap = label_rect(node1.name),
end_cap = label_rect(node2.name)),
arrow = arrow(length = unit(4, 'mm'))) +
geom_node_text(aes(label = name), size = 3) +
theme_graph()
2.2 标签
我们可以为边添加标签
edges <- edges %>%
mutate(corr = sample(-1:1, size = n(), replace = TRUE),
type = ifelse(corr == -1, "Neg",
ifelse(corr == 0, "None", "Pos"))
)
g <- tbl_graph(nodes = nodes, edges = edges)
ggraph(g, layout = 'stress') +
geom_edge_link(aes(label = type,
end_cap = circle(2, 'mm')),
arrow = arrow(length = unit(4, 'mm'))) +
geom_node_point(aes(colour = type), size = 3) +
theme_graph()
让标签沿着边放置
ggraph(g, layout = 'stress') +
geom_edge_link(aes(label = type),
angle_calc = 'along',
label_dodge = unit(2.5, 'mm'),
end_cap = circle(2, 'mm'),
arrow = arrow(length = unit(4, 'mm'))) +
geom_node_point(aes(colour = type), size = 3) +
theme_graph()