Qu’est-ce qu’un facteur?

Les variables catégoriques peuvent prendre des valeurs numériques ou être des chaines de caractères. Pour les utiliser de manière correcte en modélisation statistique et tester leur impact sur d’autres variables, il est nécessaire de les transformer en facteurs qui doivent être des entiers, ce qui peut nécessiter une conversion. Certaines fonctions sous R le font de manière cachée : c’est le cas de boxplot!

La fonction factor() permet de convertir vous-même un vecteur (une variable) en un facteur qui encode les différentes valeurs possibles de la variable. Sous R, les valeurs entières de 1 à n correspondront aux index du facteur. Ces valeurs d’index ou « codes » sont attribuées par défaut selon l’ordre alphabétique ou numérique (cf. help(factor)) des différentes valeurs possibles, appelés niveaux ou “levels”, de la variable.

Si les valeurs sont numériques, elles sont elles-mêmes renumérotées ou ré-indexées de 1 à n ! Par exemple, si vous avez les entiers de 4 à 10 dans un vecteur v1 :

v1<-4:10
factor(v1)
[1] 4  5  6  7  8  9  10
Levels: 4 5 6 7 8 9 10
str(factor(v1))
 Factor w/ 7 levels "4","5","6","7",..: 1 2 3 4 5 6 7

La factorisation de v1 a donc créé un 1 facteur à 7 niveaux correspondant aux valeurs catégoriques “4” à “10” mais indexées 1 à 7 !

Si les valeurs sont des chaines de caractère, elles sont indexées de 1 à n : Par exemple, si je factorise le vecteur suivant contenant 4 valeurs :

v2 <- c("Homme","Femme","Homme","Homme")
factor(v2)
[1] Homme Femme Homme Homme
Levels: Femme Homme

Les valeurs “Homme” et “Femme” auront respectivement comme index les valeurs 2 et 1 car “Femme” sort avant “Homme” selon l’ordre alphabétique. Les valeurs 1, 3 et 4 du vecteur v2 seront donc codées 2, tandis que la 2ème sera codée 1, ce que vous voyez en regardant la structure du facteur créé :

str(factor(v2))
 Factor w/ 2 levels "Femme","Homme": 2 1 2 2

Mais ce n’est cependant pas nécessairement le code que vous voulez garder : souvent on met « 2 » pour les femmes et « 1 » pour les « hommes ». De même, “cas” et “controles” seront convertis en 1 et 2. Or il est souvent préférable de donner la valeur 1 à la référence à laquelle on compare et donc on peut souhaiter imposer que 1 corresponde aux “controles” et 2 aux “cas”

Il peut donc être nécessaire d’imposer par vous-mêmes l’ordre de votre choix qui ne serait pas alphabétique ou numérique. L’argument “levels” permet d’imposer cet ordre.

En utilisant la fonction factor(), vous pouvez utiliser en particulier deux arguments pour choisir vous- même l’ordre des niveaux d’une part et leurs noms d’autre part:

Pour ces deux arguments, vous donnez un vecteur avec les noms des niveaux.

Exemple pas à pas illustrant l’usage de factor() avec les arguments «levels» et «labels»

Prenez par exemple le vecteur d’un score de satisfaction à une enquête:

satisfaction<-c("mauvais","bon","acceptable","bon","bon","mauvais")
satisfaction
[1] "mauvais"    "bon"        "acceptable" "bon"        "bon"        "mauvais"   

Vous pouvez le convertir en un facteur avec la fonction factor():

satisfaction_factor <- factor(satisfaction)
satisfaction_factor
[1] mauvais    bon        acceptable bon        bon        mauvais   
Levels: acceptable bon mauvais

L’objet obtenu est un facteur à 3 niveaux recodés par les index 1, 2 et 3. Vous ne voyez pas les index directement, mais ils sont visibles si vous utiliser la fonction str(). Par défaut, les codes/index 1 à n sont donnés aux niveaux selon l’ordre alphabétique des valeurs uniques. Dans l’exemple, “acceptable” est donc recodé 1, “bon” 2 et “mauvais” 3. Avant de factoriser, vous pouvez identifier l’ordre avec sort() appliqué aux valeurs uniques de votre vecteur de départ.

sort(unique(satisfaction))
[1] "acceptable" "bon"        "mauvais"   

Vous retrouvez cet ordre avec la fonction levels()appliquée à votre facteur.

levels(satisfaction_factor)
[1] "acceptable" "bon"        "mauvais"   

Ainsi, “acceptable” a l’index 1, “bon” l’index 2 et “mauvais” l’index 3 du vecteur levels Si vous regardez la structure de votre facteur avec la fonction str(), vous obtenez bien un facteur à 3 niveaux, dont le nom trié alpha-numériquement correspondant au résultats de levels() est donné, suivi de l’affichage des premières valeurs codées. Ici, vous obtenez 3 puis 2 puis 1 puis 2 puis 2 puis 3, correspondant aux index/codes des niveaux

str(satisfaction_factor)
 Factor w/ 3 levels "acceptable","bon",..: 3 2 1 2 2 3

Si vous voulez imposer que “mauvais” soit codé par 1, “acceptable” par 2 et “bon” par 3, vous ajoutez ces 3 niveaux dans cet ordre au moment de créer votre facteur avec l’argument « levels » auquel vous donnez le vecteur_des_niveaux_du_facteur_dans_ordre_souhaité, par exemple de 1 à 3 du moins bon au meilleur. Dans l’exemple, j’ajoute “2” au nom de ce nouvel objet R:

satisfaction_factor2 <- factor(satisfaction, levels = c("mauvais","acceptable", "bon"))

Vous constatez alors avec la fonction str() que les données sont désormais 1, puis 3, puis 2, puis 3 etc…selon les niveaux choisis :

str(satisfaction_factor2)
 Factor w/ 3 levels "mauvais","acceptable",..: 1 3 2 3 3 1

Si maintenant vous souhaitez en plus renommer les niveaux par “bad”, “satisfactory”, “good”, vous ajoutez l’argument “labels” dans une version 3:

satisfaction_factor3<-factor(satisfaction,levels=c("mauvais", "acceptable", "bon"), labels=c("bad","satisfactory", "good"))

Si vous regardez la structure et à quels index correspondent les données, c’est dans l’ordre souhaité comme dans le facteur version 2.

str(satisfaction_factor3)
 Factor w/ 3 levels "bad","satisfactory",..: 1 3 2 3 3 1

Et vous pouvez voir la reconversion des noms des niveaux:

satisfaction_factor3
[1] bad          good         satisfactory good         good         bad         
Levels: bad satisfactory good

Attention toutefois lorsque vous utilisez l’argument “labels” dans l’ordre souhaité en oubliant d’utiliser l’argument “levels”, car vous aboutissez à remplacer les valeurs de façon incorrecte. La version 4 ci- dessous n’est pas du tout celle souhaitée et a remplacé les données !

satisfaction_factor4<-factor(satisfaction,labels=c("bad", "satisfactory", "good"))
satisfaction_factor4
[1] good         satisfactory bad          satisfactory satisfactory good        
Levels: bad satisfactory good
str(satisfaction_factor4)
 Factor w/ 3 levels "bad","satisfactory",..: 3 2 1 2 2 3

Facteurs et dataframes

Finalement, attention aux factorisations indésirables, notamment lors de la création d’un dataframe à partir d’une matrice. Soit un vecteur numérique de 20 éléments créé avec la fonction rexp (de paramètre 7):

x <- rexp(20,7)

Avec le vecteur des 20 premières lettres de l’alphabet, on crée un data frame. On utilise pour commencer la fonction data.frame():

y <- data.frame(V1=LETTERS[1:20], V2=x)
head(y)
  V1         V2
1  A 0.03916080
2  B 0.14378779
3  C 0.06770520
4  D 0.09364081
5  E 0.37715946
6  F 0.14118918
str(y)
'data.frame':   20 obs. of  2 variables:
 $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ V2: num  0.0392 0.1438 0.0677 0.0936 0.3772 ...

Cependant très souvent, on utilise plutôt (et à tort) la fonction cbind() avant de créer le dataframe:

z <- cbind(V1=LETTERS[1:20], V2=x)
head(z)
     V1  V2                  
[1,] "A" "0.0391607987029212"
[2,] "B" "0.143787786358554" 
[3,] "C" "0.0677052043777491"
[4,] "D" "0.0936408124065825"
[5,] "E" "0.377159461114444" 
[6,] "F" "0.141189182969461" 
str(z)
 chr [1:20, 1:2] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "0.0391607987029212" "0.143787786358554" "0.0677052043777491" "0.0936408124065825" "0.377159461114444" "0.141189182969461" "0.015863621873515" "0.291982850047864" "0.2694716425297" ...
 - attr(*, "dimnames")=List of 2
  ..$ : NULL
  ..$ : chr [1:2] "V1" "V2"

On voit que x a été contraint (‘coerced’) d’être transformé en vecteur chaine de caractères. Le plus souvent, cela passe inaperçu à cette étape et le problème est révélé lors de la transformation en dataframe:

u <- as.data.frame(z)
head(u)
  V1                 V2
1  A 0.0391607987029212
2  B  0.143787786358554
3  C 0.0677052043777491
4  D 0.0936408124065825
5  E  0.377159461114444
6  F  0.141189182969461
str(u)
'data.frame':   20 obs. of  2 variables:
 $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ V2: Factor w/ 20 levels "0.00190765270963311",..: 3 11 7 8 18 10 2 17 15 5 ...

On constate que les deux variables sont maintenant des facteurs. Pour la variable x qui était numérique au départ, cela est généralement involontaire.

Pour rattraper cette “erreur”, on utilise alors spontanément la fonction as.numeric():

u$V2 <- as.numeric(u$V2)
str(u)
'data.frame':   20 obs. of  2 variables:
 $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ V2: num  3 11 7 8 18 10 2 17 15 5 ...

On constate que la transformation as.numeric() a en fait recyclé les niveaux correspondant aux valeurs, et pas du tout les valeurs elles-mêmes. Dans cet exemple, l’erreur est rapidement détectée car l’on partait de valeurs décimales. Lorsqu’il s’agit d’entiers au départ, le problème peut très facilement rester inaperçu.

Dans cette situation, il faut en fait procéder en deux étapes (après avoir recréé le dataframe à partir de la matrice):

t <- as.data.frame(z)
t$V2 <- as.character(t$V2)
str(t)
'data.frame':   20 obs. of  2 variables:
 $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ V2: chr  "0.0391607987029212" "0.143787786358554" "0.0677052043777491" "0.0936408124065825" ...
t$V2 <- as.numeric(t$V2)
str(t)
'data.frame':   20 obs. of  2 variables:
 $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ V2: num  0.0392 0.1438 0.0677 0.0936 0.3772 ...
head(t)
  V1         V2
1  A 0.03916080
2  B 0.14378779
3  C 0.06770520
4  D 0.09364081
5  E 0.37715946
6  F 0.14118918