ComplexHeatmap復(fù)雜熱圖繪制學(xué)習(xí)——2.單個熱圖(二)

2.7 熱圖分割

ComplexHeatmap包的一個主要優(yōu)點是它支持按行和列拆分熱圖,以便更好地對按照數(shù)據(jù)特征進行分組,便于突出顯示各組的信息。

控制拆分的參數(shù)有:row_km, row_split, column_km, column_split。下面,我們將分裂產(chǎn)生的子簇稱為"切片"。

2.7.1 通過k-means聚類分割

row_kmcolumn_km應(yīng)用 k-means分割。

Heatmap(mat, name = "mat", row_km = 2)
image
Heatmap(mat, name = "mat", column_km = 3)
image

行拆分和列拆分可以同時進行。

Heatmap(mat, name = "mat", row_km = 2, column_km = 3)
image

注意此時的行和列樹狀圖中有虛線,在后面說明。

Heatmap()內(nèi)部調(diào)用kmeans()具有隨機起點,這會導(dǎo)致在某些情況下從重復(fù)運行中生成不同的類群。為了解決這個問題,row_km_repeatscolumn_km_repeats可以設(shè)置為一個大于1數(shù)字(運行kmeans()多次),最終的共有K-均值聚類被使用。請注意,形成共識 k-means 的最終集群數(shù)量可能小于row_kmcolumn_km 中設(shè)置的數(shù)量。

# of course, it will be a little bit slower
Heatmap(mat, name = "mat", 
    row_km = 2, row_km_repeats = 100,
    column_km = 3, column_km_repeats = 100)

2.7.2 按分類變量拆分

一般來說,row_split或者column_split可以設(shè)置為分類向量或數(shù)據(jù)框,其中不同的級別組合將熱圖中的行/列分開。如何控制切片的順序在2.7.4節(jié)介紹。

# split by a vector
Heatmap(mat, name = "mat", 
    row_split = rep(c("A", "B"), 9), column_split = rep(c("C", "D"), 12))
image
# split by a data frame
Heatmap(mat, name = "mat", 
    row_split = data.frame(rep(c("A", "B"), 9), rep(c("C", "D"), each = 9)))
image
# split on both dimensions
Heatmap(mat, name = "mat", row_split = factor(rep(c("A", "B"), 9)),
    column_split = factor(rep(c("C", "D"), 12)))
image

實際上,k-means 聚類只是生成一個簇類向量并附加到row_splitcolumn_split。row_km/column_km可以與row_splitcolumn_split混合使用。

Heatmap(mat, name = "mat", row_split = rep(c("A", "B"), 9), row_km = 2)
image
# code only for demonstration
cl = kmeans(mat, centers = 2)$cluster
# classes from k-means are always put as the first column in `row_split`
Heatmap(mat, name = "mat", row_split = cbind(cl, rep(c("A", "B"), 9)))

如果您對默認(rèn)的 k-means 分區(qū)不滿意,只需將分區(qū)向量分配給row_split/ column_split即可輕松使用其他分區(qū)方法。

pa = cluster::pam(mat, k = 3)
Heatmap(mat, name = "mat", row_split = paste0("pam", pa$clustering))
image

如果設(shè)置了row_ordercolumn_order,在每一行/列切片中,它仍然是有序的。

# remember when `row_order` is set, row clustering is turned off
Heatmap(mat, name = "mat", row_order = 18:1, row_km = 2)
image

字符矩陣只能被row_split/column_split參數(shù)分割。

# split by the first column in `discrete_mat`
Heatmap(discrete_mat, name = "mat", col = 1:4, row_split = discrete_mat[, 1])
image

如果row_km/column_kmrow_split/column_split被設(shè)置為向量或數(shù)據(jù)框,則首先將層次聚類應(yīng)用于生成k樹狀圖的每個切片,然后基于每個切片的平均值生成父樹狀圖。通過添加所有子切片中樹狀圖的最大高度來調(diào)整父樹狀圖的高度,并將父樹狀圖添加到子樹狀圖的頂部以形成單個全局樹狀圖。這就是您在之前的熱圖中的樹狀圖中看到虛線的原因。它們用于區(qū)分父樹狀圖和子樹狀圖,并提醒用戶它們的計算方式不同。在Heatmap()這些虛線可以通過設(shè)置show_parent_dend_line = FALSE除去,或設(shè)置全局選項: ht_opt$show_parent_dend_line = FALSE.

Heatmap(mat, name = "mat", row_km = 2, column_km = 3, show_parent_dend_line = FALSE)
image

2.7.3 按樹狀圖分割

拆分的第二種情況是用戶可能仍然希望保留從完整矩陣生成的全局樹狀圖而不是首先將其拆分。在這種情況下,row_split/column_split可以設(shè)置為單個數(shù)字將應(yīng)用于cutree()行/列樹狀圖。當(dāng)cluster_rows/cluster_columns設(shè)置為TRUE 或分配有hclust/dendrogram對象時,這會起作用。

