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

84

La documentación dice

vapplyes similar a sapply, pero tiene un tipo de valor de retorno preespecificado, por lo que puede ser más seguro [...] de usar.

¿Podría explicar por qué en general es más seguro, quizás proporcionando ejemplos?


PD: Sé la respuesta y ya tiendo a evitarla sapply. Solo desearía que hubiera una buena respuesta aquí en SO para poder señalarla a mis compañeros de trabajo. Por favor, no responda "lea el manual".

Flodel
fuente
1
Es más predecible, lo que hace que el código sea menos ambiguo y más robusto. Particularmente en proyectos más grandes, digamos un paquete grande, esto es relevante.
Paul Hiemstra
1
Los ejemplos del manual de vapply para FUN.VALUE son muy complejos e intimidantes para los usuarios de sapply.
jsta

Respuestas:

73

Como ya se ha señalado, vapplyhace dos cosas:

  • Mejora leve de la velocidad
  • Mejora la coherencia al proporcionar comprobaciones de tipo de devolución limitadas.

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 robusto. Esta verificación del valor de retorno se puede hacer por separado usando sapplyseguido de stopifnotpara asegurarse de que los valores de retorno sean consistentes con lo que esperaba, pero vapplyes un poco más fácil (si es más limitado, ya que el código de verificación de errores personalizado podría verificar valores dentro de los límites, etc. ).

Este es un ejemplo de vapplycómo garantizar que su resultado sea el esperado. Esto es paralelo a algo en lo que estaba trabajando mientras raspaba PDF, donde findDusaría unpara hacer coincidir un patrón en los datos de texto sin procesar (por ejemplo, tendría una lista splitpor entidad y una expresión regular para hacer coincidir las direcciones dentro de cada entidad. Ocasionalmente, el PDF se había convertido fuera de orden y habría dos direcciones para un entidad, que causó 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 programador es cambiar su forma de pensar de "los errores son molestos" a "los errores son mi amigo".

Entradas de longitud cero
Un punto relacionado es que si la longitud de entrada es cero, sapplysiempre 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 la garantía de tener un tipo particular de salida, por lo que no necesita escribir comprobaciones adicionales para entradas de longitud cero.

Benchmarks

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

Ari B. Friedman
fuente
15

Las pulsaciones de teclas adicionales involucradas vapplypodrían ahorrarle tiempo al depurar resultados confusos más adelante. Si la función a la que está llamando puede devolver diferentes tipos de datos, vapplyciertamente debería usarse.

Un ejemplo que me viene a la mente estaría sqlQueryen el RODBCpaquete. Si hay un error al ejecutar una consulta, esta función devuelve un charactervector con el mensaje. Entonces, por ejemplo, digamos que está tratando de iterar sobre un vector de nombres de tablas tnamesy seleccione 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 las tablas son válidos, esto resultaría en un numericvector. Pero si uno de los nombres de las tablas cambia en la base de datos y la consulta falla, los resultados se convertirán en modo character. Sin embargo, usar vapplycon FUN.VALUE=numeric(1)detendrá el error aquí y evitará que aparezca en algún lugar de la línea, o peor aún, no en absoluto.

Matthew Plourde
fuente
13

Si siempre quiere que su resultado sea algo en particular ... por ejemplo, un vector lógico. vapplyse asegura de que esto suceda, pero sapplyno necesariamente.

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

is.logical(a)
is.logical(b)
usuario1317221_G
fuente
4
Creo que lo más obvio es logical(1)en este caso, ya que FALSE parece que establece una opción en "OFF" en lugar de especificar un tipo
oveja voladora