(转帖)R语言-同一数据多变量分组的boxplot

原文地址:https://mp.weixin.qq.com/s/jegI1wWc7DJzrM7mQfz77Q

                    同一数据多变量分组的boxplot?


原创:                                                Y叔

                                                                    biobabble                      2017-10-31

小密圈的问题,不是三两句话可以说明白的事情,必须要写文来解答,上一次写文是《听说你还不会画热图》,里面正好吐槽了某知乎大V的「除了ggplot2之外其它都是鸡肋」,这次正好也可以再次呼应一下。

张三的歌蔡琴 - CCTV音乐频道精彩音乐汇合辑

这个图明显是R的base graphics做的,图是可圈可点的,能做出这图来,也已经是告别了只会用plot的低级趣味。这其实是4个图拼起来的,第一个图只是多了个y轴而已,它们画起来是一样的,那就是只画boxplot,不画x和y轴,(你可能会说不是有x轴?),x轴是后面再加上去的,而且加x轴的时候,不写labels,只有线条没有文本(你可能又会说明明有文本!),因为axis这个函数只支持labels要么是水平的,要么是垂直的,旋转某个角度是不支持的,所以labels是额外再打上去的。这里一张小小的图,门道还是挺多的。

set.seed(2017-10-30)

d <- data.frame(riskScore = abs(rnorm(100)),

               BMI = sample(1:2, 100, replace=T),

               stage = sample(1:2, 100, replace=T),

               age = sample(1:2, 100, replace=T),

               gender = sample(1:2, 100, replace=T))

head(d)

##     riskScore BMI stage age gender

## 1 0.008282743   1     2   1      1

## 2 0.499375414   1     1   2      1

## 3 0.188257548   1     1   1      1

## 4 0.330772189   2     1   2      1

## 5 0.790797457   1     2   1      1

## 6 1.943465449   1     1   2      1

先搞一个数据集,都只是随机数,纯粹是为了演示用而已。下面我将定义一个myboxplot,它画boxplot,不带x和y轴,然后加x轴不带labels,再额外打labels,因为提问者的图中还有pvalue,我顺道把pvalue也整合进这个myboxplot里去,可以用pvalue=NULL来关掉这个功能。

关于箱式图,可以参考我之前写的《boxplot》,而这里用到T检验,可以参考《什么是T检验》。

myboxplot <- function(x, data, col = NULL, xlab, pvalue="auto") {

   boxplot(x, data, axes = FALSE, col = col)

   axis(1, at = 1:2, labels =FALSE)

   text(1:2, y=par()$usr[3]-0.08*(par()$usr[4]-par()$usr[3]),

        srt=60, xpd=T, adj=1, labels = xlab)

   if (pvalue == "auto") {

       pvalue <- round(t.test(x, data=data)$p.value, 3)

   }

   if (!is.null(pvalue)) {

       plab <- paste("p =", pvalue)

       text(1.5, y = par()$usr[4]*1.05, xpd=T, label=plab, col=col)

   }

}

万事具备,有函数,有数据,我们先初始化画4个column,然后你只要调用myboxplot,分4次画4个图,就大功告成了,第一个图的时候,把y轴给加上。

layout(t(1:4))

par(oma=c(2, 4, 4, 0), mar=c(5,2,1,1), cex=1)

myboxplot(riskScore~age, data=d, col='red', xlab=c("age < 60", "age > 60"))

axis(2, las=1)

myboxplot(riskScore~gender, data=d, col='green', xlab=c("Male", "Female"))

myboxplot(riskScore~stage, data=d, col='blue', xlab=c("pStage 1-2", "pStage 1-2"))

myboxplot(riskScore~BMI, data=d, col='cyan', xlab=c("BMI < 24", "BMI > 24"))

假如我们想要用ggplot2来画,该怎么搞?首先毫无意外,要把数据整理成ggplot2喜欢的样子,我定义一个convert函数专门来搞这个数据:

convert <- function(d) {

   lapply(2:ncol(d), function(i) {

       d2 <- d[, c(1,i)]

       d2$type = colnames(d2)[2]

       colnames(d2) = c("riskScore", "category", "type")

       return(d2)

   }) %>% do.call('rbind', .)

}

dd <- convert(d)

head(dd)

##     riskScore category type

## 1 0.008282743        1  BMI

## 2 0.499375414        1  BMI

## 3 0.188257548        1  BMI

## 4 0.330772189        2  BMI

## 5 0.790797457        1  BMI

## 6 1.943465449        1  BMI

然后就可以直接ggplot来画了,这里蛋疼的是颜色不是我们想要的那种用type来上色,如果你指定用type,那么不好意思,不会用category分组,如果你指定group = factor(category)呢?又不好意思了,不会让type来分组了,也就是说你画出来的是按category分的两个box,而不会有不同type是不同组数据的切分了,这就是ggplot2蛋疼之外,语法太高级,以至于有些情况没办法以它的语法表达的时候,是非常困难的。

library(ggplot2)

ggplot(dd, aes(type, riskScore, fill=factor(category))) + geom_boxplot()


当然可以通过分面来补救:

ggplot(dd, aes(type, riskScore, group=factor(category), fill=type)) +

   geom_boxplot() + facet_grid(.~type, scales = "free_x")


分面也不好啊,分面的strip text和x axis text重了,当然这个x axis text不是我们想要的。

如果我不想用分面呢?你就得用另外的变量去欺骗它,让它分好组,比如这里我用color=factor(category),这样会给boxplot的外框加颜色,但这个颜色不是我们想要用的,category分1和2,在不同的type里意义是不一样的。第二点,它的legend也不是我们想要的,所以这里又需要额外的设置了,我们要指定颜色统一,要去掉legend。

ggplot(dd, aes(type, riskScore, color=factor(category), fill=type)) + geom_boxplot() +

   scale_color_manual(values=rep('black',2), guide=FALSE)


这个图就像模像样了,和上面分面的其实差不多,有一个共同点,x axis text不是我们想要的,怎么改?还是illustrator吧,带着ggplot2的枷锁改起来可费劲了。

当然画图嘛,功夫一半在画图上,另一半在于对数据的操作,既要用ggplot2,又要用得爽,你得有70岁的觉悟,「七十而从心所欲,不逾矩」,在矩(俗称枷锁)之下,从心所欲,关键还是对数据和作图系统的理解。这数据还得再变一下,我们不分组了,不就画8个boxplot么,把不同category的1和2全部换掉,换成不同的变量,然后画8个box就完事了。

bmi = c("BMI < 24", "BMI > 24")

stage = c("pStage 1-2", "pStage 3-4")    

age = c("age < 60", "age > 60")

gender = c("Male", "Female")

d$BMI = bmi[d$BMI]

d$stage = stage[d$stage]

d$age = age[d$age]

d$gender = gender[d$gender]

dd = convert(d)

dd$category = factor(dd$category, levels=c(age, gender, stage, bmi))    

p1 = ggplot(dd, aes(category, riskScore, fill=type)) + geom_boxplot() +

       theme(axis.text.x = element_text(angle=60, vjust=1, hjust=1))

p2 = p1 + facet_grid(.~type, scales="free_x")

cowplot::plot_grid(p1, p2, ncol=2)    



数据使然,我们很容易就想到这有4组,每一组有两类,我们要分组,枷锁就来了,后面要改细节,标x

axis

text,简直是恶梦,除非你放弃治疗用illustrator。如果我们能够放开分组的概念,出王八拳,倒是豁然开朗。这里额外强调一点的是base

graphics的作图,容易理解,符合直觉这一块,还是很厉害的,hadley wikham也说ggplot2是试图结合base +

lattice的优点。多学一点「外语」对理解和应用「母语」是有帮助的。第二点,放开套路,多试试王八拳,能把对方打趴下的拳,就是好拳!

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

推荐阅读更多精彩内容