R语言绘图

R 数据可视化 —— circlize 图形

2021-06-01  本文已影响0人  名本无名

前言

这一节,我们将介绍 circlize 提供的图形对象函数,这些函数默认在当前的单元格中绘制。因此,如果在 panel.fun 函数外调用,需要传递扇形及轨迹的索引来指定单元格。

例如,点图使用方式有

circos.track(..., panel.fun = function(x, y) {
    circos.points(x, y)
})
circos.points(x, y, sector.index, track.index)

图形

1. 颜色设置

颜色对于数据的映射是非常重要的,circlize 提供了两个函数用于设置颜色:

col_fun <- colorRamp2(c(-2, 0, 2), c("blue", "white", "red"))
col_fun(seq(-5, 1, by = 1))

超出范围的值会设置为边缘颜色,即 [-5, -2] 的值均为 blue

par(mar = c(1, 1, 1, 1))
plot(NULL, xlim = c(1, 10), ylim = c(1, 8), axes = FALSE, ann = FALSE)
points(1:10, rep(1, 10), pch = 16, cex = 5, 
       col = rand_color(10, luminosity = "random"))
points(1:10, rep(2, 10), pch = 16, cex = 5, 
       col = rand_color(10, luminosity = "bright"))
points(1:10, rep(3, 10), pch = 16, cex = 5, 
       col = rand_color(10, luminosity = "light"))
points(1:10, rep(4, 10), pch = 16, cex = 5, 
       col = rand_color(10, luminosity = "dark"))
points(1:10, rep(5, 10), pch = 16, cex = 5, 
       col = rand_color(10, hue = "red", luminosity = "bright"))
points(1:10, rep(6, 10), pch = 16, cex = 5, 
       col = rand_color(10, hue = "green", luminosity = "bright"))
points(1:10, rep(7, 10), pch = 16, cex = 5, 
       col = rand_color(10, hue = "blue", luminosity = "bright"))
points(1:10, rep(8, 10), pch = 16, cex = 5, 
       col = rand_color(10, hue = "monochrome", luminosity = "bright"))

还有一个函数 col2value() 用于将颜色反映射为值,例如

> value <- seq(-2, 2, by = 0.2)
> value
 [1] -2.0 -1.8 -1.6 -1.4 -1.2 -1.0 -0.8 -0.6 -0.4 -0.2  0.0  0.2  0.4  0.6  0.8  1.0  1.2  1.4  1.6  1.8  2.0
> col <- col_fun(value)
> round(col2value(col, col_fun = col_fun), 1)
 [1] -2.0 -1.8 -1.6 -1.4 -1.2 -1.0 -0.8 -0.6 -0.4 -0.2  0.0  0.2  0.4  0.5  0.7  0.9  1.2  1.4  1.6  1.8  2.0

2. 点

添加点图的函数 circos.points()points() 类似,使用方式为

circos.points(x, y)
circos.points(x, y, sector.index, track.index)
circos.points(x, y, pch, col, cex, bg)

它有一个配对的函数 circos.trackPoints(),可以同时在所有的扇形轨迹中绘制点图,需要指定扇形,会自动将 xy 的数据根据扇形进行分类,使用方式为

circos.track(...)
circos.trackPoints(sectors, x, y)

circos.trackPoints() 相当于 circos.points()for 循环结合使用,最好的方式是在 panel.fun 函数中进行绘制

circos.track(sectors, x, y, panel.fun = function(x, y) {
    circos.points(x, y)
})

其他所有的简单绘图函数都有对应的 circos.track*() 函数,用于同时在一个轨迹中所有单元格内绘制对应的图形

3. 线

circos.lines()lines() 函数类似,但是有一个额外的功能,可以设置 area = TRUE 来绘制填充面积,可以用 baseline 参数来控制向上或向下的面积填充,也可以设置 y 轴的位置来设置填充。

如果设置了填充,则 col 用于控制填充色,border 用于控制边框颜色

使用方式

circos.lines(x, y)
circos.lines(x, y, sector.index, track.index)
circos.lines(x, y, col, lwd, lty, type, straight)
circos.lines(x, y, col, area, baseline, border)

例如

