Elimine los niveles de factor no utilizados en un marco de datos subconjunto

Tengo un marco de datos que contiene un factor. Cuando creo un subconjunto de este marco de datos usando subset u otra función de indexación, se crea un nuevo marco de datos. sin embargo, el factor La variable conserva todos sus niveles originales, incluso cuando / si no existen en el nuevo marco de datos.

Esto causa problemas al realizar gráficas facetadas o al usar funciones que dependen de niveles de factores.

¿Cuál es la forma más sucinta de eliminar niveles de un factor en el nuevo marco de datos?

He aquí un 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    

# all levels are still there!
levels(subdf$letters)
## [1] "a" "b" "c" "d" "e"

preguntado el 28 de julio de 09 a las 15:07

15 Respuestas

Todo lo que debe hacer es aplicar factor () a su variable nuevamente después de crear subconjuntos:

> subdf$letters
[1] a b c
Levels: a b c d e
subdf$letters <- factor(subdf$letters)
> subdf$letters
[1] a b c
Levels: a b c

EDITAR

Del 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 marco de datos, puede usar:

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

respondido 25 nov., 16:17

Eso está bien para una sola vez, pero en un data.frame con una gran cantidad de columnas, puede hacer eso en cada columna que sea un factor ... lo que lleva a la necesidad de una función como drop.levels () de gdata. - dirk eddelbuettel

Ya veo ... pero desde la perspectiva del usuario es rápido escribir algo como subdf [] <- lapply (subdf, function (x) if (is.factor (x)) factor (x) else x) ... Is drop.levels () mucho más eficiente computacionalmente o mejor con grandes conjuntos de datos? (Uno tendría que reescribir la línea de arriba en un bucle for para un marco de datos enorme, supongo). matriz de sombrero

Gracias Stephen y Dirk. Le doy el visto bueno a este caso por el caso de un factor, pero espero que la gente lea estos comentarios para conocer sus sugerencias sobre cómo limpiar un marco de datos completo de factores. - medriscoll

Como efecto secundario, la función convierte el marco de datos en una lista, por lo que mydf <- droplevels(mydf) Es preferible la solución sugerida por Roman Luštrik y Tommy O'Dell a continuación. - Johan

Además: este método preservar el orden de la variable. - webelo

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

levels(droplevels(subdf$letters))

respondido 26 nov., 10:11

Una ventaja de este método sobre el uso factor() es que no es necesario modificar el marco de datos original o crear un nuevo marco de datos persistente. Puedo envolver droplevels alrededor de un marco de datos subconjunto y usarlo como argumento de datos para una función de celosía, y los grupos se manejarán correctamente. - Marte

Me he dado cuenta de que si tengo un nivel de NA en mi factor (un nivel de NA genuino), los niveles disminuyen, incluso si los NA están presentes. - Meep

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

options(stringsAsFactors = FALSE)

La desventaja es que está restringido al orden alfabético. (reordenar es tu amigo para las parcelas)

Respondido 29 Jul 09, 00:07

Es un problema conocido y una posible solución la proporciona drop.levels() en la gdata paquete donde su ejemplo se convierte

