R 数据可视化 —— ggraph 边与节点

前言

将网络图结构与我们平常绘制的数据图进行对比,我们可以发现,图的布局就相当于每个数据点的坐标,而节点就相当于数据点,边就是连接数据点的几何图形,可以是直线、曲线或带箭头的线等。

所以,不同的节点形状和不同的边形状进行组合,就绘制出了上节我们展示的各式各样的图形。

下面,我们要介绍节点与边的相关操作,图结构如下

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 所提供的几何图形,但是 ggraphggplot2 的基础上定义了自己的几何图形 geom_node_*(),让我们的代码更加清晰、简洁。

ggraph 包含的节点几何图形有:

  1. 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()
  1. geom_node_textgeom_node_label

这两个函数用于将节点绘制成纯文本或带矩形框的文本,可以类比于 geom_textgeom_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 尽可能避免文本的重叠

  1. 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 将冰柱图倒置

  1. 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()
  1. 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
  1. geom_node_arc_bar

根据内外半径弧线绘制图形,主要与圆形 partition 布局搭配,绘制 sunburst

ggraph(flareGraph, 'partition', circular = TRUE, weight = size) +
  geom_node_arc_bar(aes(fill = class)) +
  coord_fixed() +
  theme_graph()
  1. 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 提供了更多的内容。

直线只是边的其中一种表现形式,有时候甚至没有绘制,而是以一种容量或位置的形式表现,如 treemapcircle packingpartition 布局。但大多数时候都是以某种线条的形式来表现的。

边的几何形状都是用 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

曲线边,通常与 linearcircular 布局一起使用。

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()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容