R:circos.heatmap-绘制环形热图包

The circos.heatmap() function

Circular heatmaps are pretty. With circlize package, it is possible to implement circular heatmaps by the low-level function circos.rect() as described in previous Chapter. From version 0.4.10, there is a new high-level function circos.heatmap() which greatly simplifies the creation of circular heatmaps. In this section, I will demostrate the usage of the new circos.heatmap() function.

First let’s generate a random matrix and randomly split it into five groups.

set.seed(123)
mat1 = rbind(cbind(matrix(rnorm(50*5, mean = 1), nr = 50), 
                   matrix(rnorm(50*5, mean = -1), nr = 50)),
             cbind(matrix(rnorm(50*5, mean = -1), nr = 50), 
                   matrix(rnorm(50*5, mean = 1), nr = 50))
            )
rownames(mat1) = paste0("R", 1:100)
colnames(mat1) = paste0("C", 1:10)
mat1 = mat1[sample(100, 100), ] # randomly permute rows
split = sample(letters[1:5], 100, replace = TRUE)
split = factor(split, levels = letters[1:5])

Following plot is the normal layout of the heatmap (by the ComplexHeatmap package).

library(ComplexHeatmap)
Heatmap(mat1, row_split = split)

unnamed-chunk-2-1-1

Figure 6.1: A normal heatmap.

In the next sections, I will demonstrate how to visualize it circularly.

6.1 Input data

The input for circos.heatmap() should be a matrix (or a vector which will be converted to a one-column matrix). If the matrix is split into groups, a categorical variable must be specified with the split argument. Note the value of spilt should be a character vector or a factor. If it is a numeric vector, it is converted to characters internally.

Colors are important aesthetic mappings for the values in the matrix. In circos.heatmap(), users must specify col argument with a user-defined color schema. If the matrix is continuous numeric, value for col should be a color mapping generated by colorRamp2(), and if the matrix is in characters, value of col should be a named color vector.

Following plot is the circular version of the previous heatmap. Note the matrix rows distribute in the circular direction and the matrix columns distribute in the radical direction. In following plot, the circle is split into five sectors where each sector corresponds to one row group.

library(circlize) # >= 0.4.10
col_fun1 = colorRamp2(c(-2, 0, 2), c("blue", "white", "red"))
circos.heatmap(mat1, split = split, col = col_fun1)

A circular heatmap which has been split.

Figure 6.2: A circular heatmap which has been split.

circos.clear()

There is one thing very important that is after creating the circular heatmap, you must call circos.clear() to remove the layout completely. I will explain this point later in this post.

If split is not specified, there is only one big sector that contains the complete heatmap.

circos.heatmap(mat1, col = col_fun1)

A circular heatmap which no split.

Figure 6.3: A circular heatmap which no split.

circos.clear()

6.2 Circular layout

Similar as other circular plots generated by circlize package, the circular layout can be controlled by circos.par() before making the plot.

The parameters for the heatmap track can be controlled in circos.heatmap() function, such as track.height (height of the track) and bg.border (border of the track).

In the following example, the labels for the sectors are added by setting the show.sector.labelsargument. The order of sectors is c("a", "b", "c", "d", "e") clock-wisely. You can see in the following plot, sector a starts from .

circos.par(start.degree = 90, gap.degree = 10)
circos.heatmap(mat1, split = split, col = col_fun1, track.height = 0.4, 
    bg.border = "green", bg.lwd = 2, bg.lty = 2, show.sector.labels = TRUE)

Circular heatmap. Control the layout.

Figure 6.4: Circular heatmap. Control the layout.

circos.clear()

If the value for split argument is a factor, the order of the factor levels controls the order of heatmaps. If split is a simple vector, the order of heatmaps is unique(split).

# note since circos.clear() was called in the previous plot,
# now the layout starts from theta = 0 (the first sector is 'e')
circos.heatmap(mat1, split = factor(split, levels = c("e", "d", "c", "b", "a")), 
    col = col_fun1, show.sector.labels = TRUE)

