本文是笔者近期使用R语言的一个简单记录。
本文包括以下五个小节:
- ggplot2 手动调整线条颜色
- ggplot2 修改坐标轴
- ggplot2 组合图形
- ggplot2 多图
- dplyr包中 filter 函数的一个坑
ggplot2 手动调整线条颜色
主要是用到scale_color_manual
函数,举例来说:
下面代码是生成一幅折线图,但是线条颜色是软件自动设置的。
n <- 5
x0 <- 1:n
y1 <- x0 + 10
y2 <- x0 + 20
y3 <- x0 + 30
d <- data.frame(x=paste0("X", rep(x0, times=3)), y=c(y1, y2, y3),
g=rep(c("a", "b", "c"), each=n))
d$x <- factor(d$x, levels=paste0("X", 1:n))
library(ggplot2)
ggplot(d, aes(x=x, y=y, group=g, color=g)) + geom_point() + geom_line()
生成的图片如下:
可以看出,一共三组数据,每组数据的线条颜色是软件自动设置的。如果添加scale_color_manual语句可以手动设置线条的颜色:
ggplot(d, aes(x=x, y=y, group=g, color=g)) + geom_point() + geom_line() +
scale_color_manual(values=c("a"="red", "b"="blue", "c"="black"))
修改后的图片如下:
类似的函数还有scale_fill_manual
,scale_size_manual
,scale_shape_manual
,scale_linetype_manual
,scale_alpha_manual
,scale_discrete_manual
等。以scale_fill_manual函数为例,从其名字上就可以看出,该函数可以手动设置填充色。
ggplot2 修改坐标轴
修改坐标轴主要用到scale_x_continuous
,scale_y_continuous
,scale_x_discrete
,scale_y_discrete
这四个函数。从这些函数的名称上可以看出,分别是针对x轴和y轴的,continuous和discrete分别是针对连续型数据和离散型数据来说的。
这些函数可以对坐标轴的诸多细节进行调整,以笔者遇到的问题为例:如果x轴要画100个点,如果这些点的标签都显示出来,会挤在一起不美观。像下面这样:
其代码如下:
n <- 100
x0 <- 1:n
y1 <- x0 + 10
y2 <- x0 + 20
y3 <- x0 + 30
d <- data.frame(x=paste0("X", rep(x0, times=3)), y=c(y1, y2, y3),
g=rep(c("a", "b", "c"), each=n))
d$x <- factor(d$x, levels=paste0("X", 1:n))
library(ggplot2)
ggplot(d, aes(x=x, y=y, group=g, color=g)) + geom_point() + geom_line() +
scale_color_manual(values=c("a"="red", "b"="blue", "c"="black"))
即使旋转坐标轴标签也没有多少改观:
ggplot(d, aes(x=x, y=y, group=g, color=g)) + geom_point() + geom_line() +
scale_color_manual(values=c("a"="red", "b"="blue", "c"="black")) +
theme(axis.text.x=element_text(angle=45, hjust=.9)) # 旋转坐标轴标签
这个时候可以使用scale_x_discrete语句,只显示其中部分标签:
ggplot(d, aes(x=x, y=y, group=g, color=g)) + geom_point() + geom_line() +
scale_color_manual(values=c("a"="red", "b"="blue", "c"="black")) +
scale_x_discrete(breaks=paste0("X", seq(10, 100, 10))) # 只显示部分标签
效果如下:
上面的代码用到了scale_x_discrete函数的breaks
参数,该参数控制着显示哪些标签。scale_x_discrete函数还有其它参数控制着坐标轴的诸多细节,比如limits
参数,该参数可以控制坐标轴标签显示的顺序。比如我们接着上面的代码,将x轴的显示顺序从X1到X100调整为从X100到X1。
ggplot(d, aes(x=x, y=y, group=g, color=g)) + geom_point() + geom_line() +
scale_color_manual(values=c("a"="red", "b"="blue", "c"="black")) +
scale_x_discrete(breaks=paste0("X", seq(10, 100, 10)), # 只显示部分标签
limits=paste0("X", n:1)) # 调整x轴标签顺序
其它的参数也是很有用的,有机会大家可以研究。
ggplot2 组合图形
组合图形就是在一张图上叠加多个图形,这多个图形分别在不同的图层上。实际上ggplot2中的组合图形一般就是叠加图层。比如,最常见的折线图其实就是一个组合图形,它包含了点图和线图这两个图层的叠加。还以上面的数据为例:
n <- 100
x0 <- 1:n
y1 <- x0 + 10
y2 <- x0 + 20
y3 <- x0 + 30
d <- data.frame(x=paste0("X", rep(x0, times=3)), y=c(y1, y2, y3),
g=rep(c("a", "b", "c"), each=n))
d$x <- factor(d$x, levels=paste0("X", 1:n))
library(ggplot2)
ggplot(d, aes(x=x, y=y, group=g, color=g)) +
geom_point() + # 先是点图
geom_line() # 后是线图
图层的顺序是很重要的,因为后面的图层会“盖”住前面的图层,有可能会影响前面图层的显示效果。比如,还是上面的数据,下面的代码先画箱线图,再画线图,正常显示:
ggplot(d, aes(x=g, y=y)) +
geom_boxplot() + # 箱线图
stat_summary(fun.y="mean", color="red", geom="line", group=1) # 各组平均值的连线
从图中可以看出,上图一共三组数据,每组数据的箱线图和平均值的连线都可以正常显示。但是,如果颠倒图层的顺序,先画线图,再画箱线图,那么箱线图会盖住线图,从而影响整体的显示效果:
ggplot(d, aes(x=g, y=y)) +
stat_summary(fun.y="mean", color="red", geom="line", group=1) + # 各组平均值的连线
geom_boxplot() # 箱线图
我们从上图可以看到,图层的顺序是很重要的。
其实,上面的代码中用stat_summary
函数实现了平均值的线图,其实我们可以用一般的geom_line
来实现,只不过需要对数据进行一下处理。因为我们是要求各组的平均值,所以先用一个数据框来存储各组平均值。
d_aver <- aggregate(d$y, list(g=d$g), mean) # 各组平均值
然后,我们对两个不同的数据框分别作箱线图和线图:
d_aver <- aggregate(d$y, list(g=d$g), mean) # 各组平均值
ggplot() +
geom_boxplot(data=d, aes(x=g, y=y)) + # 箱线图
geom_line(data=d_aver, aes(x=g, y=x, group=1, color="red")) # 各组平均值的连线
其实正如stat_summary
函数的名称显示的那样,这是一个统计画图函数,我们当然可以自己先统计然后画图。比如上面的例子,用处理过的数据经geom_line
函数画出来的图和stat_summary画出来的图是一样的。
还有一点值得注意,就是上面的代码中,geom_boxplot函数和geom_line函数分别用了不同的数据集,这一点很有意思,也就意味着不同的图层可以用不同的数据来作图。实际上,不光是数据可以不同,不同的图层还可以使用不同的映射(mapping)。ggplot2的设计思路有点像面向对象编程里面的类的继承
,ggplot
函数是“基类”,它的参数值会被下面的各个图层所继承,而各个图层是相互独立的,它们可以修改自己图层中的参数值(或者说是覆盖掉ggplot函数的参数值),而不会影响其它图层以及ggplot函数的参数值。这一点可以用下面的代码来验证以下:
d_aver <- aggregate(d$y, list(g=d$g), mean) # 各组平均值
p <- ggplot() +
geom_boxplot(data=d, aes(x=g, y=y)) + # 箱线图
geom_line(data=d_aver, aes(x=g, y=x, group=1), color="red") # 各组平均值的连线
p$layers # 显示各个图层的参数
ggplot2 多图
我们经常要将多幅图放在一起,ggplot2 处理这个问题常用两种方式,一是用ggplot2本身的facet
,二是利用与ggplot2相关的一个包gridExtra
。
首先看分面的方式(facet),有facet_wrap
函数和facet_grid
函数。我们看一个例子:
n <- 5
x0 <- 1:n
y1 <- x0 + 10
y2 <- x0 + 20
y3 <- x0 + 30
d <- data.frame(x=paste0("X", rep(x0, times=3)), y=c(y1, y2, y3),
g=rep(c("a", "b", "c"), each=n))
d$x <- factor(d$x, levels=paste0("X", 1:n))
library(ggplot2)
# 采用分面(facet)
# 这里用facet_wrap函数比较合适:
ggplot(d, aes(x=x, y=y)) + geom_line(group=1) + facet_wrap(~g, nrow=2)
画出来的效果是:
上面的例子比较适合facet_wrap函数,当然,使用facet_grid函数也是可以的:
# 当然,用facet_grid函数也可以:
ggplot(d, aes(x=x, y=y)) + geom_line(group=1) + facet_grid(.~g)
画出来的效果是:
说完了facet,我们再看看gridExtra包。这个R包可以将多个图片组合成一张大图。笔者常用包中的marrangeGrob
函数。将要组合的多个图放入一个list中即可,还以上面的数据为例:
n <- 5
x0 <- 1:n
y1 <- x0 + 10
y2 <- x0 + 20
y3 <- x0 + 30
d <- data.frame(x=paste0("X", rep(x0, times=3)), y=c(y1, y2, y3),
g=rep(c("a", "b", "c"), each=n))
d$x <- factor(d$x, levels=paste0("X", 1:n))
library(ggplot2)
library(gridExtra)
oneGrob <- function(g) { # 生成单张小图
data <- d[d$g == g, ]
ggplot(data, aes(x=x, y=y)) + geom_line(group=1) +
labs(title=paste0("group=", g)) +
theme(plot.title = element_text(hjust=0.5))
}
lgrob <- lapply(unique(d$g), oneGrob) # 将多张小图放到一个list中
# 将多张小图组合成一张大图
marrangeGrob(lgrob, nrow=2, ncol=2, top="Example of marrangeGrob function")
其效果是:
我们可以看到,图片是按照列优先的顺序进行排列的,如果要按照行的顺序进行排列,可以调整marrangeGrob函数的layout_matrix
参数,像下面这样:
marrangeGrob(lgrob, nrow=2, ncol=2,
layout_matrix = matrix(1:4, 2, 2, byrow=T), # 设置byrow=T,按行排列小图
top="Example of marrangeGrob function")
dplyr包中 filter 函数的一个坑
自从笔者接触dplyr
包以来,使用它的频率越来越高。但是,笔者近期在使用该包的filter
函数时,遇到一个问题。以上面的数据为例,假设要挑选出"a"组的数据,将其它组的数据过滤掉,可以这样:
n <- 5
x0 <- 1:n
y1 <- x0 + 10
y2 <- x0 + 20
y3 <- x0 + 30
d <- data.frame(x=paste0("X", rep(x0, times=3)), y=c(y1, y2, y3),
g=rep(c("a", "b", "c"), each=n))
d$x <- factor(d$x, levels=paste0("X", 1:n))
library(dplyr)
# 过滤前各组数据量统计
table(d$g)
# 过滤
g <- "a" # 过滤后剩下的组的组名
d1 <- d %>% filter(g == g)
table(d1$g) # 过滤失败
过滤失败的原因是filter函数中的过滤条件没有发挥作用,因为g == g
在filter函数中恒成立,其实是犯了变量重名的一个错误,把代码改动一下即可:
# 重新过滤
g <- "a" # 过滤后剩下的组的组名
g2 <- g # 为了避免变量重名,另赋值一个变量
d2 <- d %>% filter(g == g2)
table(d2$g) # 过滤成功
(公众号:生信了)