ComplexHeatmap 之 A Single Heatmap 续(二)
感谢老俊俊的大力支持。我们会每日跟新,欢迎您关注老俊俊的生信笔记。

点击上方 关注我们

这一节结束A Single Heatmap
篇,下一章介绍 热图的注释 。
7、自定义热图主体
热图主体可以自定义添加更多类型的图形。默认情况下,热图主体由具有不同填充颜色的小矩形矩阵(在本文档的其他部分可能称为网格,但在此处称为“单元格”)组成。但是,也可以在热图上添加更多图形或符号作为附加层。有两个参数 cell_fun
和 layer_fun
,它们都可以是用户自定义的函数。
cell_fun:
cell_fun 在每个单元格中重复绘制,内部在两个嵌套的 for 循环中执行,而 layer_fun 是 cell_fun 的矢量化版本。cell_fun 更容易理解,但 layer_fun 执行速度更快,更可定制。
cell_fun 需要一个带有 7 个参数的函数(参数名称可以与以下不同,但顺序必须相同),它们是:
j: 矩阵中的 列
索引。列索引对应于视口中的 x 方向,这就是将 j 作为第一个参数的原因。i: 矩阵 行
索引。x: 在热图主体的视口中测量的单元格中点的 x 坐标。 y: 在热图主体的视口中测量的单元格中点的 y 坐标。 width: 单元格的宽度。该值是 unit(1/ncol(sub_mat), "npc")
其中 sub_mat 对应于通过行拆分和列拆分的子矩阵。height: 单元格的高度。值为 unit(1/nrow(sub_mat), "npc")
。fill: 单元格的颜色。
在每个单元格中执行时,七个参数的值会自动发送到函数里面。
最常见的用途是将矩阵中的 值 添加到热图中:
small_mat = mat[1:9, 1:9]
col_fun = colorRamp2(c(-2, 0, 2), c("green", "white", "red"))
Heatmap(small_mat, name = "mat", col = col_fun,
cell_fun = function(j, i, x, y, width, height, fill) {
grid.text(sprintf("%.1f", small_mat[i, j]), x, y, gp = gpar(fontsize = 10))
})

只添加正数:
Heatmap(small_mat, name = "mat", col = col_fun,
cell_fun = function(j, i, x, y, width, height, fill) {
if(small_mat[i, j] > 0)
grid.text(sprintf("%.1f", small_mat[i, j]), x, y, gp = gpar(fontsize = 10))
})

分割热图:
Heatmap(small_mat, name = "mat", col = col_fun,
row_km = 2, column_km = 2,
cell_fun = function(j, i, x, y, width, height, fill) {
grid.text(sprintf("%.1f", small_mat[i, j]), x, y, gp = gpar(fontsize = 10))
})

在下面的例子中,我们制作了一个热图,其中显示了与 corrplot 包相似的相关矩阵:
cor_mat = cor(small_mat)
od = hclust(dist(cor_mat))$order
cor_mat = cor_mat[od, od]
nm = rownames(cor_mat)
col_fun = circlize::colorRamp2(c(-1, 0, 1), c("green", "white", "red"))
# `col = col_fun` here is used to generate the legend
Heatmap(cor_mat, name = "correlation", col = col_fun, rect_gp = gpar(type = "none"),
cell_fun = function(j, i, x, y, width, height, fill) {
grid.rect(x = x, y = y, width = width, height = height,
gp = gpar(col = "grey", fill = NA))
if(i == j) {
grid.text(nm[i], x = x, y = y)
} else if(i > j) {
grid.circle(x = x, y = y, r = abs(cor_mat[i, j])/2 * min(unit.c(width, height)),
gp = gpar(fill = col_fun(cor_mat[i, j]), col = NA))
} else {
grid.text(sprintf("%.1f", cor_mat[i, j]), x, y, gp = gpar(fontsize = 10))
}
}, cluster_rows = FALSE, cluster_columns = FALSE,
show_row_names = FALSE, show_column_names = FALSE)