Circular heatmap. Control the order of heatmaps.

Figure 6.5: Circular heatmap. Control the order of heatmaps.

circos.clear()

6.3 Dendrograms and row names

By default, the numeric matrix is clustered on rows, thus, there are dendrograms generated from the clustering. dend.side argument controls the position of dendrograms relative to the heatmap track. Note, the dendrograms are on a separated track.

circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside")
circos.clear()
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside")
circos.clear()

Circular heatmap. Control the dendrograms.

Figure 6.6: Circular heatmap. Control the dendrograms.

The height of the dendrograms is controlled by dend.track.height argument.

Row names of the matrix can be drawn by setting rownames.side argument. Row names are also drawn in a separated track.

circos.heatmap(mat1, split = split, col = col_fun1, rownames.side = "inside")
circos.clear()
text(0, 0, 'rownames.side = "inside"')
circos.heatmap(mat1, split = split, col = col_fun1, rownames.side = "outside")
circos.clear()
text(0, 0, 'rownames.side = "outside"')

Circular heatmap. Control the row names.

Figure 6.7: Circular heatmap. Control the row names.

Row names of the matrix and the dendrograms can be both drawn. Of course, they cannot be on the same side of the heatmap track.

circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside", 
    rownames.side = "outside")
circos.clear()
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside", 
    rownames.side = "inside")
circos.clear()

Circular heatmap. Control both the dendrograms and row names.

Figure 6.8: Circular heatmap. Control both the dendrograms and row names.

Graphic parameters for row names can be set as a scalar or a vector with the length same as the number of rows in the matrix.

circos.heatmap(mat1, split = split, col = col_fun1, rownames.side = "outside",
    rownames.col = 1:nrow(mat1) %% 10 + 1,
    rownames.cex = runif(nrow(mat1), min = 0.3, max = 2),
    rownames.font = 1:nrow(mat1) %% 4 + 1)

Circular heatmap. Control graphic parameters for row names.

Figure 6.9: Circular heatmap. Control graphic parameters for row names.

circos.clear()

The graphic parameters of dendrogram can be set by directly rendering the dendrograms through a callback function, as will be demonstrated later.

6.4 Clustering

By default, the numeric matrix is clustered on rows. cluster argument can be set to FALSE to turn off the clustering.

Of course, when cluster is set to FALSE, no dendrogram is drawn even if dend.side is set.

circos.heatmap(mat1, split = split, cluster = FALSE, col = col_fun1)
circos.clear()

Circular heatmap. Control clusterings.

Figure 6.10: Circular heatmap. Control clusterings.

Clustering method and distance method are controlled by clustering.method and distance.methodarguments.

Please note circos.heatmap() does not directly support clustering on matrix columns. You should apply column reordering before send to circos.heatmap(), e.g.,

column_od = hclust(dist(t(mat1)))$order
circos.heatmap(mat1[, column_od])
circos.clear()

6.5 Callback on dendrograms

The clustering generates dendrograms. Callback function can be applied to every dendrogram after it is generated in the corresponding sector. The callback function edits the dendrograms such as 1. reorder the dendrograms, or 2. color the dendrograms.

In circos.heatmap(), a user-defined function should be set to dend.callback argument. The user-defined function should have three arguments:

  • dend: The dendrogram in the current sector.
  • m: The sub-matrix that corresponds to the current sector.
  • si: The sector index (or the sector name) for the current sector.

The default callback function is defined as follows and it reorders the dendrogram by weighting the matrix row means.

function(dend, m, si) reorder(dend, rowMeans(m))

Following example reorders the dendrograms in every sector by dendsort::dendsort().

library(dendsort)
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside",
    dend.callback = function(dend, m, si) {
        dendsort(dend)
    }
)
circos.clear()

Circular Heatmap. Reorder dendrograms.

Figure 6.11: Circular Heatmap. Reorder dendrograms.

We can use color_branches() from dendextend package to render the dendrogram edges. E.g., to assign different colors for the dendrograms in the five sectors. Here the height of the dendrogram track is increased by the dend.track.height argument.

library(dendextend)
dend_col = structure(1:5, names = letters[1:5])
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "inside",
    dend.track.height = 0.2,
    dend.callback = function(dend, m, si) {
        # when k = 1, it renders one same color for the whole dendrogram
        color_branches(dend, k = 1, col = dend_col[si])
    }
)

Circular heatmap. Render dendrograms that were split.

Figure 6.12: Circular heatmap. Render dendrograms that were split.

circos.clear()

Or if the matrix is not split, we can assign the sub-dendrograms with different colors.

circos.heatmap(mat1, col = col_fun1, dend.side = "inside",
    dend.track.height = 0.2,
    dend.callback = function(dend, m, si) {
        color_branches(dend, k = 4, col = 2:5)
    }
)

Circular heatmap. Render dendrograms.

Figure 6.13: Circular heatmap. Render dendrograms.

circos.clear()

6.6 Multiple heatmap tracks

If you make a circular plot which only contains one heatmap track, using circos.heatmap() is very straightforward. If you make a more complex circular plot which contains multiple tracks, there are more details on circos.heatmap() you should know.

The first call of circos.heatmap() actually initializes the layout, i.e., applying clustering and splitting the matrix. The dendrograms and split variable are stored internally. This is why you should explicitly callcircos.clear() to remove all the internal variables so that it can ensure when you make a new circular heatmap, the first call of circos.heatmap() is in a clean environment.

The first call of circos.heatmap() determines the row ordering (the order in the circular direction) for all tracks, thus, matrices in the following tracks share the same row ordering as in the first track. Also, the matrices in the following tracks are also split accordingly to the split in the first heatmap track.

If clustering is not applied in the first heatmap track, the natural ordering of rows (i.e., c(1, 2, ..., n)) is used.

mat2 = mat1[sample(100, 100), ] # randomly permute mat1 by rows
col_fun2 = colorRamp2(c(-2, 0, 2), c("green", "white", "red"))

circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside")
circos.heatmap(mat2, col = col_fun2)

R:circos.heatmap-绘制环形热图包

circos.clear()

If I switch the two tracks, you can see now the clustering is controlled by the first heatmap track which is the green-red heatmap track.

circos.heatmap(mat2, split = split, col = col_fun2, dend.side = "outside")
circos.heatmap(mat1, col = col_fun1)

R:circos.heatmap-绘制环形热图包

circos.clear()

You might want to ask, what if I don’t want the clustering to be determined by the first track, while the second or the third track? The solution is simple. As I mentioned, the first call of circos.heatmap()initializes the layout. Actually the initialization can be manually done by explicitly callingcircos.heatmap.initialize() function which circos.heatmap() internally calls.

In circos.heatmap.initialize(), you specify whatever matrix you want to apply clustering as well as the split variable, then, the following circos.heatmap() calls all share this layout.

In the following example, the global layout is determined by mat1 which is visualized in the second track. I set dend.side = "outside" in the first track and actually you can find the dendrograms are actually generatd based on the matrix in the second track.

circos.heatmap.initialize(mat1, split = split)
circos.heatmap(mat2, col = col_fun2, dend.side = "outside")
circos.heatmap(mat1, col = col_fun1)

R:circos.heatmap-绘制环形热图包

circos.clear()

In the next example, the heatmap layout is generated from mat1, while the two heatmap tracks only contain five columns for each.

circos.heatmap.initialize(mat1, split = split)
circos.heatmap(mat1[, 1:5], col = col_fun1)
circos.heatmap(mat1[, 6:10], col = col_fun1)

R:circos.heatmap-绘制环形热图包

circos.clear()

6.7 With other tracks

