R函数中的Magrittr管道
Magrittr pipe in R functions
从 (1) 速度和 (2) 有效调试能力的角度来看,是否存在在 R 函数内部使用 magrittr 管道不利的情况?
在函数内部使用管道有优点也有缺点。最大的优点是当你阅读代码时更容易看到函数内部发生了什么。最大的缺点是错误消息变得更难解释,并且管道破坏了 R 的一些评估规则。
这是一个例子。假设我们想对 mtcars
数据集进行无意义的转换。这是我们如何使用管道来做到这一点...
library(tidyverse)
tidy_function <- function() {
mtcars %>%
group_by(cyl) %>%
summarise(disp = sum(disp)) %>%
mutate(disp = (disp ^ 4) / 10000000000)
}
你可以清楚地看到每个阶段发生了什么,即使它没有做任何有用的事情。现在让我们看看使用 Dagwood Sandwich 方法的时间码...
base_function <- function() {
mutate(summarise(group_by(mtcars, cyl), disp = sum(disp)), disp = (disp^5) / 10000000000)
}
更难阅读,即使它给我们相同的结果...
all.equal(tidy_function(), base_function())
# [1] TRUE
避免使用管道或 Dagwood 三明治的最常见方法是将每个步骤的结果保存到中间变量...
intermediate_function <- function() {
x <- mtcars
x <- group_by(x, cyl)
x <- summarise(x, disp = sum(disp))
mutate(x, disp = (disp^5) / 10000000000)
}
比上一个函数更具可读性,R 会在出现错误时为您提供更详细的信息。此外,它遵守传统的评估规则。同样,它给出了与其他两个函数相同的结果...
all.equal(tidy_function(), intermediate_function())
# [1] TRUE
你特意问的是速度,那我们就把这三个函数运行各做1000次比较一下...
library(microbenchmark)
timing <-
microbenchmark(tidy_function(),
intermediate_function(),
base_function(),
times = 1000L)
timing
#Unit: milliseconds
#expr min lq mean median uq max neval cld
#tidy_function() 3.809009 4.403243 5.531429 4.800918 5.860111 23.37589 1000 a
#intermediate_function() 3.560666 4.106216 5.154006 4.519938 5.538834 21.43292 1000 a
#base_function() 3.610992 4.136850 5.519869 4.583573 5.696737 203.66175 1000 a
即使在这个简单的示例中,管道也比其他两个选项慢一点。
结论
如果管道是您编写代码的最舒适方式,请随意在您的函数中使用它。如果您开始 运行 遇到问题,或者如果您需要您的代码尽可能快,那么请切换到不同的范例。
从 (1) 速度和 (2) 有效调试能力的角度来看,是否存在在 R 函数内部使用 magrittr 管道不利的情况?
在函数内部使用管道有优点也有缺点。最大的优点是当你阅读代码时更容易看到函数内部发生了什么。最大的缺点是错误消息变得更难解释,并且管道破坏了 R 的一些评估规则。
这是一个例子。假设我们想对 mtcars
数据集进行无意义的转换。这是我们如何使用管道来做到这一点...
library(tidyverse)
tidy_function <- function() {
mtcars %>%
group_by(cyl) %>%
summarise(disp = sum(disp)) %>%
mutate(disp = (disp ^ 4) / 10000000000)
}
你可以清楚地看到每个阶段发生了什么,即使它没有做任何有用的事情。现在让我们看看使用 Dagwood Sandwich 方法的时间码...
base_function <- function() {
mutate(summarise(group_by(mtcars, cyl), disp = sum(disp)), disp = (disp^5) / 10000000000)
}
更难阅读,即使它给我们相同的结果...
all.equal(tidy_function(), base_function())
# [1] TRUE
避免使用管道或 Dagwood 三明治的最常见方法是将每个步骤的结果保存到中间变量...
intermediate_function <- function() {
x <- mtcars
x <- group_by(x, cyl)
x <- summarise(x, disp = sum(disp))
mutate(x, disp = (disp^5) / 10000000000)
}
比上一个函数更具可读性,R 会在出现错误时为您提供更详细的信息。此外,它遵守传统的评估规则。同样,它给出了与其他两个函数相同的结果...
all.equal(tidy_function(), intermediate_function())
# [1] TRUE
你特意问的是速度,那我们就把这三个函数运行各做1000次比较一下...
library(microbenchmark)
timing <-
microbenchmark(tidy_function(),
intermediate_function(),
base_function(),
times = 1000L)
timing
#Unit: milliseconds
#expr min lq mean median uq max neval cld
#tidy_function() 3.809009 4.403243 5.531429 4.800918 5.860111 23.37589 1000 a
#intermediate_function() 3.560666 4.106216 5.154006 4.519938 5.538834 21.43292 1000 a
#base_function() 3.610992 4.136850 5.519869 4.583573 5.696737 203.66175 1000 a
即使在这个简单的示例中,管道也比其他两个选项慢一点。
结论
如果管道是您编写代码的最舒适方式,请随意在您的函数中使用它。如果您开始 运行 遇到问题,或者如果您需要您的代码尽可能快,那么请切换到不同的范例。