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"
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)
Depuis R Version 2.12, il existe 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 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)
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"
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"
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"
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.
Voici une façon de le faire
varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
, 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
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
}
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))