R 数据可视化 —— circlize 高级布局

1. 放大扇形

默认情况下,扇形的宽度(即角度大小)是根据数据范围自动确定的,一般也不会去修改其宽度。如果修改扇形的大小,会使得不同分类之间不好比较。

在某些情况下,我们可能想通过设置扇形的宽度来凸显其内部结构,或突出展示扇形内的某一区域的信息。

要突出显示扇形区域,最简单的方式是将其作为一个新的分类,添加到数据中,并设置对应扇形的宽度

例如,我们有如下数据

df <- data.frame(
  sectors = sample(letters[1:6], 400, replace = TRUE),
  x = rnorm(400),
  y = rnorm(400),
  stringsAsFactors = FALSE
)

现在,我们想讲扇形 a 和扇形 b 的前 10 个点进行放大,先提取对应的数据

zoom_df_a <- df[df$sectors == "a", ]
zoom_df_b <- df[df$sectors == "b", ]
zoom_df_b <- zoom_df_b[order(zoom_df_b[, 2])[1:10], ]
zoom_df <- rbind(zoom_df_a, zoom_df_b)

然后为这两份数据添加扇形名称,并追加到绘图数据中

zoom_df$sectors <- paste0("zoom_", zoom_df$sectors)
df2 <- rbind(df, zoom_df)

现在,需要为不同扇形分配宽度,原始数据和放大区域的数据应为 1:1,即各占半圆

# 根据极差分配宽度
xrange <- tapply(df2$x, df2$sectors, function(x) max(x) - min(x))
normal_sector_index <- unique(df$sectors)
zoomed_sector_index <- unique(zoom_df$sectors)
# 将宽度标准化为 1
sector.width <- c(xrange[normal_sector_index] / sum(xrange[normal_sector_index]), 
                 xrange[zoomed_sector_index] / sum(xrange[zoomed_sector_index]))
> xrange
       a        b        c        d        e        f   zoom_a   zoom_b 
6.597049 5.010210 4.674496 4.286670 4.439024 4.881668 6.597049 1.885344 
> sector.width
        d         f         e         a         b         c    zoom_a    zoom_b 
0.1434191 0.1633259 0.1485164 0.2207174 0.1676266 0.1563946 0.7777344 0.2222656

绘制轨迹

# 设置起始角度,并关闭溢出警告
circos.par(start.degree = 90, points.overflow.warning = FALSE)
# 在初始化时使用 sector.width 指定各扇形的宽度
circos.initialize(df2$sectors, x = df2$x, sector.width = sector.width)
circos.track(
  df2$sectors, x = df2$x, y = df2$y, 
  panel.fun = function(x, y) {
    circos.points(x, y, col = "red", pch = 16, cex = 0.5)
    # 添加扇形名称
    circos.text(
      CELL_META$xcenter,
      CELL_META$cell.ylim[2] + mm_y(2),
      CELL_META$sector.index,
      niceFacing = TRUE
    )
  })

