Chapter1 Introduction

前言

圆形布局在表示复杂信息时非常有用。首先,它用长轴或大量类别优雅地表示信息;第二,它直观地显示了聚焦在同一物体上的多个轨迹的数据;第三,它很容易展示元素之间的关系。它提供了一种有效的方式来安排圆圈上的信息,它是美丽的。

Circos是一个先锋工具,广泛用于Perl实现的循环布局表示。它极大地提高了科学结果的可视化(特别是在基因组学领域)。因此,圆形布局的地块通常被称为”circos地块”。在这里,circlize包的目标是在R中实现Circos。

在R中实现的一个重要优势是,R是一个理想的环境,它提供了数据分析和数据可视化之间的无缝连接。circlize并不是一个为Circos生成配置文件的前端包装器,它完全采用R风格,使用R优雅的统计和图形引擎进行编码。我们的目标是保持Circos的灵活性和可配置性,但也使软件包更容易使用,并增强它以支持更多类型的图形。在本书中,第一部分的章节详细概述了循环的一般功能。第二部分介绍了专门为可视化基因组数据集设计的功能。第三部分给出了通过和弦图可视化关系的综合指南。

1.1设计原则

圆形布局由扇区和轨道组成。对于不同类别的数据,它们被分配到不同的扇区,对于同一类别的多个测量,它们被表示为从圈外到圈内的堆叠轨道。扇形和轨迹的交点称为单元(或网格、面板),它是圆形布局的基本单元。它是一个虚构的绘图区域,用于绘制数据点。

由于大多数图形由简单的图形组成,如点、线、多边形,circlize实现了低级图形功能,用于在圆形绘图区域中添加图形,因此可以通过低级图形功能的不同组合轻松生成更复杂的图形。这一原则确保了通用性,即高级图形的类型不受软件本身的限制,并且可以在此基础上构建专注于特定兴趣的高级软件包。

目前有以下低级图形函数可用于添加图形。其用法与不带circos的函数非常相似。前缀来自基本图形引擎,但有一些专门为圆形可视化设计的增强。

> circos.points() # 在单元格中添加点
> circos.lines() # 在单元格中添加线
> circos.segments() #在单元格中添加段
> circos.rect() #在单元格中添加矩形
> circos.polygon() #在单元格中添加多边形
> circos.text() #在单元格中添加文本
> circos.axis() #在单元格中添加轴
> circos.yaxis() #在单元格中添加轴

下面的函数用来绘制圆中两个位置之间的链接:

> circos.link()

以下函数绘制高级图形:

> circos.barplot() #条形图
> circos.boxplot() #箱形图
> circos.violin() #提琴图
> circos.heatmap() #环形热力图.
> circos.raster() #光栅图
> circos.arrow() #圆形箭头

以下函数安排圆形布局:

> circos.initialize() #分配扇区
> circos.track() #在单个轨道中为单元格创建绘图区域
> circos.update() #更新一个存在的单元格
> circos.par() #图形参数
> circos.info() #打印当前圆形图的一般参数
> circos.clear() #重置图形参数和内部变量

因此,从理论上讲,您可以通过上述功能绘制大多数类型的圆形图形。图1.1列出了几个由circlize绘制的复杂圆形图。看完这本书后,你肯定能实现.

Figure1.1 一些例子
Figure1.1 一些例子

1.2快速开始

在我们深入研究细节之前,我首先演示了一个简单的例子,在circlize软件包中使用基本功能,以帮助您了解软件包的工作原理。

首先让我们生成一些随机数据。需要一个字符向量来表示类别,一个x值的数字向量和一个y值的向量。

a.生成随机数据

> set.seed(999)
> n = 1000
> df <- data.frame(sectors = sample(letters[1:8], n, replace = T),
+                  x = rnorm(n), y = runif(n))
> head(df)
  sectors           x         y
