¿Por qué `vapply` es más seguro que` sapply`?

La documentación dice

vapply es similar a sapply , pero tiene un tipo de valor de retorno previamente especificado, por lo que puede ser más seguro […] de usar.

¿Podrían explicar por qué es generalmente más seguro, tal vez dando ejemplos?


PD: Conozco la respuesta y ya tiendo a evitar sapply . Solo desearía que hubiera una buena respuesta aquí en SO, así puedo señalarles a mis compañeros de trabajo. Por favor, no hay una respuesta de “leer el manual”.

Como ya se ha notado, vapply hace dos cosas:

  • Leve mejora de velocidad
  • Mejora la coherencia al proporcionar controles de tipo de devolución limitados.

El segundo punto es la mayor ventaja, ya que ayuda a detectar errores antes de que sucedan y conduce a un código más sólido. Esta comprobación del valor de retorno podría realizarse por separado utilizando sapply seguido de stopifnot para asegurarse de que los valores devueltos sean consistentes con lo que esperaba, pero vapply es un poco más fácil (si es más limitado, ya que el código de comprobación de errores podría verificar valores dentro de límites , etc.)

Aquí hay un ejemplo de vapply garantizar que su resultado sea el esperado. Esto es paralelo a algo en lo que estaba trabajando mientras findD PDF, donde findD usaría una expresión regular para hacer coincidir un patrón en datos de texto sin procesar (por ejemplo, tendría una lista split por entidad y una expresión regular para hacer coincidir direcciones dentro de cada entidad. Ocasionalmente, el PDF se había convertido fuera de servicio y habría dos direcciones para una entidad, lo que causaba maldad).

 > 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 

Como les digo a mis alumnos, parte de convertirse en progtwigdor es cambiar su mentalidad de “los errores son molestos” a “los errores son mis amigos”.

Entradas de longitud cero
Un punto relacionado es que si la longitud de entrada es cero, sapply siempre devolverá una lista vacía, independientemente del tipo de entrada. Comparar:

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

Con vapply , tiene garantizado un tipo particular de salida, por lo que no necesita escribir comprobaciones adicionales para entradas de longitud cero.

Puntos de referencia

vapply puede ser un poco más rápido porque ya sabe en qué formato debería esperar los resultados.

 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) 

autoplot

Las vapply teclas adicionales implicadas en vapply podrían ahorrarle tiempo para vapply errores de resultados confusos más adelante. Si la función a la que llama puede devolver diferentes tipos de datos, ciertamente se debe usar vapply .

Un ejemplo que viene a la mente sería sqlQuery en el paquete RODBC . Si hay un error al ejecutar una consulta, esta función devuelve un vector de character con el mensaje. Por ejemplo, supongamos que intenta iterar sobre un vector de nombres de tablas tnames y selecciona el valor máximo de la columna numérica ‘NumCol’ en cada tabla con:

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

Si todos los nombres de tabla son válidos, esto daría como resultado un vector numeric . Pero si uno de los nombres de la tabla cambia en la base de datos y la consulta falla, los resultados se convertirán en character modo. Sin embargo, usar vapply con FUN.VALUE=numeric(1) detendrá el error aquí y evitará que aparezca en algún lugar de la línea, o peor, en absoluto.

Si siempre quiere que su resultado sea algo en particular … por ejemplo, un vector lógico. vapply se asegura de que esto suceda, pero sapply no necesariamente lo hace.

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