vapply
类似于sapply
,但它有一个预先指定的返回值类型,因此使用起来可以更安全[...]。
您能详细说明一下为什么通常更安全,可能提供例子吗?
P.S.: 我知道答案,我已经倾向于避免使用 sapply
。我只是希望在SO上有一个好的答案,这样我就可以引导我的同事了。请不要回答“阅读手册”。
vapply
类似于sapply
,但它有一个预先指定的返回值类型,因此使用起来可以更安全[...]。
您能详细说明一下为什么通常更安全,可能提供例子吗?
P.S.: 我知道答案,我已经倾向于避免使用 sapply
。我只是希望在SO上有一个好的答案,这样我就可以引导我的同事了。请不要回答“阅读手册”。
正如已经注意到的那样,vapply
做了两件事:
第二点是更大的优势,因为它有助于在错误发生之前捕获错误并导致更健壮的代码。这个返回值检查可以通过使用 sapply
然后使用 stopifnot
单独完成,以确保返回值与您预期的相一致,但是 vapply
更容易(如果更有限,因为自定义错误检查代码可以检查边界内的值等)。
这里是一个使用 vapply
确保结果符合预期的示例。这类似于我刚刚在 PDF 抓取时处理的内容,其中 findD
将使用regex 在原始文本数据中匹配模式(例如,我会有一个被实体分割的列表,并且一个正则表达式用于在每个实体内匹配地址。偶尔,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)
vapply
相比其他函数会多几个按键,但这可以帮你在后期调试时节省大量时间。如果你调用的函数可能返回不同类型的数据,一定要使用vapply
。
一个例子是RODBC
包中的sqlQuery
函数。如果执行查询时出现错误,该函数将返回一个包含消息的character
向量。例如,假设你想迭代一个表名向量tnames
并从每个表中选择数值列“NumCol”的最大值:
sapply(tnames,
function(tname) sqlQuery(cnxn, paste("SELECT MAX(NumCol) FROM", tname))[[1]])
如果所有的表名都是有效的,这将导致一个 "numeric" 向量。但是,如果其中一个表名在数据库中发生更改并且查询失败,结果将被强制转换为 "character" 模式。然而,使用带有 "FUN.VALUE=numeric(1)" 的 vapply 将在此处停止错误,并防止它在后续行中弹出 - 或更糟地,根本不弹出。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 看起来更像是将选项设置为“关闭”而不是指定类型。 - flying sheep