web-dev-qa-db-fra.com

Niveaux de facteur de chute dans une trame de données sous-configurée

J'ai un cadre de données contenant un facteur. Lorsque je crée un sous-ensemble de ce cadre de données à l'aide de subset() ou d'une autre fonction d'indexation, un nouveau cadre 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 lors du traçage par facettes ou de l'utilisation de fonctions qui reposent sur des niveaux de facteurs.

Quel est le moyen le plus succinct de 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"
461
medriscoll

Tout ce que vous avez à faire est d’appliquer facteur () à votre variable après avoir défini 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

À 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 facteur dans une image, vous pouvez utiliser:

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

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

levels(droplevels(subdf$letters))
465
Roman Luštrik

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(stringsAsFactors = FALSE)

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

38
hadley

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 que si vous modifiez l'opérateur de sous-ensemble [.

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

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

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

Modifier: 

Travaille aussi! Merci à agenis

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

Voici une autre façon, 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"
12
ars

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

Il diffère de droplevels dans la façon dont il traite 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
8
Aurèle

En regardant les droplevels méthodes code dans la source R, vous pouvez voir il encapsule la fonction factor Cela signifie que vous pouvez fondamentalement recréer la colonne avec la fonction 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"
7
jangorecki

voici une façon de le faire

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

C'est odieux. Voici comment 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 (lettres subdf $), 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
Matt Parker

J'ai écrit des fonctions utilitaires pour le faire. Maintenant que je connais les drop.levels de gdata, le résultat est 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
Brendan OConnor

Fil très intéressant, j'ai particulièrement aimé l'idée de simplement factoriser à nouveau la sous-sélection. J'ai eu le même problème avant et je viens de convertir en personnage, puis de nouveau 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))
3
DfAC

Malheureusement, factor () ne semble pas fonctionner avec rxDataStep de RevoScaleR. Je le fais en deux étapes: 1) Convertir en caractère et stocker dans un cadre de données externe temporaire (.xdf) . 2) Convertir en facteur et stocker dans un cadre de données externe définitif. Ceci élimine les niveaux de facteur non utilisés, sans charger toutes les données en mémoire.

# 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)
0
Jerome Smith