對于這種情況,樹狀圖仍然和原來的一樣,只是樹狀圖葉子的位置被切片之間的間隙稍微調(diào)整了一下。(沒有虛線,因為這里樹狀圖被計算為一個完整的樹狀圖,并且沒有父樹狀圖或子樹狀圖。)

Heatmap(mat, name = "mat", row_split = 2, column_split = 3)
image
dend = as.dendrogram(hclust(dist(mat)))
dend = color_branches(dend, k = 2)
Heatmap(mat, name = "mat", cluster_rows = dend, row_split = 2)
image

如果你想結(jié)合cutree()拆分其他分類變量,你需要首先生成類cutree(),添加到數(shù)據(jù)幀,例如 row_split作用數(shù)據(jù)幀,然后將其發(fā)送到 row_split參數(shù)。

# code only for demonstration
split = data.frame(cutree(hclust(dist(mat)), k = 2), rep(c("A", "B"), 9))
Heatmap(mat, name = "mat", row_split = split)

2.7.4 切片順序

當(dāng)row_split/column_splitrow_km/column_km設(shè)置為分類變量(向量或數(shù)據(jù)框)時,默認(rèn)情況下,會對切片的均值應(yīng)用額外的聚類以顯示切片級別的層次結(jié)構(gòu)。在這種情況下,您無法精確控制切片的順序,因為它是由切片的聚類控制的。

不過,你可以設(shè)置cluster_row_slicescluster_column_slicesFALSE進而關(guān)閉聚類上切片,現(xiàn)在你可以精確地控制切片的順序。

當(dāng)沒有切片聚類時,每個切片的順序可以由levelsrow_split/column_split 的每個變量的控制(在這種情況下,每個變量都應(yīng)該是一個因子)。如果所有變量都是字符,則默認(rèn)順序為unique(row_split)orunique(column_split) 。比較以下熱圖:

Heatmap(mat, name = "mat", 
  row_split = rep(LETTERS[1:3], 6),
    column_split = rep(letters[1:6], 4))
image
# clustering is similar as previous heatmap with branches in some nodes in the dendrogram flipped
Heatmap(mat, name = "mat", 
  row_split = factor(rep(LETTERS[1:3], 6), levels = LETTERS[3:1]),
    column_split = factor(rep(letters[1:6], 4), levels = letters[6:1]))
image
# now the order is exactly what we set
Heatmap(mat, name = "mat", 
  row_split = factor(rep(LETTERS[1:3], 6), levels = LETTERS[3:1]),
    column_split = factor(rep(letters[1:6], 4), levels = letters[6:1]),
    cluster_row_slices = FALSE, 
    cluster_column_slices = FALSE)
image

2.7.5 拆分標(biāo)題

當(dāng)row_split/column_split被設(shè)置為單個數(shù)字,只有一個分類變量,而當(dāng)row_km/column_kmrow_split/column_split同時被設(shè)置為分類變量,將存在多個分類變量。默認(rèn)情況下,標(biāo)題的形式 "level1,level2,..."對應(yīng)于所有分類變量中的每個級別組合。拆分的標(biāo)題可以通過“標(biāo)題模板”來控制。

ComplexHeatmap支持三種類型的模板。第一個是被%s相應(yīng)級別替換的sprintf()地方。在下面的例子中,由于所有的組合split都是A,C,A,DB,CB,D,如果row_title設(shè)置為%s|%s,四大行的標(biāo)題將是 A|CA|D,B|C,B|D

split = data.frame(rep(c("A", "B"), 9), rep(c("C", "D"), each = 9))
Heatmap(mat, name = "mat", row_split = split, row_title = "%s|%s")
image

對于sprintf()模板,您只能放置A,B,C,D 的級別在標(biāo)題中,并且C,D始終在A,B之后(即始終A,CA,D)。但是,在制作熱圖時,您可能希望放置更有意義的文本而不是內(nèi)部級別。一旦知道如何將文本與級別對應(yīng)起來,就可以通過以下兩種模板方法來添加它。

在以下兩個模板方法中,特殊標(biāo)記用于標(biāo)記可執(zhí)行的R代碼(稱為變量插值,其中提取并執(zhí)行代碼并將返回值放回字符串)。有兩種類型的模板標(biāo)記@{}{}. 第一個來自 GetoptLong包,在您安裝ComplexHeatmap包時應(yīng)該已經(jīng)安裝 ,第二個來自您需要先安裝的glue包。

x當(dāng)您使用后兩個模板時,您應(yīng)該使用一個內(nèi)部變量。x只是一個包含當(dāng)前類別級別的簡單向量(例如 c("A", "C"))。

# We only run the code for the first heatmap
map = c("A" = "aaa", "B" = "bbb", "C" = "333", "D" = "444")
Heatmap(mat, name = "mat", row_split = split, row_title = "@{map[ x[1] ]}|@{map[ x[2] ]}")
Heatmap(mat, name = "mat", row_split = split, row_title = "{map[ x[1] ]}|{map[ x[2] ]}")
image

