自选3 Task-for-Use 生涯彩虹图的绘制

使用R中的Circlize包绘制圆圈图

Posted by Jiawen Wu on December 28, 2018

前言

本post以职业生涯访谈实例讲解R中circlize包的应用,并制作Super的生涯彩虹图。


Super的生涯彩虹图介绍

根据Super的看法,一个人一生中扮演的许许多多角色就像彩虹同时具有许多色带。Super将显著角色的概念引入了生涯彩虹图。他认为角色除与年龄及社会期望有关外,与个人所涉入的时间及情绪程度都有关联,因此每一阶段都有显著角色。

图片来源:http://wportfolio.wzu.edu.tw/files/14-1000-447,r7-1.php

图片来源:http://wportfolio.wzu.edu.tw/files/14-1000-447,r7-1.php

现在有许多生涯规划师都会让来咨询的人通过绘制生涯彩虹图来分析其生涯发展的各个阶段以及主要角色彼此间的相互影响。同时,我们也可以通过绘制名人或者其他我们想要了解的人的生涯彩虹图,来更好地了解他/她的人生。

然而,现在生涯彩虹图的绘制大多是以手绘的方式制作,美观性就不说了。当然来访者自己绘制能够帮助其了解自身的情况,思考其生涯选择和未来规划。但是,如果作为一个第三方,或生涯规划咨询师,这样的手绘还是有些费时费力,修改起来也非常不方便。所以本post尝试使用R中的Circlize包来绘制和课本中的demo(上面第一张图)差不多的生涯彩虹图。

circlize包介绍

circlize包是德国癌症中心的华人博士Zuguang Gu开发的,它主要服务于生科医药领域多维数据的可视化,可以方便地画出优秀的圆圈图。当然,人们elaborate了这个包的可能性。这个包中documents,一个是介绍基本原理绘制简单圆圈图的。另外一个专门介绍基因组数据圈圈图的绘制,也就是Genomic Circos Plot。在本post中使用的是第一个文档。具体的包中函数及上手的介绍,前人TaoYan已经说的很详细了,大家有兴趣的可以check it out!

“这个包与ggplot2很相似,也是先创建一个图层,然后不断的添加图形元素(point、line、bar等),这些简单的图形元素都有circos.这个前缀进行绘制,比如要绘制点,则用circos.points()”

下面这个demo是Dr. Zuguang Gu放在github上的,有需求的童鞋可以去逛一逛

图片来自Zuguang Gu

画彩虹图

在本post中,我以一位爷爷辈人物的生涯数据为例子,来介绍这个图是怎么一步一步画出来的。希望以后能把它编成一个函数包,支持更多形式的(交互的)数据输入,丰富它的表现形式及可调节性,使得这个彩虹图能更方便地服务更多的人。

环境配置

把需要用到的包先装好

  • library(RCircos) - 这个包和“circlize”是基本上一样的,两者选其一就好
  • library(circlize) - 本post中使用的是“circlize”这个包
  • library(openxlsx) - 这个包纯粹是为了解决打开带有中文字的xlsx乱码问题

数据组织形式及数据预处理

下面就是需要输的数据的组织形式。对从0-x(对象的年龄)岁的个体每一年在各个角色上的分配进行0-100的赋分。本post中的对象85岁,excel数据中包含了其0-85岁每一年在“持家者”、“工作者”、“公民”、“休闲者”、“学生”、“儿童”六个角色上的分值及一列年龄变量。

grandpa_career <- read.xlsx(file.choose())
head(grandpa_career)

 $     年龄  持家者  工作者 公民  休闲者  学生 儿童
 $ 1    1      0      0    0      0    0  100
 $ 2    2      0      0    0      0    0  100
 $ 3    3      0      0    0      0    0  100
 $ 4    4      0      0    0      0    0  100
 $ 5    5      0      0    0      0   80   95
 $ 6    6      0      0    0      0   85   85