n <- 90
df <- data.frame(
  sectors = rep(letters[1:9], each = 10),
  x = rep(1:10, 9), 
  y = runif(n)
)

circos.par("track.height" = 0.3)
circos.initialize(df$sectors, x = df$x)
circos.track(
  df$sectors, x = df$x, y = df$y, 
  panel.fun = function(x, y) {
    switch(CELL_META$sector.index,
           'a' = circos.lines(x, y, type = 'l'),
           'b' = circos.lines(
             x, y, type = 'o', pt.col = 'blue', cex = 0.7),
           'c' = circos.lines(x, y, type = 'h'),
           'd' = circos.lines(
             x, y, type = 'h', baseline = 0.5, 
             col = rainbow(10)),
           'e' = circos.lines(x, y, type = 's'),
           'f' = circos.lines(
             x, y, type = 'l', area = TRUE),
           'g' = circos.lines(
             x, y, type = 'o', area = TRUE),
           'h' = circos.lines(
             x, y, type = 's', area = TRUE),
           'i' = circos.lines(
             x, y, type = 'l', area = TRUE,
             baseline = 'top')
    )
    circos.text(CELL_META$xcenter, 
                CELL_META$cell.ylim[2] + mm_y(3), 
                CELL_META$sector.index)
  }
)

4. 线段

可以使用 circos.segments() 添加线段,类似于 segments() 函数,使用方式为

circos.segments(x0, y0, x1, y1)
circos.segments(x0, y0, x1, y1, straight)

例如

circos.initialize(letters[1:8], xlim = c(0, 1))
circos.track(ylim = c(0, 1), track.height = 0.3, panel.fun = function(x, y) {
  x = seq(0.2, 0.8, by = 0.2)
  y = seq(0.2, 0.8, by = 0.2)
  
  circos.segments(x, 0.1, x, 0.9)
  circos.segments(0.1, y, 0.9, y)
})

5. 文本

使用 circos.text() 添加文本,类似于 text() 函数,使用方式

circos.text(x, y, labels)
circos.text(x, y, labels, sector.index, track.index)
circos.text(x, y, labels, facing, niceFacing, adj, cex, col, font)

7 种文本朝向可以选择:

inside, outside, clockwise, 
reverse.clockwise, downward, 
bending.inside 和 bending.outside

例如

sectors = letters[1:4]
circos.par(points.overflow.warning = FALSE, 
           canvas.xlim = c(0, 1), canvas.ylim = c(0, 1))
circos.initialize(sectors, xlim = c(0, 10))
circos.trackPlotRegion(
  sectors[4], ylim = c(0, 10),
  track.height = 0.5,
  panel.fun = function(x, y) {
    circos.text(3, 1, "inside", facing = "inside", cex = 0.8)
    circos.text(7, 1, "outside", facing = "outside", cex = 0.8)
    circos.text(
      0, 5, "reverse.clockwise", facing = "reverse.clockwise",
      adj = c(0.5, 0), cex = 0.8
    )
    circos.text(
      10, 5, "clockwise", facing = "clockwise", adj = c(0.5, 0),
      cex = 0.8
    )
    circos.text(5, 5, "downward", facing = "downward", cex = 0.8)
    circos.text(3, 9, "===bending.inside===", 
                facing = "bending.inside", cex = 0.8)
    circos.text(7, 9, "===bending.outside===", 
                facing = "bending.outside", cex = 0.8)
  })
circos.clear()

6. 矩形和多边形

circos.rect() 绘制的矩形在极坐标系上看,其底边和顶边变成了弧形,使用方式为

circos.rect(xleft, ybottom, xright, ytop)
circos.rect(xleft, ybottom, xright, ytop, sector.index, track.index)
circos.rect(xleft, ybottom, xright, ytop, col, border, lty, lwd)

例如

circos.initialize(c("a", "b", "c", "d"), xlim = c(0, 10))
circos.track(ylim = c(0, 10), panel.fun = function(x, y) {
  for(rot in seq(0, 90, by = 30)) {
    circos.rect(2, 2, 6, 6, rot = rot)
  }
}, track.height = 0.5)
circos.clear()

circos.polygon() 用于绘制多边形,第一个数据点必须与最后一个数据点重叠

