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)
= rbind(cbind(matrix(rnorm(50*5, mean = 1), nr = 50),
mat1 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[sample(100, 100), ] # randomly permute rows
mat1 = sample(letters[1:5], 100, replace = TRUE)
split = factor(split, levels = letters[1:5]) split
Following plot is the normal layout of the heatmap (by the ComplexHeatmap package).
library(ComplexHeatmap)
Heatmap(mat1, row_split = split)
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
= colorRamp2(c(-2, 0, 2), c("blue", "white", "red"))
col_fun1 circos.heatmap(mat1, split = split, col = col_fun1)
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)
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.labels
argument. 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)
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)
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()
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"')
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()
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)
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()
Clustering method and distance method are controlled by clustering.method
and distance.method
arguments.
Please note circos.heatmap()
does not directly support clustering on matrix columns. You should apply column reordering before send to circos.heatmap()
, e.g.,
= hclust(dist(t(mat1)))$order
column_od 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()
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)
= structure(1:5, names = letters[1:5])
dend_col 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])
} )
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)
} )
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.
= mat1[sample(100, 100), ] # randomly permute mat1 by rows
mat2 = colorRamp2(c(-2, 0, 2), c("green", "white", "red"))
col_fun2
circos.heatmap(mat1, split = split, col = col_fun1, dend.side = "outside")
circos.heatmap(mat2, col = col_fun2)
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)
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)
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)
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 simplyCELL_META$dend
: the dendrogram in the current sector. If no clustering was done, the value isNULL
.CELL_META$row_order
or simplyCELL_META$order
: the row ordering of the sub-matrix in the current sector after clustering. If no clustering was done, the value isc(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)
= rowMeans(mat1[, 1:5])
row_mean circos.track(ylim = range(row_mean), panel.fun = function(x, y) {
= row_mean[CELL_META$subset]
y = y[CELL_META$row_order]
y 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.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) {
= row_mean[CELL_META$subset]
y = y[CELL_META$row_order]
y 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
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) {
= mat1[CELL_META$subset, 1:5, drop = FALSE]
m = m[CELL_META$row_order, , drop = FALSE]
m = nrow(m)
n # 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)) },
circos.clear()
如若转载,请注明出处:https://www.ouq.net/2669.html