行標(biāo)題默認(rèn)旋轉(zhuǎn),您可以設(shè)置row_title_rot = 0為水平:

Heatmap(mat, name = "mat", row_split = split, row_title = "%s|%s", row_title_rot = 0)
image

當(dāng)row_split/column_split設(shè)置為數(shù)字時,您還可以使用模板來調(diào)整切片的標(biāo)題。

Heatmap(mat, name = "mat", row_split = 2, row_title = "cluster_%s")
image

如果你知道最后的行切片數(shù),你可以直接設(shè)置一個 titles 向量為row_title。請注意行切片的數(shù)量并不總是與nlevel_1*nlevel_2*....

# we know there are four slices
Heatmap(mat, name = "mat", row_split = split, 
    row_title = c("top_slice", "middle_top_slice", "middle_bottom_slice", "bottom_slice"),
    row_title_rot = 0)
image

如果將row_title指定為單個字符串長度,則它就像所有切片的單個標(biāo)題。

Heatmap(mat, name = "mat", row_split = split, row_title = "there are four slices")
image

如果您仍然想要每個切片的標(biāo)題,但也需要全局標(biāo)題,您可以執(zhí)行以下操作。

ht = Heatmap(mat, name = "mat", row_split = split, row_title = "%s|%s")
# This row_title is actually a heatmap-list-level row title
draw(ht, row_title = "I am a row title")
image

實際上row_title是熱圖列表draw()函數(shù)的行標(biāo)題,盡管在示例中只有一個熱圖。該draw() 功能和熱圖列表將后續(xù)介紹 。

如果row_title設(shè)置為NULL,則不繪制行標(biāo)題。

Heatmap(mat, name = "mat", row_split = split, row_title = NULL)
image

所有這些規(guī)則也適用于列標(biāo)題切片。

2.7.6 分割的圖形參數(shù)

在行/列上應(yīng)用拆分時,可以將行/列標(biāo)題和行/列名稱的圖形參數(shù)指定為與切片數(shù)相同的長度。

# by defalt, there no space on the top of the title, here we add 4pt to the top.
# it can be reset by `ht_opt(RESET = TRUE)`
ht_opt$TITLE_PADDING = unit(c(4, 4), "points")
Heatmap(mat, name = "mat", 
    row_km = 2, row_title_gp = gpar(col = c("red", "blue"), font = 1:2),
    row_names_gp = gpar(col = c("green", "orange"), fontsize = c(10, 14)),
    column_km = 3, column_title_gp = gpar(fill = c("red", "blue", "green"), font = 1:3),
    column_names_gp = gpar(col = c("green", "orange", "purple"), fontsize = c(10, 14, 8)))
image

2.7.7 切片之間的間隙

行/列切片之間的間隙空間可以通過row_gap/ column_gap控制。該值可以是單個單位或單位向量。

Heatmap(mat, name = "mat", row_km = 3, row_gap = unit(5, "mm"))
image
Heatmap(mat, name = "mat", row_km = 3, row_gap = unit(c(2, 4), "mm"))
image
Heatmap(mat, name = "mat", row_km = 3, row_gap = unit(c(2, 4), "mm"),
    column_km = 3, column_gap = unit(c(2, 4), "mm"))
image

通過設(shè)置border = TRUE添加熱圖邊框時,會添加每個切片的邊框。

Heatmap(mat, name = "mat", row_km = 2, column_km = 3, border = TRUE)
image

如果您將間隙大小設(shè)置為零,則熱圖看起來就像是由垂直線和水平線分隔的。

Heatmap(mat, name = "mat", row_km = 2, column_km = 3, 
    row_gap = unit(0, "mm"), column_gap = unit(0, "mm"), border = TRUE)
image

2.7.8 拆分熱圖注釋

當(dāng)熱圖被分割時,所有的熱圖組件也被相應(yīng)地分割。以下為您提供了一個簡單的例子,熱圖注釋將在第三章介紹。

Heatmap(mat, name = "mat", row_km = 2, column_km = 3,
    top_annotation = HeatmapAnnotation(foo1 = 1:24, bar1 = anno_points(runif(24))),
    right_annotation = rowAnnotation(foo2 = 18:1, bar2 = anno_barplot(runif(18)))
)
image

2.8 光柵圖像

當(dāng)我們要生成所謂的“高品質(zhì)的圖形”,通常我們保存數(shù)據(jù),用作矢量圖形的格式為:PDF或SVG。矢量圖形基本上存儲了單個圖形元素的細節(jié),因此,如果將一個由非常大的矩陣制成的熱圖保存為矢量圖形,則最終的文件大小會非常大。另一方面,當(dāng)在屏幕上打開pdf 文件時,由于屏幕尺寸有限,熱圖中的多個網(wǎng)格實際上僅映射到單個像素。因此,需要一些方法來有效地縮小原始圖像,并且不需要為熱圖存儲完整的矩陣。