1       c -0.33837650 0.2277292
2       d  0.20128773 0.8345422
3       e  0.60271173 0.6507941
4       g -0.22501815 0.8613092
5       a -0.01011359 0.3624672
6       f -0.19359724 0.5251656

首先,我们初始化圆形布局。圆圈根据每个类别的x轴上的数据范围划分为扇区。在下面的代码中,df\(x 被 df\)sectors分割,扇区的宽度根据每个类别中的数据范围自动计算。默认情况下,扇区从θ = 0开始定位(在极坐标系统中),并沿着圆形时钟明智地移动。运行以下代码后可能看不到任何内容,因为尚未添加任何轨道。

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)

> circos.clear()

我们通过选项函数circis.par()将全局参数track.height设置为0.1,以便将要添加的所有轨道的默认高度为0.1。Circlize使用的圆的半径总是为1,因此高度为0.1意味着圆半径的10%。在后面的章节中,您可以找到如何使用物理单位设置高度,例如厘米。

请注意,扇区的分配只需要x方向(或圆形方向)上的值,y方向(激进方向)上的值将在创建轨道的步骤中使用。

初始化圆形布局后,可以以逐轨方式将图形添加到绘图中。在绘制任何东西之前,我们需要知道所有轨道都应该首先由circos.trackPlotRegion()或简称circos.track()创建,然后可以添加低级函数。想想在基础R图形引擎中,您需要首先调用plot(),然后可以使用points()和lines()等函数来添加图形。由于轨道中单元格的x范围已经在初始化步骤中定义,因此我们只需要为每个单元格指定y范围。

Y 范围可以由 y 参数指定为数字向量(因此 y 范围将在每个单元格中自动提取和计算)或 ylim 参数指定为长度为 2 的向量。原则上,同一轨道中的所有单元的y范围应该是相同的。(见图1.2)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)

> circos.clear()

圆形图的轴通常画在圆的最外。在这里,我们通过将circos.axis()放入自定义的函数panel.fun中,在第一个轨道中添加轴(见上面的代码)。circos.track()以逐个单元的方式创建绘图区域,panel.fun实际上在创建特定单元的绘图区域后立即执行。因此,panel.fun实际上意味着在“当前单元格”中添加图形(第2.7节进一步讨论了panel.fun的含义)。在不指定任何参数的情况下,circos.axis()在每个单元格的顶部(或每个单元格的外部)绘制x轴。

此外,我们使用circos.text()在第一个轨道之外添加扇区名称。CELL_META提供当前单元格的“元信息”。有几个参数可以通过CELL_META检索。它的所有用法将在第2.7节中解释。在上面的代码中,扇区名称绘制在单元格之外,您可能会看到警告消息,指出数据点超出了绘图区域。这完全没问题,不用担心。您还可以通过创建一个没有边界的空轨道作为第一个轨道并在其中添加扇区名称来添加扇区名称(就像circos.initializewithIdeogram()和chordDiagram()所做的那样,在您完成以下章节之后)。

在y方向上指定文本位置时,在文本的y位置上添加mm_y(5)(5mm)的偏移量。在circos.text()中,x和y值在数据坐标(单元格中的坐标)中测量,并且有一些辅助函数将绝对单位转换为数据坐标中的相应值。第2.8.2节提供了不同坐标单位转换的更多信息。

创建轨道后,由circos.trackPoints()将点添加到第一个轨道。circos.trackPoints()只是同时在所有单元格中添加点。正如第3.2节中进一步解释的那样,它可以通过将circos.text()放在panel.fun中来代替,但是,如果只需要将点放入单元格中,circos.trackPoints()会更方便(但我真的不推荐)。很容易理解,这个函数需要一个分类变量(df\(扇区),x方向和y方向的值(df\)x和df$y)。