我们可以添加链接,用于标识放大区域的来源。颜色我们采用 16 进制 RBGA 格式,后两位为透明度(即 80

circos.link(
  "a", get.cell.meta.data("cell.xlim", sector.index = "a"),
  "zoom_a", get.cell.meta.data("cell.xlim", sector.index = "zoom_a"),
  border = NA, col = "#dfc27d80"
  )
circos.link(
  "b", c(zoom_df_b[1, 2], zoom_df_b[10, 2]),
  "zoom_b", get.cell.meta.data("cell.xlim", sector.index = "zoom_b"),
  rou1 = get.cell.meta.data("cell.top.radius", sector.index = "b"),
  border = NA, col = "#80cdc180"
  )
circos.clear()

2. 局部可视化

如果我们只是想显示某一类别的数据,可以在绘制轨迹时指定 sectors 为某一分类。或者在 circos.par() 函数中设置 canvas.xlimcanvas.ylim 参数的值,可以显示圆形的某一部分。

画布中 xy 的值都是在 [-1,1] 范围之间,如果设置 xy 都为 [0,1] 则只显示四分之一圆。例如

df <- data.frame(
  sectors = rep("a", 100),
  x = runif(100),
  y = runif(100)
  )
sectors <- letters[1:4]
# 按列排列两幅图
par(mfcol = c(1, 2))
circos.par("start.degree" = 90, "gap.degree" = 0)
# 将整个圆分割为 4 个扇形
circos.initialize(sectors, xlim = c(0, 1))
# 绘制第一个四分之一圆
circos.track(
  df$sectors, x = df$x, y = df$y,
  panel.fun = function(x, y) {
    circos.points(x, y, pch = 16, cex = 0.5, col = 2)
  }
)
circos.track(df$sectors, x = df$x, y = df$y,
             panel.fun = function(x, y) {
               circos.lines(1:100/100, y, col = 3)
             })
circos.clear()
# 添加矩形框和数值
rect(0, 0, 1, 1)
text(0, 0, 0, col = "red", adj = c(0.5, 1))
text(1, 0, 1, col = "red", adj = c(0.5, 1))
text(0, 1, 1, col = "red", adj = c(0.5, 0))
# 设置画布范围,以及间隔,间隔 270 意味着宽度为 90
par(mar = c(1, 1, 1, 1))
circos.par("canvas.xlim" = c(0, 1), "canvas.ylim" = c(0, 1),
           "start.degree" = 90, "gap.after" = 270)

circos.initialize(sectors = df$sectors, xlim = c(0, 1))
circos.track(
  df$sectors, x = df$x, y = df$y,
  panel.fun = function(x, y) {
    circos.points(x, y, pch = 16, cex = 0.5, col = 2)
  }
)
circos.track(
  df$sectors, x = df$x, y = df$y,
  panel.fun = function(x, y) {
    circos.lines(1:100/100, y, col = 3)
  }
)
circos.clear()
# 添加外框以及数值
box()
par(xpd = NA)
text(0, 0, 0, col = "red", adj = c(0.5, 1))
text(1, 0, 1, col = "red", adj = c(0.5, 1))
text(0, 1, 1, col = "red", adj = c(0.5, 0))

获取代码:https://github.com/dxsbiocc/learn/blob/main/R/plot/circos_part.R

在某一单元格绘制图形的方式有两种,上面是通过指定扇形对应的数据的方式来绘制的,还有一种方式是,先创建一个空的轨迹,然后使用 circos.update() 来添加图形

circos.initialize(sectors, xlim = c(0, 1))

circos.track(
  df$sectors, x = df$x, y = df$y, 
  panel.fun = function(x, y) {
    circos.points(x, y, pch = 16, cex = 0.5, col = 5)
  }
)

# 先创建空轨迹,然后添加图形
circos.track(ylim = range(df$y), bg.border = NA)
circos.update(sector.index = "a", bg.border = "black")
circos.points(df$x, df$y, pch = 16, cex = 0.5, col = 4)

circos.track(sectors = sectors, ylim = c(0, 1))
circos.clear()

3. 多图组合

circlize 是基于基础的 R 图形系统,使用 par(new = TRUE) 可以将一个新的图形作为一个新的图层,添加到之前的画布中。

通过与 canvas.xlimcanvas.ylim 参数结合使用,可以绘制出更加复杂的组合图

例如,我们可以组合两个包含不同分类的圆形

# 获取当前图形参数,用于复原
op <- par(no.readonly = TRUE)

# 设置一行三列图形排布
par(mar = c(2, 2, 2, 2), mfrow = c(1, 3))

# 1. 第一幅图
plot_circos1 <- function() {
  sectors <- letters[1:4]
  circos.initialize(sectors = sectors, xlim = c(0, 1))
  circos.trackPlotRegion(
    ylim = c(0, 1), 
    panel.fun = function(x, y) {
      circos.text(
        0.5, 0.5, "inner circos", col = 6,
        niceFacing = TRUE, facing = "bending.outside"
      )
    }
  )
  circos.clear()
}
plot_circos1()

# 添加外框
box()
# 添加坐标轴
axis(side = 1)
axis(side = 2)

# 2. 第二幅图
# 设置更大的 canvas.xlim 和 canvas.ylim
# 同样的 xlim  = c(0, 1),会绘制更小的圆形
plot_circos2 <- function() {
  circos.par("canvas.xlim" = c(-2, 2), "canvas.ylim" = c(-2, 2))
  sectors <- letters[1:3]
  circos.initialize(sectors = sectors, xlim = c(0, 1))
  circos.trackPlotRegion(
    ylim = c(0, 1), 
    panel.fun = function(x, y) {
      circos.text(
        0.5, 0.5, "inner circos", col = 7,
        niceFacing = TRUE, facing = "bending.outside"
        )
    }
  )
  circos.clear()
}
plot_circos2()

box()
axis(side = 1)
axis(side = 2)

# 3. 第三幅图
plot_circos1()
# 添加图层
par(new = TRUE)
plot_circos2()

# 复原参数
par(op)

获取代码:https://github.com/dxsbiocc/learn/blob/main/R/plot/circos_combine_circular.R

也可以将一个圆形分割为不同的块,每块之间相互分离。例如,通过合并两个圆形,每个圆形绘制一半,可以达到将圆形分离为两部分的目的

# 获取当前图形参数,用于复原
op <- par(no.readonly = TRUE)

# 设置一行三列图形排布
par(mar = c(2, 2, 2, 2), mfrow = c(1, 3))

# 1. 第一幅图

plot_circos1 <- function() {
  circos.par("canvas.xlim" = c(-1, 1.5), "canvas.ylim" = c(-1, 1.5), start.degree = -45)
  circos.initialize(sectors = letters[1:4], xlim = c(0, 1))
  # 添加空轨迹
  circos.trackPlotRegion(ylim = c(0, 1), bg.col = NA, bg.border = NA)
  # 绘制 a、b 两个扇形区域,并添加文本
  circos.updatePlotRegion(sector.index = "a")
  circos.text(0.5, 0.5, "first one", niceFacing = TRUE, 
              facing = "bending.outside")
  
  circos.updatePlotRegion(sector.index = "b")
  circos.text(0.5, 0.5, "first one", niceFacing = TRUE, 
              facing = "bending.outside")
  highlight.sector(
    c("a", "b"), track.index = 1, 
    col = "#5aae6180"
  )
  circos.clear()
}
plot_circos1()

# 添加外框和轴
box()
axis(side = 1)
axis(side = 2)

# 2. 第二幅图

plot_circos2 <- function() {
  circos.par("canvas.xlim" = c(-1.5, 1), "canvas.ylim" = c(-1.5, 1), start.degree = -45)
  circos.initialize(sectors = letters[1:4], xlim = c(0, 1))
  circos.trackPlotRegion(ylim = c(0, 1), bg.col = NA, bg.border = NA)
  # 绘制 c、d 两个扇形区域,并添加文本
  circos.updatePlotRegion(sector.index = "d")
  circos.text(0.5, 0.5, "second one", niceFacing = TRUE,
              facing = "bending.outside")
  circos.updatePlotRegion(sector.index = "c")
  circos.text(0.5, 0.5, "second one", niceFacing = TRUE,
              facing = "bending.outside")
  highlight.sector(
    c("d", "c"), track.index = 1, 
    col = "#9970ab80"
  )
  circos.clear()
}
plot_circos2()

# 添加外框和轴
box()
axis(side = 1)
axis(side = 2)

# 3. 第三幅图

plot_circos1()
# 添加图层
par(new = TRUE)
plot_circos2()

# 复原参数
par(op)

获取代码:https://github.com/dxsbiocc/learn/blob/main/R/plot/circos_combine_separated.R

最后一个例子,可以为不同扇形设置不同的半径,例如

sectors <- letters[1:4]
lim = c(1, 1.1, 1.2, 1.3)
# 绘制 4 个圆形
for(i in 1:4) {
  # 每个圆形设置不同的范围
  circos.par("canvas.xlim" = c(-lim[i], lim[i]), 
             "canvas.ylim" = c(-lim[i], lim[i]), 
             "track.height" = 0.4)
  circos.initialize(sectors, xlim = c(0, 1))
  # 添加空白轨迹
  circos.track(ylim = c(0, 1), bg.border = NA)
  # 每个圆绘制一个扇形
  circos.update(sector.index = sectors[i], bg.border = "black",
                bg.col = i + 1)
  circos.points(runif(10), runif(10), pch = 21, col = "black", bg = "white")
  circos.clear()
  par(new = TRUE)
}
par(new = FALSE)

4. 多图排列

circlize 是基于基础 R 图形系统的,所以,可以使用 parlayout 来排列多张圆形图。前面的例子中,我们使用的都是 par 函数的 mfrow(按行排列)或 mfcol(按列排列)来排列多个图形。

下面,举例说明 layout 的使用

layout(matrix(1:9, 3, 3))
for(i in 1:9) {
  sectors = 1:6
  par(mar = c(0.5, 0.5, 0.5, 0.5))
  circos.par(cell.padding = c(0, 0, 0, 0))
  circos.initialize(sectors, xlim = c(0, 1))
  circos.track(ylim = c(0, 1), track.height = 0.05,
               bg.col = rand_color(8), bg.border = NA)
  for(i in 1:5) {
    se = sample(1:6, 2)
    circos.link(se[1], runif(2), se[2], runif(2), 
                col = rand_color(1, transparency = 0.4), border = NA)
  }
  circos.clear()
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容