特别声明:本部分(系列)内容均来自顾祖光博士对ComplexHeatmap的介绍,仅为学习交流,尊重原创。
热图系列我们已经有:
- R可视化之ComplexHeatmap【一】:颜色、标题、聚类
- R可视化之ComplexHeatmap【二】:行(列)顺序、行(列)名
- R可视化之ComplexHeatmap【三】:拆分
今天分享:热图小方格个性化修饰、提取亚集及热图信息。
热图小方格个性化修饰
前段时间很多平台都在流传一个帖子:如何在热图上显示数值?这部分就将解决这个问题。
我们对热图小方格的修饰是通过:
cell_fun
layer_fun
两个用户自定义函数来实现的。
-
将热图原始数据显示在热图小方格内:
先看代码:
library(ComplexHeatmap)
mat <- matrix(rnorm(10*10), ncol = 10)
rownames(mat) <- paste('row', 1:10, sep = "_")
colnames(mat) <- paste('col', 1:10, sep = "_")
Heatmap(matrix = mat,
name = 'mat',
cell_fun = function(j, i, x, y, width, height, fill){
grid.text(sprintf("%.1f", mat[i, j]),
x,
y,
gp = gpar(fontsize = 10))
})
下面来一一解释其中的参数:
- j: 热图矩阵列信息;
- i: 热图矩阵行信息;
- x: 热图小方格中心点x坐标;
- y: 热图小方格中心点y坐标;
- width: 热图小方格的宽度;
- height: 热图小方格的高度;
- fill: 热图小方格的颜色。
重点在于,这些参数的名称可以改变,但是顺序不能变!
实际上,你无需改变这些参数的值,因为在绘制热图时,属于每个小方格的这些参数都已经产生了,你所要做的是利用这些参数来添加一些你想要的元素,就比如上面的数值,把这段代码抽出来:
grid.text(sprintf("%.1f", mat[i, j]),
x,
y,
gp = gpar(fontsize = 10))
把mat[i,j]
的数值进行格式化打印(保留一位小数),打印出来的位置是x
和y
,也就是放在了每个小方格中央的位置,此外也通过了gpar()
函数指定了字体的属性,当然在这里只指定了字体大小,你也可以指定颜色之类的其它属性。
此外,你也可以只显示出部分的文字:
Heatmap(matrix = mat,
name = 'mat',
cell_fun = function(j, i, x, y, width, height, fill){
if(mat[i, j] > 0){
grid.text(sprintf("%.1f", mat[i, j]),
x,
y,
gp = gpar(fontsize = 10))
}
})
在这里我们就只显示出了大于0的部分。
毫无疑问,再加文字的同时,热图的分割也是可以同时进行的,这不会带来任何问题:
Heatmap(matrix = mat,
name = 'mat',
column_split = 3,
row_split = 2,
cell_fun = function(j, i, x, y, width, height, fill){
grid.text(sprintf("%.1f", mat[i, j]),
x,
y,
gp = gpar(fontsize = 10))
})
顾祖光老师的manual里面还有一些其它的操作,但目前我们可能还用的少,大家可以在用到的时候再去看一下。
同样的工作我们还能通过 layer_fun
函数来实现:
layer_fun
和cell_fun
之间的区别于,后者是逐个小方格进行处理,但前者是以部分方格为单位进行的处理。此外layer_fun
和cell_fun
的基本参数构成一致,但layer_fun
所有的参数都是向量,而不是单个元素,也就是说原来我们可以通过mat[i, j]
来访问矩阵元素,现在就不行了。也许你会疑惑,为什么就不行了呢?mat[c(1, 2), c(3, 4)]
不也能访问矩阵?问题的答案在于layer_fun
的mat[c(1, 2), c(3, 4)]
并不是表示取矩阵的第1、2行和第3、4列,而是取mat[1, 3]
和mat[2, 4]
。那么如何实现这种特殊的定义呢?ComplexHeatmap
为我们提供了pindex()
函数:
mat <- matrix(1:9, ncol = 3)
mat
# [,1] [,2] [,3]
#[1,] 1 4 7
#[2,] 2 5 8
#[3,] 3 6 9
pindex(m = mat, i = 1:2, j = 2:3)
#[1] 4 8
这样你就取出了mat[1, 2]
和mat[2, 3]
。
看起来这很烦,不过到实际例子你就能知道layer_fun
函数的强大功能了:
例1:将矩阵信息添加到热图上
Heatmap(matrix = mat,
name = 'mat',
layer_fun = function(j, i, x, y, width, height, fill){
grid.text(sprintf("%.1f", pindex(mat, i, j)),
x,
y,
gp = gpar(fontsize = 10))
})
注意,mat[i, j]
已经变成了pindex(mat, i, j)
。
例2:将部分矩阵信息添加到热图上
Heatmap(matrix = mat,
name = 'mat',
layer_fun = function(j, i, x, y, width, height, fill){
l = pindex(mat, i, j)
j = l > 0
grid.text(sprintf("%.1f", l[j]),
x[j],
y[j],
gp = gpar(fontsize = 10))
})
解析一下这段代码,由于layer_fun
和cell_fun
在矩阵索引上的区别,这里我们就不得不麻烦一些:首先将所有的矩阵元素存储到向量l
,然后再找到l
中大于0的元素的索引并存到j
中,最后再把索引j
对应的矩阵元素展示在图上,展示的位置为x[j]
和y[j]
,因为前面提到过了,x
和y
在这里已经是向量了。
到目前为止,似乎
layer_fun
都还是在弄巧成拙,别着急,看下面这个例子:
例3:对热图拆分后的不同部分进行个性化修饰:
当我们对热图拆分后要想对不同的部分进行修饰就离不开layer_fun
函数了:
Heatmap(matrix = mat,
name = 'mat',
column_split = 2,
row_split = 2,
layer_fun = function(j, i, x, y, width, height, fill){
l = pindex(mat, i, j)
j = l > 0
grid.text(sprintf("%.1f", l[j]),
x[j],
y[j],
gp = gpar(fontsize = 10))
if(sum(l > 0)/length(l) > 0.5){
grid.rect(gp = gpar(lwd = 2,
fill = "transparent",
color = 'black'))
}
})
这段代码干了这样一件事:首先把所有大于0的矩阵元素添加到了热图上,然后再对热图分割后形成的四个部分分别进行检验,如果该部分中大于0的元素占比超过50%,就将这个部分加上黑色的边框(右下角):
除了上面的7个参数以外,
layer_fun
还有两个参数slice_r
、slice_c
,它们是热图分割后每个部分的坐标。既然layer_fun
是对每个部分进行修饰的,那当然它也能充分利用slice_r
、slice_c
参数:
Heatmap(matrix = mat,
name = 'mat',
column_split = 2,
row_split = 2,
layer_fun = function(j, i, x, y, width, height, fill, slice_r, slice_c){
l = pindex(mat, i, j)
j = l > 0
grid.text(sprintf("%.1f", l[j]),
x[j],
y[j],
gp = gpar(fontsize = 10))
if(slice_r != slice_c){
grid.rect(gp = gpar(lwd = 2,
fill = "transparent",
color = 'black'))
}
})
因为右上角和左下角的两个部分其横纵坐标不相等(分别是1和2),所以会被加上黑色边框。
提取热图信息
很多时候当我们绘制出已经聚类的热图后想获知途中行和列的顺序,这个时候就涉及到如何提取热图的信息,很简单,我们只需要使用:
row_order()
column_order()
两个函数就可以实现。
例如:
p1 <- Heatmap(matrix = mat,
name = 'mat')
column_order(draw(p1))
# [1] 5 7 9 2 3 8 6 10 4 1
注意到这里出现了一个新的函数draw()
,这是什么呢?如果我们直接column_order(p1)
会有如下的warning:
Warning message:
The heatmap has not been initialized. You might have different results if you
repeatedly execute this function, e.g. when row_km/column_km was set. It is more
suggested to do as `ht = draw(ht); column_order(ht)`.
这是因为,当我们进行k均值聚类时,前面已经介绍到结果每次都会发生一定程度的改变,只有当我们使用draw()
函数时才能保证我们每次提取出来的结果是一样的,所以即使我们不是用的k均值聚类,也推荐在提取行列顺序时使用draw()
函数。
你一定会很好奇如果我们在一个拆分过的热图上进行提取行列顺序的操作时会出现什么呢?
p1 <- Heatmap(matrix = mat,
name = 'mat',
column_split = 2)
column_order(draw(p1))
#[[1]]
#[1] 6 8 4 1 10
#[[2]]
#[1] 5 3 7 2 9
最终返回一个列表,分别是两个部分的行列顺序。
提取热图亚集
最后一部分来介绍一下如何对热图取亚集,这听起来很有趣,但实现起来真的很简单,和矩阵提取规则一样。
p1 <- Heatmap(matrix = mat,
name = 'mat')
dim(p1)
#[1] 10 10
draw(p1)
可以看到我们的热图是10行10列,现在我们尝试提取出1到5行和2到7列的部分:
p1[1:5, 2:7]
但目前对热图取亚集的操作似乎没有办法在被分割的热图上存在,但使用k均值聚类的结果是可以的。