如上述代码所示,circos.text()等低级函数也可以在panel.fun之外使用。如果是这样,需要明确指定sector.index和track.index,因为“当前”扇区和“当前”轨道可能不是您想要的。如果图形直接添加到最近创建的轨道中,则可以省略track.index,因为该轨道只是被标记为“当前”轨道。

好的,现在我们把直方图加到第二条轨道上。这里circos.trackHist()是一个高级函数,这意味着它创建了一个新的轨道(正如你可以想象的那样,hist()也是一个高级函数)。bin.size明确设置大小,以便所有单元格中直方图的bin大小相同,并且可以相互比较。(见图1.3)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> # 设置轨道
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> # 添加一行文本
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)
> # 添加第二条直方图轨道
> bgcol <- rep(c('#EFEFEF','#CCCCCC'),4)
> circos.trackHist(df$sectors, df$x, bin.size = 0.2, bg.col = bgcol, col = NA)

> circos.clear()

在第三个轨道和panel.fun中,我们在每个单元格中随机选择10个数据点,按x值对它们进行排序,并以线条连接。在下面的代码中,当sectors(第一个未命名参数)在circos.track()中设置x和y参数时,x值和y值由df$sectors拆分,x和y值的相应子集通过panel.fun的x和y参数发送到panel.fun。因此,panel.fun中的x 和 y正是“当前”单元格中的值。(见图1.4)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> # 设置轨道
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> # 添加一行文本
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)
> # 添加第二条直方图轨道
> bgcol <- rep(c('#EFEFEF','#CCCCCC'),4)
> circos.trackHist(df$sectors, df$x, bin.size = 0.2, bg.col = bgcol, col = NA)
> # 添加第三条折线轨道
> circos.track(df$sectors, x = df$x, y = df$y,
+              panel.fun = function(x,y){
+                ind = sample(length(x), 10)
+                x2 = x[ind]
+                y2 = y[ind]
+                od = order(x2)
+                circos.lines(x2[od], y2[od])
+              })

> circos.clear()

现在我们回到第二轨道,并更新扇区“d”中的单元格。这是由circos.updatePlotRegion()或短版本circos.update()完成的。该函数擦除已添加的图形。circos.update()无法修改单元格的xlim和ylim以及与单元格位置相关的其他设置。circos.update()需要明确指定扇区索引和轨道索引,除非“当前”单元格是您要更新的单元格。调用circos.update()后,“当前”单元格将重定向到您刚刚指定的单元格,您可以使用低级图形函数直接向其中添加图形。(见图1.5)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> # 设置轨道
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> # 添加一行文本
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)
> # 添加第二条直方图轨道
> bgcol <- rep(c('#EFEFEF','#CCCCCC'),4)
> circos.trackHist(df$sectors, df$x, bin.size = 0.2, bg.col = bgcol, col = NA)

> circos.clear()

在第三个轨道和panel.fun中,我们在每个单元格中随机选择10个数据点,按x值对它们进行排序,并以线条连接。在下面的代码中,当sectors(第一个未命名参数)在circos.track()中设置x和y参数时,x值和y值由df$sectors拆分,x和y值的相应子集通过panel.fun的x和y参数发送到panel.fun。因此,panel.fun中的x 和 y正是“当前”单元格中的值。(见图1.4)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> # 设置轨道
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> # 添加一行文本
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)
> # 添加第二条直方图轨道
> bgcol <- rep(c('#EFEFEF','#CCCCCC'),4)
> circos.trackHist(df$sectors, df$x, bin.size = 0.2, bg.col = bgcol, col = NA)
> # 添加第三条折线轨道
> circos.track(df$sectors, x = df$x, y = df$y,
+              panel.fun = function(x,y){
+                ind = sample(length(x), 10)
+                x2 = x[ind]
+                y2 = y[ind]
+                od = order(x2)
+                circos.lines(x2[od], y2[od])
+              })
> # 更新d区2轨
> circos.update(sector.index = 'd', track.index = 2,
+               bg.col = '#FF8080', bg.border = 'black')
> circos.points(x = -2:2, y = rep(0.5,5), col = 'white')
> circos.text(CELL_META$xcenter, CELL_META$ycenter, 'update', col = 'white')

