查看原文
其他

ComplexHeatmap 之 Heatmap Annotations

JunJunLab 老俊俊的生信笔记 2022-08-15


关注一下,夏天一起过小暑


新的开始




这节介绍热图的注释部分。

热图注释是热图的重要组成部分,它显示与热图中的行或列相关联的附加信息。ComplexHeatmap 包为设置注释和定义新的注释图形提供了非常灵活的支持。注释可以放在热图的 四个边上 ,通过 top_annotationbottom_annotationleft_annotationright_annotation 参数控制。

四个参数的值应该在 HeatmapAnnotation 类 中,并且应该由 HeatmapAnnotation() 函数构造,如果是行注释,则由 rowAnnotation() 函数构造。(rowAnnotation() 只是一个与 HeatmapAnnotation(..., which = "row") 相同的辅助函数) 热图注释的简单用法如下:

set.seed(123)
mat = matrix(rnorm(100), 10)
rownames(mat) = paste0("R"1:10)
colnames(mat) = paste0("C"1:10)
column_ha = HeatmapAnnotation(foo1 = runif(10), bar1 = anno_barplot(runif(10)))
row_ha = rowAnnotation(foo2 = runif(10), bar2 = anno_barplot(runif(10)))
Heatmap(mat, name = "mat", top_annotation = column_ha, right_annotation = row_ha)

或指定为底部注释和左侧注释:

Heatmap(mat, name = "mat", bottom_annotation = column_ha, left_annotation = row_ha)

在上面的例子中,column_harow_ha 都有两个注释,其中 foo1foo2 是数字向量,而 bar1bar2 是条形图。类向量注解在此称为 “简单注释”,条形图注解称为 “复杂注释”。可以看到注释必须定义为名称-值对应(例如 foo = ...)。

热图注释也可以独立于热图。如果它是水平的,它们可以通过 + 连接,如果它是竖直的,可以通过 %v% 连接。

# code only for demonstration
Heatmap(...) + rowAnnotation() + ...
Heatmap(...) %v% HeatmapAnnotation(...) + ...

HeatmapAnnotation() 返回一个 HeatmapAnnotation 类对象:

column_ha
## A HeatmapAnnotation object with 2 annotations
##   name: heatmap_annotation_0
##   position: column
##   items: 10
##   width: 1npc
##   height: 15.3514598035146mm
##   this object is subsetable
##   5.92288888888889mm extension on the left
##   9.4709mm extension on the right
##
##  name   annotation_type color_mapping height
##  foo1 continuous vector        random    5mm
##  bar1    anno_barplot()                 10mm

row_ha
## A HeatmapAnnotation object with 2 annotations
##   name: heatmap_annotation_1
##   position: row
##   items: 10
##   width: 15.3514598035146mm
##   height: 1npc
##   this object is subsetable
##   9.96242222222222mm extension on the bottom
##
##  name   annotation_type color_mapping width
##  foo2 continuous vector        random   5mm
##  bar2    anno_barplot()                10mm

1、简单注释

所谓的 “简单注释” 是最常用的注释样式,它是类似热图或类似网格的图形,其中使用颜色映射到注释值。要生成简单的注释,只需将注释向量放入具有特定名称的 HeatmapAnnotation() 中即可:

ha = HeatmapAnnotation(foo = 1:10)

分类变量注释:

ha = HeatmapAnnotation(bar = sample(letters[1:3], 10, replace = TRUE))

如果未指定颜色,则随机生成颜色。要设置注释的颜色,需要将 col 设置为命名列表。对于连续值,颜色映射应该是由 circlize::colorRamp2() 颜色映射函数生成:

library(circlize)
col_fun = colorRamp2(c(0510), c("blue""white""red"))
ha = HeatmapAnnotation(foo = 1:10, col = list(foo = col_fun))

对于离散型注释,颜色应该是命名向量,其中名称对应于注释中的 levels

ha = HeatmapAnnotation(bar = sample(letters[1:3], 10, replace = TRUE),
    col = list(bar = c("a" = "red""b" = "green""c" = "blue")))

如果指定了多个向量,则会有多个注释(下例中的 foo 和 bar)。还可以看到当 foo 和 bar 都放入单个 HeatmapAnnotation() 时如何设置 col。也许现在可以理解颜色列表中的名称实际上是用来映射到注释名称的。col 中的值将用于构建简单注释的图例:

ha = HeatmapAnnotation(
    foo = 1:10,
    bar = sample(letters[1:3], 10, replace = TRUE),
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    )
)

