在R语言中可以使用magrittr包的%>%进行管道操作。
管道操作可以减少中间变量的产生,并且使得多层嵌套的函数逻辑更加清晰。
一个管道操作的示意如下:
library("magrittr")
library("tidyverse")
# 使用管道:
rnorm(10) %>% sort() %>% plot
# 如果不使用管道,函数嵌套变多,难以阅读
plot(sort(rnorm(10)))
# 或者产生很多中间变量
x <- rnorm(10)
y <- sort(x)
plot(y)
magrittr的管道操作符里面有多个:%>%, %$%, %T>%, %<>%
,最常用的就是%>%操作符,它将操作符之前的运算结果传递给操作符之后的函数的第一个参数。
由于管道操作传递结果的特殊性——传递给第一个参数(在管道操作符后一个函数中使用.来表示此结果),因此并不是所有的函数都可以使用管道操作,比如base R中的一系列字符串函数:grep、sub等等,其第一个参数往往不是前一步的运算结果,这个时候就无法直接使用%>%操作符进行管道操作了。
管道操作也不适用同一个步骤需要同步进行多个操作的情况。
幸好这些可以通过{}来解决。
base R函数也可以使用管道
并不是所有的base R函数都不支持管道,那些第一个参数往往不是前一步的运算结果的函数才不支持。
此外,还有一些函数是不支持管道的:使用当前环境的函数,因为管道操作符会新建一个环境,因此操作当前环境的函数实际上在操作管道符创建的新环境,这很可能不是你想要的;使用惰性求值的函数:如try、tryCatch等等。
这些函数使用{}就可以正常进行管道操作了,示例如下:
mtcars %>% head %>% { colnames(.) <- 1:ncol(.); . }
# 1 2 3 4 5 6 7 8 9 10 11
#Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
#Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
#Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
#Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
#Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
#Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
{}代表创建了一个新环境,操作符上一步的运算结果在此环境中使用.来表示,上例是对传进来的数据框的列名进行修改,修改为列号。
colnames函数就是一种典型的无法直接使用管道操作符的函数。
需要注意的是,由于colnames函数运算完成后不会返回其操作的数据框,所以需要最后使用.显式的将结果传出{}环境。
一旦熟悉了{}和.的配合,那么这种操作方式就可以让管道变得非常灵活,相比较而言虽然其不如普通管道显得整洁,但是只要多用几次,就可以很容易的理解适应这种用法。
mtcars %>% head %>% {
colnames(.) <- 1:ncol(.)
rownames(.) <- paste0("row", 1:nrow(.))
.
}
# 1 2 3 4 5 6 7 8 9 10 11
#row1 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
#row2 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
#row3 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
#row4 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
#row5 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
#row6 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
{}后面可以继续进行管道操作:
mtcars %>% head %>% { colnames(.) <- 1:ncol(.); . } %>% slice(1)
# 1 2 3 4 5 6 7 8 9 10 11
#1 21 6 160 110 3.9 2.62 16.46 0 1 4 4
更好用的ggplot2
ggplot2只能支持一般的管道操作,只支持传入,不支持传出。但是使用了{}就可以不受此限制。
这里其实是以ggplot2举例,其实并不局限于ggplot2,{}在需要单步执行多个命令时是很有效的。下面是一个示例:
iris %>% {
# 正常绘图
ggplot(., aes(x = Species, y = Sepal.Length, fill = Species)) +
geom_boxplot() -> p1
# 添加部分文字图层的图
# geom_text的data参数可以对.进行运算
ggplot(., aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(color = Species)) +
geom_text(data = top_frac(., n = 0.1, wt = Sepal.Width),
aes(label = Sepal.Width)) -> p2
# 绘制第三张图用于拼图
ggplot(., aes(x = Petal.Length, fill = Species)) +
geom_histogram() -> p3
require(patchwork)
(p1|p2)/p3
}
由于{}内是新建环境,所以临时变量p1、p2、p3并不会影响.GlobalEnv。如果所需绘制的图很多,那么在{}内可以随意创建p1、p2、p3,不会影响下一批的图片绘制。
其后还可以继续进行管道操作,继续使用%>%即可。
上述命令的结果图如下: