`levels <-` (¿Qué hechicería es esto?

En una respuesta a otra pregunta, @Marek publicó la siguiente solución: https://stackoverflow.com/a/10432263/636656

dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, -20L), class = "data.frame") `levels<-`( factor(dat$product), list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) ) 

Que produce como salida:

  [1] Generic Generic Bayer Bayer Advil Tylenol Generic Advil Bayer Generic Advil Generic Advil Tylenol [15] Generic Bayer Generic Advil Bayer Bayer 

Esta es solo la impresión de un vector, por lo que para almacenarlo puede hacer aún más confuso:

 res <- `levels<-`( factor(dat$product), list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) ) 

Claramente, este es un tipo de llamada a la función de niveles, pero no tengo idea de lo que se está haciendo aquí. ¿Cuál es el término para este tipo de hechicería, y cómo puedo boost mi habilidad mágica en este dominio?

Las respuestas aquí son buenas, pero les falta un punto importante. Déjame intentarlo y describirlo.

R es un lenguaje funcional y no le gusta mutar sus objetos. Pero sí permite declaraciones de asignación, usando funciones de reemplazo:

 levels(x) <- y 

es equivalente a

 x <- `levels<-`(x, y) 

El truco es que esta reescritura se hace por <- ; no está hecho por levels<- . levels<- es solo una función regular que toma una entrada y da salida; no muta nada

Una consecuencia de eso es que, de acuerdo con la regla anterior, <- debe ser recursiva:

 levels(factor(x)) <- y 

es

 factor(x) <- `levels<-`(factor(x), y) 

es

 x <- `factor<-`(x, `levels<-`(factor(x), y)) 

Es hermoso que esta transformación puramente funcional (hasta el final, donde ocurre la asignación) sea equivalente a lo que sería una tarea en un lenguaje imperativo. Si mal no recuerdo, este constructo en lenguajes funcionales se llama lente.

Pero luego, una vez que hayas definido las funciones de reemplazo como los levels<- , obtienes otra inesperada ganancia inesperada: no solo tienes la capacidad de hacer asignaciones, tienes una función práctica que toma en cuenta un factor, y da otro factor con niveles diferentes. ¡Realmente no hay nada de "asignación" al respecto!

Entonces, el código que está describiendo está haciendo uso de esta otra interpretación de levels<- . Admito que los levels<- nombre levels<- son un poco confusos porque sugieren una tarea, pero esto no es lo que está sucediendo. El código simplemente está configurando una especie de canalización:

  • Comience con dat$product

  • Convertirlo a un factor

  • Cambiar los niveles

  • Almacene eso en res

Personalmente, creo que esa línea de código es hermosa;)

La razón de esa “magia” es que el formulario de “asignación” debe tener una variable real para trabajar. Y el factor(dat$product) no se asignó a nada.

 # This works since its done in several steps x <- factor(dat$product) levels(x) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) x # This doesn't work although it's the "same" thing: levels(factor(dat$product)) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) # Error: could not find function "factor<-" # and this is the magic work-around that does work `levels<-`( factor(dat$product), list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) ) 

Sin brujería, así es como se definen las funciones (sub) asignación. levels<- es un poco diferente porque es una primitiva para (sub) asignar los atributos de un factor, no los elementos en sí mismos. Hay muchos ejemplos de este tipo de funciones:

 `<-` # assignment `[<-` # sub-assignment `[<-.data.frame` # sub-assignment data.frame method `dimnames<-` # change dimname attribute `attributes<-` # change any attributes 

Otros operadores binarios se pueden llamar así también:

 `+`(1,2) # 3 `-`(1,2) # -1 `*`(1,2) # 2 `/`(1,2) # 0.5 

Ahora que lo sabes, algo como esto realmente te dejará boquiabierto:

 Data <- data.frame(x=1:10, y=10:1) names(Data)[1] <- "HI" # How does that work?!? Magic! ;-) 

Para el código de usuario, ¿me pregunto por qué se usan tales manipulaciones lingüísticas? Usted pregunta qué magia es esta y otros han señalado que está llamando a la función de reemplazo que tiene los levels<- nombre levels<- . Para la mayoría de las personas esto es mágico y realmente el uso previsto es levels(foo) <- bar .

El caso de uso que muestra es diferente porque el product no existe en el entorno global, por lo que solo existe en el entorno local de la llamada a levels<- tanto, el cambio que desea no persiste - no hubo reasignación de dat .

En estas circunstancias, within() es la función ideal para usar. Naturalmente, desearía escribir

 levels(product) <- bar 

en R pero, por supuesto, el product no existe como un objeto. within() soluciona esto porque configura el entorno sobre el que desea ejecutar su código R y evalúa su expresión dentro de ese entorno. Al asignar el objeto de retorno desde la llamada a within() , se logra el dataframe modificado correctamente.

Aquí hay un ejemplo (no es necesario crear un datX nuevo, simplemente lo hago para que los pasos intermedios permanezcan al final)

 ## one or t'other #dat2 <- transform(dat, product = factor(product)) dat2 <- within(dat, product <- factor(product)) ## then dat3 <- within(dat2, levels(product) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)) 

Lo que da:

 > head(dat3) product 1 Generic 2 Generic 3 Bayer 4 Bayer 5 Advil 6 Tylenol > str(dat3) 'data.frame': 20 obs. of 1 variable: $ product: Factor w/ 4 levels "Tylenol","Advil",..: 4 4 3 3 2 1 4 2 3 4 ... 

Me cuesta ver cómo construcciones como la que muestra son útiles en la mayoría de los casos: si quiere cambiar los datos, cambie los datos, no cree otra copia y cambie eso (que es todos los levels<- llamada es haciendo después de todo).

    Intereting Posts