NA 值的颜色由 na_col 参数控制:

ha = HeatmapAnnotation(
    foo = c(1:4NA6:10),
    bar = c(NA, sample(letters[1:3], 9, replace = TRUE)),
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    ),
    na_col = "black"
)

gp 参数主要控制网格边框的图形参数:

ha = HeatmapAnnotation(
    foo = 1:10,
    bar = sample(letters[1:3], 10, replace = TRUE),
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    ),
    gp = gpar(col = "black")
)

简单注释也可以是矩阵(数字或字符),矩阵中的所有列共享相同的颜色映射模式。注意矩阵中的列对应于列注释中的行。矩阵的列名称也用作注释名称:

ha = HeatmapAnnotation(foo = cbind(a = runif(10), b = runif(10)))

如果矩阵没有列名,则仍然使用注释的名称,但绘制在注解的中间:

ha = HeatmapAnnotation(foo = cbind(runif(10), runif(10)))

由于简单的注释可以采用不同的模式(例如数字或字符),它们可以组合为数据框并发送到 df 参数。你可能已经有了一个注释表格,你可以直接通过 df 设置它:

anno_df = data.frame(foo = 1:10,
    bar = sample(letters[1:3], 10, replace = TRUE))
ha = HeatmapAnnotation(df = anno_df,
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    )
)

单个注释和数据框可以 混合使用。在以下示例中,未指定 foo2 的颜色,将使用随机颜色:

ha = HeatmapAnnotation(df = anno_df,
    foo2 = rnorm(10),
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    )
)

border 控制每个注释的边框:

ha = HeatmapAnnotation(
    foo = cbind(1:1010:1),
    bar = sample(letters[1:3], 10, replace = TRUE),
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    ),
    border = TRUE
)

简单注释的高度由 simple_anno_size 参数控制。由于所有单个注释具有相同的高度,因此 simple_anno_size 的值是单个单位值。请注意,widthheightannotation_widthannotation_height 之类的参数,但它们用于调整完整 heamtap 注释的宽度/高度(它们总是几个注释的混合):

ha = HeatmapAnnotation(
    foo = cbind(a = 1:10, b = 10:1),
    bar = sample(letters[1:3], 10, replace = TRUE),
    col = list(foo = col_fun,
               bar = c("a" = "red""b" = "green""c" = "blue")
    ),
    simple_anno_size = unit(1"cm")
)

当您有多个热图时,最好将所有热图上的简单注释的大小保持相同的大小。ht_opt$simple_anno_size 可以设置全局控制简单注释大小。

2、用注释函数注释

HeatmapAnnotation() 通过将注释设置为函数来支持 “复杂注释”。annotation 函数定义了如何在热图中的列或行对应的某个位置绘制图形。ComplexHeatmap 包中预定义了很多注释函数。在本章的最后,我们将介绍如何通过 AnnotationFunction 类构建自己的注释函数。

对于 anno_*() 形式的所有注释函数,如果在 HeatmapAnnotation() 或 rowAnnotation() 中指定,则不需要在 anno_*() 上明确做任何事情来判断是绘制在行上还是列。anno_*() 自动检测是行注释还是列注释。

上一节中展示的简单注释是由 anno_simple() 注解函数内部构造的。直接使用 anno_simple() 不会自动为最终的绘图生成图例,但是,它可以为更多的注释图形提供更大的灵活性(虽然 anno_simple() 不能自动生成图例,但可以 控制图例 并手动添加到最终图中)。

# code only for demonstration
ha = HeatmapAnnotation(foo = 1:10)

等价于:

# code only for demonstration
ha = HeatmapAnnotation(foo = anno_simple(1:10))

anno_simple() 制作类似热图的注释(或简单的注释)。基本上如果用户只做类似热图的注释,他们不需要直接使用 anno_simple(),但是这个函数允许在注释网格上添加 更多的符号

anno_simple() 允许在注释网格顶部添加 “点” 或单字母符号。pchpt_gppt_size 控制点的设置。pch 的值可以是含有的 NA 值的向量:

ha = HeatmapAnnotation(foo = anno_simple(1:10, pch = 1,
    pt_gp = gpar(col = "red"), pt_size = unit(1:10"mm")))

pch 设为一个向量:

ha = HeatmapAnnotation(foo = anno_simple(1:10, pch = 1:10))

pch 设置为字母向量:

ha = HeatmapAnnotation(foo = anno_simple(1:10,
    pch = sample(letters[1:3], 10, replace = TRUE)))

pch 设置为具有 NA 值的向量(不为 NA pch 值绘制任何内容):