str(grandpa_career)

 $'data.frame':	85 obs. of  7 variables:
 $ 年龄  : num  1 2 3 4 5 6 7 8 9 10 ...
 $ 持家者: num  0 0 0 0 0 0 0 0 0 0 ...
 $ 工作者: num  0 0 0 0 0 0 30 35 40 45 ...
 $ 公民  : num  0 0 0 0 0 0 0 0 0 0 ...
 $ 休闲者: num  0 0 0 0 0 0 0 0 0 0 ...
 $ 学生  : num  0 0 0 0 80 85 90 90 85 80 ...
 $ 儿童  : num  100 100 100 100 95 85 85 85 85 80 ...

接下来,我们需要把数据转成方便circlize作图的形式

# 给数据加上“真数据”的标签“T”
grandpa_career$control <- rep("T",85)

# 因为我们要做的是半圆图,所以造出一个年龄范围(在本post中是0-85)范围一样的假数据
grandpa_career <- rbind(grandpa_career,c(0,0,0,0,0,0,0,"F"))
grandpa_career <- rbind(grandpa_career,c(85,0,0,0,0,0,0,"F"))

# 对数据的格式进行处理
grandpa_career$年龄 <- as.numeric(grandpa_career$年龄)
grandpa_career$持家者 <- as.numeric(grandpa_career$持家者)
grandpa_career$工作者 <- as.numeric(grandpa_career$工作者)
grandpa_career$公民 <- as.numeric(grandpa_career$公民)
grandpa_career$休闲者 <- as.numeric(grandpa_career$休闲者)
grandpa_career$学生 <- as.numeric(grandpa_career$学生)
grandpa_career$儿童 <- as.numeric(grandpa_career$儿童)

circos作图

话不多说,开始画图,首先可以通过par函数来设置一下整个圈圈图的基调,比如在我们的彩虹图中,因为有六种角色,所以至少有六层需要叠加的轨道。因此我把每层轨道的高度设置为0.1,这样才不会“超纲”。

接下来就是用我们需要使用到的参数来初始化我们的圈圈图,factors奠定了我们的圈圈图被分为几部分,而x则是用来确定每部分占据圆弧的度数,这也是为什么我们在数据处理的步骤需要加入“假数据”。

circos.par(track.height = 0.1)
circos.initialize(factors = grandpa_career$control,  x = grandpa_career$年龄)

其实圈圈图的每一层画法都差不多,只不过第一层需要加上ticks,所以要费点心思。在circos.trackPlotRegion()函数中需要加上panel.fun来给圈圈图的外围加上标尺,并将默认为TRUE的labels.pos.adjust设置为FALSE(让最后一个数字的label出现在它应该出现的位置上)

# Layer1
circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$持家者,bg.col =c(NA,"#ECF0F2"),bg.border = c(NA,"black"),
                        panel.fun = function(x, y) { 
                            circos.axis(labels.pos.adjust = FALSE)})

circos.trackLines(factors = grandpa_career$control, 
                  x = grandpa_career$年龄, y = grandpa_career$持家者,lwd = 2,straight = FALSE,area = TRUE,col = "#455C7B", type = "s")

画好之后就会出现像下面这样画好了第一层的半成品啦。 同样的我们加入接下来的二三四五六层,只不过这些轨道上我们不需要加入x轴的标签ticks。

# Layer2
circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$工作者, bg.col =c(NA,"#ECF0F2"),bg.border = c(NA,"black"))
circos.lines(x = grandpa_career$年龄[1:85], y = grandpa_career$工作者[1:85],
              lwd = 1.5,straight = FALSE,
              area = TRUE,col = "#685C79", type = "s")
# Layer3     
circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$公民, bg.col =c(NA,"#ECF0F2"),bg.border = c(NA,"black"))
circos.lines(x = grandpa_career$年龄[1:85], 
              y = grandpa_career$公民[1:85],lwd = 1.5,straight = FALSE,
              area = TRUE,col = "#AC6C82", type = "s")