layer_fun:
cell_fun 逐个单元添加图形,而 layer_fun 以块方式添加图形。与 cell_fun 类似,layer_fun 也需要七个参数,但都是向量形式(layer_fun 也可以有第八个和第九个参数,本节稍后介绍):
# code only for demonstration
Heatmap(..., layer_fun = function(j, i, x, y, w, h, fill) {...})
# or you can capitalize the arguments to mark they are vectors,
# the names of the argumetn do not matter
Heatmap(..., layer_fun = function(J, I, X, Y, W, H, F) {...})
j 和 i 仍然包含与原始矩阵对应的列和行索引,但由于现在 layer_fun 适用于一个单元格块(或热图块,如果热图被分割),j 和 i 是当前热图切片里所有单元格的向量。同理,x、y、w、h 和 fill 都是当前热图切片中所有单元格对应的向量。
由于 j 和 i 现在是向量,为了获得矩阵中的相应值,我们不能使用 mat[j, i]
形式,因为它为你提供了一个具有 length(i) 行和 length(j) 列的子矩阵。相反,我们可以使用 ComplexHeatmap 中的 pindex()
函数,这类似于矩阵的成对索引,以下示例:
mfoo = matrix(1:9, nr = 3)
mfoo[1:2, c(1, 3)]
## [,1] [,2]
## [1,] 1 7
## [2,] 2 8
# but we actually want mfoo[1, 1] and mfoo[2, 3]
pindex(mfoo, 1:2, c(1, 3))
## [1] 1 8
下一个示例显示了在热图上添加文本的 layer_fun 版本。和 cell_fun 版本基本一致:
col_fun = colorRamp2(c(-2, 0, 2), c("green", "white", "red"))
Heatmap(small_mat, name = "mat", col = col_fun,
layer_fun = function(j, i, x, y, width, height, fill) {
# since grid.text can also be vectorized
grid.text(sprintf("%.1f", pindex(small_mat, i, j)), x, y, gp = gpar(fontsize = 10))
})

添加正数:
Heatmap(small_mat, name = "mat", col = col_fun,
layer_fun = function(j, i, x, y, width, height, fill) {
v = pindex(small_mat, i, j)
l = v > 0
grid.text(sprintf("%.1f", v[l]), x[l], y[l], gp = gpar(fontsize = 10))
})

当热图被分割时,layer_fun 应用于每个切片:
Heatmap(small_mat, name = "mat", col = col_fun,
row_km = 2, column_km = 2,
layer_fun = function(j, i, x, y, width, height, fill) {
v = pindex(small_mat, i, j)
grid.text(sprintf("%.1f", v), x, y, gp = gpar(fontsize = 10))
if(sum(v > 0)/length(v) > 0.75) {
grid.rect(gp = gpar(lwd = 2, fill = "transparent"))
}
})

layer_fun 还可以有另外两个参数,它们是当前行切片和列切片的索引。例如 我们想为右上角和左下角的切片添加边框:
Heatmap(small_mat, name = "mat", col = col_fun,
row_km = 2, column_km = 2,
layer_fun = function(j, i, x, y, width, height, fill, slice_r, slice_c) {
v = pindex(small_mat, i, j)
grid.text(sprintf("%.1f", v), x, y, gp = gpar(fontsize = 10))
if(slice_r != slice_c) {
grid.rect(gp = gpar(lwd = 2, fill = "transparent"))
}
})

layer_fun 的优势在于不仅可以 快速添加图形
,而且还提供了更多 自定义热图的可能性
。考虑以下可视化:对于热图中的每一行,如果相邻两列中的值具有相同的符号,我们根据两个值的符号添加一条红线或一条绿线。由于现在单元格中的图形依赖于其他单元格,因此只能通过 layer_fun 来实现它:
Heatmap(small_mat, name = "mat", col = col_fun,
row_km = 2, column_km = 2,
layer_fun = function(j, i, x, y, w, h, fill) {
# restore_matrix() is explained after this chunk of code
ind_mat = restore_matrix(j, i, x, y)
for(ir in seq_len(nrow(ind_mat))) {
# start from the second column
for(ic in seq_len(ncol(ind_mat))[-1]) {
ind1 = ind_mat[ir, ic-1] # previous column
ind2 = ind_mat[ir, ic] # current column
v1 = small_mat[i[ind1], j[ind1]]
v2 = small_mat[i[ind2], j[ind2]]
if(v1 * v2 > 0) { # if they have the same sign
col = ifelse(v1 > 0, "darkred", "darkgreen")
grid.segments(x[ind1], y[ind1], x[ind2], y[ind2],
gp = gpar(col = col, lwd = 2))
grid.points(x[c(ind1, ind2)], y[c(ind1, ind2)],
pch = 16, gp = gpar(col = col), size = unit(4, "mm"))
}
}
}
}
)

