¿Por qué es `[` mejor que `subconjunto`?

400

Cuando necesito filtrar un data.frame, es decir, extraer filas que cumplen ciertas condiciones, prefiero usar la subsetfunción:

subset(airquality, Month == 8 & Temp > 90)

En lugar de la [función:

airquality[airquality$Month == 8 & airquality$Temp > 90, ]

Hay dos razones principales para mi preferencia:

  1. Encuentro que el código se lee mejor, de izquierda a derecha. Incluso las personas que no saben nada sobre R podrían decir qué subsetestá haciendo la declaración anterior.

  2. Debido a que las columnas pueden denominarse variables en la selectexpresión, puedo guardar algunas pulsaciones de teclas. En mi ejemplo anterior, solo tuve que escribir airqualityuna vez con subset, pero tres veces con [.

Así que estaba viviendo feliz, utilizándolo en subsettodas partes porque es más corto y se lee mejor, incluso defendiendo su belleza para mis compañeros codificadores R. Pero ayer mi mundo se rompió. Mientras leo la subsetdocumentación, noto esta sección:

Advertencia

Esta es una función de conveniencia diseñada para su uso interactivo. Para programar es mejor usar las funciones de subconjunto estándar como [, y en particular la evaluación no estándar del subconjunto de argumentos puede tener consecuencias imprevistas.

¿Alguien podría ayudar a aclarar lo que quieren decir los autores?

Primero, ¿qué quieren decir con " para uso interactivo "? Sé lo que es una sesión interactiva, a diferencia de un script ejecutado en modo BATCH, pero no veo qué diferencia debería hacer.

Entonces, ¿podría explicar " la evaluación no estándar del subconjunto de argumentos " y por qué es peligroso, tal vez dar un ejemplo?

flodel
fuente
14
Es un poco menos (pero menos que un subconjunto) para usar,with(airquality, airquality[Month == 8 & Temp > 90, ])
Tyler Rinker
77
También puede echar un vistazo a Cirlces 8.2.31 y 8.2.32 de 'The R Inferno' burns-stat.com/pages/Tutor/R_inferno.pdf
Patrick Burns
99
Intente data.table en su lugar, la sintaxis predeterminada es como airquality [Month == 8 & Temp> 90,] - muy legible y mucho más rápido.
Stian Håklev
3
OKAY. entonces, si el subconjunto es malo de usar, ¿qué pasa con [vs. dplyr :: filter ()?
userJT
44
Para aquellos que se preguntan, dplyr::filtertiene el mismo problema. Es decir, si el entorno tiene una variable con ese nombre, lo usará en lugar de la variable en el marco de datos. ¡Hace para la depuración confusa!
Deleet

Respuestas:

241

Esta pregunta fue respondida bien en los comentarios de @James, señalando una excelente explicación de Hadley Wickham de los peligros de subset(y funciones similares) [aquí] . Ve a leerlo!

Es una lectura un tanto larga, por lo que puede ser útil registrar aquí el ejemplo que Hadley usa que aborda más directamente la pregunta de "¿qué puede salir mal?":

Hadley sugiere el siguiente ejemplo: supongamos que queremos subconjuntos y luego reordenar un marco de datos utilizando las siguientes funciones:

scramble <- function(x) x[sample(nrow(x)), ]

subscramble <- function(x, condition) {
  scramble(subset(x, condition))
}

subscramble(mtcars, cyl == 4)

Esto devuelve el error:

Error en eval (expr, envir, enclos): objeto 'cyl' no encontrado

porque R ya no "sabe" dónde encontrar el objeto llamado 'cyl'. También señala las cosas realmente extrañas que pueden suceder si por casualidad hay un objeto llamado 'cyl' en el entorno global:

cyl <- 4
subscramble(mtcars, cyl == 4)

cyl <- sample(10, 100, rep = T)
subscramble(mtcars, cyl == 4)

(Ejecútelos y vea por usted mismo, es bastante loco)

joran
fuente
2
¿Puedo tener algunas preguntas para novatos para aclaraciones? Cuando escribimos subset(mtcars, cyl == 4)(en el nivel superior), ¿dónde busca R cyl? Si examina el mtcarsobjeto al que se pasa subset(), ¿no debería poder encontrarlo cylincluso si scrambleestá dentro de otra función, ya mtcarsque todavía se le pasa? Si mi pregunta no tiene sentido, podría explicar con más detalle por qué R ya no puede encontrarla cyl. ¡Gracias!
Heisenberg
44
@Anh Inside subset.data.frame, lo que estamos tratando de evaluar en ese momento es justo condition. Eso no existe en mtcars. Por lo tanto, se subset.data.frameutiliza enclos = parent.frame()para garantizar que conditionse evalúe correctamente como cyl == 4. Pero luego volvimos a aparecer en el marco adjunto, y ahora, cuando R lo busca, cylya no está mirando hacia adentro mtcars. Si no lo usáramos enclos, algo así subset(mtcars,cyl == a)no funcionaría en absoluto.
joran
¿Alguien sabe por qué subset () no solo implementaría el método más rápido y seguro [,] detrás de escena?
Fanático número uno de Bjorks el
1
@MikePalmice lo hace. La última línea de subset.data.framees x[r, vars, drop = drop]. El problema es cómo pasar de lo no citado subsety los selectargumentos a algo que puede pasar válidamente [.data.frame.
joran
@joran lo entendió, gracias. ¿Cómo piensa si usar el filtro de dplyr en lugar de []?
Fanático número uno de Bjorks el
30

También [es más rápido:

require(microbenchmark)        
microbenchmark(subset(airquality, Month == 8 & Temp > 90),airquality[airquality$Month == 8 & airquality$Temp > 90,])
    Unit: microseconds
                                                           expr     min       lq   median       uq     max neval
                     subset(airquality, Month == 8 & Temp > 90) 301.994 312.1565 317.3600 349.4170 500.903   100
     airquality[airquality$Month == 8 & airquality$Temp > 90, ] 234.807 239.3125 244.2715 271.7885 340.058   100
bartektartanus
fuente
36
Si y no. Creo que la diferencia horaria que estás viendo se debe a dos cosas. 1) una sobrecarga pequeña (<100 microsegundos) y 2) a subsetdiferencia, [elimina las filas donde se evalúa el filtro NA. Haga esto y verá que ambos son tan rápidos cuando se comparan "bastante":x <- do.call(rbind, rep(list(airquality), 100)); microbenchmark(subset(x, Month == 8 & Temp > 90),{ i <- x$Month == 8 & x$Temp > 90; x[!is.na(i) & i ,] })
flodel