circos.polygon(x, y)
circos.polygon(x, y, col, border, lty, lwd)

例如,为数据添加拟合曲线及误差范围

sectors = letters[1:4]
circos.initialize(sectors, xlim = c(0, 1))
circos.trackPlotRegion(ylim = c(-3, 3), track.height = 0.4, panel.fun = function(x, y) {
  x1 = runif(20)
  y1 = x1 + rnorm(20)
  or = order(x1)
  x1 = x1[or]
  y1 = y1[or]
  loess.fit = loess(y1 ~ x1)
  loess.predict = predict(loess.fit, x1, se = TRUE)
  d1 = c(x1, rev(x1))
  d2 = c(loess.predict$fit + loess.predict$se.fit,
         rev(loess.predict$fit - loess.predict$se.fit))
  circos.polygon(d1, d2, col = "#CCCCCC", border = NA)
  circos.points(x1, y1, cex = 0.5)
  circos.lines(x1, loess.predict$fit)
})
circos.clear()

7. 坐标轴

通常,我们只需要绘制 x 轴,使用 circos.axis()circos.xaxis() 函数可以设置轴的各种参数。

使用方式为

circos.axis(h)
circos.axis(h, sector.index, track.index)
circos.axis(h, major.at, labels, major.tick, direction)
circos.axis(h, major.at, labels, major.tick, labels.font, labels.cex,
            labels.facing, labels.niceFacing)
circos.axis(h, major.at, labels, major.tick, minor.ticks,
            major.tick.length, lwd)

例如,轴的各种设置

sectors = letters[1:8]
circos.par(points.overflow.warning = FALSE)
circos.initialize(sectors, xlim = c(0, 10))
# 添加外层标签
circos.trackPlotRegion(sectors, ylim = c(0, 10), track.height = 0.1,
                       bg.border = NA, panel.fun = function(x, y) {
                         circos.text(5, 10, get.cell.meta.data("sector.index"))
                       })

circos.trackPlotRegion(sectors, ylim = c(0, 10))
# 默认轴
circos.axis(sector.index = "a")
# 设置刻度和标签的朝向
circos.axis(sector.index = "b", direction = "inside", labels.facing = "outside")
# 放置在内侧
circos.axis(sector.index = "c", h = "bottom")
# 内侧的内部朝向
circos.axis(sector.index = "d", h = "bottom", direction = "inside",
            labels.facing = "reverse.clockwise")
# 设置放置位置及主刻度的断点
circos.axis(sector.index = "e", h = 5, major.at = c(1, 3, 5, 7, 9))
# 设置断点标签,并设置次刻度线长度为 0
circos.axis(sector.index = "f", h = 5, major.at = c(1, 3, 5, 7, 9),
            labels = c("a", "c", "e", "g", "f"), minor.ticks = 0)
# 隐藏主、次刻度,并设置标签的朝向
circos.axis(sector.index = "g", h = 5, major.at = c(1, 3, 5, 7, 9),
            labels = c("a1", "c1", "e1", "g1", "f1"), major.tick = FALSE,
            labels.facing = "reverse.clockwise")
# 设置主、次刻度线长度
circos.axis(sector.index = "h", h = 2, major.at = c(1, 3, 5, 7, 9),
            labels = c("a1", "c1", "e1", "g1", "f1"), minor.ticks = 2, 
            major.tick.length = mm_y(5), labels.facing = "clockwise")
circos.clear()

y 轴的设置也是类似的,设置 y 轴时,要保证扇形之间的间距足够(使用 gap.degree/gap.after 设置)

8. 条形图、箱线图和小提琴图

circos.barplot()circos.boxplot()circos.violin() 分别用于绘制条形图、箱线图和小提琴图,这些函数的使用方式是类似的。需要在 circos.initialize() 中设置 xlim 参数的值来控制其数量

例如,条形图可以是正常的形式,也可以是堆积的形式

par(mfrow = c(1, 2))
# 设置条形的数量为 10
circos.initialize(letters[1:4], xlim = c(0, 10))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  # 传递一个向量
  value = runif(10)
  circos.barplot(value, 1:10 - 0.5, col = 1:10)
})
circos.track(ylim = c(-1, 1), panel.fun = function(x, y) {
  value = runif(10, min = -1, max = 1)
  circos.barplot(value, 1:10 - 0.5, col = ifelse(value > 0, 2, 3))
})
circos.clear()

circos.initialize(letters[1:4], xlim = c(0, 10))
circos.track(ylim = c(0, 4), panel.fun = function(x, y) {
  # 如果传递的数据为矩阵,则列就是对应的条形的高度
  value = matrix(runif(10*4), ncol = 4)
  circos.barplot(value, 1:10 - 0.5, col = 2:5)
})

对于箱线图,可以一个个绘制,或者使用 listmatrix 一次性绘制

par(mfrow = c(1, 2))
circos.initialize(letters[1:4], xlim = c(0, 10))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  # 在一个单元格中,依次绘制 10 个
  for(pos in seq(0.5, 9.5, by = 1)) {
    value = runif(10)
    circos.boxplot(value, pos)
  }
})
circos.clear()

circos.initialize(letters[1:4], xlim = c(0, 10))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  # 对于一个 list/matrix,一次性绘制所有的数据
  value = replicate(runif(10), n = 10, simplify = FALSE)
  circos.boxplot(value, 1:10 - 0.5, col = 1:10)
})

小提琴图的绘制也是类似的,但是,如果想要让不同的小提琴图具有可比性,需要设置 max_density 参数

par(mfrow = c(1, 2))
circos.initialize(letters[1:4], xlim = c(0, 10))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  for(pos in seq(0.5, 9.5, by = 1)) {
    value = runif(10)
    circos.violin(value, pos)
  }
})
circos.clear()

circos.initialize(letters[1:4], xlim = c(0, 10))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  value = replicate(runif(10), n = 10, simplify = FALSE)
  circos.violin(value, 1:10 - 0.5, col = 1:10, max_density = 1)
})

9. 圆形箭头

circos.arrow() 可以绘制沿着圆周方向的箭头,参数设置如下:

par(mfrow = c(1, 2))
circos.initialize(letters[1:4], xlim = c(0, 1))
col <- rand_color(4)
tail <- c("point", "normal", "point", "normal")
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  circos.arrow(x1 = 0, x2 = 1, y = 0.5, width = 0.4, 
               arrow.head.width = 0.6, arrow.head.length = cm_x(1), 
               col = col[CELL_META$sector.numeric.index], 
               tail = tail[CELL_META$sector.numeric.index])
}, bg.border = NA, track.height = 0.4)
circos.clear()

circos.initialize(letters[1:4], xlim = c(0, 1))

circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  circos.arrow(x1 = 0, x2 = 1, y = 0.5, width = 0.4, 
               arrow.head.width = 0.6, arrow.head.length = cm_x(1), 
               col = col[CELL_META$sector.numeric.index], 
               tail = tail[CELL_META$sector.numeric.index],
               arrow.position = "start")
}, bg.border = NA, track.height = 0.4)

我们可以绘制细胞周期的四个阶段,扇形的长度与阶段的时间长度成正比。也可以绘制圆形基因组,如线粒体或质粒基因组。

例如

cell_cycle <- data.frame(
  phase = factor(c("G1", "S", "G2", "M"), 
                 levels = c("G1", "S", "G2", "M")),
  hour = c(11, 8, 4, 1)
)
color <- c("#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3")
circos.par(start.degree = 90)
circos.initialize(cell_cycle$phase, xlim = cbind(rep(0, 4), cell_cycle$hour))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  # 绘制箭头
  circos.arrow(CELL_META$xlim[1], CELL_META$xlim[2], 
               arrow.head.width = CELL_META$yrange*0.8, arrow.head.length = cm_x(0.5),
               col = color[CELL_META$sector.numeric.index])
  # 添加文本你
  circos.text(CELL_META$xcenter, CELL_META$ycenter, CELL_META$sector.index, 
              facing = "downward")
  # 添加轴线
  circos.axis(h = 1, major.at = seq(0, round(CELL_META$xlim[2])), minor.ticks = 1,
              labels.cex = 0.6)
}, bg.border = NA, track.height = 0.3)