# Layer4     
circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$休闲者, bg.col =c(NA,"#ECF0F2"),bg.border = c(NA,"black"))
circos.lines(x = grandpa_career$年龄[1:85], y = grandpa_career$休闲者[1:85],
            lwd = 1.5,straight = FALSE,area = TRUE,col = "#DA727E", type = "s")
# Layer5     
circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$学生, bg.col =c(NA,"#ECF0F2"),bg.border = c(NA,"black"))
circos.lines(x = grandpa_career$年龄[1:85], y = grandpa_career$学生[1:85],
            lwd = 1.5,straight = FALSE,area = TRUE,col = "#F8B195", type = "s")
# Layer6     
circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$儿童, bg.col =c(NA,"#ECF0F2"),bg.border = c(NA,"black"))
circos.lines(x = grandpa_career$年龄[1:85], y = grandpa_career$儿童[1:85],
            lwd = 1.5,straight = FALSE,area = TRUE,col = "#FFBC67", type = "s")

所有的轨道都按照我们excel中的数据画好以后,我们会看到下面这样的图。

接下来,我们就需要加上文字的内容并对图形进行一些调整。circos.text函数可以帮助我们添加文字。我想在圆圈中间的部分写上图片的标题,再加一个轨道用来写字也是个不错的选择。在R的图片上添加中文字是一个很麻烦的问题,添加一个family参数也许能解决。

circos.trackPlotRegion(factors = grandpa_career$control, 
                        y = grandpa_career$儿童, bg.col = NA,bg.border = NA)
circos.text(43, 30, "生涯彩虹图", sector.index = "T", 
            track.index = 7,family = "STHeiti",cex = 1.5)
circos.text(43, -90, "爷爷的0-85岁", sector.index = "T", 
            track.index = 7,family = "STHeiti",cex = 1.5)
circos.text(43, -180, "嘉嘉出品", sector.index = "T", 
            track.index = 7,family = "STHeiti",cex = 0.8)

然后,就是要添加角色标签和阶段标签了。本来想通过一个for loop来实现的,但是想着文字和图的关系可能需要手动调整一下下,以免这些标签挡住了我的图,所以还是使用了一条条加的人工操作方式。最后的最后以circos.clear()函数作结,结束这一切。

circos.text(4, 65, "持家者", sector.index = "T", 
            track.index = 1,family = "STHeiti",cex = 1)
circos.text(4, 70, "工作者", sector.index = "T", 
            track.index = 2,family = "STHeiti",cex = 1)
circos.text(4, 70, "公民", sector.index = "T", 
            track.index = 3,family = "STHeiti",cex = 1)
circos.text(4, 45, "休闲者", sector.index = "T", 
            track.index = 4,family = "STHeiti",cex = 1)
circos.text(3, 70, "学生", sector.index = "T", 
            track.index = 5,family = "STHeiti",cex = 1)
circos.text(5, 52, "儿童", sector.index = "T", 
            track.index = 6,family = "STHeiti",cex = 1)
circos.text(7, 280, "成长期", sector.index = "T", 
            track.index = 1,family = "STHeiti",cex = 1)
circos.text(20, 280, "探索期", sector.index = "T", 
            track.index = 1,family = "STHeiti",cex = 1)
circos.text(32, 280, "建立期", sector.index = "T", 
            track.index = 1,family = "STHeiti",cex = 1)
circos.text(54, 280, "维持期", sector.index = "T", 
            track.index = 1,family = "STHeiti",cex = 1)
circos.text(67, 280, "卸任期", sector.index = "T", 
            track.index = 1,family = "STHeiti",cex = 1)

circos.clear()

我们要的东西就在这儿了!当当当当! 也算部分还原了课本上的demo图吧哈哈哈。


还有许多需要解决的bugs,如果小伙伴们有解决方法,拜托留言告诉我!

  • 画完图之后保存成pdf格式出现所有中文消失的情况。
  • 不知道如何优雅地只画半圆(设置画图的角度,不画出整个圆),或者把另外半个圆mute掉。

参考文件下载: