1.7 链接函数和管道操作符%>%

问题

如何以一种易读的方式调用一个函数并将其结果传递给另一个函数?

方法

使用管道操作符%>%。例如:

library(dplyr) # 管道操作符由dplyr包提供

morley # 来看一下morley数据集
#> Expt Run Speed
#> 001 1 1 850
#> 002 1 2 740
#> 003 1 3 900
#> ...<94 more rows>...
#> 098 5 18 800
#> 099 5 19 810
#> 100 5 20 870

morley %>% 
  filter(Expt == 1) %>% 
  summary()
#> Expt Run Speed
#> Min. :1 Min. : 1.00 Min. : 650
#> 1st Qu.:1 1st Qu.: 5.75 1st Qu.: 850
#> Median :1 Median :10.50 Median : 940
#> Mean :1 Mean :10.50 Mean : 909
#> 3rd Qu.:1 3rd Qu.:15.25 3rd Qu.: 980
#> Max. :1 Max. :20.00 Max. :1070

上述代码加载了morley数据集并将其传递给dplyr包中的filter()函数,仅仅保留了数据中Expt值为1的那些行。之后,该结果被传递给summary()函数,由该函数计算数据的某些统计结果。

如果不使用管道操作符,前面的代码可以写成:

summary(filter(morley, Expt == 1))

在上面这行代码中,函数的调用是自内向外依次处理的。从数学的观点来看,这种写法很好,但是并不利于代码的可读性,特别是有很多嵌套函数调用的情况下,反而会让人觉得困惑难懂。

讨论

这种带有%>%操作符的模式广泛应用于tidyverse的包中,因为它们包含了很多功能相对简单的函数。这些函数就像一块块积木,可以由用户自行组合、调用,来得到他们想要的结果。

我们可以用一个很简单的例子来清楚地展示管道操作符的机制。这里有两段等价的代码:

f(x)

# 等价于:
x %>% f()

管道操作符本质上是将操作符左侧的内容作为右侧函数调用的第一个参数。它可以用于链式地调用多个函数:

h(g(f(x)))

# 等价于:
x %>% 
  f() %>% 
  g() %>% 
  h()

在一个函数链中,函数调用的词法顺序和它们的计算顺序是一致的。

如果想要保存最终结果,可以在一开始使用<-操作符。例如,下面的代码将会用函数链的最终结果替代原来的x

x <- x %>% 
  f() %>% 
  g() %>% 
  h()

如果函数调用需要额外的参数,在使用管道操作符的情况下这些参数应移至操作符右侧。让我们回到前面第一个例子的代码中,这两种表达方式是等价的:

filter(morley, Expt == 1)

morley %>% filter(Expt == 1)

管道操作符实际上来自magrittr包,但dplyr包也导入了该管道操作符,因此在调用library(dplyr)时也可以使用管道操作符。

另见

关于在数据处理中使用%>%的更多例子见第15章。