10. 栅格图

circos.raster() 函数用于将栅格图添加到圆形布局中,首先需要使用 as.raster() 将输入图形转换为 raster 对象。

图形的朝向设置与 circos.text() 类似,图形的大小需要使用数值单位值,如 20mm, 1.2cm0.5inche

例如,我们使用包自带的图片数据 img_list,并将 facing 设置为 bending.insidebending.outside 将图片绘制成圆形矩形形状

# 导入图片数据
load(system.file("extdata", "doodle.RData", package = "circlize"))

circos.par("cell.padding" = c(0, 0, 0, 0))
circos.initialize(letters[1:16], xlim = c(0, 1))
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  img = img_list[[CELL_META$sector.numeric.index]]
  circos.raster(img, CELL_META$xcenter, CELL_META$ycenter, 
                width = CELL_META$xrange, height = CELL_META$yrange, 
                facing = "bending.inside")
}, track.height = 0.25, bg.border = NA)
circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
  img = img_list[[CELL_META$sector.numeric.index + 16]]
  circos.raster(img, CELL_META$xcenter, CELL_META$ycenter, 
                width = CELL_META$xrange, height = CELL_META$yrange, 
                facing = "bending.inside")
}, track.height = 0.25, bg.border = NA)
circos.clear()

11. link

连接线或条带也是圆形布局中的一种重要的图形,可以用于表示不同类别之间的关系或相互作用。circos.link() 可以绘制点与区间或区间与区间之间的连接

该函数有 4 个强制参数,即第一个扇形索引及其上的某一位置,以及第二个扇形的索引及其上的位置,使用方式如下

# 点与点之间,构成一条线
circos.link(sector.index1, 0, sector.index2, 0)
# 区间与点
circos.link(sector.index1, c(0, 1), sector.index2, 0)
# 区间和区间
circos.link(sector.index1, c(0, 1), sector.index2, c(1, 2))
# 设置线条及填充样式
circos.link(sector.index1, c(0, 1), sector.index2, 0, col, lwd, lty, border)

连接的高度可以用 h 参数来控制,如果连接是条带,则 h2 用于控制内侧线的高度,默认会自动推断,不需要手动指定。

而连接线是二次贝塞尔曲线,可以使用 w 参数来控制曲线的形状,如果是条带,则 w2 用于控制内侧曲线的形状

circos.link(sector.index1, 0, sector.index2, 0, h, w)
circos.link(sector.index1, 0, sector.index2, 0, h, h2, w, w2)

directional 参数可以控制连接的方向:

circos.link(sector.index1, 0, sector.index2, 0, directional = 1)
circos.link(sector.index1, c(0, 1), sector.index2, c(0, 1), directional = -1)

12. 高亮

draw.sector() 可以绘制扇形、圆环,或者它们中的某一部分,该函数是独立于圆形布局的,使用方式为

draw.sector(start.degree, end.degree, rou1)
draw.sector(start.degree, end.degree, rou1, rou2, center)
draw.sector(start.degree, end.degree, rou1, rou2, center, col, border, lwd, lty)

start.degreeend.degree 之间的方向是很重要的,默认是顺时针方向

draw.sector(start.degree, end.degree, clock.wise = TRUE)

例如

par(mar = c(1, 1, 1, 1))
plot(c(-1, 1), c(-1, 1), type = "n", axes = FALSE, ann = FALSE, asp = 1)
# 无填充色的扇形区域
draw.sector(20, 0)
# 红色圆形矩形区域
draw.sector(30, 60, rou1 = 0.8, rou2 = 0.5, clock.wise = FALSE, col = "red")
# 绿色填充扇形
draw.sector(350, 1000, col = "green", border = NA)
# 虚线半圆
draw.sector(0, 180, rou1 = 0.25, center = c(-0.5, 0.5), border = 2, lwd = 2, lty = 2)
# 蓝色圆环
draw.sector(0, 360, rou1 = 0.7, rou2 = 0.6, col = "blue")

想要高亮某一个单元格,我们首先要使用 get.cell.meta.data() 函数来获取单元格的位置信息,其中 cell.start.degreecell.end.degree 表示单元格的起止角度,cell.top.radiuscell.bottom.radius 表示上下的径向位置

