管道解决了什么问题
管道把前面的值传给后面的函数调用:x |> f() 的意思就是 f(x)。这听起来只是换了个写法,但当你把三四个步骤串起来时,差别就出来了。没有管道时,多步转换只能层层嵌套,而嵌套调用是倒着读的:最先发生的操作反而位于最里层。
读 round(sqrt(x), 1) 时,你要从中间(x)开始,向外读(先 sqrt,再 round),还要记清楚末尾的 , 1) 属于哪个调用。嵌套三层已经够烦了;嵌套五层,每层还是带多个参数的数据框操作,那就真的很难看懂。另一种常见做法是把每个中间结果都存到 tmp1、tmp2、tmp3 里,结果工作区里堆满了没人需要的名字。
管道按照步骤实际发生的顺序重写这条链:拿到 x,然后开平方,然后取整。
原生管道 |>
从 R 4.1 开始,管道就内置在语言中,不需要任何包。|> 把前一个值作为下一个调用的第一个参数插入:
c(1, 4, 9, 16) |> sqrt() |> round(1) 和 round(sqrt(c(1, 4, 9, 16)), 1) 完全等价:R 在解析时就把前者改写成后者,所以运行时没有任何额外开销。但现在代码读起来像一份菜谱,从上到下:先是数据,然后按顺序列出每一步转换。这适用于任何函数,而不仅仅是数据工具,mtcars |> head(3) 从头到尾都是纯粹的基础 R。
注意,管道和那些第一个参数是“数据”的函数配合得非常好:head、sort、unique、summary 以及所有 dplyr 动词都是这样设计的,这也是为什么在 R 中写管道显得这么自然。
|> 的规则
原生管道是有意设计得很严格的。三条规则几乎涵盖了一切:
1. 下一步必须是一个带括号的调用。 x |> sqrt 是语法错误,你必须写成 x |> sqrt():
c(1, 4, 9) |> sqrt # Error: The pipe operator requires a function call as RHS
c(1, 4, 9) |> sqrt() # correct
2. 值会被放到第一个参数的位置。 你写的其他参数依次排在它后面,x |> round(1) 就是 round(x, 1)。
3. 要放到其他位置,就用 _ 占位符配合具名参数(R 4.2+)。经典例子是 lm:它的第一个参数是公式,第二个才是数据,所以把数据框通过管道传给它时需要写 data = _:
_ 只能出现一次,并且只能作为具名参数。如果这限制太多,可以把值通过管道传给一个匿名函数,它可以把值放在任何地方:
这个 lambda 用括号包起来,再用 () 调用,这样就满足了规则 1。如果你发现自己在一条管道里经常这么写,那这一步也许更适合写成一个具名函数。
magrittr 管道 %>%
在 R 语言本身还没有管道的时候,magrittr 包提供了一个,于是 %>% 成了 tidyverse 代码的标志,加载 dplyr 就会自动带上它。过去十年里写的 dplyr 教程和脚本,几乎都能看到它:
library(dplyr)
mtcars %>%
filter(mpg > 30) %>%
select(mpg, wt)
因为 %>% 来自一个包,这些示例在空白的 R 会话里是运行不了的:如果你忘了 library(dplyr)(或 library(magrittr)),得到的正是 Error: could not find function "%>%"。
在常见情况下,它的行为和 |> 完全相同:把前一个值传给下一个调用的第一个参数。但 magrittr 更宽松:
c(1, 4, 9) %>% sqrt # bare function name - no parentheses needed
mtcars %>% lm(mpg ~ wt, data = .) # . placeholder, usable anywhere
c(-2, 3) %>% { max(abs(.)) } # . can even appear several times, in nested calls
|> 和 %>%:真正重要的区别
对于“传给第一个参数”的管道(实际代码中绝大多数都是这样),两者可以互换。区别都在边缘情况上:
- 可用性:
|>属于基础 R 4.1+;%>%需要 magrittr(通常通过 dplyr 加载)。 - 括号:
|>要求写sqrt();%>%接受不带括号的sqrt。 - 占位符:
|>用_,只能用一次,并且只能作为具名参数(4.2+);%>%用.,可以放在任何位置、用任意多次,包括嵌套调用内部。 - 速度:
|>在解析时就被改写成嵌套调用,零开销。%>%是一次普通的函数调用,有一点点开销(实际使用中可以忽略)。
这些都不意味着 %>% 是错的:它久经考验,还稍微灵活一些。但它比 |> 多出来的功能都可以用 lambda 实现,而且基础管道不需要任何依赖。
应该用哪一个?
写新代码时:用 |>。它是语言的一部分,在所有 R 4.1+ 环境中都能用,不需要任何包;它的严格反而是优点:管道要么保持简单,要么被重构成具名函数。
但你必须能熟练读懂 %>%,因为 tidyverse 代码库、Stack Overflow 上的回答,以及 2014 年以来出版的大多数 R 书籍里到处都是它。已经形成 dplyr 风格的团队往往会为了一致性继续用 %>%,这也是合理的选择;最糟糕的做法是在同一个文件里混用两种运算符。不管你用哪一个,有一条约定是通用的:在长管道中,每一步单独占一行,管道符写在行尾,这样整个菜谱读起来就像一张步骤清单。
这里的取舍和 apply 函数族一样:管道适合线性的、一步接一步的转换。当计算需要分支,或者某个中间结果要用两次时,就把它赋给一个命名清楚的变量,而不是硬把所有东西塞进一条链里。
要点回顾
- 管道把前一个值传给下一个调用:
x |> f() |> g()就是g(f(x)),只是按发生的顺序来写。 |>属于基础 R(4.1+):必须带括号,作用于第一个参数,_占位符配合具名参数使用(4.2+)。%>%来自 magrittr/tidyverse:可以只写函数名,.占位符可以放在任何位置,但需要安装包。- 新代码优先用
|>;在其他地方遇到%>%也要能轻松读懂。 - 管道适合线性的菜谱;有分支的逻辑应该用命名变量。
下一篇:包,也就是 %>%、dplyr 以及 R 的其余 20,000 个扩展到底是从哪里来的。
常见问题
R 中的 %>% 是什么意思?
%>% 是 magrittr 管道:它把前面的值作为第一个参数传给后面的调用,所以 x %>% f() %>% g() 的意思就是 g(f(x))。它来自 magrittr 包,加载 dplyr 和 tidyverse 时会自动加载,并不属于基础 R。
在 R 中使用 |> 需要安装包吗?
不需要。|> 是原生管道,从 4.1 版起就内置在基础 R 中,在什么都没安装的 R 会话里也能用。需要安装包的是 %>%(magrittr,或任何重新导出它的包,比如 dplyr)。
R 中 |> 和 %>% 有什么区别?
两者都把前一个值传给下一个调用。|> 属于基础 R,必须写明括号(x |> sqrt()),占位符 _ 只能用在具名参数上。%>% 需要 magrittr,可以只写函数名(x %>% sqrt),它的占位符 . 可以放在任何位置,用任意多次。在最常见的“传给第一个参数”的情况下,两者行为完全相同。