Abaisser les niveaux de facteur dans une trame de données subsettée

J’ai un bloc de données contenant un facteur. Lorsque je crée un sous-ensemble de ce bloc de données à l’aide de subset() ou d’une autre fonction d’indexation, un nouveau bloc de données est créé. Cependant, la variable facteur conserve tous ses niveaux d’origine, même s’ils n’existent pas dans le nouveau bloc de données.

Cela crée des maux de tête lorsque vous effectuez un tracé à facettes ou utilisez des fonctions qui reposent sur des niveaux de facteurs.

Quel est le moyen le plus succinct pour supprimer des niveaux d’un facteur dans mon nouveau bloc de données?

Voici mon exemple:

 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" 

Tout ce que vous devez avoir à faire est d’appliquer de nouveau factor () à votre variable après avoir sous-défini:

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

MODIFIER

A partir de l’exemple de page de facteur:

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

Pour supprimer des niveaux de toutes les colonnes de facteurs dans un dataframe, vous pouvez utiliser:

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

Depuis R version 2.12, il y a une fonction droplevels() .

 levels(droplevels(subdf$letters)) 

Si vous ne voulez pas ce comportement, n’utilisez pas de facteurs, utilisez plutôt des vecteurs de caractères. Je pense que cela a plus de sens que de réparer les choses par la suite. Essayez ce qui suit avant de charger vos données avec read.table ou read.csv :

 options(ssortingngsAsFactors = FALSE) 

L’inconvénient est que vous êtes limité à l’ordre alphabétique. (Réorganiser est votre ami pour les plots)

C’est un problème connu, et une solution possible est fournie par drop.levels() dans le package gdata , où votre exemple devient

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

Il existe également la fonction dropUnusedLevels dans le package Hmisc . Cependant, cela ne fonctionne qu’en modifiant l’opérateur de sous-ensemble [ et n’est pas applicable ici.

En corollaire, une approche directe par colonne est 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" 

Une autre façon de faire la même chose mais avec dplyr

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

Modifier:

Fonctionne aussi! Merci à agenis

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

Voici un autre moyen, qui, à mon avis, équivaut à l’approche 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" 

C’est odieux. C’est comme ça que je le fais habituellement, pour éviter de charger d’autres paquets:

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

ce qui vous amène:

 > subdf$letters [1] abc Levels: abc 

Notez que les nouveaux niveaux remplaceront tout ce qui occupe leur index dans les anciens niveaux (subdf $ lettres), donc quelque chose comme:

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

ne marchera pas

Ce n'est évidemment pas idéal lorsque vous avez beaucoup de niveaux, mais pour quelques-uns, c'est rapide et facile.

voici une façon de le faire

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

En regardant le code de méthodes droplevels dans la source R, vous pouvez voir qu’il enveloppe la fonction factor . Cela signifie que vous pouvez essentiellement recréer la colonne avec la fonction de factor .
En dessous de la méthode data.table pour supprimer les niveaux de toutes les colonnes de facteurs.

 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" 

Pour être complet, il y a maintenant fct_drop dans le package forcats http://forcats.tidyverse.org/reference/fct_drop.html .

Il diffère de droplevels dans la manière dont il traite avec NA :

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

J’ai écrit des fonctions utilitaires pour ce faire. Maintenant que je connais les drop.levels de gdata, cela ressemble beaucoup. Les voici (d’ ici ):

 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 } 

Fil de discussion très intéressant, j’ai particulièrement aimé l’idée de ne prendre en compte que la sous-sélection. J’ai eu le même problème avant et je viens de convertir en personnage et ensuite de nouveau à facteur.

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