Niveles de factor de caída en un dataframe subconjunto

Tengo un dataframe que contiene un factor. Cuando creo un subconjunto de este dataframe usando subset() u otra función de indexación, se crea un nuevo dataframe. Sin embargo, la variable de factor conserva todos sus niveles originales, incluso cuando no existen en el nuevo dataframe.

Esto crea dolores de cabeza al hacer trazados con facetas o al usar funciones que dependen de los niveles de los factores.

¿Cuál es la forma más sucinta de eliminar niveles de un factor en mi nuevo dataframe?

Aquí está mi ejemplo:

 df <- data.frame(letters=letters[1:5], numbers=seq(1:5)) levels(df$letters) ## [1] "a" "b" "c" "d" "e" subdf <- subset(df, numbers <= 3) ## letters numbers ## 1 a 1 ## 2 b 2 ## 3 c 3 ## but the levels are still there! levels(subdf$letters) ## [1] "a" "b" "c" "d" "e" 

Todo lo que debe hacer es aplicar factor () a su variable nuevamente después del subconjunto:

 > subdf$letters [1] abc Levels: abcde subdf$letters < - factor(subdf$letters) > subdf$letters [1] abc Levels: abc 

EDITAR

Desde el ejemplo de la página de factores:

 factor(ff) # drops the levels that do not occur 

Para eliminar niveles de todas las columnas de factores en un dataframe, puede usar:

 subdf < - subset(df, numbers <= 3) subdf[] <- lapply(subdf, function(x) if(is.factor(x)) factor(x) else x) 

Desde R versión 2.12, hay una función droplevels() .

 levels(droplevels(subdf$letters)) 

Si no desea este comportamiento, no use factores, use vectores de caracteres en su lugar. Creo que esto tiene más sentido que arreglarlo después. Pruebe lo siguiente antes de cargar sus datos con read.table o read.csv :

 options(stringsAsFactors = FALSE) 

La desventaja es que está restringido a ordenar alfabéticamente. (reordenar es tu amigo para plots)

Es un problema conocido, y drop.levels() proporciona un remedio posible en el paquete gdata donde se convierte su ejemplo

 > drop.levels(subdf) letters numbers 1 a 1 2 b 2 3 c 3 > levels(drop.levels(subdf)$letters) [1] "a" "b" "c" 

También está la función dropUnusedLevels en el paquete Hmisc . Sin embargo, solo funciona alterando el operador del subconjunto [ y no es aplicable aquí.

Como corolario, un enfoque directo por columna es un simple como as.factor(as.character(data)) :

 > levels(subdf$letters) [1] "a" "b" "c" "d" "e" > subdf$letters < - as.factor(as.character(subdf$letters)) > levels(subdf$letters) [1] "a" "b" "c" 

Otra forma de hacer lo mismo pero con dplyr

 library(dplyr) subdf < - df %>% filter(numbers < = 3) %>% droplevels() str(subdf) 

Editar:

¡También funciona! Gracias a agenis

 subdf < - df %>% filter(numbers < = 3) %>% droplevels levels(subdf$letters) 

Aquí hay otra manera, que creo que es equivalente al enfoque del factor(..) :

 > df < - data.frame(let=letters[1:5], num=1:5) > subdf < - df[df$num <= 3, ] > subdf$let < - subdf$let[ , drop=TRUE] > levels(subdf$let) [1] "a" "b" "c" 

Esto es desagradable. Así es como generalmente lo hago, para evitar cargar otros paquetes:

 levels(subdf$letters)< -c("a","b","c",NA,NA) 

que te atrapa:

 > subdf$letters [1] abc Levels: abc 

Tenga en cuenta que los nuevos niveles reemplazarán lo que ocupe su índice en los niveles anteriores (subdf $ letters), así que algo así como:

 levels(subdf$letters)< -c(NA,"a","c",NA,"b") 

no funcionará

Esto obviamente no es ideal cuando tienes muchos niveles, pero para algunos es rápido y fácil.

aquí hay una manera de hacerlo

 varFactor < - factor(letters[1:15]) varFactor <- varFactor[1:5] varFactor <- varFactor[drop=T] 

Al droplevels código de métodos droplevels en la fuente R, puede ver que se ajusta a la función de factor . Eso significa que básicamente puedes recrear la columna con la función factor .
Debajo de la manera data.table para bajar los niveles de todas las columnas de factores.

 library(data.table) dt = data.table(letters=factor(letters[1:5]), numbers=seq(1:5)) levels(dt$letters) #[1] "a" "b" "c" "d" "e" subdt = dt[numbers < = 3] levels(subdt$letters) #[1] "a" "b" "c" "d" "e" upd.cols = sapply(subdt, is.factor) subdt[, names(subdt)[upd.cols] := lapply(.SD, factor), .SDcols = upd.cols] levels(subdt$letters) #[1] "a" "b" "c" 

En aras de la exhaustividad, ahora también está fct_drop en el paquete forcats http://forcats.tidyverse.org/reference/fct_drop.html .

Se diferencia de droplevels en la forma en que trata con NA :

 f < - factor(c("a", "b", NA), exclude = NULL) droplevels(f) # [1] ab  # Levels: ab  forcats::fct_drop(f) # [1] ab  # Levels: ab 

Escribí funciones de utilidad para hacer esto. Ahora que sé sobre drop.levels de gdata, se ve bastante similar. Aquí están (de aquí ):

 present_levels < - function(x) intersect(levels(x), x) trim_levels <- function(...) UseMethod("trim_levels") trim_levels.factor <- function(x) factor(x, levels=present_levels(x)) trim_levels.data.frame <- function(x) { for (n in names(x)) if (is.factor(x[,n])) x[,n] = trim_levels(x[,n]) x } 

Muy interesante hilo, me gustó especialmente la idea de simplemente factorizar la subselección de nuevo. Tuve el problema similar antes y acabo de convertirme en personaje y luego volver al factor.

  df < - data.frame(letters=letters[1:5],numbers=seq(1:5)) levels(df$letters) ## [1] "a" "b" "c" "d" "e" subdf <- df[df$numbers <= 3] subdf$letters<-factor(as.character(subdf$letters))