Niveaux de facteur de baisse dans une trame de données sous-définie

J'ai une trame de données contenant un facteur. Lorsque je crée un sous-ensemble de cette trame de données à l'aide de subset() ou d'une autre fonction d'indexation, une nouvelle trame de données est créée. Cependant, la variable factorielle conserve tous ses niveaux initiaux, même lorsqu'ils n'existent pas dans la nouvelle base de données.

Cela crée des maux de tête lors du traçage à facettes ou de l'utilisation de fonctions qui reposent sur des niveaux de facteurs.

Quelle est la façon la plus succincte supprimer les niveaux d'un facteur dans ma nouvelles données cadre?

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"
441
demandé sur divibisan 2009-07-28 22:21:47

12 réponses

Tout ce que vous devriez avoir à faire est d'appliquer factor () à votre variable après le sous-ensemble:

> 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

Modifier

De l'exemple de page factor:

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)
343
répondu hatmatrix 2016-11-25 17:37:05

Depuis R Version 2.12, il existe une fonction droplevels().

levels(droplevels(subdf$letters))
457
répondu Roman Luštrik 2010-11-26 11:37:26

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 après. Essayez ce qui suit avant de charger vos données avec read.table ou read.csv:

options(stringsAsFactors = FALSE)

L'inconvénient est que vous êtes limité à l'ordre alphabétique. (réordonner est votre ami pour les parcelles)

37
répondu hadley 2009-07-28 23:53:43

C'est un problème connu, et un remède possible est fourni par drop.levels() dans le paquetgdata 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 y a aussi la fonction dropUnusedLevels dans le paquet 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 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"
33
répondu Dirk Eddelbuettel 2009-07-28 19:04:18

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 ! Grâce à agenis

subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
15
répondu Prradep 2017-05-23 12:18:18

Voici une autre façon, que je crois équivalente à 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"
10
répondu ars 2009-07-29 03:40:37

En regardant le code droplevels Méthodes dans la source R, Vous pouvez voir il enveloppe la fonction factor. Cela signifie que vous pouvez essentiellement recréer la colonne avec la fonction factor.
Ci-dessous les données.façon de 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"
7
répondu jangorecki 2016-03-11 23:30:39

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)

, Qui vous obtient:

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

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

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

Ne fonctionnera pas.

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

6
répondu Matt Parker 2009-07-28 19:06:07

Voici une façon de le faire

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
6
répondu Diogo 2014-11-09 10:03:06

, Par souci d'exhaustivité, maintenant il y a aussi fct_drop dans le forcats package http://forcats.tidyverse.org/reference/fct_drop.html.

, Il diffère de droplevels dans la façon dont il traite avec 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
6
répondu Aurèle 2017-06-12 09:44:29

J'ai écrit des fonctions utilitaires pour le faire. Maintenant que je connais la chute de gdata.les niveaux, il semble assez similaire. Les voici (de 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
}
5
répondu Brendan OConnor 2009-09-01 20:37:36

Fil très intéressant, j'ai particulièrement aimé l'idée de factoriser à nouveau la sous-sélection. J'ai eu le même problème avant et je viens de me convertir au caractère, puis de nouveau au 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))
4
répondu DfAC 2015-05-25 12:08:08