circos.heatmap() can also be integrated with other non-heatmap tracks, however, it is a little bit tricky. In the circular layout, values on x-axes and y-axes are just the numeric indices. Assume there are nr rows and nc columns for the heatmap in a sector, the heatmap rows are drawn in intervals of (0, 1), c(1, 2), …, c(nr-1, nr) and similar for the heatmap columns. Also the original matrix is reordered. All these effects need to be considered if more tracks are added to make sure to have the correct correspondance to the heatmap track.

After the heatmap layout is done, additional information for the tracks/sectors/cells can be retrieved by the special variable CELL_META. The additional meta data for the cell/sector are listed as follows and they are important for correctly corresponding to the heatmap track.

  • CELL_META$row_dend or simply CELL_META$dend: the dendrogram in the current sector. If no clustering was done, the value is NULL.
  • CELL_META$row_order or simply CELL_META$order: the row ordering of the sub-matrix in the current sector after clustering. If no clustering was done, the value is c(1, 2, ..., ).
  • CELL_META$subset: The subset of indices in the original complete matrix. The values are sorted increasing.

Following are the outputs of CELL_META$row_dend, CELL_META$row_order and CELL_META$subset in the first sector in the example circular heatmap.

CELL_META$row_dend
## 'dendrogram' with 2 branches and 14 members total, at height 10.51736 

CELL_META$row_order
## [1]  2  6  4 12  8  1  5 10  7  9 13 11  3 14

CELL_META$subset
## [1]  8  9 14 18 20 37 55 62 66 72 78 85 93 97

In following example, I add a track which visualizes the row means of the first five columns in mat1. I added cell.padding = c(0.02, 0, 0.02, 0) so that the maximal and minimal points won’t overlap with the top and bottom borders of the cells.

circos.heatmap(mat1, split = split, col = col_fun1)
row_mean = rowMeans(mat1[, 1:5])
circos.track(ylim = range(row_mean), panel.fun = function(x, y) {
    y = row_mean[CELL_META$subset]
    y = y[CELL_META$row_order]
    circos.lines(CELL_META$cell.xlim, c(0, 0), lty = 2, col = "grey")
    circos.points(seq_along(y) - 0.5, y, col = ifelse(y > 0, "red", "blue"))
}, cell.padding = c(0.02, 0, 0.02, 0))

R:circos.heatmap-绘制环形热图包

circos.clear()

Similarly, if the points track is put as the first track, the layout should be initialized in advance.

circos.heatmap.initialize(mat1, split = split)
# This is the same as the previous example
circos.track(ylim = range(row_mean), panel.fun = function(x, y) {
    y = row_mean[CELL_META$subset]
    y = y[CELL_META$row_order]
    circos.lines(CELL_META$cell.xlim, c(0, 0), lty = 2, col = "grey")
    circos.points(seq_along(y) - 0.5, y, col = ifelse(y > 0, "red", "blue"))
}, cell.padding = c(0.02, 0, 0.02, 0))
circos.heatmap(mat1, col = col_fun1) # no need to specify 'split' here

R:circos.heatmap-绘制环形热图包

circos.clear()

Boxplots are very frequently used to correspond to the matrix rows.

circos.heatmap(mat1, split = split, col = col_fun1)
circos.track(ylim = range(mat1), panel.fun = function(x, y) {
    m = mat1[CELL_META$subset, 1:5, drop = FALSE]
    m = m[CELL_META$row_order, , drop = FALSE]
    n = nrow(m)
    # circos.boxplot is applied on matrix columns, so here we transpose it.
    circos.boxplot(t(m), pos = 1:n - 0.5, pch = 16, cex = 0.3)
    circos.lines(CELL_META$cell.xlim, c(0, 0), lty = 2, col = "grey")
}, cell.padding = c(0.02, 0, 0.02, 0))

R:circos.heatmap-绘制环形热图包

circos.clear()

如若转载,请注明出处:https://www.ouq.net/2669.html

(0)
打赏 微信打赏,为服务器增加50M流量 微信打赏,为服务器增加50M流量 支付宝打赏,为服务器增加50M流量 支付宝打赏,为服务器增加50M流量
上一篇 01/03/2024
下一篇 01/08/2024

相关推荐