ha = HeatmapAnnotation(foo = anno_simple(1:10, pch = c(1:4NA6:8NA1011)))

如果 anno_simple() 的值是 矩阵,则 pch 也有效。pch 的长度应该与矩阵的行数或列数甚至矩阵的长度相同(矩阵的长度是矩阵中所有数据点的长度)。

pch 的长度对应于矩阵列:

ha = HeatmapAnnotation(foo = anno_simple(cbind(1:1010:1), pch = 1:2))

pch 的长度对应于矩阵行:

ha = HeatmapAnnotation(foo = anno_simple(cbind(1:1010:1), pch = 1:10))

pch 是一个矩阵:

pch = matrix(1:20, nc = 2)
pch[sample(length(pch), 10)] = NA
ha = HeatmapAnnotation(foo = anno_simple(cbind(1:1010:1), pch = pch))

到现在为止,你可能想知道如何设置您添加到简单注释中的符号的图例。这里我们只展示一个简单的例子,这个功能将在后面讨论。在下面的例子中,我们假设简单注释是一种 p 值,我们为 p 值小于 0.01 添加 * :

set.seed(123)
pvalue = 10^-runif(10, min = 0, max = 3)
is_sig = pvalue < 0.01
pch = rep("*"10)
pch[!is_sig] = NA
# color mapping for -log10(pvalue)
pvalue_col_fun = colorRamp2(c(023), c("green""white""red"))
ha = HeatmapAnnotation(
    pvalue = anno_simple(-log10(pvalue), col = pvalue_col_fun, pch = pch),
    annotation_name_side = "left")
ht = Heatmap(matrix(rnorm(100), 10), name = "mat", top_annotation = ha)
# now we generate two legends, one for the p-value
# see how we define the legend for pvalue
lgd_pvalue = Legend(title = "p-value", col_fun = pvalue_col_fun, at = c(0123),
    labels = c("1""0.1""0.01""0.001"))
# and one for the significant p-values
lgd_sig = Legend(pch = "*", type = "points", labels = "< 0.01")
# these two self-defined legends are added to the plot by `annotation_legend_list`
draw(ht, annotation_legend_list = list(lgd_pvalue, lgd_sig))

简单注释的高度可以通过 height 参数或 anno_simple() 中的 simple_anno_size 来控制。simple_anno_size 控制单行注释的大小height/width 控制简单注释的总高度/宽度。如果设置了高度/宽度,则忽略 simple_anno_size:

ha = HeatmapAnnotation(foo = anno_simple(1:10, height = unit(2"cm")))
ha = HeatmapAnnotation(foo = anno_simple(cbind(1:1010:1),
    simple_anno_size = unit(2"cm")))

3、空注释

anno_empty() 是一个不绘制任何内容的占位符。稍后可以通过 decorate_annotation() 函数添加用户定义的图形:

ha = HeatmapAnnotation(foo = anno_empty(border = TRUE))

在基因表达表达分析中,有些情况下我们将热图分成几组,我们希望突出显示每组中的一些关键基因。在这种情况下,我们只需将基因名称添加到热图的右侧,而不将它们与相应的行对齐。(anno_mark() 可以将标签正确地与其对应的行对齐,但在我们在这里展示的示例中,这不是必需的)。

在以下示例中,由于行被拆分为四个切片,因此空注释也被拆分为四个切片。基本上我们所做的是在每个空的注释切片中,我们添加一个颜色段和文本:

random_text = function(n) {
    sapply(1:n, function(i) {
        paste0(sample(letters, sample(4:101)), collapse = "")
    })
}
text_list = list(
    text1 = random_text(4),
    text2 = random_text(4),
    text3 = random_text(4),
    text4 = random_text(4)
)
# note how we set the width of this empty annotation
ha = rowAnnotation(foo = anno_empty(border = FALSE,
    width = max_text_width(unlist(text_list)) + unit(4"mm")))
Heatmap(matrix(rnorm(1000), nrow = 100), name = "mat", row_km = 4, right_annotation = ha)
for(i in 1:4) {
    decorate_annotation("foo", slice = i, {
        grid.rect(x = 0, width = unit(2"mm"), gp = gpar(fill = i, col = NA), just = "left")
        grid.text(paste(text_list[[i]], collapse = "\n"), x = unit(4"mm"), just = "left")
    })
}

空注释的第二个用途是添加 复杂的注释图形,其中空注释伪装成虚拟绘图区域。可以通过 AnnotationFunction 类为复杂的注释图形构造一个注释函数,它允许子集和拆分,但仍然可以作为次要选择直接在空注释内部绘制,这样实现起来更容易和更快(但不太灵活并且不允许拆分)。

