为啥 `vapply` 比 `sapply` 更安全?

Posted

技术标签:

【中文标题】为啥 `vapply` 比 `sapply` 更安全?【英文标题】:Why is `vapply` safer than `sapply`?为什么 `vapply` 比 `sapply` 更安全? 【发布时间】:2012-09-02 14:37:03 【问题描述】:

文档说

vapply 类似于sapply,但具有预先指定的返回值类型,因此使用起来会更安全[...]。

您能否详细说明为什么它通常更安全,也许可以提供示例?


P.S.:我知道答案并且我已经倾向于避免sapply。我只是希望这里有一个很好的答案,所以我可以向我的同事指出它。请不要“阅读手册”的答案。

【问题讨论】:

它更可预测,使代码不那么模棱两可,更健壮。特别是在较大的项目中,比如一个大包,这是相关的。 FUN.VALUE 的 vapply 手册示例对于 sapply 用户来说非常复杂且令人生畏。 【参考方案1】:

如前所述,vapply 做了两件事:

速度略有提升 通过提供有限的返回类型检查来提高一致性。

第二点是更大的优势,因为它有助于在错误发生之前捕获错误并导致更健壮的代码。这种返回值检查可以通过使用sapply 后跟stopifnot 来单独完成,以确保返回值与您的预期一致,但vapply 更容易一些(如果更有限,因为自定义错误检查代码可以检查范围内的值等)。

以下是vapply 的示例,可确保您的结果符合预期。这与我在 PDF 抓取时所做的事情相似,其中 findD 将使用 regex 来匹配原始文本数据中的模式(例如,我将有一个按实体为 split 的列表,以及一个正则表达式匹配每个实体内的地址。有时 PDF 被乱序转换,一个实体会有两个地址,这会导致错误)。

> input1 <- list( letters[1:5], letters[3:12], letters[c(5,2,4,7,1)] )
> input2 <- list( letters[1:5], letters[3:12], letters[c(2,5,4,7,15,4)] )
> findD <- function(x) x[x=="d"]
> sapply(input1, findD )
[1] "d" "d" "d"
> sapply(input2, findD )
[[1]]
[1] "d"

[[2]]
[1] "d"

[[3]]
[1] "d" "d"

> vapply(input1, findD, "" )
[1] "d" "d" "d"
> vapply(input2, findD, "" )
Error in vapply(input2, findD, "") : values must be length 1,
 but FUN(X[[3]]) result is length 2

因为两个input2的第三个元素有两个d,所以vapply会产生错误。但是 sapply 将输出的类从字符向量更改为列表,这可能会破坏下游代码。

正如我告诉我的学生的那样,成为程序员的一部分就是将您的思维模式从“错误令人讨厌”转变为“错误是我的朋友”。

零长度输入 相关的一点是,如果输入长度为零,sapply 将始终返回一个空列表,而不管输入类型如何。比较:

sapply(1:5, identity)
## [1] 1 2 3 4 5
sapply(integer(), identity)
## list()    
vapply(1:5, identity, integer(1))
## [1] 1 2 3 4 5
vapply(integer(), identity, integer(1))
## integer(0)

使用vapply,可以保证您拥有特定类型的输出,因此您无需为零长度输入编写额外的检查。

基准测试

vapply 可能会快一点,因为它已经知道它应该期待什么格式的结果。

input1.long <- rep(input1,10000)

library(microbenchmark)
m <- microbenchmark(
  sapply(input1.long, findD ),
  vapply(input1.long, findD, "" )
)
library(ggplot2)
library(taRifx) # autoplot.microbenchmark is moving to the microbenchmark package in the next release so this should be unnecessary soon
autoplot(m)

【讨论】:

【参考方案2】:

vapply 涉及的额外击键可以节省您稍后调试令人困惑的结果的时间。如果你调用的函数可以返回不同的数据类型,vapply当然应该使用。

想到的一个例子是RODBC 包中的sqlQuery。如果执行查询时出错,此函数将返回带有消息的character 向量。因此,例如,假设您尝试遍历表名向量tnames,并从每个表中的数字列“NumCol”中选择最大值:

sapply(tnames, 
   function(tname) sqlQuery(cnxn, paste("SELECT MAX(NumCol) FROM", tname))[[1]])

如果所有表名都有效,这将产生一个numeric 向量。但是如果其中一个表名在数据库中发生更改并且查询失败,则结果将被强制转换为模式character。但是,将vapplyFUN.VALUE=numeric(1) 一起使用会在此处停止错误并防止其在某个地方弹出——或者更糟的是,根本不会。

【讨论】:

【参考方案3】:

如果你总是希望你的结果是特别的……例如一个逻辑向量。 vapply 确保发生这种情况,但 sapply 不一定这样做。

a<-vapply(NULL, is.factor, FUN.VALUE=logical(1))
b<-sapply(NULL, is.factor)

is.logical(a)
is.logical(b)

【讨论】:

我认为在这种情况下最明显的做法是logical(1),因为 FALSE 看起来将选项设置为“OFF”而不是指定类型

以上是关于为啥 `vapply` 比 `sapply` 更安全?的主要内容,如果未能解决你的问题,请参考以下文章

R语言apply函数详解及实战(lapply, sapply, vapply, tapply,mapply)

Snowfall 的 sfApply 和 sfClusterApplyLB 比正常循环或 sapply 慢 [重复]

为啥在 C++ 中更喜欢 char* 而不是字符串?

R中的高效批量处理函数(lapply sapply apply tapply mapply)(转)

R中的高效批量处理函数(lapply sapply apply tapply mapply)(转)

R语言的基本操作--读取和写入txt,sapply&lapply