R可视化之ComplexHeatmap【四】:热图小方格个性化修饰、提取亚集及热图信息

特别声明:本部分(系列)内容均来自顾祖光博士对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]的数值进行格式化打印(保留一位小数),打印出来的位置是xy,也就是放在了每个小方格中央的位置,此外也通过了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_funcell_fun之间的区别于,后者是逐个小方格进行处理,但前者是以部分方格为单位进行的处理。此外layer_funcell_fun的基本参数构成一致,但layer_fun所有的参数都是向量,而不是单个元素,也就是说原来我们可以通过mat[i, j]来访问矩阵元素,现在就不行了。也许你会疑惑,为什么就不行了呢?mat[c(1, 2), c(3, 4)]不也能访问矩阵?问题的答案在于layer_funmat[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_funcell_fun在矩阵索引上的区别,这里我们就不得不麻烦一些:首先将所有的矩阵元素存储到向量l,然后再找到l中大于0的元素的索引并存到j中,最后再把索引j对应的矩阵元素展示在图上,展示的位置为x[j]y[j],因为前面提到过了,xy在这里已经是向量了。


到目前为止,似乎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_rslice_c,它们是热图分割后每个部分的坐标。既然layer_fun是对每个部分进行修饰的,那当然它也能充分利用slice_rslice_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均值聚类的结果是可以的。

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

推荐阅读更多精彩内容