光柵化是一種將矢量圖形轉(zhuǎn)換為顏色矩陣的方法。在這種情況下,圖像表示為 RGB 值的矩陣,稱為光柵圖像。如果熱圖大于屏幕的大小或當(dāng)前圖形設(shè)備可以支持的像素,我們可以轉(zhuǎn)換熱圖并縮小它,通過將其保存為與設(shè)備具有相同維度的顏色矩陣的形式。

讓我們假設(shè)一個矩陣有 nr 行和 nc列。當(dāng)它在某個圖形設(shè)備上繪制時,例如 屏幕設(shè)備,相應(yīng)的熱圖主體具有pr 和 pc分別用于行和列的像素(或點)。當(dāng)nr>pr或 nc>pc,矩陣中的多個值被映射到單個像素。這里我們需要減少nr或 nc。

為了簡單起見,我假設(shè)兩者 nr>pr和 nc>pc. 對于矩陣只有一維比設(shè)備大的場景,原理基本相同。
ComplexHeatmap 2.5.4 版本中,有以下圖像光柵化實現(xiàn)。請注意,實現(xiàn)與早期版本略有不同(當(dāng)然,比早期版本更好)。

  1. 首先是一個圖像(以特定格式,例如png 或 jpeg)(pr?a)×(pc?a)分辨率保存在一個臨時文件中 a是一個縮放因子,接下來raster通過 png::readPNG()jpeg::readJPEG()將其作為對象讀回,然后通過將光柵對象填充到熱圖主體中grid::grid.raster()。所以我們可以說,光柵化是由光柵圖像設(shè)備(png()jpeg())完成的。

    當(dāng)行數(shù)或列數(shù)超過 2000 時(您將看到一條消息。它不會靜默地發(fā)生),這種類型的光柵化會自動打開(如果沒有安裝 magick 包)。也可以通過設(shè)置use_raster參數(shù)來手動控制:

Heatmap(..., use_raster = TRUE)

縮放因子由raster_quality參數(shù)控制。大于 1 的值會生成更大的文件。

Heatmap(..., use_raster = TRUE, raster_quality = 5)
  1. 只需將原始矩陣減少到pr×pc現(xiàn)在每個單個值都可以對應(yīng)單個像素。在規(guī)定中,應(yīng)用用戶定義的函數(shù)來匯總子矩陣。

    這可以通過raster_resize_mat參數(shù)設(shè)置:

# the default summary function is mean()
Heatmap(..., use_raster = TRUE, raster_resize_mat = TRUE)
# use max() as the summary function
Heatmap(..., use_raster = TRUE, raster_resize_mat = max)
# randomly pick one
Heatmap(..., use_raster = TRUE, raster_resize_mat = function(x) sample(x, 1))
  1. 具有分辨率的臨時圖像 nr×nc首先生成,這里 magick::image_resize()用來縮小圖片到尺寸pr×pc,將縮小的圖像作為raster對象讀取并填充到熱圖主體中。magick提供了很多“resizing”/“scaling”圖像的方法,在magick術(shù)語下稱為“過濾方法” 。所有過濾方法 都可以通過 獲得magick::filter_types()。

    這種類型的光柵化可以通過設(shè)置raster_by_magick = TRUEraster_magick_filter來選。

Heatmap(..., use_raster = TRUE, raster_by_magick = TRUE)
Heatmap(..., use_raster = TRUE, raster_by_magick = TRUE, raster_magick_filter = ...)

臨時圖像的類型由raster_device參數(shù)控制。所有支持“光柵設(shè)備”是:png,CairoPNGagg_png,jpegtiff,CairoJPEGCairoTIFF。CairoPNG被視為默認(rèn)光柵設(shè)備,因為以前的默認(rèn)設(shè)備png偶爾會在光柵圖像中產(chǎn)生白色的垂直和水平線。

ComplexHeatmap 中use_raster如果矩陣中的行數(shù)或列數(shù)超過 2000 ,則默認(rèn)打開。

在這篇文章的以下部分,我比較了不同圖像光柵化方法之間的視覺差異。

下面的例子來自Guillaume Devailly 的模擬數(shù)據(jù),但做了小改動。此示例顯示了圖頂部中心的富集模式。

在下面的例子中,我不會展示制作熱圖的代碼,因為熱圖太多,具體設(shè)置已經(jīng)寫成每個熱圖的行標(biāo)題。

我為所有熱圖設(shè)置了相同的顏色映射,以便您可以看到不同的光柵化如何改變原始模式。

為了進行比較,我生成了許多熱圖。它們可以分為三組,對應(yīng)于我之前提到的三種光柵化方法。

  • 通過png()/ CairoPNG()/ agg_png()/ jpeg()/ tiff():光柵化方法1。第一排中的以下熱圖。
  • raster_resize_mat = *: 光柵化方法2,有不同的匯總方法。以下熱圖中的第二行。
  • filter = *:光柵化方法3,使用不同的filterring方法。字符串filter 應(yīng)該是raster_magick_filter. 它被截斷,以便行標(biāo)題不會被繪圖區(qū)域剪切。
image
image
image

更多關(guān)于圖像光柵化的例子可以在這篇博文中找到:ComplexHeatmap 中的光柵化。

根據(jù)已顯示的示例,我會說通過magick包進行光柵化效果更好,因此,默認(rèn)情況下,在 ComplexHeatmap 中,光柵化由magick完成("Lanczos" 作為默認(rèn)過濾器方法),如果未安裝magick,則使用 CairoPNG()打印,建議用戶安裝 magick

以下示例將 PDF 文件大小與不同光柵設(shè)備的光柵圖像進行比較。

library(GetoptLong)
set.seed(123)
mat2 = matrix(rnorm(10000*100), ncol = 100)
pdf(qq("heatmap_no_rasteration.pdf"), width = 8, height = 8)
ht = Heatmap(mat2, cluster_rows = FALSE, cluster_columns = FALSE, use_raster = FALSE)
draw(ht)
dev.off()

# Here I removed CairoTIFF because it is not working on my laptop
all_devices = c("png", "CairoPNG", "agg_png", "jpeg", "CairoJPEG", "tiff")
for(device in all_devices) {
    pdf(qq("heatmap_@{device}.pdf"), width = 8, height = 8)
    ht = Heatmap(mat2, cluster_rows = FALSE, cluster_columns = FALSE, use_raster = TRUE, 
        raster_device = device)
    draw(ht)
    dev.off()
}
all_files = qq("heatmap_@{all_devices}.pdf", collapse = FALSE)
all_files = c("heatmap_no_rasteration.pdf", all_files)
fs = file.size(all_files)
names(fs) = all_files
sapply(fs, function(x) paste(round(x/1024), "KB"))
## heatmap_no_rasteration.pdf            heatmap_png.pdf 
##                  "6356 KB"                   "175 KB" 
##       heatmap_CairoPNG.pdf        heatmap_agg_png.pdf 
##                   "177 KB"                   "175 KB" 
##           heatmap_jpeg.pdf      heatmap_CairoJPEG.pdf 
##                   "667 KB"                   "677 KB" 
##           heatmap_tiff.pdf 
##                   "175 KB"

CairoPNG 生成較小的文件大小,因此它用作默認(rèn)光柵設(shè)備。

2.9 自定義熱圖主體

熱圖主體可以自定義添加更多類型的圖形。默認(rèn)情況下,熱圖主體由具有不同填充顏色的小矩形矩陣(在本文檔的其他部分可能稱為網(wǎng)格,但在此處稱為“cells”)組成。但是,也可以在熱圖上添加更多圖形或符號作為附加層。有兩個參數(shù)cell_funlayer_fun它們都應(yīng)該是用戶定義的函數(shù)。

2.9.1 cell_fun

cell_fun繪制在每個cells中重復(fù),這是在兩個內(nèi)部嵌套執(zhí)行for循環(huán),而layer_fun是的向量化版本cell_fun。 cell_fun更容易理解,但layer_fun執(zhí)行速度更快,更可定制。

cell_fun 是一個具有 7 個參數(shù)的函數(shù)(參數(shù)名稱可以與以下不同,但順序必須相同),它們是:

  • j: 矩陣中的列索引。列索引對應(yīng)于視圖中的 x 方向,這j就是將其作為第一個參數(shù)的原因。
  • i: 矩陣中的行索引。
  • x:在熱圖主體的視圖中測量的單元格中點的 x 坐標(biāo)。
  • y:在熱圖主體的視圖中測量的單元格中點的 y 坐標(biāo)。
  • width: 單元格的寬度。該值為unit(1/ncol(sub_mat), "npc") ,其中sub_mat對應(yīng)于由行分裂和分裂列的子矩陣。
  • height: 單元格的高度。該值為unit(1/nrow(sub_mat), "npc")。
  • fill: 單元格的顏色。

在每個單元格中執(zhí)行時,七個參數(shù)的值會自動發(fā)送到函數(shù)。

最常見的用途是將矩陣中的值添加到熱圖中:

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))
})
image

我們也可以選擇只為具有正值的單元格添加文本:

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))
})
image

您可以拆分熱圖而無需執(zhí)行任何額外操作cell_fun

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))
})
image

在下面的例子中,我們制作了一個熱圖,它顯示了與corrplot包類似的相關(guān)矩陣:

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)
image

正如您在前面的圖中所看到的,設(shè)置非標(biāo)準(zhǔn)參數(shù)時 rect_gp = gpar(type = "none"),會執(zhí)行聚類,但在熱圖主體上沒有繪制任何內(nèi)容。

