web-dev-qa-db-fra.com

Nettoyage des niveaux de facteurs (réduction de plusieurs niveaux/étiquettes)

Quel est le moyen le plus efficace (c.-à-d. Efficace/approprié) de nettoyer un facteur contenant plusieurs niveaux à réduire? Comment combiner deux ou plusieurs niveaux de facteurs en un seul.

Voici un exemple où les deux niveaux "Oui" et "Y" doivent être réduits à "Oui", et "Non" et "N" à "Non": 

## Given: 
x <- c("Y", "Y", "Yes", "N", "No", "H")   # The 'H' should be treated as NA

## expectedOutput
[1] Yes  Yes  Yes  No   No   <NA>
Levels: Yes No  # <~~ NOTICE ONLY **TWO** LEVELS

Une option consiste bien entendu à nettoyer les chaînes avant d'utiliser manuellement sub et ses amis. 

Une autre méthode consiste à autoriser les étiquettes en double, puis à les supprimer.

## Duplicate levels ==> "Warning: deprecated"
x.f <- factor(x, levels=c("Y", "Yes", "No", "N"), labels=c("Yes", "Yes", "No", "No"))

## the above line can be wrapped in either of the next two lines
factor(x.f)      
droplevels(x.f) 

Cependant, existe-t-il un moyen plus efficace


Bien que je sache que les arguments levels et labels devraient être des vecteurs, j'ai essayé des listes et des listes nommées ainsi que des vecteurs nommés pour voir ce qui se passe. 

  factor(x, levels=list(c("Yes", "Y"), c("No", "N")), labels=c("Yes", "No"))
  factor(x, levels=c("Yes", "No"), labels=list(c("Yes", "Y"), c("No", "N")))

  factor(x, levels=c("Y", "Yes", "No", "N"), labels=c(Y="Yes", Yes="Yes", No="No", N="No"))
  factor(x, levels=c("Y", "Yes", "No", "N"), labels=c(Yes="Y", Yes="Yes", No="No", No="N"))
  factor(x, levels=c("Yes", "No"), labels=c(Y="Yes", Yes="Yes", No="No", N="No"))
48
Ricardo Saporta

Utilisez la fonction levels et transmettez-lui une liste nommée, avec les noms correspondant aux noms souhaités des niveaux et les éléments correspondant aux noms actuels à renommer.

x <- c("Y", "Y", "Yes", "N", "No", "H")
x <- factor(x)
levels(x) <- list(Yes=c("Y", "Yes"), No=c("N", "No"))
x
## [1] Yes  Yes  Yes  No   No   <NA>
## Levels: Yes No

Comme mentionné dans la documentation levels; voir aussi les exemples ici.

valeur: pour la méthode 'factor', a vecteur de chaînes de caractères dont la longueur est au moins égale au nombre des niveaux de 'x', ou une liste nommée spécifiant comment renommer les niveaux.

Cela peut également être fait en une ligne, comme Marek le fait ici: https://stackoverflow.com/a/10432263/210673 ; la sorcerie levels<- est expliquée ici https://stackoverflow.com/a/10491881/210673 .

> `levels<-`(factor(x), list(Yes=c("Y", "Yes"), No=c("N", "No")))
[1] Yes  Yes  Yes  No   No   <NA>
Levels: Yes No
73
Aaron

La question étant intitulée Niveaux de facteur de nettoyage (regroupant plusieurs niveaux/étiquettes), le paquetage forcats doit également être mentionné ici, par souci d'exhaustivité. forcats est apparu sur CRAN en août 2016.

Il existe plusieurs fonctions pratiques pour nettoyer les niveaux de facteurs:

x <- c("Y", "Y", "Yes", "N", "No", "H") 

library(forcats)

Réduire les niveaux de facteur en groupes définis manuellement

fct_collapse(x, Yes = c("Y", "Yes"), No = c("N", "No"), NULL = "H")
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: No Yes

Changer les niveaux de facteur à la main

fct_recode(x, Yes = "Y", Yes = "Yes", No = "N", No = "No", NULL = "H")
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: No Yes

Ré-étiqueter automatiquement les niveaux de facteurs, réduire si nécessaire

fun <- function(z) {
  z[z == "Y"] <- "Yes"
  z[z == "N"] <- "No"
  z[!(z %in% c("Yes", "No"))] <- NA
  z
}
fct_relabel(factor(x), fun)
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: No Yes

Notez que fct_relabel() fonctionne avec des niveaux de facteurs, il attend donc un facteur comme premier argument. Les deux autres fonctions, fct_collapse() et fct_recode(), acceptent également un vecteur character qui est une fonctionnalité non documentée.

Facteurs de réorganisation par première apparition

Le résultat attendu donné par le PO est

[1] Yes  Yes  Yes  No   No   <NA>
Levels: Yes No