> circos.clear()

接下来我们继续创建新的轨道。虽然我们已经回到了第二个轨道,当创建一个新轨道时,新轨道仍然是在最里面的轨道之后创建的。在这个新轨道中,我们通过circos.rect()添加热图。注意,这里我们没有设置输入数据,而只是设置了ylim参数,因为热图只是从最左到右,从下到上填充整个单元格。此外,ylim的确切值并不重要,panel.fun()中的x,y也不使用(实际上它们都是NULL)。(见图1.6)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> # 设置轨道
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> # 添加一行文本
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)
> # 添加第二条直方图轨道
> bgcol <- rep(c('#EFEFEF','#CCCCCC'),4)
> circos.trackHist(df$sectors, df$x, bin.size = 0.2, bg.col = bgcol, col = NA)
> # 添加第三条折线轨道
> circos.track(df$sectors, x = df$x, y = df$y,
+              panel.fun = function(x,y){
+                ind = sample(length(x), 10)
+                x2 = x[ind]
+                y2 = y[ind]
+                od = order(x2)
+                circos.lines(x2[od], y2[od])
+              })
> # 更新d区2轨
> circos.update(sector.index = 'd', track.index = 2,
+               bg.col = '#FF8080', bg.border = 'black')
> circos.points(x = -2:2, y = rep(0.5,5), col = 'white')
> circos.text(CELL_META$xcenter, CELL_META$ycenter, 'update', col = 'white')
> # 添加热图轨道
> circos.track(ylim = c(0,1), panel.fun = function(x,y){
+   xlim = CELL_META$xlim
+   ylim = CELL_META$ylim
+   breaks = seq(xlim[1], xlim[2], by = 0.1)
+   n_breaks = length(breaks)
+   circos.rect(breaks[-n_breaks], rep(ylim[1],n_breaks - 1),
+               breaks[-1], rep(ylim[2], n_breaks - 1),
+               col = rand_color(n_breaks), border = NA)
+ })

> circos.clear()

在圆圈的最里面,添加了链接或丝带。可以有从单点到点、点到区间或区间到区间的链接。第3.12节给出了链接的详细用法。(见图1.7)

> library(circlize)
> circos.par('track.height' = 0.1)
> circos.initialize(df$sectors, x = df$x)
> # 设置轨道
> circos.track(df$sectors, y = df$y,
+              panel.fun = function(x,y){
+                circos.text(CELL_META$xcenter,
+                            CELL_META$cell.ylim[2] + mm_y(5),
+                            CELL_META$sector.index)
+                circos.axis(labels.cex = 0.6)
+              })
> col = rep(c('#FF0000','#00FF00'),4)
> # 将点放入单元格中
> circos.trackPoints(df$sectors, df$x, df$y, col = col, pch = 16, cex = 0.5)
> # 添加一行文本
> circos.text(-1, 0.5, 'text', sector.index = 'a', track.index = 1)
> # 添加第二条直方图轨道
> bgcol <- rep(c('#EFEFEF','#CCCCCC'),4)
> circos.trackHist(df$sectors, df$x, bin.size = 0.2, bg.col = bgcol, col = NA)
> # 添加第三条折线轨道
> circos.track(df$sectors, x = df$x, y = df$y,
+              panel.fun = function(x,y){
+                ind = sample(length(x), 10)
+                x2 = x[ind]
+                y2 = y[ind]
+                od = order(x2)
+                circos.lines(x2[od], y2[od])
+              })
> # 更新d区2轨
> circos.update(sector.index = 'd', track.index = 2,
+               bg.col = '#FF8080', bg.border = 'black')
> circos.points(x = -2:2, y = rep(0.5,5), col = 'white')
> circos.text(CELL_META$xcenter, CELL_META$ycenter, 'update', col = 'white')
> # 添加热图轨道
> circos.track(ylim = c(0,1), panel.fun = function(x,y){
+   xlim = CELL_META$xlim
+   ylim = CELL_META$ylim
+   breaks = seq(xlim[1], xlim[2], by = 0.1)
+   n_breaks = length(breaks)
+   circos.rect(breaks[-n_breaks], rep(ylim[1],n_breaks - 1),
+               breaks[-1], rep(ylim[2], n_breaks - 1),
+               col = rand_color(n_breaks), border = NA)
+ })
> 
> # 添加区间链接
> circos.link('a',0,'b',0, h = 0.4)
> circos.link('c',c(-0.5,0.5),'d',c(-0.5,0.5), col = 'red',border = 'blue', h = 0.2)
> circos.link('e',0,'g',c(-1,1),col = 'green', border = 'black',lwd = 2, lty = 2)