例如,我们绘制这样一个包含 8 个扇形和 3 个轨迹的圆形图

sectors <- letters[1:8]
circos.initialize(sectors, xlim = c(0, 1))
for(i in 1:3) {
  circos.track(ylim = c(0, 1))
}
# 将圆形布局信息绘制到图中而不是输出
circos.info(plot = TRUE)

现在,我们可以先为扇形 a 添加填充色

draw.sector(
  # 获取扇形 a 的起始角度
  get.cell.meta.data("cell.start.degree", sector.index = "a"),
  # 获取扇形 a 的终止角度
  get.cell.meta.data("cell.end.degree", sector.index = "a"),
  # # 获取扇形 a 最外侧弧形的径向距离
  rou1 = get.cell.meta.data("cell.top.radius", track.index = 1),
  # 设置填充色
  col = "#FF000040"
)

然后,高亮第一个轨迹 h

draw.sector(
  # 0-360 一圈
  0, 360, 
  rou1 = get.cell.meta.data("cell.top.radius", track.index = 1),
  rou2 = get.cell.meta.data("cell.bottom.radius", track.index = 1),
  col = "#00FF0040"
) 

或者,高亮扇形 ef 中的轨迹 23

draw.sector(
  # e-f 之间的扇形区域的角度
  get.cell.meta.data("cell.start.degree", sector.index = "e"),
  get.cell.meta.data("cell.end.degree", sector.index = "f"),
  # 轨迹 2-3 之间的径向距离
  rou1 = get.cell.meta.data("cell.top.radius", track.index = 2),
  rou2 = get.cell.meta.data("cell.bottom.radius", track.index = 3),
  col = "#0000FF40"
)

也可以在某一单元格中绘制一个小的图形,需要先使用 circlize() 将位置转换为极坐标形式,然后在对应的位置绘制图形。例如,在 h:2 中绘制一个矩形

# 数据坐标的角度转换为极坐标
pos <- circlize(c(0.2, 0.8), c(0.2, 0.8), sector.index = "h", track.index = 2)
# 根据极坐标绘制圆形矩形
draw.sector(pos[1, "theta"], pos[2, "theta"], pos[1, "rou"], pos[2, "rou"], 
            clock.wise = TRUE, col = "#00FFFF40")
circos.clear()

如果只是想高亮整个单元格,可以使用 highlight.sector() 函数。该函数可以在单元格的中心位置添加文本。

sectors <- letters[1:8]
circos.initialize(sectors, xlim = c(0, 1))
for(i in 1:4) {
  circos.track(ylim = c(0, 1))
}
circos.info(plot = TRUE)
# 高亮 a 和 h 扇形区域的第一个轨迹,并在该区域的 x 中间位置添加文本
highlight.sector(
  c("a", "h"), track.index = 1, 
  text = "a and h belong to a same group",
  facing = "bending.outside", niceFacing = TRUE, 
  text.vjust = "8mm", cex = 0.8
)
# 为扇形 c 添加填充色
highlight.sector("c", col = "#00FF0040")
# 为扇形 d 添加红色边框
highlight.sector("d", col = NA, border = "red", lwd = 2)
# 为扇形 e 的第 2、3 个轨迹添加填充色
highlight.sector("e", col = "#0000FF40", track.index = c(2, 3))
# 为第四个轨迹添加填充色
highlight.sector(sectors, col = "#FFFF0040", track.index = 4)

还可以使用 padding 来设置单元格周围的延伸区域的宽度和高度,该参数接受 4 个值,表示要延伸出去的百分比

highlight.sector(
  c("f", "g"), col = NA, border = "green", 
  lwd = 2, track.index = c(2, 3), 
  padding = c(0.1, 0.1, 0.1, 0.1)
)

13. 与基础图形联用

circlize 是基于基础的 R 图形系统,因此,可以和基础图形函数结合使用。例如,使用 title()text()legend() 来添加附加信息

text(0, 0, "This is\nthe center", cex = 1.5)
legend("bottomleft", pch = 1, legend = "This is the legend")
title("This is the title")
上一篇下一篇

猜你喜欢

热点阅读