R函数中的Magrittr管道

5

在使用R函数时,从速度和有效调试的角度考虑,是否存在不使用magrittr管道的情况?


1
你是在问执行速度还是开发时间的速度? - Dason
1
无论如何,总有一些人会问你是否有任何情况下使用magrittr管道在速度或调试能力方面具有优势... - Dason
回答这个问题的唯一方法是设置几个示例并对它们进行基准测试。你试过这样做吗? - Hack-R
我不使用管道。因此,在函数内部使用管道意味着我无法调试它。我无法判断这是否对您构成了不利。我相信R核心成员曾经在R-devel列表的电子邮件中称带有magrittr管道的代码为“胡言乱语”或类似的话。 - Roland
1个回答

7

在函数中使用管道有优点和缺点。最大的优点是,在阅读代码时,更容易看出函数内部发生了什么。最大的缺点是,错误信息变得更难以解释,而且管道打破了一些 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

即使在这个微不足道的例子中,管道比其他两个选项慢一点。

结论

如果使用管道是您编写代码最舒适的方式,请随意在您的函数中使用它。 如果您开始遇到问题或需要尽可能快地运行代码,则切换到不同的范例。


2
你的三明治只需要加入一些换行和缩进,就可以让它更易读了。 - Roland
1
有人知道哪些包使用管道运算符吗?除了脚本示例外,我还没有看到它被使用过。 - user2506086
对我来说,管道似乎更接近于编写执行某些操作的指令,因此我想在需要使代码非常易读的情况下使用它。另一方面,我发现调试很困难。如果您尝试调试管道,则必须通过“%>%”函数并找出要进入的哪一行来进行调试。似乎如果我为其他人编写软件包,无论我是否使用管道都将是使代码易于阅读和调试的平衡。 - user2506086
你的问题是关于在函数中使用管道符,而不一定是包。我在我的函数中使用管道符,甚至在我的包中也使用它,但只有在我不预期会出现任何错误的情况下才使用。如果你想看看包如何使用管道符,可以在Github上搜索:https://github.com/search?utf8=%E2%9C%93&q=%22%40importFrom+magrittr%22&type=Code - Andrew Brēza
1
管道函数可以将临时变量添加到工作区,这是我非常喜欢的功能,但在生产中的函数中,这并不是一个标准。我理解管道函数在某些情况下可能比中间值方法更快,因为它们大多数时间使用惰性求值。 - moodymudskipper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接