Limpiar los niveles de factor (colapsando múltiples niveles / tags)

¿Cuál es la forma más efectiva (es decir, eficiente / apropiada) de limpiar un factor que contiene múltiples niveles que deben colapsarse? Es decir, cómo combinar dos o más niveles de factores en uno.

Aquí hay un ejemplo donde los dos niveles “Sí” e “Y” deben colapsarse a “Sí”, y “No” y “N” colapsados ​​a “No”:

## Given: x <- c("Y", "Y", "Yes", "N", "No", "H") # The 'H' should be treated as NA ## expectedOutput [1] Yes Yes Yes No No  Levels: Yes No # <~~ NOTICE ONLY **TWO** LEVELS 

Una opción es, por supuesto, limpiar las cuerdas antes de la mano usando sub y amigos.

Otro método es permitir tags duplicadas y luego colocarlas

 ## Duplicate levels ==> "Warning: deprecated" xf <- factor(x, levels=c("Y", "Yes", "No", "N"), labels=c("Yes", "Yes", "No", "No")) ## the above line can be wrapped in either of the next two lines factor(xf) droplevels(xf) 

Sin embargo, ¿hay una manera más efectiva ?


Si bien sé que los argumentos de levels y labels deben ser vectores, experimenté con listas y listas de nombres y vectores nombrados para ver qué sucede. De más está decir que ninguno de los siguientes me acercó más a mi objective.

  factor(x, levels=list(c("Yes", "Y"), c("No", "N")), labels=c("Yes", "No")) factor(x, levels=c("Yes", "No"), labels=list(c("Yes", "Y"), c("No", "N"))) factor(x, levels=c("Y", "Yes", "No", "N"), labels=c(Y="Yes", Yes="Yes", No="No", N="No")) factor(x, levels=c("Y", "Yes", "No", "N"), labels=c(Yes="Y", Yes="Yes", No="No", No="N")) factor(x, levels=c("Yes", "No"), labels=c(Y="Yes", Yes="Yes", No="No", N="No")) 

Utilice la función de levels y páselo por una lista con nombre, siendo los nombres los nombres deseados de los niveles y los elementos los nombres actuales que deben renombrarse.

 x < - c("Y", "Y", "Yes", "N", "No", "H") x <- factor(x) levels(x) <- list(Yes=c("Y", "Yes"), No=c("N", "No")) x ## [1] Yes Yes Yes No No  ## Levels: Yes No 

Como se menciona en la documentación de levels ; también vea los ejemplos allí.

valor: para el método ‘factor’, un vector de cadenas de caracteres con una longitud de al menos el número de niveles de ‘x’, o una lista con nombre que especifica cómo cambiar el nombre de los niveles.

Esto también se puede hacer en una línea, como lo hace Marek aquí: https://stackoverflow.com/a/10432263/210673 ; los levels< - hechicería se explican aquí https://stackoverflow.com/a/10491881/210673 .

 > `levels< -`(factor(x), list(Yes=c("Y", "Yes"), No=c("N", "No"))) [1] Yes Yes Yes No No  Levels: Yes No 

Como la pregunta se titula Limpieza de los niveles de los factores (colapso de múltiples niveles / tags) , el paquete de los forcats debe mencionarse aquí, en aras de la exhaustividad. forcats aparecieron en CRAN en agosto de 2016.

Hay varias funciones de conveniencia disponibles para limpiar niveles de factor:

 x < - c("Y", "Y", "Yes", "N", "No", "H") library(forcats) 

Contraer los niveles de factor en grupos definidos manualmente

 fct_collapse(x, Yes = c("Y", "Yes"), No = c("N", "No"), NULL = "H") #[1] Yes Yes Yes No No  #Levels: No Yes 

Cambiar los niveles de factor a mano

 fct_recode(x, Yes = "Y", Yes = "Yes", No = "N", No = "No", NULL = "H") #[1] Yes Yes Yes No No  #Levels: No Yes 