Ici, les niveaux sont classés comme ils apparaissent dans x, ce qui diffère de la valeur par défaut (?factor: Les niveaux d'un facteur sont triés par défaut). 

Pour être conforme au résultat attendu, vous pouvez utiliser fct_inorder()before en réduisant les niveaux:

fct_collapse(fct_inorder(x), Yes = c("Y", "Yes"), No = c("N", "No"), NULL = "H")
fct_recode(fct_inorder(x), Yes = "Y", Yes = "Yes", No = "N", No = "No", NULL = "H")

Les deux renvoient maintenant la sortie attendue avec les niveaux dans le même ordre.

14
Uwe

Peut-être qu'un vecteur nommé comme clé pourrait être utile:

> factor(unname(c(Y = "Yes", Yes = "Yes", N = "No", No = "No", H = NA)[x]))
[1] Yes  Yes  Yes  No   No   <NA>
Levels: No Yes

Cela ressemble beaucoup à votre dernière tentative ... mais celle-ci fonctionne :-)

7

Une autre méthode consiste à créer une table contenant le mappage:

# stacking the list from Aaron's answer
fmap = stack(list(Yes = c("Y", "Yes"), No = c("N", "No")))

fmap$ind[ match(x, fmap$values) ]
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes

# or...

library(data.table)
setDT(fmap)[x, on=.(values), ind ]
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes

Je préfère cette façon, car elle laisse derrière elle un objet facilement inspecté qui résume la carte; et le code data.table ressemble à n'importe quelle autre jointure dans cette syntaxe.


Bien sûr, si vous ne voulez pas qu'un objet comme fmap résume le changement, cela peut être un "one-liner":

library(data.table)
setDT(stack(list(Yes = c("Y", "Yes"), No = c("N", "No"))))[x, on=.(values), ind ]
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes
5
Frank

Je ne connais pas votre cas d'utilisation réel, mais strtrim serait-il utile ici ...

factor( strtrim( x , 1 ) , levels = c("Y" , "N" ) , labels = c("Yes" , "No" ) )
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: Yes No
2
Simon O'Hanlon

J'ajoute cette réponse pour démontrer que la réponse acceptée fonctionne sur un facteur spécifique dans un cadre de données, car ce n'était pas évident pour moi au départ (bien que cela aurait probablement dû l'être). 

levels(df$var1)
# "0" "1" "Z"
summary(df$var1)
#    0    1    Z 
# 7012 2507    8 
levels(df$var1) <- list("0"=c("Z", "0"), "1"=c("1"))
levels(df$var1)
# "0" "1"
summary(df$var1)
#    0    1 
# 7020 2507
2
Karl Baker

Semblable à l'approche de @ Aaron, mais légèrement plus simple serait: 

x <- c("Y", "Y", "Yes", "N", "No", "H")
x <- factor(x)
# levels(x)  
# [1] "H"   "N"   "No"  "Y"   "Yes"
# NB: the offending levels are 1, 2, & 4
levels(x)[c(1,2,4)] <- c(NA, "No", "Yes")
x
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes
2
gung

Vous pouvez utiliser la fonction ci-dessous pour combiner/réduire plusieurs facteurs: 

combofactor <- function(pattern_vector,
         replacement_vector,
         data) {
 levels <- levels(data)
 for (i in 1:length(pattern_vector))
      levels[which(pattern_vector[i] == levels)] <-
        replacement_vector[i]
 levels(data) <- levels
  data
}

Exemple:

Initialiser x

x <- factor(c(rep("Y",20),rep("N",20),rep("y",20),
rep("yes",20),rep("Yes",20),rep("No",20)))

Vérifiez la structure

str(x)
# Factor w/ 6 levels "N","No","y","Y",..: 4 4 4 4 4 4 4 4 4 4 ...

Utilisez la fonction:

x_new <- combofactor(c("Y","N","y","yes"),c("Yes","No","Yes","Yes"),x)

Revérifier la structure:

str(x_new)
# Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2 2 2 ...
1
Nikhil

Notons d'abord que dans ce cas particulier, nous pouvons utiliser une correspondance partielle:

x <- c("Y", "Y", "Yes", "N", "No", "H")
y <- c("Yes","No")
x <- factor(y[pmatch(x,y,duplicates.ok = TRUE)])
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes

Dans un cas plus général j'irais avec dplyr::recode:

library(dplyr)
x <- c("Y", "Y", "Yes", "N", "No", "H")
y <- c(Y="Yes",N="No")
x <- recode(x,!!!y)
x <- factor(x,y)
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: Yes No

Légèrement modifié si le point de départ est un facteur:

x <- factor(c("Y", "Y", "Yes", "N", "No", "H"))
y <- c(Y="Yes",N="No")
x <- recode_factor(x,!!!y)
x <- factor(x,y)
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: Yes No
1
Moody_Mudskipper