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