> circos.clear()

circos.heatmap()函数

首先,让我们生成一个随机矩阵,并将其随机分成五组。

> sample(letters[1:5], 100, replace = T)
  [1] "d" "e" "b" "b" "b" "a" "e" "d" "c" "c" "e" "e" "a" "a" "d" "e" "c" "e"
 [19] "a" "e" "d" "c" "b" "c" "e" "b" "d" "c" "c" "e" "b" "c" "c" "c" "a" "a"
 [37] "e" "b" "a" "b" "c" "a" "d" "d" "e" "d" "c" "d" "a" "c" "e" "d" "d" "d"
 [55] "e" "d" "b" "b" "c" "e" "c" "d" "c" "a" "d" "a" "d" "c" "c" "c" "a" "c"
 [73] "e" "e" "d" "c" "c" "b" "a" "a" "b" "b" "b" "a" "b" "b" "d" "e" "c" "d"
 [91] "b" "e" "c" "a" "a" "e" "d" "c" "c" "a"
> 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),] # 随机排列行
> split <- sample(letters[1:5], 100, replace = T)
> split <- factor(split, levels = letters[1:5])
> split
  [1] c e c c d c c a a d d e d a c c e a c a c d e b e e d c d b e d d e d c a
 [38] d b e c e e d c c b d e b b c c e a e b c c d e a e b b a e e c e c a d b
 [75] d d e a d b d d c c a b b d b c d c a d d e a c b b
Levels: a b c d e

下图是热图的正常布局(由ComplexHeatmap包绘制):

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

> circos.clear()

在下一节中,我将演示如何循环地可视化它。

2.1输入数据

circos.heatmap()的输入应该是一个矩阵(或一个将被转换为一列矩阵的向量)。如果将矩阵分成组,则必须使用split参数指定分类变量。注意spilt的值应该是一个字符串或因子。如果是数字向量,则在内部将其转换为字符。

颜色是矩阵中值的重要美学映射。在circos.heatmap()中,用户必须使用用户定义的颜色模式指定col参数。如果矩阵是连续的数字,则col的值应该是由colorRamp2()生成的颜色映射,如果矩阵是字符,则col的值应该是一个命名的颜色向量。

下图是之前热图的圆形版本。注意矩阵的行沿圆形方向分布,矩阵的列沿径向分布。在下面的图中,圆圈被分成五个扇区,每个扇区对应一个行组。

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

> circos.clear()

有一件事非常重要,那就是在创建圆形热图之后,你必须调用circos.clear()来完全删除布局。我将在这篇文章的后面解释这一点。

如果没有指定split,则只有一个大扇区包含完整的热图。

> circos.heatmap(mat1, split = split, col = col_fun1)

> circos.clear()

2.2环形布局

与由circlize包生成的其他圆形图类似,圆形布局可以在制作图之前通过circos.par()进行控制。