下面我们将展示如何添加点注释的“复杂版本”。唯一需要注意的是 x 轴(y 轴如果是行注释)上的位置应该对应列重新排序后的列索引:

ha = HeatmapAnnotation(foo = anno_empty(border = TRUE, height = unit(3"cm")))
ht = Heatmap(matrix(rnorm(100), nrow = 10), name = "mat", top_annotation = ha)
ht = draw(ht)
co = column_order(ht)
value = runif(10)
decorate_annotation("foo", {
    # value on x-axis is always 1:ncol(mat)
    x = 1:10
    # while values on y-axis is the value after column reordering
    value = value[co]
    pushViewport(viewport(xscale = c(0.510.5), yscale = c(01)))
    grid.lines(c(0.510.5), c(0.50.5), gp = gpar(lty = 2),
        default.units = "native")
    grid.points(x, value, pch = 16, size = unit(2"mm"),
        gp = gpar(col = ifelse(value > 0.5"red""blue")), default.units = "native")
    grid.yaxis(at = c(00.51))
    popViewport()
})

4、块注释

块注释更像是一个颜色块,它在热图的行或列被分割时标识组别:

Heatmap(matrix(rnorm(100), 10), name = "mat",
    top_annotation = HeatmapAnnotation(foo = anno_block(gp = gpar(fill = 2:4))),
    column_km = 3)

添加标签到区块里:

Heatmap(matrix(rnorm(100), 10),
    top_annotation = HeatmapAnnotation(foo = anno_block(gp = gpar(fill = 2:4),
        labels = c("group1""group2""group3"),
        labels_gp = gpar(col = "white", fontsize = 10))),
    column_km = 3,
    left_annotation = rowAnnotation(foo = anno_block(gp = gpar(fill = 2:4),
        labels = c("group1""group2""group3"),
        labels_gp = gpar(col = "white", fontsize = 10))),
    row_km = 3)

函数 anno_block()为行/列切片绘制矩形,其中一个矩形只对应一个切片。那么,如果我们想要在几个切片上画出矩形来显示它们属于某些组,比如下面的热度图?

set.seed(123)
mat2 = matrix(rnorm(50*50), nrow = 50)
ha = HeatmapAnnotation(foo = anno_block(gp = gpar(fill = 2:6), labels = LETTERS[1:5]))
split = rep(1:5, each = 10)
Heatmap(mat2, name = "mat2", column_split = split, top_annotation = ha,
    column_title = NULL)

目前,很难在 anno_block() 中直接支持它,但是,有解决方法。实际上,要在多个切片上绘制矩形,我们需要知道两件事:1. 切片在图中的位置。2. 绘制矩形的空间。幸运的是,位置可以通过直接转到对应的视口获得,并且可以通过 anno_empty() 函数分配空间。

在下面的代码中,我们使用 anno_empty() 创建一个空注释:

ha = HeatmapAnnotation(
    empty = anno_empty(border = FALSE),
    foo = anno_block(gp = gpar(fill = 2:6), labels = LETTERS[1:5])
)
Heatmap(mat2, name = "mat2", column_split = split, top_annotation = ha,
    column_title = NULL)

假设我们要将前三列切片作为一组,将后两列切片作为第二组。

第一个和第三个切片的位置获得:

seekViewport("annotation_empty_1")
loc1 = deviceLoc(x = unit(0"npc"), y = unit(0"npc"))
seekViewport("annotation_empty_3")
loc2 = deviceLoc(x = unit(1"npc"), y = unit(1"npc"))
loc2
## $x
## [1] 4.07403126835173inches
##
## $y
## [1] 6.51051067246731inches

视口名称 “annotation_empty_1” 对应第一个切片的空注释,我们取第一个 “空”注释切片的 左下角 和第三个切片的 右上角 ,保存在 loc1 和 loc2 变量中。

这里重要的是使用 grid::deviceLoc() 函数。它直接将在某个视口中测量的位置转换为图形设备中的位置。

最后,我们转到 “全局” 视口,因为 “全局” 视口的大小是图形设备的大小,并绘制矩形并添加标签:

seekViewport("global")
grid.rect(loc1$x, loc1$y, width = loc2$x - loc1$x, height = loc2$y - loc1$y,
    just = c("left""bottom"), gp = gpar(fill = "red"))