Reetiquetar automáticamente los niveles de factor, colapsar según sea necesario

 fun < - function(z) { z[z == "Y"] <- "Yes" z[z == "N"] <- "No" z[!(z %in% c("Yes", "No"))] <- NA z } fct_relabel(factor(x), fun) #[1] Yes Yes Yes No No  #Levels: No Yes 

Tenga en cuenta que fct_relabel() funciona con niveles de factor, por lo que espera un factor como primer argumento. Las otras dos funciones, fct_collapse() y fct_recode() , también aceptan un vector de caracteres que es una característica no documentada.

Reordenar los niveles de factor por primera aparición

El resultado esperado dado por el OP es

 [1] Yes Yes Yes No No  Levels: Yes No 

Aquí los niveles se ordenan tal como aparecen en x que es diferente del valor predeterminado ( ?factor ?: Los niveles de un factor se clasifican por defecto ).

Para estar en línea con el resultado esperado, esto se puede lograr utilizando fct_inorder() antes de colapsar los niveles:

 fct_collapse(fct_inorder(x), Yes = c("Y", "Yes"), No = c("N", "No"), NULL = "H") fct_recode(fct_inorder(x), Yes = "Y", Yes = "Yes", No = "N", No = "No", NULL = "H") 

Ambos devuelven el resultado esperado con niveles en el mismo orden, ahora.

Tal vez un vector con nombre como clave pueda ser de utilidad:

 > factor(unname(c(Y = "Yes", Yes = "Yes", N = "No", No = "No", H = NA)[x])) [1] Yes Yes Yes No No  Levels: No Yes 

Esto se ve muy similar a tu último bash … pero este funciona 🙂

Otra forma es hacer una tabla que contenga el mapeo:

 # stacking the list from Aaron's answer fmap = stack(list(Yes = c("Y", "Yes"), No = c("N", "No"))) fmap$ind[ match(x, fmap$values) ] # [1] Yes Yes Yes No No  # Levels: No Yes # or... library(data.table) setDT(fmap)[x, on=.(values), ind ] # [1] Yes Yes Yes No No  # Levels: No Yes 

Prefiero esta forma, ya que deja un objeto fácilmente inspeccionable que resume el mapa; y el código data.table se parece a cualquier otra unión en esa syntax.


Por supuesto, si no desea que un objeto como fmap resum el cambio, puede ser un “trazador de líneas”:

 library(data.table) setDT(stack(list(Yes = c("Y", "Yes"), No = c("N", "No"))))[x, on=.(values), ind ] # [1] Yes Yes Yes No No  # Levels: No Yes 

No sé cuál es tu caso de uso real, pero ¿qué strtrim tendría strtrim aquí?

 factor( strtrim( x , 1 ) , levels = c("Y" , "N" ) , labels = c("Yes" , "No" ) ) #[1] Yes Yes Yes No No  #Levels: Yes No 

Similar al enfoque de @ Aaron, pero un poco más simple sería:

 x < - c("Y", "Y", "Yes", "N", "No", "H") x <- factor(x) # levels(x) # [1] "H" "N" "No" "Y" "Yes" # NB: the offending levels are 1, 2, & 4 levels(x)[c(1,2,4)] <- c(NA, "No", "Yes") x # [1] Yes Yes Yes No No  # Levels: No Yes 

Puede usar la función siguiente para combinar / contraer múltiples factores:

 combofactor < - function(pattern_vector, replacement_vector, data) { levels <- levels(data) for (i in 1:length(pattern_vector)) levels[which(pattern_vector[i] == levels)] <- replacement_vector[i] levels(data) <- levels data } 

Ejemplo:

Inicializar x

 x < - factor(c(rep("Y",20),rep("N",20),rep("y",20), rep("yes",20),rep("Yes",20),rep("No",20))) 

Verifica la estructura

 str(x) # Factor w/ 6 levels "N","No","y","Y",..: 4 4 4 4 4 4 4 4 4 4 ... 

Usa la función:

 x_new < - combofactor(c("Y","N","y","yes"),c("Yes","No","Yes","Yes"),x) 

Vuelva a verificar la estructura:

 str(x_new) # Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2 2 2 ...