R 数据可视化 —— igraph 布局与绘图

前言

图是一种抽象的数学结构,不同对象之间通过线条连接起来,而对象在图中并没有固定的位置表示,不同的放置位置显示出的效果通常是不一样的。

选择一种优秀的布局方式,可以让图形呈现出更好的效果,而 igraph 的工作方式是通过一类 node-edge 的算法来进行布局的。

算法会将节点作为二维或三维空间上的点,使用直线或曲线来连接两个相邻的节点。对于有向图来说,带箭头的线表示连接方向。在边两端的节点可以由不同的几何图形来表示,而一些重要的节点或边的属性可以用来设置图形参数值。

图的可视化通常由三个步骤组成:

  1. 找到节点在二维或三维空间上合适的排列方式,这一步可能是最重要的。好的布局往往能够解释一些有趣的现象,如对称性、密集连接区域。


从上面的图可以看出,第一个随机布局,完全看不出什么有价值的信息,而从第二和第三幅可以看出存在某些对称性

  1. 将节点和边的重要属性映射到图像上

  2. 安排好节点与边的绘制顺序

布局

布局算法用于寻找合适的排列方式,但是找到优秀的排列确实不容易,因此,大部分的算法都是通过间接测量来评估布局的好坏。而且是一种启发式的方法来求解,所以并不是每次都能找到最优解。

大部分的布局算法只适用于较小的图,较大的图需要一些特殊技术来处理。

igraph 中所有布局算法都是 layout_*() 形式,每个布局算法都会返回一个 layout 实例,类似列表型的对象,包含了每个节点在图中的 x、y 坐标。

布局函数


布局是通过 plot() 函数的 layout 参数来控制的,例如

我们随机生成一张图

g <- sample_gnm(n = 15, m = 25)
plot(g)

随机布局

plot(g, layout = layout_randomly)

圆形布局

plot(g, layout = layout_in_circle)

力导向布局,最常用的是 Fruchterman-Reingold 算法

plot(g, layout = layout_with_fr)

另一种比较常用的力导向算法 Kamada Kawai

plot(g, layout = layout_with_kk)

设置树状图

par(mfrow=c(1, 3))
tree <- make_tree(20, 3)
plot(tree, layout=layout_as_tree)
# 自底向上
plot(tree, layout=layout_as_tree(tree, flip.y=FALSE))
# 圆形排列
plot(tree, layout=layout_as_tree(tree, circular=TRUE))

设置根节点

par(mfrow = c(1, 2))
tree2 <- make_tree(10, 3) + make_tree(10, 2)
plot(tree2, layout = layout_as_tree)
plot(tree2, layout = 
       layout_as_tree(
         tree2, root = c(1, 11),
         rootlevel = c(2, 1)
         )
     )

传递位置矩阵

g <- sample_gnm(n = 15, m = 25)

l <- cbind(1:vcount(g), c(1, vcount(g):2))
plot(g, layout = l)

绘制图形

我们在前面的介绍中,都只用了 plot() 函数来绘制图形,所有的一切图形属性都是默认的,默认的颜色、大小、布局等。在我们设置了边和节点的颜色属性之后,图片才显示出了不同的颜色。

在这里,我们将介绍如何来调整节点和边的图形属性,igraph 的绘制参数包括

我们以 KEEE 数据库中的 ErbB 信号通路中的 EGF-ERBB2-RAS-ERK 信号通路为例。

利用 KEGG 官网提供的 API 接口,处理并获取通路的图结构,然后将其转换为基因 symbol 边列表,代码如下

library(rvest)
library(tidyverse)

# 解析 ErbB signaling pathway 网络结构
kmgl <- read_html('http://rest.kegg.jp/get/hsa04012/kgml')
# 获取所有基因的 entry id
node <- kmgl %>% html_elements(xpath = '//entry[@type="gene"]') %>%
  html_attrs() %>%
  lapply(function (x) c(x['id'], x['name'])) %>%
  do.call(rbind, .) %>%
  as.data.frame()
# 获取所有 entry 之间的互作关系
edge <- kmgl %>% html_elements(xpath = '//relation') %>%
  html_attrs() %>%
  lapply(function (x) c(x['entry1'], x['entry2'])) %>%
  do.call(rbind, .) %>%
  as.data.frame()
# 将 entry id 转换为 KEGG gene ID
net <- inner_join(node, edge, by = c('id' = 'entry1')) %>%
  inner_join(node, by = c('entry2' = 'id')) %>%
  dplyr::select(name.x, name.y) %>%
  dplyr::rename(source = name.x, target = name.y)
# 将多对一转换为多个一对一
edges_list <- list()
for (i in seq_along(net[,1])) {
  gids <- str_split(gsub("hsa:", '', net[i,]), ' ')
  s <- data.frame('source' = gids[[1]])
  t <- data.frame('target' = gids[[2]])
  edges_list[[i]] <- crossing(s, t)
}
# 合并为边列表
all_edges <- do.call(rbind, edges_list)

# EGF-ERBB2-RAS-ERK signaling pathway genes
sub_path <- c(
  1950, 2064, 1956, 2885,
  6654, 6655, 3265, 3845,
  4893, 369, 673, 5894, 5604,
  5605, 5594, 5595
  )

path_id <- all_edges %>%
  filter(source %in% sub_path & target %in% sub_path)

# 将 ENTREZID 转换为 SYMBOL
library(org.Hs.eg.db)
s <- select(org.Hs.eg.db, keys = path_id$source, columns = 'SYMBOL', 'ENTREZID')
t <- select(org.Hs.eg.db, keys = path_id$target, columns = 'SYMBOL', 'ENTREZID')
# 获取最终的 SYMBOL 边结构
sub_path <- tibble(
  source = s$SYMBOL,
  target = t$SYMBOL
) %>%
  distinct()

# 设置基因的类型,是否癌基因或抑癌基因
genes <- unique(union(sub_path$source, sub_path$target))
gtype <- rep("other", length(genes))
gtype[grep("(RAS)|(RAF)", genes)] <- "proto-oncogene"

gene_type <- data.frame(
  genes = genes,
  type = gtype
)
# 获取突变信息
mut <- read_delim('~/Downloads/luad_broad/data_mutations_mskcc.txt', delim = '\t') %>%
  dplyr::select(Hugo_Symbol, Variant_Classification) %>%
  filter(Hugo_Symbol %in% genes)

# 每个基因的突变频数,即突变类型
mut_cnt <- group_by(mut, Hugo_Symbol) %>%
  summarise(mut_count = n())
mut_info <- distinct(mut) %>%
  group_by(Hugo_Symbol) %>%
  summarise(mut_class = n()) %>%
  left_join(mut_cnt)
# 将基因的所有信息整合在一起
node_attr <- left_join(gene_type, mut_info, by = c("genes" = "Hugo_Symbol")) %>%
  replace_na(list(mut_class = 0, mut_count = 0))

获取代码:https://github.com/dxsbiocc/learn/blob/main/R/data_process/get_hsa04012_kgml.R

突变数据:https://github.com/dxsbiocc/learn/blob/main/data/mutation/data_mutations_mskcc.txt

为了方便,我们并没有考虑通路中基因或蛋白复合物的情况。

创建图

g <- graph_from_data_frame(d = sub_path, vertices = node_attr)
plot(g, layout = layout_with_fr)

有两种方法来设置节点和边的图形属性,第一种就是在 plot() 函数中传递对应的参数

例如,设置边的箭头大小和曲率

plot(g, layout = layout_as_tree, 
     edge.arrow.size=.4, 
     edge.curved=.1
     )
plot(g, layout = layout.circle, 
     edge.arrow.size=.2, 
     edge.color="#1f78b4",
     vertex.color="#33a02c", 
     vertex.frame.color="#fb9a99",
     vertex.label.color="#ff7f00"
     )

第二种方法,将图形属性添加到 igraph 对象中。

例如,我们想根据基因的类型设置不同的颜色,根据基因突变频率设置节点的大小

# 设置基因的颜色
V(g)$color <- if_else(V(g)$type == "other", "#80b1d3", "#fb8072")
# 设置基因的大小
V(g)$size <- log10(V(g)$mut_count + 1) * 10
# 不显示基因名
V(g)$label <- NA
# 假设我们有基因之间的相关性,用于设置边的宽度
E(g)$width <- abs(rnorm(n = ecount(g)))
# 设置箭头大小和边的颜色
E(g)$arrow.size <- .2
E(g)$edge.color <- "gray80"
# 设置布局
graph_attr(g, "layout") <- layout_with_lgl

plot(g)

可以在 plot 中覆盖图形属性参数的值

plot(g, edge.color="orange", vertex.color="gray50") 

添加图例

legend(x=-1.5, y=-1.1, c("other","proto-oncogene"), pch=21,
       col="#777777", pt.bg=c("#80b1d3", "#fb8072"), 
       pt.cex=2, cex=.8, bty="n", ncol=1)

我们可以只显示节点的文本,而不添加形状

plot(g, vertex.shape="none", 
     vertex.label.font=2, 
     vertex.label.color="gray40",
     vertex.label.cex=.7, 
     edge.color="gray85",
     edge.arrow.size = .2
     )

为基因之间的调控与被调控关系设置不同的颜色

graph_attr(g, "layout") <- layout_with_lgl
V(g)$color <- if_else(V(g)$type == "other", "#80b1d3", "#fb8072")

edge.start <- ends(g, es=E(g), names=F)[,1]
edge.col <- V(g)$color[edge.start]

plot(g, edge.color=edge.col, edge.curved=.1, edge.arrow.size = .3) 

根据基因的度来设置基因的大小

deg <- degree(g, mode="all")
V(g)$size <- deg * 6

plot(g, edge.color=edge.col, 
     edge.curved=.1, edge.arrow.size = .3)

大家可以根据需求自己设置不同的颜色属性,就不再一一列举了

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

推荐阅读更多精彩内容