发送到 layer_fun 的值都是向量(用于网格图形函数的向量化),但是,layer_fun 应用到的热图切片仍然由矩阵表示,因此,如果所有参数都非常方便 在 layer_fun 中可以转换为当前切片的子矩阵。在这里,如上例所示,restore_matrix()
完成了这项工作。restore_matrix()
直接接受 layer_fun 中的前四个参数,返回一个 索引矩阵 ,其中的行和列对应当前切片中的行和列,从上到下,从左到右。矩阵中的值是例如的自然顺序 当前切片中的向量 j。
Heatmap(small_mat, name = "mat", col = col_fun,
row_km = 2, column_km = 2,
layer_fun = function(j, i, x, y, w, h, fill) {
ind_mat = restore_matrix(j, i, x, y)
print(ind_mat)
}
)
左上角切片的第一个输出:
[,1] [,2] [,3] [,4] [,5]
[1,] 1 4 7 10 13
[2,] 2 5 8 11 14
[3,] 3 6 9 12 15
这是一个三行五列的索引矩阵,其中第一行对应于切片中的顶行。矩阵中的值对应于 layer_fun 中 j、i、x、y、… 中的自然索引(即 1、2、…)。现在,如果我们想在左上角切片的第二列上添加值,放置在 layer_fun 中的代码将如下所示:
for(ind in ind_mat[, 2]) {
grid.text(small_mat[i[ind], j[ind]], x[ind], y[ind], ...)
}
现在更容易理解第二个例子:我们想在每个切片的第二行和第三列添加点:
Heatmap(small_mat, name = "mat", col = col_fun,
row_km = 2, column_km = 2,
layer_fun = function(j, i, x, y, w, h, fill) {
ind_mat = restore_matrix(j, i, x, y)
ind = unique(c(ind_mat[2, ], ind_mat[, 3]))
grid.points(x[ind], y[ind], pch = 16, size = unit(4, "mm"))
}
)

8、热图尺寸
width
、heatmap_width
、height
和 heatmap_height
控制热图的大小。默认情况下,所有热图组件都有固定的宽度或高度,例如行树状图的宽度为 1cm。热图主体的宽度或高度填充最终绘图区域的其余区域,这意味着,如果在交互式图形窗口中绘制它并通过拖动它来更改窗口的大小,则热图主体的大小为自动调整。
heatmap_width
和 heatmap_height
控制包括所有热图组件(不包括图例)在内的完整热图的宽度/高度,而 width
和 height
仅控制 heamtap 主体的宽度/高度。所有这四个参数都可以设置为绝对单位:
Heatmap(mat, name = "mat", width = unit(8, "cm"), height = unit(8, "cm"))

Heatmap(mat, name = "mat", heatmap_width = unit(8, "cm"), heatmap_height = unit(8, "cm"))

当热图的大小设置为绝对单位时,图的大小可能大于热图的大小,这会在热图周围产生空白区域。热图的大小可以通过 ComplexHeatmap::width()
和 ComplexHeatmap::height()
函数获取:
ht = Heatmap(mat, name = "mat", width = unit(8, "cm"), height = unit(8, "cm"))
ht = draw(ht) # you must call draw() to reassign the heatmap variable
ComplexHeatmap:::width(ht)
## [1] 118.851591171994mm
ComplexHeatmap:::height(ht)
## [1] 114.717557838661mm
ht = Heatmap(mat, name = "mat", heatmap_width = unit(8, "cm"),
heatmap_height = unit(8, "cm"))
ht = draw(ht)
ComplexHeatmap:::width(ht)
## [1] 94.8877245053272mm
ComplexHeatmap:::height(ht)
## [1] 83.8660578386606mm
9、绘图
Heatmap()
函数实际上只是一个构造函数,也就是说它只是将所有的数据和配置放入 Heatmap 类中的对象中。只有在调用 draw()
方法时才会执行聚类。在交互模式下(例如,交互式 R 终端,可以在其中逐行键入 R 代码),直接调用 Heatmap() 而不返回任何对象会打印对象和热图的打印方法(或 S4 show() 方法
) 类对象在内部调用 draw()。因此,如果在 R 终端中键入 Heatmap(…),它看起来像是 plot() 之类的绘图函数,需要注意它实际上不是真的,并且在以下情况下,可能看不到任何绘图:
你把 Heatmap(…) 放在一个函数里, 你把 Heatmap(…) 放在像 for 或 if-else 这样的代码块中 你把 Heatmap(…) 放在一个 Rscript 中,然后在命令行下运行它。
原因是在以上三种情况下,show() 方法不会被调用,因此 draw() 方法也不会被执行。因此,要绘制绘图,需要显式调用 draw():draw(Heatmap(...))
:
# code only for demonstration
ht = Heatmap(...)
draw(ht)
draw() 函数实际上应用于 HeatmapList 类中的热图列表。单个 Heatmap 类的 draw() 方法构造一个只有一个 heatmap 的 HeatmapList 并调用 HeatmapList 类的 draw() 方法。draw() 函数接受更多参数,例如 控制图例:
draw(ht, heatmap_legend_side, padding, ...)
10、提取顺序
热图的 行/列顺序 可以通过 row_order()/column_order()
函数获得。可以直接应用于 Heatmap() 返回的热图对象或 draw() 返回的对象。下面我们以 row_order() 为例:
small_mat = mat[1:9, 1:9]
ht1 = Heatmap(small_mat)
row_order(ht1)
## [1] 5 7 2 4 9 6 8 1 3
ht2 = draw(ht1)
row_order(ht2)
## [1] 5 7 2 4 9 6 8 1 3
如上一节所述,Heatmap() 函数 不执行聚类,因此,当直接在 ht1 上应用 row_order() 时,将首先执行聚类。稍后在通过 draw(ht1) 制作热图时,将再次应用聚类。如果在热图中设置 k-means 聚类,这可能是一个问题。由于聚类应用了两次,k-means 可能会给你不同的聚类,这意味着你可能从 row_order() 得到不同的结果,你可能有不同的热图。
在下面的代码块中,o1、o2 和 o3 可能不同,因为每次都执行 k 均值聚类:
# code only for demonstration
ht1 = Heatmap(small_mat, row_km = 2)
o1 = row_order(ht1)
o2 = row_order(ht1)
ht2 = draw(ht1)
o3 = row_order(ht2)
o4 = row_order(ht2)
draw()
函数返回已经重新排序的热图(或者更准确地说,热图列表),并且应用 row_order()
只是从对象中提取行顺序,这确保行顺序与所示的行顺序完全相同热图。在上面的代码中,o3 始终与 o4 相同。
因此,获取行/列顺序
的优选方法如下:
# code only for demonstration
ht = Heatmap(small_mat)
ht = draw(ht)
row_order(ht)
column_order(ht)
如果行/列被拆分,行顺序或列顺序将是一个列表:
ht = Heatmap(small_mat, row_km = 2, column_km = 3)
ht = draw(ht)
row_order(ht)
## $`2`
## [1] 5 7 2 4
##
## $`1`
## [1] 9 6 8 1 3
column_order(ht)
## $`1`
## [1] 6 2 7
##
## $`3`
## [1] 1 3 4 5
##
## $`2`
## [1] 8 9
同样,row_dend()/column_dend()
函数返回树状图。它返回单个树状图或树状图列表,具体取决于热图是否被分割:
ht = Heatmap(small_mat, row_km = 2)
ht = draw(ht)
row_dend(ht)
## $`2`
## 'dendrogram' with 2 branches and 4 members total, at height 2.946428
##
## $`1`
## 'dendrogram' with 2 branches and 5 members total, at height 2.681351
column_dend(ht)
## 'dendrogram' with 2 branches and 9 members total, at height 4.574114
11、提取子集热图
由于热图是矩阵的表示,因此也有热图类的子集方法:
ht = Heatmap(mat, name = "mat")
dim(ht)
## [1] 18 24
ht[1:10, 1:10]

注释子集也可被提取:
ht = Heatmap(mat, name = "mat", row_km = 2, column_km = 3,
col = colorRamp2(c(-2, 0, 2), c("green", "white", "red")),
top_annotation = HeatmapAnnotation(foo1 = 1:24, bar1 = anno_points(runif(24))),
right_annotation = rowAnnotation(foo2 = 18:1, bar2 = anno_barplot(runif(18)))
)
ht[1:9*2 - 1, 1:12*2] # odd rows, even columns

如果热图组件类似于向量,则它们也被 子集化 。热图中的某些配置在子集化时保持不变,例如:如果在原始热图中设置了 row_km
,则保留 k-means
的配置并在子热图中执行。所以在下面的例子中,k-means 聚类只在为 ht2 制作热图时执行:
ht = Heatmap(mat, name = "mat", row_km = 2)
ht2 = ht[1:10, 1:10]
ht2

收官!
代码 我上传到 QQ 群 老俊俊生信交流群
文件夹里。欢迎加入。加我微信我也拉你进 微信群聊 老俊俊生信交流群
哦。
群二维码:

老俊俊微信:
知识星球:
所以今天你学习了吗?
欢迎小伙伴留言评论!
今天的分享就到这里了,敬请期待下一篇!
最后欢迎大家分享转发,您的点赞是对我的鼓励和肯定!
如果觉得对您帮助很大,赏杯快乐水喝喝吧!
推 荐 阅 读
请关注“恒诺新知”微信公众号,感谢“R语言“,”数据那些事儿“,”老俊俊的生信笔记“,”冷🈚️思“,“珞珈R”,“生信星球”的支持!