使用circlize从数据框中制作圆形/弦图。

12

我想使用circlize软件包制作一个弦图。我有一个包含四列汽车信息的数据框。前两列包含拥有的汽车品牌和型号的信息,下面两列是受访者迁移到的品牌和型号。

这里是数据框的一个简单示例:

   Brand_from model_from Brand_to Model_to
1:      VOLVO        s80      BMW  5series
2:        BMW    3series      BMW  3series
3:      VOLVO        s60    VOLVO      s60
4:      VOLVO        s60    VOLVO      s80
5:        BMW    3series     AUDI       s4
6:       AUDI         a4      BMW  3series
7:       AUDI         a5     AUDI       a5

能够将这个内容制作成一个弦图就太好了。我在帮助文件中找到了一个例子,它可以正常运行,但我无法将我的数据转换为正确的格式以进行绘图。 这段代码来自circlize包的帮助文档。它生成一个层,我想我需要两个层,品牌和型号。

mat = matrix(1:18, 3, 6)
rownames(mat) = paste0("S", 1:3)
colnames(mat) = paste0("E", 1:6)

rn = rownames(mat)
cn = colnames(mat)
factors = c(rn, cn)
factors = factor(factors, levels = factors)
col_sum = apply(mat, 2, sum)
row_sum = apply(mat, 1, sum)
xlim = cbind(rep(0, length(factors)), c(row_sum, col_sum))

par(mar = c(1, 1, 1, 1))
circos.par(cell.padding = c(0, 0, 0, 0))
circos.initialize(factors = factors, xlim = xlim)
circos.trackPlotRegion(factors = factors, ylim = c(0, 1), bg.border = NA,
                       bg.col = c("red", "green", "blue", rep("grey", 6)), track.height = 0.05,
                       panel.fun = function(x, y) {
                         sector.name = get.cell.meta.data("sector.index")
                         xlim = get.cell.meta.data("xlim")
                         circos.text(mean(xlim), 1.5, sector.name, adj = c(0.5, 0))
})

col = c("#FF000020", "#00FF0020", "#0000FF20")
for(i in seq_len(nrow(mat))) {
  for(j in seq_len(ncol(mat))) {
    circos.link(rn[i], c(sum(mat[i, seq_len(j-1)]), sum(mat[i, seq_len(j)])),
                cn[j], c(sum(mat[seq_len(i-1), j]), sum(mat[seq_len(i), j])),
                col = col[i], border = "white")
  }
}
circos.clear()
这段代码生成以下的图表: enter image description here 期望的结果是像这个例子一样,但是替换了大洲我想要的是汽车品牌,在内圆中放置属于该品牌的汽车型号。 enter image description here
3个回答

13

由于我稍微更新了包,现在有一种更简单的方法来完成它。如果有人感兴趣,我将在这里给出另一个答案。

在最新的几个版本的circlize中,chordDiagram()接受邻接矩阵和邻接表作为输入,这意味着,现在你可以向函数提供包含成对关系的数据框。此外还有一个highlight.sector()函数,可以同时突出或标记多个扇区。

我将使用更短的代码实现之前展示过的图:

df = read.table(textConnection("
 brand_from model_from brand_to model_to
      VOLVO        s80      BMW  5series
        BMW    3series      BMW  3series
      VOLVO        s60    VOLVO      s60
      VOLVO        s60    VOLVO      s80
        BMW    3series     AUDI       s4
       AUDI         a4      BMW  3series
       AUDI         a5     AUDI       a5
"), header = TRUE, stringsAsFactors = FALSE)

brand = c(structure(df$brand_from, names=df$model_from),
          structure(df$brand_to,names= df$model_to))
brand = brand[!duplicated(names(brand))]
brand = brand[order(brand, names(brand))]
brand_color = structure(2:4, names = unique(brand))
model_color = structure(2:8, names = names(brand))

brandbrand_colormodel_color的值为:

> brand
     a4      a5      s4 3series 5series     s60     s80
 "AUDI"  "AUDI"  "AUDI"   "BMW"   "BMW" "VOLVO" "VOLVO"
> brand_color
 AUDI   BMW VOLVO
    2     3     4
> model_color
     a4      a5      s4 3series 5series     s60     s80
      2       3       4       5       6       7       8

这次,我们只增加了一个额外的轨道,放置了线条和品牌名称。此外,您可以发现输入变量实际上是一个数据框 (df[, c(2, 4)])。

library(circlize)
gap.degree = do.call("c", lapply(table(brand), function(i) c(rep(2, i-1), 8)))
circos.par(gap.degree = gap.degree)

chordDiagram(df[, c(2, 4)], order = names(brand), grid.col = model_color,
    directional = 1, annotationTrack = "grid", preAllocateTracks = list(
        list(track.height = 0.02))
)

与之前相同,模型名称是手动添加的:

circos.trackPlotRegion(track.index = 2, panel.fun = function(x, y) {
    xlim = get.cell.meta.data("xlim")
    ylim = get.cell.meta.data("ylim")
    sector.index = get.cell.meta.data("sector.index")
    circos.text(mean(xlim), mean(ylim), sector.index, col = "white", cex = 0.6, facing = "inside", niceFacing = TRUE)
}, bg.border = NA)

最后,我们通过highlight.sector()函数添加了线条和品牌名称。在这里,sector.index的值可以是长度大于1的向量,而线条(或细长的矩形)将覆盖所有指定的扇区。标签将添加在扇区中央,并且根部位置由text.vjust选项控制。

for(b in unique(brand)) {
  model = names(brand[brand == b])
  highlight.sector(sector.index = model, track.index = 1, col = brand_color[b], 
    text = b, text.vjust = -1, niceFacing = TRUE)
}

circos.clear()

输入图片描述


这很棒。对于用户的一个警告:brand_color和model_color的2:42:8是硬编码的,如果您使用自己的数据,则需要动态编码,例如model_color = structure(seq(2,length(names(brand))+1), names = names(brand)) - JustinJDavies

8

关键在于将您的数据转换为矩阵(邻接矩阵,其中行对应“从”而列对应“到”)。

df = read.table(textConnection("
 Brand_from model_from Brand_to Model_to
      VOLVO        s80      BMW  5series
        BMW    3series      BMW  3series
      VOLVO        s60    VOLVO      s60
      VOLVO        s60    VOLVO      s80
        BMW    3series     AUDI       s4
       AUDI         a4      BMW  3series
       AUDI         a5     AUDI       a5
"), header = TRUE, stringsAsFactors = FALSE)

from = paste(df[[1]], df[[2]], sep = ",")
to = paste(df[[3]], df[[4]], sep = ",")

mat = matrix(0, nrow = length(unique(from)), ncol = length(unique(to)))
rownames(mat) = unique(from)
colnames(mat) = unique(to)
for(i in seq_along(from)) mat[from[i], to[i]] = 1

mat的值为:

> mat
            BMW,5series BMW,3series VOLVO,s60 VOLVO,s80 AUDI,s4 AUDI,a5
VOLVO,s80             1           0         0         0       0       0
BMW,3series           0           1         0         0       1       0
VOLVO,s60             0           0         1         1       0       0
AUDI,a4               0           1         0         0       0       0
AUDI,a5               0           0         0         0       0       1

然后将矩阵发送到chordDiagram,并指定orderdirectional。手动指定order是为了确保相同品牌被分组在一起。
par(mar = c(1, 1, 1, 1))
chordDiagram(mat, order = sort(union(from, to)), directional = TRUE)
circos.clear()

为了让图形更加复杂,您可以创建一个品牌名称的轨道、一个品牌识别的轨道、一个型号名称的轨道。此外,我们可以将品牌之间的间隔设置得比品牌内部的间隔大。
1 设置 gap.degree
circos.par(gap.degree = c(2, 2, 8, 2, 8, 2, 8))

在绘制弦图之前,我们需要创建两个空轨道,一个用于品牌名称,另一个用于标识线,通过preAllocateTracks参数实现。
par(mar = c(1, 1, 1, 1))
chordDiagram(mat, order = sort(union(from, to)),
    direction = TRUE, annotationTrack = "grid", preAllocateTracks = list(
        list(track.height = 0.02),
        list(track.height = 0.02))
)

步骤3:将模型名称添加到注释轨道(该轨道默认创建,位于左右两个图中的粗轨道。请注意,这是从外向内数第三轨道)

circos.trackPlotRegion(track.index = 3, panel.fun = function(x, y) {
    xlim = get.cell.meta.data("xlim")
    ylim = get.cell.meta.data("ylim")
    sector.index = get.cell.meta.data("sector.index")
    model = strsplit(sector.index, ",")[[1]][2]
    circos.text(mean(xlim), mean(ylim), model, col = "white", cex = 0.8, facing = "inside", niceFacing = TRUE)
}, bg.border = NA)

4 添加品牌识别线。由于品牌涵盖多个行业,我们需要手动计算线(弧)的起始和结束度数。以下是第二轨道中rou1rou2两个边框的高度。识别线绘制在第二轨道上。

all_sectors = get.all.sector.index()
rou1 = get.cell.meta.data("yplot", sector.index = all_sectors[1], track.index = 2)[1]
rou2 = get.cell.meta.data("yplot", sector.index = all_sectors[1], track.index = 2)[2]

start.degree = get.cell.meta.data("xplot", sector.index = all_sectors[1], track.index = 2)[1]
end.degree = get.cell.meta.data("xplot", sector.index = all_sectors[3], track.index = 2)[2]
draw.sector(start.degree, end.degree, rou1, rou2, clock.wise = TRUE, col = "red", border = NA)

首先在极坐标系中获取文本的坐标,然后通过reverse.circlize映射到数据坐标系中。请注意,映射坐标回来并绘制文本的单元格应该是相同的单元格。

m = reverse.circlize( (start.degree + end.degree)/2, 1, sector.index = all_sectors[1], track.index = 1)
circos.text(m[1, 1], m[1, 2], "AUDI", cex = 1.2, facing = "inside", adj = c(0.5, 0), niceFacing = TRUE, 
    sector.index = all_sectors[1], track.index = 1)

对于另外两个品牌,代码相同。
start.degree = get.cell.meta.data("xplot", sector.index = all_sectors[4], track.index = 2)[1]
end.degree   = get.cell.meta.data("xplot", sector.index = all_sectors[5], track.index = 2)[2]
draw.sector(start.degree, end.degree, rou1, rou2, clock.wise = TRUE, col = "green", border = NA)
m = reverse.circlize( (start.degree + end.degree)/2, 1, sector.index = all_sectors[1], track.index = 1)
circos.text(m[1, 1], m[1, 2], "BMW", cex = 1.2, facing = "inside", adj = c(0.5, 0), niceFacing = TRUE, 
    sector.index = all_sectors[1], track.index = 1)

start.degree = get.cell.meta.data("xplot", sector.index = all_sectors[6], track.index = 2)[1]
end.degree  = get.cell.meta.data("xplot", sector.index = all_sectors[7], track.index = 2)[2]
draw.sector(start.degree, end.degree, rou1, rou2, clock.wise = TRUE, col = "blue", border = NA)
m = reverse.circlize( (start.degree + end.degree)/2, 1, sector.index = all_sectors[1], track.index = 1)
circos.text(m[1, 1], m[1, 2], "VOLVO", cex = 1.2, facing = "inside", adj = c(0.5, 0), niceFacing = TRUE, 
    sector.index = all_sectors[1], track.index = 1)

circos.clear()

如果你想设置颜色,请查看包vignette。如果需要,也可以使用circos.axis在图上添加坐标轴。

enter image description here


-1
使用read.table读取数据,结果为7x4的数据框(brand.txt应该是用制表符分隔的)。
mt <- read.table("//your-path/brand.txt",header=T,sep="\t",na.string="NA")

你的变量名(mt)是:"Brand_from"、"model_from"、"Brand_to"和"Model_to"。 选择你感兴趣的两个变量,例如:

mat <- table(mt$Brand_from, mt$model_from)

这将导致以下表格:

# >mat
#        3series a4 a5 s60 s80
# AUDI        0  1  1   0   0
# BMW         2  0  0   0   0
# VOLVO       0  0  0   2   1

然后您可以像在circlize脚本中提供的那样从"rn = rownames(mat)"运行所有内容

enter image description here


我尝试了你的建议,但在运行circos.trackPlotRegion函数时出现了以下错误信息:Error in if (ncut) { : argument is not interpretable as logical。 - jonas
也许可以检查一下mat是否与我在答案中提供的相对应。供您参考:我正在使用“R版本3.0.2(2013-09-25)”。 - Ruthger Righart
当我尝试使用包含品牌列的表时,出现了错误。在您的情况下,从品牌到型号可以正常工作,但是我想看到从品牌迁移。 - jonas
当两个集合中的名称完全相同(如AUDI,BMW,VOLVO)时,似乎会发生这种情况,这在实际中并不常见,但在这种情况下确实发生了。目前我还不知道原因。作为一种解决方法,如果您更改名称(例如,AUDI1,BMW1,VOLVO1与AUDI2,BMW2,VOLVO2),它就可以工作了。 - Ruthger Righart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接