> 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á el dropUnusedLevels en función de la Hmisc paquete. Sin embargo, solo funciona alterando el operador de subconjunto [ y no es aplicable aquí.

Como corolario, un enfoque directo por columna es un simple 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"

Respondido 28 Jul 09, 20:07

La reorder parámetro de la drop.levels Vale la pena mencionar la función: si tiene que preservar el orden original de sus factores, úselo con FALSE valor. - daroczig

El uso de gdata solo para drop.levels produce "compatibilidad con gdata: read.xls para archivos 'XLS' (Excel 97-2004) ENABLED". "gdata: no se pueden cargar las bibliotecas de perl que necesita read.xls ()" "gdata: para admitir archivos 'XLSX' (Excel 2007+)." "gdata: Ejecute la función 'installXLSXsupport ()'" "gdata: para descargar e instalar automáticamente el perl". Utilice gotas de baseR (stackoverflow.com/a/17218028/9295807) - Vrokipal

Las cosas pasan con el tiempo. Tú se encuentran las comentando una respuesta que escribí hace nueve años. Así que tomemos esto como una pista para preferir en general las soluciones base R, ya que son las que usan la funcionalidad que aún existirá. N años a partir de ahora. - dirk eddelbuettel

Otra forma de hacer lo mismo pero con dplyr

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

Edit:

¡También funciona! Gracias a agencia

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

contestado el 23 de mayo de 17 a las 13:05

En aras de la integridad, ahora también hay fct_drop en la forcats paquete http://forcats.tidyverse.org/reference/fct_drop.html.

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

f <- factor(c("a", "b", NA), exclude = NULL)

droplevels(f)
# [1] a    b    <NA>
# Levels: a b <NA>

forcats::fct_drop(f)
# [1] a    b    <NA>
# Levels: a b

Respondido el 12 de junio de 17 a las 10:06

Aquí hay otra forma, que creo que es equivalente a la factor(..) enfoque:

> 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"

Respondido 29 Jul 09, 04:07

Ja, después de todos estos años no sabía que había un `[.factor` método que tiene un drop argumento y has publicado esto en 2009 ... - David Aremburgo

Esto es desagradable. Así es como suelo hacerlo, para evitar cargar otros paquetes:

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

que te consigue:

> subdf$letters
[1] a b c
Levels: a b c

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

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

no funcionará

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

Respondido 28 Jul 09, 20:07

En cuanto al droplevels métodos código en la fuente R que puede ver envuelve a factor función. Eso significa que básicamente puedes recrear la columna con factor función.
Debajo de la tabla de datos, se pueden eliminar 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"

respondido 11 mar '16, 23:03

Pienso que el data.table camino sería algo así como for (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]])) - David Aremburgo

@DavidArenburg no cambia mucho aquí como llamamos [.data.table sólo una vez - Jangorecki

aquí hay una forma de hacer eso

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

respondido 09 nov., 14:10

Esto es un engaño de este respuesta que se publicó 5 años antes. - David Aremburgo

Escribí funciones de utilidad para hacer esto. Ahora que conozco los niveles de 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
}

Respondido el 01 de Septiembre de 09 a las 21:09

Hilo muy interesante, me gustó especialmente la idea de factorizar la subselección nuevamente. Tuve un problema similar antes y simplemente me convertí en personaje y luego de nuevo en 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))

contestado el 25 de mayo de 15 a las 13:05

Quiero decir, factor(as.chracter(...)) funciona, pero de manera menos eficiente y sucinta que factor(...). Parece estrictamente peor que las otras respuestas. - gregorio tomas

Desafortunadamente, el factor () no parece funcionar cuando se usa rxDataStep de RevoScaleR. Lo hago en dos pasos: 1) Convertir a carácter y almacenar en un marco de datos externo temporal (.xdf). 2) Convierta de nuevo a factor y almacene en un marco de datos externo definitivo. Esto elimina cualquier nivel de factor no utilizado, sin cargar todos los datos en la memoria.

# Step 1) Converts to character, in temporary xdf file:
rxDataStep(inData = "input.xdf", outFile = "temp.xdf", transforms = list(VAR_X = as.character(VAR_X)), overwrite = T)
# Step 2) Converts back to factor:
rxDataStep(inData = "temp.xdf", outFile = "output.xdf", transforms = list(VAR_X = as.factor(VAR_X)), overwrite = T)

Respondido el 28 de enero de 19 a las 21:01

He probado la mayoría de los ejemplos aquí si no todos, pero ninguno parece funcionar en mi caso. Después de luchar durante bastante tiempo, he intentado usar como.carácter () en la columna de factor para cambiarlo a una columna con cadenas que parece funcionar bien.

No estoy seguro de problemas de rendimiento.

Respondido el 02 de Septiembre de 19 a las 04:09

Una función genuina de gotas que es mucho más rápida que droplevels y no realiza ningún tipo de comparación o tabulación de valores innecesarios. collapse::fdroplevels. Ejemplo:

library(collapse)
library(microbenchmark)

# wlddev data supplied in collapse, iso3c is a factor
data <- fsubset(wlddev, iso3c %!in% "USA")

microbenchmark(fdroplevels(data), droplevels(data), unit = "relative")
## Unit: relative
##               expr  min       lq     mean   median       uq      max neval cld
##  fdroplevels(data)  1.0  1.00000  1.00000  1.00000  1.00000  1.00000   100  a 
##   droplevels(data) 30.2 29.15873 24.54175 24.86147 22.11553 14.23274   100   b

Respondido el 09 de enero de 21 a las 08:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.