热图轨迹的参数可以在circos.heatmap()函数中控制,例如track.height(轨道的高度)和bg.border(赛道边界)

在下面的示例中,通过设置show.sector.labels参数来添加扇区的标签。扇区的顺序是c(“a”,“b”,“c”,“d”,“e”)clock-wisely。你可以在下面的图表中看到,区域a从θ = 90°开始。

> 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 = T)

> circos.clear()

如果split参数的值是一个因子,则因子级别的顺序控制热图的顺序。如果split是一个简单向量,热图的顺序是unique(split)

> circos.heatmap(mat1, split = factor(split, levels = c('e','d','c','b','a')),
+                col = col_fun1, show.sector.labels = T)

> circos.clear()

2.3树形图和行名

默认情况下,数字矩阵是按行聚集的,因此,有从聚类生成的树形图。dend.side参数控制树形图相对于热图轨迹的位置。注意,树突图在分开的轨道上。

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

树形图的高度由dend.track.height参数控制。

可以通过设置行名来绘制矩阵的行名。rownames.side行名也绘制在单独的轨道中。

> circos.clear()
> 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')

矩阵和树状图的行名都可以绘制。当然,他们不能在热图轨道的同一侧。

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

行名的图形参数可以设置为标量或矢量,其长度与矩阵中的行数相同。

> 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.02, max = 1),
+                rownames.font = 1:nrow(mat1) %% 4 + 1
+ )

> circos.clear()

树形图的图形参数可以通过回调函数直接呈现树形图来设置,稍后将演示

2.4 聚类

默认情况下,数字矩阵按行聚集。参数cluster可以设置为FALSE以关闭集群。

当然,当集群设置为FALSE时,即使设置了dend.side,也不会绘制dendrogram。

> circos.heatmap(mat1, split, cluster = F, col = col_fun1)

> circos.clear()

聚类方法和距离方法由 clustering.method 和 distance.method 参数控制。

请注意,circos.heatmap()不直接支持矩阵列上的聚类。在发送到circos.heatmap()之前,您应该应用列重新排序,例如:

> column_od <- hclust(dist(t(mat1)))$order
> circos.heatmap(mat1[, column_od], col = col_fun1)

> circos.clear()

2.5树形图的回调

聚类生成树形图。回调函数可以在对应扇区生成每个树状图后应用。回调函数编辑树形图,例如1.重新排列树突图,或2.给树形图上色

在circos.heatmap()中,用户定义的函数应设置为dend.callback参数。用户定义的函数应该有三个参数:

dend:当前扇区的树突图;

m:对应于当前扇区的子矩阵;

si:当前扇区的扇区索引(或扇区名称);

默认回调函数定义如下,它通过加权矩阵行均值对树形图重新排序。

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

下面的示例通过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()

我们可以使用dendextend包中的color_branches()来渲染dendrogram边缘。例如,为五个扇区的树状图分配不同的颜色。在这里,dendrogram轨道的高度因dend.track.height参数而增加。

> 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){
+                  # 当k = 1时,它为整个树形图呈现相同的颜色
+                  color_branches(dend, k = 1, col = dend_col[si])
+                })

> circos.clear()

或者,如果矩阵没有拆分,我们可以分配不同颜色的子树状图:

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

##2.6多个热图轨道

如果你制作一个只包含一条热图轨迹的圆形图,使用circos.heatmap()非常简单。如果你制作一个包含多个轨道的更复杂的圆形图,你应该知道circos.heatmap()的更多细节。

第一次调用circos.heatmap()实际上初始化布局,即应用聚类和分割矩阵。树形图和split变量存储在内部。这就是为什么你应该调用circos.clear()来删除所有内部变量,以便它可以确保当你创建一个新的圆形热图时,第一次调用circos.heatmap()是在一个干净的环境中。

第一次调用circos.heatmap()确定所有轨道的行顺序(圆形方向上的顺序),因此,以下轨道中的矩阵与第一个轨道共享相同的行顺序。此外,下面轨道中的矩阵也会根据第一个热图轨道中的分割进行分割。