grid.text("group 1", x = (loc1$x + loc2$x)*0.5, y = (loc1$y + loc2$y)*0.5)

注释的视口名称采用固定格式,即 annotation_{annotation_name}_{slice_index}。可以通过 list_components() 函数获取完整的视口名称:

list_components()
##  [1] "ROOT"                        "global"
##  [3] "global_layout"               "global-heatmaplist"
##  [5] "main_heatmap_list"           "heatmap_mat2"
##  [7] "mat2_heatmap_body_wrap"      "mat2_heatmap_body_1_1"
##  [9] "mat2_heatmap_body_1_2"       "mat2_heatmap_body_1_3"
## [11] "mat2_heatmap_body_1_4"       "mat2_heatmap_body_1_5"
## [13] "mat2_dend_row_1"             "mat2_dend_column_1"
## [15] "mat2_dend_column_2"          "mat2_dend_column_3"
## [17] "mat2_dend_column_4"          "mat2_dend_column_5"
## [19] "annotation_empty_1"          "annotation_foo_1"
## [21] "annotation_empty_2"          "annotation_foo_2"
## [23] "annotation_empty_3"          "annotation_foo_3"
## [25] "annotation_empty_4"          "annotation_foo_4"
## [27] "annotation_empty_5"          "annotation_foo_5"
## [29] "global-heatmap_legend_right" "heatmap_legend"

如果要添加多个组级矩形,我们可以将代码包装到一个简单的函数 group_block_anno() 中:

ha = HeatmapAnnotation(
    empty = anno_empty(border = FALSE, height = unit(8"mm")),
    foo = anno_block(gp = gpar(fill = 2:6), labels = LETTERS[1:5])
)
Heatmap(mat2, name = "mat2", column_split = split, top_annotation = ha,
    column_title = NULL)

library(GetoptLong)  # for the function qq()
group_block_anno = function(group, empty_anno, gp = gpar(),
    label = NULL, label_gp = gpar()) {

    seekViewport(qq("annotation_@{empty_anno}_@{min(group)}"))
    loc1 = deviceLoc(x = unit(0"npc"), y = unit(0"npc"))
    seekViewport(qq("annotation_@{empty_anno}_@{max(group)}"))
    loc2 = deviceLoc(x = unit(1"npc"), y = unit(1"npc"))

    seekViewport("global")
    grid.rect(loc1$x, loc1$y, width = loc2$x - loc1$x, height = loc2$y - loc1$y,
        just = c("left""bottom"), gp = gp)
    if(!is.null(label)) {
        grid.text(label, x = (loc1$x + loc2$x)*0.5, y = (loc1$y + loc2$y)*0.5, gp = label_gp)
    }
}

group_block_anno(1:3"empty", gp = gpar(fill = "red"), label = "group 1")
group_block_anno(4:5"empty", gp = gpar(fill = "blue"), label = "group 2")

当热图被分割时,块注释中的每个块都可以被认为是一个虚拟的绘图区域。anno_block() 允许一个新的参数 graphics ,它接受一个在每个切片中绘制图形的自定义函数。它必须有两个参数:

  • 1、当前切片的行/列索引(我们称之为索引)。
  • 2、来自 split 变量的与当前切片相对应的水平向量(我们称之为水平)。当例如 row_km 仅设置或 row_split 仅设置为一个分类变量,则 level 是长度为 1 的向量。如果用 row_km 和 row_split 设置了多个分类变量,则 level 是一个长度与分类变量个数相同的向量。

设置图形时,将忽略 anno_block 中的所有其他图形参数。请参阅以下示例:

col = c("1" = "red""2" = "blue""A" = "green""B" = "orange")
Heatmap(matrix(rnorm(100), 10), row_km = 2, row_split = sample(c("A""B"), 10, replace = TRUE)) +
rowAnnotation(foo = anno_block(
    graphics = function(index, levels) {
        grid.rect(gp = gpar(fill = col[levels[2]], col = "black"))
        txt = paste(levels, collapse = ",")
        txt = paste0(txt, "\n", length(index), " rows")
        grid.text(txt, 0.50.5, rot = 0,
            gp = gpar(col = col[levels[1]]))
    },
    width = unit(3"cm")
))

未完待续。


收官!


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

群二维码:


老俊俊微信:




知识星球:



所以今天你学习了吗?

欢迎小伙伴留言评论!

今天的分享就到这里了,敬请期待下一篇!

最后欢迎大家分享转发,您的点赞是对我的鼓励肯定

如果觉得对您帮助很大,赏杯快乐水喝喝吧!

推 荐 阅 读




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存