R 数据可视化 —— circlize 和弦图

前言

和弦图可以用连接线或条带的方式展示不同对象之间的关系

和弦图主要从以下几个层面来展示关系信息:

  1. 连接,可以直接显示不同对象之间存在关系
  2. 连接的宽度与关系的强度成正比
  3. 连接的颜色可以代表关系的另一种映射,如关系的类型
  4. 扇形的大小,代表对象的度量

circlize 中,有一个专门的函数用于绘制和弦图:chordDiagram(),并不需要使用 circos.link 来一个个绘制。

chordDiagram() 函数支持两种格式的输入数据:

  • 邻接矩阵:

    行和列分别表示连接的对象,如果对象之间存在关系,则对应行列的值将表示关系的强度,例如

> mat <- matrix(1:9, 3)
> rownames(mat) <- letters[1:3]
> colnames(mat) <- LETTERS[1:3]
> mat
  A B C
a 1 4 7
b 2 5 8
c 3 6 9
  • 邻接列表:

    包含三列数据,前两列的值表示连接的两个对象,第三列值为连接的强度,例如

> df <- data.frame(from = letters[1:3], to = LETTERS[1:3], value = 1:3)
> df
  from to value
1    a  A     1
2    b  B     2
3    c  C     3

输入格式的不同,也会影响图形参数的输入格式。如果输入是矩阵,则图形参数也是以矩阵的方式传递,如果输入的是数据框,则图形参数直接添加到后续的列中即可

使用矩阵会比较直观,而数据框能更加灵活的控制图像,两种输入格式也可以相互转换

library(tibble)
library(tidyr)

> mat_df <- rownames_to_column(as.data.frame(mat), "from") %>%
+   pivot_longer(cols = -from, names_to = "to", values_to = "value")
>
> mat_df
# A tibble: 9 x 3
  from  to    value
  <chr> <chr> <int>
1 a     A         1
2 a     B         4
3 a     C         7
4 b     A         2
5 b     B         5
6 b     C         8
7 c     A         3
8 c     B         6
9 c     C         9

> pivot_wider(mat_df, names_from = to) %>%
+   column_to_rownames("from") %>%
+   as.matrix()
  A B C
a 1 4 7
b 2 5 8
c 3 6 9

和弦图

1. 简单绘制

要绘制和弦图也很简单,我们先构造矩阵类型的数据

mat <- matrix(sample(18, 18), 3, 6) 
rownames(mat) <- paste0("S", 1:3)
colnames(mat) <- paste0("E", 1:6)

> mat
   E1 E2 E3 E4 E5 E6
S1 14  8  9 18  6  5
S2 13  7  2 15 16 12
S3 17  4  3  1 10 11

绘制方式很简单

chordDiagram(mat)
# chordDiagram(df)
circos.clear()
image.png

绘制数据框类型的数据也是类似的,这里不再展示

扇形的排列顺序是 union(rownames(mat), colnames(mat))union(df[[1]], df[[2]]),我们可以使用 order 参数来指定排列顺序

par(mfrow = c(1, 2))
chordDiagram(mat, order = c("S2", "S1", "S3", "E4", "E1", "E5", "E2", "E6", "E3"))
circos.clear()

chordDiagram(mat, order = c("S2", "E2", "E3", "S1", "E4", "E1", "S3", "E5", "E6"))
circos.clear()

2. 使用 circos.par() 调整

chordDiagram() 函数是使用 circlize 的基础函数实现的,可以用 circos.par() 函数来控制布局

例如,如果想要让行和列的扇形之间间隔更大,可以设置 gap.after

circos.par(gap.after = c(rep(5, nrow(mat)-1), 15, rep(5, ncol(mat)-1), 15))
chordDiagram(mat)
circos.clear()

也可以使用命名向量的形式,指定每个扇形的间隔

circos.par(gap.after = c(
  "S1" = 5, "S2" = 5, "S3" = 15, 
  "E1" = 5, "E2" = 5, "E3" = 5, 
  "E4" = 5, "E5" = 5, "E6" = 15)
  )