最后一個例子是可視化一個GO 游戲。輸入數(shù)據(jù)記錄游戲中的動作。

str = "B[cp];W[pq];B[dc];W[qd];B[eq];W[od];B[de];W[jc];B[qk];W[qn]
;B[qh];W[ck];B[ci];W[cn];B[hc];W[je];B[jq];W[df];B[ee];W[cf]
;B[ei];W[bc];B[ce];W[be];B[bd];W[cd];B[bf];W[ad];B[bg];W[cc]
;B[eb];W[db];B[ec];W[lq];B[nq];W[jp];B[iq];W[kq];B[pp];W[op]
;B[po];W[oq];B[rp];W[ql];B[oo];W[no];B[pl];W[pm];B[np];W[qq]
;B[om];W[ol];B[pk];W[qp];B[on];W[rm];B[mo];W[nr];B[rl];W[rk]
;B[qm];W[dp];B[dq];W[ql];B[or];W[mp];B[nn];W[mq];B[qm];W[bp]
;B[co];W[ql];B[no];W[pr];B[qm];W[dd];B[pn];W[ed];B[bo];W[eg]
;B[ef];W[dg];B[ge];W[gh];B[gf];W[gg];B[ek];W[ig];B[fd];W[en]
;B[bn];W[ip];B[dm];W[ff];B[cb];W[fe];B[hp];W[ho];B[hq];W[el]
;B[dl];W[fk];B[ej];W[fp];B[go];W[hn];B[fo];W[em];B[dn];W[eo]
;B[gp];W[ib];B[gc];W[pg];B[qg];W[ng];B[qc];W[re];B[pf];W[of]
;B[rc];W[ob];B[ph];W[qo];B[rn];W[mi];B[og];W[oe];B[qe];W[rd]
;B[rf];W[pd];B[gm];W[gl];B[fm];W[fl];B[lj];W[mj];B[lk];W[ro]
;B[hl];W[hk];B[ik];W[dk];B[bi];W[di];B[dj];W[dh];B[hj];W[gj]
;B[li];W[lh];B[kh];W[lg];B[jn];W[do];B[cl];W[ij];B[gk];W[bl]
;B[cm];W[hk];B[jk];W[lo];B[hi];W[hm];B[gk];W[bm];B[cn];W[hk]
;B[il];W[cq];B[bq];W[ii];B[sm];W[jo];B[kn];W[fq];B[ep];W[cj]
;B[bk];W[er];B[cr];W[gr];B[gk];W[fj];B[ko];W[kp];B[hr];W[jr]
;B[nh];W[mh];B[mk];W[bb];B[da];W[jh];B[ic];W[id];B[hb];W[jb]
;B[oj];W[fn];B[fs];W[fr];B[gs];W[es];B[hs];W[gn];B[kr];W[is]
;B[dr];W[fi];B[bj];W[hd];B[gd];W[ln];B[lm];W[oi];B[oh];W[ni]
;B[pi];W[ki];B[kj];W[ji];B[so];W[rq];B[if];W[jf];B[hh];W[hf]
;B[he];W[ie];B[hg];W[ba];B[ca];W[sp];B[im];W[sn];B[rm];W[pe]
;B[qf];W[if];B[hk];W[nj];B[nk];W[lr];B[mn];W[af];B[ag];W[ch]
;B[bh];W[lp];B[ia];W[ja];B[ha];W[sf];B[sg];W[se];B[eh];W[fh]
;B[in];W[ih];B[ae];W[so];B[af]"

我們將其轉(zhuǎn)換為矩陣:

str = gsub("\\n", "", str)
step = strsplit(str, ";")[[1]]
type = gsub("(B|W).*", "\\1", step)
row = gsub("(B|W)\\[(.).\\]", "\\2", step)
column = gsub("(B|W)\\[.(.)\\]", "\\2", step)

go_mat = matrix(nrow = 19, ncol = 19)
rownames(go_mat) = letters[1:19]
colnames(go_mat) = letters[1:19]
for(i in seq_along(row)) {
    go_mat[row[i], column[i]] = type[i]
}
go_mat[1:4, 1:4]
##   a   b   c   d  
## a NA  NA  NA  "W"
## b "W" "W" "W" "B"
## c "B" "B" "W" "W"
## d "B" "W" "B" "W"

根據(jù)矩陣中的值放置黑色和白色的石頭:

Heatmap(go_mat, name = "go", rect_gp = gpar(type = "none"),
    cell_fun = function(j, i, x, y, w, h, col) {
        grid.rect(x, y, w, h, gp = gpar(fill = "#dcb35c", col = NA))
        if(i == 1) {
            grid.segments(x, y-h*0.5, x, y)
        } else if(i == nrow(go_mat)) {
            grid.segments(x, y, x, y+h*0.5)
        } else {
            grid.segments(x, y-h*0.5, x, y+h*0.5)
        }
        if(j == 1) {
            grid.segments(x, y, x+w*0.5, y)        
        } else if(j == ncol(go_mat)) {
            grid.segments(x-w*0.5, y, x, y)
        } else {
            grid.segments(x-w*0.5, y, x+w*0.5, y)
        }

        if(i %in% c(4, 10, 16) & j %in% c(4, 10, 16)) {
            grid.points(x, y, pch = 16, size = unit(2, "mm"))
        }

        r = min(unit.c(w, h))*0.45
        if(is.na(go_mat[i, j])) {
        } else if(go_mat[i, j] == "W") {
            grid.circle(x, y, r, gp = gpar(fill = "white", col = "white"))
        } else if(go_mat[i, j] == "B") {
            grid.circle(x, y, r, gp = gpar(fill = "black", col = "black"))
        }
    },
    col = c("B" = "black", "W" = "white"),
    show_row_names = FALSE, show_column_names = FALSE,
    column_title = "One famous GO game",
    heatmap_legend_param = list(title = "Player", at = c("B", "W"), 
        labels = c("player1", "player2"), border = "black")
)
image

2.9.2 layer_fun

cell_fun逐個單元添加圖形,而layer_fun以塊方式添加圖形。與cell_fun,類似,layer_fun也需要七個參數(shù),但它們都是向量形式(layer_fun也可以有第八個和第九個參數(shù),這將在本節(jié)后面介紹):

# 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) {...})

ji仍然包含與原始矩陣對應(yīng)的列和行索引,但因為現(xiàn)在layer_fun適用于一個單元格塊(如果熱圖被分割,則為一個熱圖塊),ji是當(dāng)前熱圖切片中所有單元格的向量。類似y,x,w,hfill是對應(yīng)于在當(dāng)前熱圖片中的所有小區(qū)中的所有載體。

由于ji現(xiàn)在是向量,為了獲得矩陣中的相應(yīng)值,我們不能使用該形式,mat[j, i]因為它為您提供了一個包含length(i)行和length(j)列的子矩陣 。相反,我們可以使用ComplexHeatmap中的pindex() 函數(shù),這類似于矩陣的成對索引。請參閱以下示例:

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))
})
image

只向具有正值的單元格添加文本:

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))
})
image

當(dāng)熱圖被分割時,layer_fun應(yīng)用于每個切片。

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"))
        }
})
image

layer_fun還可以有另外兩個參數(shù),它們是當(dāng)前行切片和列切片的索引。例如,我們想為右上角和左下角的切片添加邊框。

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"))
        }
})
image

這樣做的好處layer_fun是不僅可以快速添加圖形,還可以提供更多自定義熱圖的可能性??紤]以下可視化:對于熱圖中的每一行,如果相鄰兩列中的值具有相同的符號,我們根據(jù)兩個值的符號添加一條紅線或一條綠線。由于現(xiàn)在單元格中的圖形依賴于其他單元格,因此只能通過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"))
                }
            }
        }
    }
)
image

發(fā)送到的值layer_fun都是向量(用于grid圖形函數(shù)的向量化),但是,layer_fun應(yīng)用到的熱圖切片仍然由矩陣表示,因此,如果所有參數(shù)都layer_fun可以,這將非常方便轉(zhuǎn)換為當(dāng)前切片的子矩陣。在這里,如上例所示, restore_matrix()完成這項工作。restore_matrix()直接接受in的前四個參數(shù)layer_fun并返回一個索引矩陣,其中行和列對應(yīng)當(dāng)前切片中的行和列,從上到下,從左到右。矩陣中的值是j當(dāng)前切片中向量的自然順序。

如果您運行以下代碼:

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

如您所見,這是一個三行五列的索引矩陣,其中第一行對應(yīng)于切片中的頂行。矩陣中的值對應(yīng)于 in j, i, x, y, ... in的自然索引(即 1, 2, ...)layer_fun?,F(xiàn)在,如果我們想在左上角切片的第二列上添加值,放在里面的代碼layer_fun看起來像:

for(ind in ind_mat[, 2]) {
    grid.text(small_mat[i[ind], j[ind]], x[ind], y[ind], ...)
}

現(xiàn)在更容易理解第二個例子:我們想在每個切片的第二行和第三列添加點:

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"))
    }
)
image

2.10 熱圖的大小

width, heatmap_width,heightheatmap_height控制熱圖的大小。默認(rèn)情況下,所有熱圖組件都有固定的寬度或高度,例如行樹狀圖的寬度為1cm。熱圖主體的寬度或高度填充最終繪圖區(qū)域的其余區(qū)域,這意味著,如果您在交互式圖形窗口中繪制它并通過拖動它來更改窗口的大小,則熱圖主體的大小為自動調(diào)整。

heatmap_widthheatmap_height控制整個熱圖包括所有熱圖組件(不包括圖例)的寬度/高度,同時widthheight控制heamtap主體的寬度/高度。所有這四個參數(shù)都可以設(shè)置為絕對單位。

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

在調(diào)整熱圖列表中的大小時,這四個參數(shù)更為重要(參見第4.2節(jié) )。

當(dāng)熱圖的大小設(shè)置為絕對單位時,圖的大小可能大于熱圖的大小,這會在熱圖周圍產(chǎn)生空白區(qū)域。熱圖的大小可以通過ComplexHeatmap:::width()ComplexHeatmap:::height()函數(shù)檢索。

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

如果要將熱圖保存在與熱圖大小完全相同的圖形文件中,請查看此博客文章(在熱圖中設(shè)置單元格寬度/高度)。

2.11 繪制熱圖

Heatmap()函數(shù)實際上只是一個構(gòu)造函數(shù),也就是說它只將所有的數(shù)據(jù)和配置放入Heatmap類中的對象中。只有在draw()調(diào)用該方法時才會執(zhí)行聚類。在交互模式下(例如,您可以在交互式 R 終端中逐行鍵入 R 代碼),直接調(diào)用Heatmap()而不返回任何對象會打印對象和類對象內(nèi)部調(diào)用的打印方法(或 S4show()方法)。因此,如果您 在 R 終端中輸入,它看起來像是一個繪圖函數(shù),例如,您需要注意它實際上不是真的,并且在以下情況下您可能看不到任何繪圖。

  • 你將Heatmap(...)放入一個函數(shù)中,
  • 你將Heatmap(...)放入一個代碼塊,如forif-else
  • 你將Heatmap(...)放入一個 Rscript 并在命令行下運行它。

原因是在以上三種情況下,show()方法不會被調(diào)用,因此draw()方法也不會被執(zhí)行。因此,要繪制該圖,您需要draw()顯式調(diào)用:draw(Heatmap(...))或:

# code only for demonstration
ht = Heatmap(...)
draw(ht)

draw()函數(shù)實際上應(yīng)用于HeatmapList類中的熱圖列表 。draw()一個Heatmap類的方法構(gòu)造一個HeatmapList只有一個熱圖和類的調(diào)用draw()方法HeatmapList。該draw()函數(shù)接受更多參數(shù),例如控制圖例。它將在第4章中討論 。

draw(ht, heatmap_legend_side, padding, ...)

2.12 獲取順序和樹狀圖

熱圖的行/列順序可以通過row_order()/column_order()函數(shù)獲得 。您可以直接應(yīng)用于由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()函數(shù)不執(zhí)行聚類,因此,當(dāng)直接將row_order()應(yīng)用在ht1時,將首先執(zhí)行聚類。稍后在通過draw(ht1)制作熱圖時,將再次應(yīng)用聚類。如果您在熱圖中設(shè)置 k-means 聚類,這可能是一個問題。由于聚類應(yīng)用了兩次,k-means 可能會給你不同的聚類,這意味著你可能有不同的row_order()結(jié)果,你可能有不同的熱圖。

在以下代碼塊中o1,o2o3可能會有所不同,因為每次都執(zhí)行 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()函數(shù)返回已經(jīng)重新排序的熱圖(或更準(zhǔn)確地說,熱圖列表),并且row_order()只是從對象中提取行順序,這確保行順序與熱圖中顯示的行順序完全相同。在上面的代碼中的o3o4.

因此,獲取行/列順序的優(yōu)選方法如下。

# code only for demonstration
ht = Heatmap(small_mat)
ht = draw(ht)
row_order(ht)
column_order(ht)

如果行/列被拆分,行順序或列順序?qū)⑹且粋€列表。

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()函數(shù)返回樹狀圖。它返回單個樹狀圖或樹狀圖列表,具體取決于熱圖是否被分割。

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

row_order(), column_order(),row_dend()column_dend()也適用于熱圖列表,它將在第4.12節(jié)中介紹 。

2.13 熱圖子集

由于熱圖是矩陣的表示,因此Heatmap還有一個子集方法。

ht = Heatmap(mat, name = "mat")
dim(ht)
## [1] 18 24
ht[1:10, 1:10]
image

注釋也相應(yīng)地進行了子集化。

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
image

如果熱圖組件類似于向量,則它們被子集化。熱圖中的一些配置在子化時保持不變,例如如果row_km 在原始熱圖中設(shè)置,則保留k-means的配置并在子熱圖中執(zhí)行。所以在下面的例子中,k-means 聚類只在為 制作熱圖時執(zhí)行ht2

ht = Heatmap(mat, name = "mat", row_km = 2)
ht2 = ht[1:10, 1:10]
ht2
image

子集熱圖的實現(xiàn)是非常實驗性的。它并不總是有效,如果cell_fun已定義并使用外部矩陣,或者將聚類對象分配給cluster_rowscluster_columns。

HeatmapAnnotation類(第3.19節(jié))和HeatmapList類(第4.10節(jié) )也有子集方法,但兩者都非常具有實驗性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容