ggbreak:你们要的坐标轴截断,它来了
坐标轴截断在生物医学论文中,是一种处理Figure的常用手段,由于数据的分布不呈现正态,有些数据特别大,而大部分的数据又比较小,这样画出来的图没法看,如果数据是呈现log normal分布的话,可以通过对数转换来解决,但数据不一定符合某种分布,通过数据转换不一定能解决问题,有时候数据只是单纯有些outliers而已。比如在系统发育树上,如果我们引入一个外类分支(outgroup),这个外类分支的支长就可能特别长(当然系统发育树本身可能某种原因也会有分支特别长的情况),这样对可视化带来很大的问题,树的整体会因为特别长的分支被压缩到无法看清主体的树结构。这种情况在别的图形中也是可能出现的,截断是一种很好的解决方案。
截断图如果通过操作数据来达到目标是不可取的,因为它只能针对简单的图,诸如柱状图等,对于复杂的图,因为数据变了,图就会变。所以最好的方法就是在原图上设置窗口,把中间要截掉的那块拿走,然后拼在一起,借由拿走的那块,为图的细节部分增加空间,所以本质上是拼图,而且使用拼图,对任何图形都可适用。
别人的解决方案都是简单粗暴地拼图,一拼图就死了,没拼之前,你可以继续加图层,但你看不清楚细节部分,你把图画出来一看,看不清楚你自己加的图层效果怎么样(比如说柱状图上加标记p值)。你截断图之后,看是看得清了,但你没法再加图层了,因为图被搞死了。
针对这样的问题,我们开发了ggbreak包,图一画,有outlier,先截断,然后在看清楚细节的情况下,可以继续用ggplot2的语法,继续搞事。这就是我想要的,完全与ggplot2兼容。
开发这个包源自于5月8号在校内杨老师组织的小型会议上,我讲了ggtree,朱老师问我有枝长很长的情况下,能不能截断。基本上我以前也是觉得坐标轴有啥好截断的,做个数据转换就行了(比如log),这一问,我想到了数据就单纯只是有outliers,而outliers是有意义的,这样子的话,截断确实是个好方法。然后在下一个教授演讲的时候,我大概想了一下,好用的包应该是完全与ggplot2兼容的。第二天早上参加答辩,下午晚上带娃,大概想清楚了该怎么样来设计,想法其实很简单,但抄作业容易,原创却难,我想如果我不是写了plotbb
和aplot
两个包的话,应该也不会很快有想法。
第三天,早上花了两小时,把我的想法写出来,于是v 0.0.1出来了,奠定了包的基调和设计框架,这一点很重要,一个包的设计,才是包的品味,能干活不算什么,能让用户毫无学习成本,用着觉得爽,才是真的好。
然后就是让我的博士生徐双斌(就是ggtreeExtra
的作者)接手支持多个断点,他顺手就支持了scale_x/y_reverse
。于是v0.0.2上线CRAN。
后面我还提了不少要求,都被一一实现了,于是v0.0.3上线,可以和大家见面了。可以说,麻雀虽小,五脏具全,虽然是干一点小事的包,但基本上能满足你所有的想像。
Gap plot:截断坐标轴
这是通常我们说的,中间砍掉一些的图,我们只提供两个标尺函数,scale_x_break
和scale_y_break
,设置断点,它拥有一堆特性。
1. 与ggplot2兼容
你可以继续加图层,应用主题,想干啥就干啥。
library(ggplot2)
library(ggbreak)
library(patchwork)
set.seed(2019-01-19)
d <- data.frame(x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)
p1 <- ggplot(d, aes(y, x)) + geom_col(orientation="y")
d2 <- data.frame(x = c(2, 18), y = c(7, 26), label = c("hello", "world"))
p2 <- p1 + scale_x_break(c(7, 17)) +
geom_text(aes(y, x, label=label), data=d2, hjust=1, colour = 'firebrick') +
xlab(NULL) + ylab(NULL) + theme_minimal()
p1 + p2
2. 支持多个断点
p2 + scale_x_break(c(18, 21))
3. 支持子图放大缩小
p1 + scale_x_break(c(7, 17), scales = 1.5) + scale_x_break(c(18, 21), scales=2)
4. 支持反转坐标轴
g <- ggplot(d, aes(x, y)) + geom_col()
g2 <- g + scale_y_break(c(7, 17), scales = 1.5) +
scale_y_break(c(18, 21), scale=2) + scale_y_reverse()
g + g2
5. 支持坐标轴变换
诸如scale_x_log10
和scale_x_sqrt
等坐标轴变换,都能够应用到截断图中
p2 <- p1 + scale_x_break(c(7, 17))
p3 <- p1 + scale_x_break(c(7, 17)) + scale_x_log10()
p2 + p3
6. 支持反转坐标
g + coord_flip() + scale_y_break(c(7, 18))
7. 支持分面
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22),
group = c(rep("A", 10), rep("B", 10)),
face=c(rep("C", 5), rep("D", 5), rep("E", 5), rep("F", 5))
)
p <- ggplot(d, aes(x=x, y=y)) +
geom_col(orientation="x") +
scale_y_reverse() +
facet_wrap(group~.,
scales="free_y",
strip.position="right",
nrow=2
) +
coord_flip()
pg <- p +
scale_y_break(c(7, 17), scales="free") +
scale_y_break(c(19, 21), scales="free")
print(pg)
8. 兼容图例
pg <- pg + aes(fill=group)
print(pg)
9. 兼容所有的图标签
pg + labs(title="test title", subtitle="test subtitle", tag="A tag", caption="A caption") +
theme_bw() +
theme(
legend.position = c(0.8, 0.8),
strip.placement = "outside",
axis.title.x=element_text(size=10),
plot.title = element_text(size = 22),
plot.subtitle = element_text(size = 16),
plot.tag = element_text(size = 10),
plot.title.position = "plot",
plot.tag.position = "topright",
plot.caption = element_text(face="bold.italic"),
)
10. 支持设置子图的坐标轴tick labels
require(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22),
group = c(rep("A", 10), rep("B", 10))
)
p <- ggplot(d, aes(x=x, y=y)) +
scale_y_reverse() +
scale_x_reverse() +
geom_col(aes(fill=group)) +
scale_fill_manual(values=c("#00AED7", "#009E73")) +
facet_wrap(
group~.,
scales="free_y",
strip.position="right",
nrow=2
) +
coord_flip()
p +
scale_y_break(c(7, 10), scales=0.5, ticklabels=c(10, 11.5, 13)) +
scale_y_break(c(13, 17), scales=0.5, ticklabels=c(17, 18, 19)) +
scale_y_break(c(19,21), scales=1, ticklabels=c(21, 22, 23))
11. 兼容patchwork
library(patchwork)
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)
p <- ggplot(d, aes(x, y)) + geom_col()
x <- p+scale_y_break(c(7, 17 ))
x + p
Wrap plot:切分长图
我们提供了scale_wrap()
标尺函数,专门针对x轴的数据很长的情况下,为了打印在纸上,你的图通常是压缩得太厉害,看不清细节的,那么这个函数呢,把你的图切成几个等份,画成几行,这样让你能更容易地去阅读、解说图。
p <- ggplot(economics, aes(x=date, y = unemploy, colour = uempmed)) +
geom_line()
p + scale_wrap(n=4)
Cut plot:图切片
这里提供了scale_x_cut
和scale_y_cut
把图切成几片,和Gap plot相比,它不截断,它只是切,切成子图后,子图可以缩放。我设计这个图的初衷在于,比如你画gwas,比如你画火山图,是不是可以切片,然后把p值显著的子图放大,这样你能看清楚细节,同时你要标记基因或者位点,有足够的空间,当然这功能现在还需要再提升,因为在切点的位置,会出现于两个子图中,以后再改改。
library(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)
p <- ggplot(d, aes(x, y)) + geom_col()
p + scale_y_cut(breaks=c(7, 18), which=c(1, 3), scales=c(3, 0.5))
Note
在Gap plot里介绍的特性,同时也应该是Wrap plot和Cut plot所支持的,如果不支持,请github上反馈,以前如果你觉得有一些细节上还没有处理好的,github上反馈,如果你有一些别的截断图的想法或需求的,github上反馈。欢迎勾搭,一切尽在github。
往期精彩