有一个专门的参数 big.gap 可以用来指定行列扇形之间的间隔

chordDiagram(mat, big.gap = 30)
circos.clear()

但是,必须保证行列之间的扇形没有交叠

与正常的圆形图类似,也可以设置扇形的排列方向以及起始位置

par(mfrow = c(1, 2))
circos.par(start.degree = 85, clock.wise = FALSE)
chordDiagram(mat)
circos.clear()

circos.par(start.degree = 85)
chordDiagram(mat, order = c(rev(colnames(mat)), rev(rownames(mat))))
circos.clear()

3. 颜色

3.1 设置扇形颜色

通常扇形分为两个组,其中一个分组为矩阵的行或数据框的第一列,另一个分组为矩阵的列或数据框的第二列。

连接的颜色默认对应于第一个分组的扇形的颜色,所以,改变扇形的颜色也会影响连接的颜色。

扇形的颜色使用 grid.col 参数来设置,最好使用命名向量的方式设置颜色映射。如果只给定颜色向量值,则按照扇形的顺序设置颜色

par(mfcol = c(1, 2))
grid.col <- c(
  S1 = "#ff7f00", S2 = "#984ea3", S3 = "#4daf4a",
  E1 = "grey", E2 = "grey", E3 = "grey", 
  E4 = "grey", E5 = "grey", E6 = "grey"
  )
chordDiagram(mat, grid.col = grid.col)
chordDiagram(t(mat), grid.col = grid.col)
circos.clear()

3.2 设置连接颜色

transparency 可以设置连接颜色的透明度,0 表示不透明,1 表示完全透明,默认透明度为 0.5

chordDiagram(mat, grid.col = grid.col, transparency = 0.3)

对于邻接矩阵,连接的颜色可以使用颜色矩阵来指定

col_mat <- rand_color(length(mat), transparency = 0.5)

chordDiagram(mat, grid.col = grid.col, col = col_mat)
circos.clear()

因为在创建颜色矩阵时,以及指定了透明度,如果再设置 transparency 参数的值将会被忽略

对于邻接列表,可以使用一个与数据框长度相同的颜色向量来指定

col <- rand_color(nrow(df))
chordDiagram(df, grid.col = grid.col, col = col)

如果关系的强度,即矩阵的值是连续型的,可以传递一个自定义的颜色映射函数

col_fun <- colorRamp2(
  range(mat), c("#9970ab", "#5aae61"), 
  transparency = 0.5
  )
chordDiagram(mat, grid.col = grid.col, col = col_fun)

对于邻接列表也是类似的,可以使用颜色映射函数或第三列的值

chordDiagram(df, grid.col = grid.col, col = col_fun)
# or
chordDiagram(df, grid.col = grid.col, col = col_fun(df[, 3]))

对于邻接矩阵,还可以使用 row.colcolumn.col 为行或列设置对应的颜色,例如

par(mfcol = c(1, 2))
chordDiagram(mat, grid.col = grid.col, row.col = 1:3)
chordDiagram(mat, grid.col = grid.col, column.col = 1:6)
circos.clear()

4. 连接的边框

link.lwdlink.ltylink.border 三个参数用于控制边框的宽度、线型和颜色,参数的值可以是标量值或矩阵。例如

chordDiagram(
  mat, grid.col = grid.col, link.lwd = 2, 
  link.lty = 2, link.border = "red"
  )
circos.clear()

设置为矩阵

lwd_mat <- matrix(1, nrow = nrow(mat), ncol = ncol(mat))
lwd_mat[mat > 12] <- 2
border_mat <- matrix(NA, nrow = nrow(mat), ncol = ncol(mat))
border_mat[mat > 12] <- "red"
chordDiagram(
  mat, grid.col = grid.col, link.lwd = lwd_mat, 
  link.border = border_mat
  )
circos.clear()

矩阵的大小不一定要与输入数据相同,可以是其子集,但需要保证行列名称对应

border_mat2 <- matrix("black", nrow = 1, ncol = ncol(mat))
rownames(border_mat2) <- rownames(mat)[2]
colnames(border_mat2) <- colnames(mat)
chordDiagram(mat, grid.col = grid.col, link.lwd = 2, 
             link.border = border_mat2)
circos.clear()

还可以将图形参数设置为三列的数据框,前两列用于标识矩阵的行列,第三列为对应的图形参数的值,例如

lty_df <- data.frame(c("S1", "S2", "S3"), c("E5", "E6", "E4"), c(1, 2, 3))
lwd_df <- data.frame(c("S1", "S2", "S3"), c("E5", "E6", "E4"), c(2, 2, 2))
border_df <- data.frame(c("S1", "S2", "S3"), c("E5", "E6", "E4"), c(1, 1, 1))
chordDiagram(
  mat, grid.col = grid.col, link.lty = lty_df, 
  link.lwd = lwd_df, link.border = border_df
  )
circos.clear()

如果输入数据是数据框,只要将图形参数设置为一个向量即可,会更加方便

5. 高亮连接

有时候,我们可能需要着重强调一些连接,我们可以为这些连接设置不同的透明度或者只绘制需要强调的连接

例如,我们为其他连接设置更高的透明度,来凸显我们需要展示的连接

chordDiagram(
  mat, grid.col = grid.col, 
  row.col = c("#FF000080", "#00FF0010", "#0000FF10")
  )
circos.clear()

如果是高亮超过阈值的值,且传递的是颜色矩阵,可以将低于阈值的颜色值设置完全透明

col_mat[mat < 12] <- "#00000000"
chordDiagram(mat, grid.col = grid.col, col = col_mat) 
circos.clear()

或者传递颜色映射函数

col_fun <- function(x) ifelse(x < 12, "#00000000", "#FF000080")
chordDiagram(mat, grid.col = grid.col, col = col_fun)
circos.clear()

但是这两种方法都会绘制所有的连接,如果以数据框的形式来指定需要绘制的连接,那未指定的将不会被绘制

col_df <- data.frame(
  c("S1", "S2", "S3"), c("E5", "E6", "E4"), 
  c("#FF000080", "#00FF0080", "#0000FF80")
  )
chordDiagram(mat, grid.col = grid.col, col = col_df)
circos.clear()

高亮邻接列表的连接会更简单,只要传递颜色向量即可

df <- rownames_to_column(as.data.frame(mat), "from") %>%
  pivot_longer(cols = -from, names_to = "to", values_to = "value")
  
col <- rand_color(nrow(df))
col[df[[3]] < 10] <- "#00000000"
chordDiagram(df, grid.col = grid.col, col = col)
circos.clear()

有些图像格式不支持透明度,比如 GIF,可以使用 link.visible 参数来设置连接的显示,可以是逻辑矩阵或逻辑向量

col <- rand_color(nrow(df))
chordDiagram(df, grid.col = grid.col, link.visible = df[[3]] >= 10)
circos.clear()

6. 连接的顺序

每个扇形中的连接都会自动调整,让图形看起来更好看,但有时根据宽度对连接进行排序也很有用。

可以使用 link.sortlink.decreasing 两个参数来控制连接的顺序

par(mfcol = c(1, 2))
chordDiagram(
  mat, grid.col = grid.col, 
  link.sort = TRUE, link.decreasing = TRUE
  )
title("link.decreasing = TRUE", cex = 0.6)
chordDiagram(
  mat, grid.col = grid.col, 
  link.sort = TRUE, link.decreasing = FALSE
  )
title("link.decreasing = FALSE", cex = 0.6)

7. 添加连接的顺序

默认情况下,连接的绘制顺序按照其在矩阵或数据框中出现的顺序进行绘制,link.zindex 可以控制连接的绘制顺序,通常更宽的连接在前面绘制

par(mfcol = c(1, 2))
chordDiagram(mat, grid.col = grid.col, transparency = 0)
# 根据值的大小设置连接的添加顺序
chordDiagram(mat, grid.col = grid.col, transparency = 0, 
             link.zindex = rank(mat))

8. 自连接

self.link 用于控制自连接的样式,可选的值为 12

par(mfcol = c(1, 2))
df2 <- data.frame(start = c("a", "b", "c", "a"), end = c("a", "a", "b", "c"))
chordDiagram(df2, grid.col = 1:3, self.link = 1)
title("self.link = 1")
chordDiagram(df2, grid.col = 1:3, self.link = 2)
title("self.link = 2")

9. 对称矩阵

如果邻接矩阵是对称的,可以设置 symmetric = TRUE,将绘制下三角,不包含对角线

par(mfcol = c(1, 2))
mat3 <- matrix(rnorm(25), 5)
colnames(mat3) <- letters[1:5]
cor_mat <- cor(mat3)
col_fun <- colorRamp2(c(-1, 0, 1), c("green", "white", "red"))
chordDiagram(cor_mat, grid.col = 1:5, symmetric = TRUE, col = col_fun)
title("symmetric = TRUE")
chordDiagram(cor_mat, grid.col = 1:5, col = col_fun)
title("symmetric = FALSE")

10. 连接方向

directional 参数用于设置连接的方向,对于邻接矩阵,1 表示行指向列,邻接列表为第一列指向第二列,-1 为反向,2 为双向。例如

par(mfrow = c(1, 3))
chordDiagram(
  mat, grid.col = grid.col,
  directional = 1)
chordDiagram(
  mat, grid.col = grid.col, 
  directional = 1, diffHeight = mm_h(5))
chordDiagram(
  mat, grid.col = grid.col, 
  directional = -1)

默认情况下,连接的两端中代表起始方向的那端会更矮,可以使用 diffHeight 设置其高度

邻接矩阵的行名和类名是可以重叠的,可以根据其连接方向来进去区分

mat2 <- matrix(sample(100, 35), nrow = 5)
rownames(mat2) <- letters[1:5]
colnames(mat2) <- letters[1:7]
chordDiagram(mat2, grid.col = 1:7, directional = 1, row.col = 1:5)

可以删除自连接

mat3 <- mat2
for(cn in intersect(rownames(mat3), colnames(mat3))) {
  mat3[cn, cn] = 0
}

连接可以在内部添加箭头来标识方向,当 direction.type 参数设置为 arrows 时,我们可以为箭头设置不同的图形属性。

如果只要为某些连接添加箭头,则需要传递包含三列的数据框,例如

arr.col <- data.frame(
  c("S1", "S2", "S3"), 
  c("E5", "E6", "E4"), 
  c("black", "red", "blue")
  )
chordDiagram(
  mat, grid.col = grid.col, 
  directional = 1, direction.type = "arrows",
  link.arr.col = arr.col, link.arr.length = 0.2
  )

结合 arrowsdiffHeight

arr.col <- data.frame(
  c("S1", "S2", "S3"), 
  c("E5", "E6", "E4"), 
  c("black", "red", "blue")
  )
chordDiagram(
  mat, grid.col = grid.col, directional = 1, 
  direction.type = c("diffHeight", "arrows"),
  link.arr.col = arr.col, link.arr.length = 0.2
  )

使用 link.arr.type 可以设置箭头的类型,如 circleellipsecurved 等。对于连接较多的情况,可以设置为 big.arrow 让连接条带显示箭头

chordDiagram(
  matrix(rnorm(64), 8), directional = 1, 
  direction.type = c("diffHeight", "arrows"),
  link.arr.type = "big.arrow"
  )

如果 diffHeight 设置为负值,则连接的起始端会比终止端更长

chordDiagram(
  matrix(rnorm(64), 8), directional = 1, 
  direction.type = c("diffHeight", "arrows"),
  link.arr.type = "big.arrow",
  diffHeight = -mm_h(2)
  )

在前面的图形中,当 diffHeight 为正值时,较短的连接端会出现一个条形,表示的是高度的比例。如果要将其删除,可以设置 link.target.prop = FALSEtarget.prop.height 参数可以设置条形的高度

par(mfrow = c(1, 2))
chordDiagram(
  mat, grid.col = grid.col, 
  directional = 1,  
  link.target.prop = FALSE
  )
chordDiagram(
  mat, grid.col = grid.col, 
  directional = 1, diffHeight = mm_h(10), 
  target.prop.height = mm_h(8)
  )

11. 缩放

默认情况下,扇形的范围是根据值来划分的,通过设置 scale = TRUE,可以让每个扇形宽度相同,且连接的宽度将表示占比

mat <- matrix(sample(18, 18), 3, 6) 
rownames(mat) <- paste0("S", 1:3)
colnames(mat) <- paste0("E", 1:6)

grid.col <- c(
  S1 = "red", S2 = "green", S3 = "blue",
  E1 = "grey", E2 = "grey", E3 = "grey", 
  E4 = "grey", E5 = "grey", E6 = "grey"
  )
par(mfrow = c(1, 2))
chordDiagram(mat, grid.col = grid.col)
title("Default")
chordDiagram(mat, grid.col = grid.col, scale = TRUE)
title("scale = TRUE")

12. 删减

如果矩阵中存在某些极小的值时,会将其删除,不会显示在图中。例如

mat <- matrix(rnorm(36), 6, 6)
rownames(mat) <- paste0("R", 1:6)
colnames(mat) <- paste0("C", 1:6)
mat[2, ] <- 1e-10
mat[, 3] <- 1e-10

chordDiagram(mat)

图中,第二行和第三列没有显示

> circos.info()
All your sectors:
 [1] "R1" "R3" "R4" "R5" "R6" "C1" "C2" "C4" "C5" "C6"

All your tracks:
[1] 1 2

Your current sector.index is C6
Your current track.index is 2

从图像信息也可以看出

可以设置 reduce 参数的值,如果某一扇形区域占整个圆的比例少于该值,将会删除该区域。

如果将对应行列的值设置为 0,也可以删除对应的扇形

mat[2, ] = 0
mat[, 3] = 0

13. 数据框输入

在前面的例子中,我们着重介绍的是矩阵形式的输入格式,而对于数据框形式的输入,其设置方式也是类似的。

对于数据框形式的输入,其每一行代表的是以连接,许多图形参数都可以以列的方式添加到数据框的后面。

例如下面这些图形属性名称

transparency
col # 也可以是函数
link.border
link.lwd
link.lty
link.arr.length
link.arr.width
link.arr.type
link.arr.lwd
link.arr.lty
link.arr.col
link.visible
link.zindex

下面主要介绍其不一样的地方

13.1 两个扇形之间的多个连接

对于矩阵来说,两个扇形之间只能有一个连接,而数据框可以任意多个连接,例如

df <- expand.grid(letters[1:3], LETTERS[1:4])
df1 <- df
df1$value <- sample(10, nrow(df), replace = TRUE)
df2 <- df
df2$value <- -sample(10, nrow(df), replace = TRUE)
df <- rbind(df1, df2)

grid.col <- structure(1:7, names = c(letters[1:3], LETTERS[1:4]))
chordDiagram(
  df, grid.col = grid.col, 
  col = ifelse(df$value > 0, "#fb8072", "#80b1d3")
)

每对扇形之间都有两个连接,一个为正值,一个为负值。可以将它们分开绘制

par(mfrow = c(1, 2))
chordDiagram(
  df, col = "#fb8072", grid.col = grid.col, 
  link.visible = df$value > 0)
title("Positive links")
chordDiagram(
  df, col = "#80b1d3", grid.col = grid.col, 
  link.visible = df$value < 0)
title("Negative links")

13.2 设置两端的宽度

在前面的所有图形中,连接两端的宽度是一样的,相当于等量信息量在两边进行传递。数据框格式支持两列数据,可以表示两端的宽度

par(mfrow = c(1, 2))

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

推荐阅读更多精彩内容