如果在第一个热图轨道上不应用聚类,则按行的自然顺序排列(即c(1,2,…)), n)):

> mat2 <- mat1[sample(100,100),] #随机排列行
> col_fun3 <- colorRamp2(c(-2,0,2), c('green','white','red'))
> circos.heatmap(mat1, split = split, col = col_fun1)
> circos.heatmap(mat2, col = col_fun3)

> circos.clear()

如果我切换两条轨道,你可以看到现在聚类是由第一条热图轨道控制的也就是红绿热图轨道。

> circos.heatmap(mat2, split = split, col = col_fun3, dend.side = 'outside')
> circos.heatmap(mat1, col = col_fun1)

> circos.clear()

您可能会问,如果我不希望聚类由第一个轨道决定,而由第二个或第三个轨道决定,该怎么办?解决办法很简单。正如我提到的,第一次调用circos.heatmap()会初始化布局。实际上,初始化可以通过显式调用circos.heatmap.initialize()函数来手动完成,circos.heatmap()内部会调用该函数。

在circos.heatmap.initialize()中,指定任何想要应用聚类的矩阵以及split变量,然后,下面的circos.heatmap()调用都共享此布局。

在下面的示例中,全局布局由mat1确定,它在第二个轨道中显示。我设置了dend.side = “outside”在第一个轨道中实际上你可以发现树形图实际上是基于第二个轨道中的矩阵生成的。

> # 双轨
> # 先画外环,再画内环
> circos.heatmap.initialize(mat1, split = split)
> circos.heatmap(mat2, col = col_fun3, dend.side = 'outside')
> circos.heatmap(mat1, col = col_fun1)

> circos.clear()

在下一个示例中,热图布局是从mat1生成的,而两个热图轨道各自包含五列。

> 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其他轨道

Circos.heatmap()也可以与其他非热图轨道集成,但是,这有点棘手。在圆形布局中,x轴和y轴上的值只是数字索引。假设在一个扇区中热图有nr行和nc列,热图行以(0,1),c(1,2),…,c(nr-1, nr)的间隔绘制,热图列也类似。原始矩阵也被重新排序。如果添加更多的轨道以确保与热图轨道正确对应,则需要考虑所有这些效果。

热图布局完成后,可以通过特殊变量CELL_META检索轨道/扇区/单元的附加信息。单元格/扇区的附加元数据如下所示,它们对于正确对应热图轨道非常重要。

CELL_META\(row_dend或简称CELL_META\)dend:当前扇区的树状图。如果没有进行聚类,则该值为NULL;

CELL_META\(row_order或简称CELL_META\)order:聚类后当前扇区中子矩阵的行顺序。如果没有进行聚类,则值为c(1,2,…),);

CELL_META$subset:原始完整矩阵中索引的子集。这些值按递增顺序排序。

下面是示例循环热图中CELL_META\(row_end、CELL_META\)row_order和CELL_META$子集在第一个扇区中的输出。

> CELL_META$row_dend
> CELL_META$row_order
> CELL_META$subset

在下面的示例中,我添加了一个轨道,用于显示mat1中前五列的行均值。我加了cell.padding = c(0.02, 0,0.02, 0),以便最大值和最小值点不会与单元格的顶部和底部边界重叠。

> 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] # heatmap中索引的行
+   y = y[CELL_META$row_order] # heatmap中索引行的排序
+   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()

同样,如果将点轨道作为第一条轨道,则应该提前初始化布局。

> circos.heatmap.initialize(mat1, split = split)
> 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

> circos.clear()

箱形图经常用于对应矩阵行。

> 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) # 第一扇区共索引了n行原矩阵数据
+     # Circos.boxplot应用于矩阵列,所以在这里我们转置它
+     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()

2.8添加注释

扇区标签sector split

扇区的标签可以通过设置show.sector.labels = TRUE来添加,但是,这并不提供标签上的任何自定义。

用户可以通过panel.fun来定制自己的标签。演示如下。在这里,标签被添加到距离热图轨道2mm的地方(通过convert_y(2,“mm”),它定义了y方向上的偏移量)。

在这里,我设置了track.index = get.current.track.index(),以确保标签始终添加到正确的轨道中。

> circos.heatmap(mat1, split = split, col = col_fun1)
> circos.track(track.index = get.current.track.index(), panel.fun = function(x, y) {
+     circos.text(CELL_META$xcenter, CELL_META$cell.ylim[2] + convert_y(2, "mm"), 
+         paste0("this is group ", CELL_META$sector.index),
+         facing = "bending.inside", cex = 0.8,
+         adj = c(0.5, 0), niceFacing = TRUE)
+ }, bg.border = NA)

> circos.clear()

添加列名

circos.heatmap()不直接支持矩阵的列名,但也可以通过自定义panel.fun函数轻松添加它们。在下面的例子中,我通过circos.par()中的gap.after参数在最后一个扇区(第五扇区)之后设置更大的空间(10度,用户通常需要尝试几个值以获得最佳空间),后来我在panel.fun的最后一个扇区中绘制列名。

> circos.par(gap.after = c(2, 2, 2, 2, 10))
> circos.heatmap(mat1, split = split, col = col_fun1, track.height = 0.4)
> circos.track(track.index = get.current.track.index(), panel.fun = function(x, y) {
+     if(CELL_META$sector.numeric.index == 5) { # 最后一个扇区
+         cn = colnames(mat1)
+         n = length(cn)
+         circos.text(rep(CELL_META$cell.xlim[2], n) + convert_x(1, "mm"), 
+             1:n - 0.5, cn, 
+             cex = 0.5, adj = c(0, 0.5), facing = "inside")
+     }
+ }, bg.border = NA)

> circos.clear()

添加矩形标签

下一个示例添加了矩形和标签,以显示矩阵中的两组列。Panel.fun内部的代码很简单。它基本上绘制矩形和文本。convert_x()将x方向上的一个单位转换为以极坐标系测量的正确值。

> circos.par(gap.after = c(2, 2, 2, 2, 10))
> circos.heatmap(mat1, split = split, col = col_fun1, track.height = 0.4)
> circos.track(track.index = get.current.track.index(), panel.fun = function(x, y) {
+     if(CELL_META$sector.numeric.index == 5) { # 最后一个扇区
+         circos.rect(CELL_META$cell.xlim[2] + convert_x(1, "mm"), 0,
+                     CELL_META$cell.xlim[2] + convert_x(5, "mm"), 5,
+                     col = "orange", border = NA)
+         circos.text(CELL_META$cell.xlim[2] + convert_x(3, "mm"), 2.5,
+                     "group 1", cex = 0.5, facing = "clockwise")
+ 
+         circos.rect(CELL_META$cell.xlim[2] + convert_x(1, "mm"), 5,
+                     CELL_META$cell.xlim[2] + convert_x(5, "mm"), 10,
+                     col = "pink", border = NA)
+         circos.text(CELL_META$cell.xlim[2] + convert_x(3, "mm"), 7.5,
+                     "group 2", cex = 0.5, facing = "clockwise")
+     }
+ }, bg.border = NA)

> circos.clear()

添加图例

circlize不生成图例,但是图例可以通过ComplexHeatmap::Legend()函数手动生成并添加到圆形图中。下面是添加图例的简单示例。在下一节中,您将看到一个添加许多图例的更复杂的示例。

> circos.heatmap(mat1, split = split, col = col_fun1)
> circos.clear()
> library(ComplexHeatmap)
> lgd = Legend(title = 'mat1', col_fun = col_fun1)
> grid.draw(lgd)