J'ai récemment eu à travailler avec des données d'enquête (ce qui m'arrive somme toute assez rarement) et notamment à tenter de produire des tableaux et des figures rendant compte de ces données, souvent catégorielles.

J'ai été assez rapidement confrontée à un problème qui peut sembler trivial de prime abord, mais qui néanmoins n'est pas si anecdotique que cela au regard du temps qu'il peut faire perdre: la manipulation de factors, et notamment les changements d'ordre et de noms de leurs niveaux.

Ce changement de noms est souvent nécessaire pour rendre les noms de niveaux explicites (i.e. transformation d'un "code", par exemple "SiteTemAvt", en langage naturel, comme "Site témoin avant traitement"). Quand au changement d'ordre, il peut être utile pour rendre compte d'une ordination naturelle de niveaux (par exemple les niveaux "Adolescent", "Adulte", "Enfant", "Nourrisson" d'un factor "Tranche d'âge" devraient a priori s'organiser dans l'ordre "Nourrisson","Enfant","Adolescent","Adulte").

Or, ces opérations peuvent être assez pénibles en n'utilisant que les fonctions de R base. Je suis donc allée faire un tour du côté du tidyverse pour voir s'il n'existerait pas, par hasard, un package pour faire ce genre de choses... Et bien sûr, il y en a un. Il s'agit du package forcats (nom de package qui confirme, une fois de plus, que le monde des développeurs R est un monde de crazy cat ladies et crazy cat buddies ergo je suis juste parfaitement adaptée à mon milieu).

Je me suis donc empressée d'aller voir ce billet de blog dont voici ci-après ma ré-interprétation/mise en images.

Et pour un autre billet, toujours en français, et bien plus complet (et avec un vrai meme de chat -à ne pas confondre avec la mémère à chat-), c'est ici, sur le blog de ThinkR, que ça se passe.

Chargeons de ce pas forcats, avant de décomposer les différents éléments de cette figure...

library(forcats)

Créer des factors

On peut créer un factor à l'aide de la fonction factor() (pour le coup il ne s'agit pas d'une fonction de forcats mais d'une fonction de base).

Par défaut, quand on crée un factor à partir d'un vecteur, les niveaux sont créés en considérant l'ensemble des valeurs uniques de ce vecteur.

vecteur=c("b","a","c","a","d","c","d","d","d","b","d","a")
f1=factor(vecteur)
print(f1)

##  [1] b a c a d c d d d b d a
## Levels: a b c d

Notez que les niveaux du factor sont ordonnés par défaut par ordre alphabétique.

Il est possible que certaines valeurs possibles pour ce factor soient absentes du vecteur, auquel cas on peut spécifier l'ensemble des valeurs prises de la manière suivante:

f2= factor(vecteur, levels=c("a","b","c","d","e"))
print(f2)

##  [1] b a c a d c d d d b d a
## Levels: a b c d e

Changement des noms de niveaux

Si l'on souhaite modifier les noms de niveaux, on peut procéder de deux manières:

  • soit en changeant l'ensemble des noms de niveaux d'un coup, avec la fonction levels() de base
  • soit en choisissant spécifiquement le niveau qu'on souhaite modifier, à l'aide de la fonction fct_recode() de forcats

Dans les deux cas, bien sûr, le nom n'est pas seulement modifié dans la partie "levels" du factor, mais également dans les valeurs mêmes.

f1

##  [1] b a c a d c d d d b d a
## Levels: a b c d

f2=f1
levels(f2)=c("tagada","pouetpouet","tsointsoin","boum")
f2

##  [1] pouetpouet tagada     tsointsoin tagada     boum       tsointsoin boum       boum       boum       pouetpouet boum       tagada    
## Levels: tagada pouetpouet tsointsoin boum

f2=fct_recode(f1, tsointsoin="c")
f2

##  [1] b          a          tsointsoin a          d          tsointsoin d          d          d          b          d          a         
## Levels: a b tsointsoin d

Changement de l'ordre des niveaux

Plusieurs fonctions permettent de réordonner les niveaux.

  • fct_rev() permet d'inverser l'ordre des niveaux
  • fct_relevel() permet de spécifier le rang d'un ou plusieurs niveaux spécifiques
  • fct_inorder() permet de ranger les niveaux selon leur ordre d'apparition dans la série de valeurs
  • fct_infreq() permet de ranger les niveaux selon leur ordre (décroissant) de fréquence.

fct_rev(f1) #inverse les niveaux

##  [1] b a c a d c d d d b d a
## Levels: d c b a

fct_relevel(f1,"c") #remonte "c" en tête

##  [1] b a c a d c d d d b d a
## Levels: c a b d

fct_relevel(f1,"c","d") #remonte "c" et "d" en tête

##  [1] b a c a d c d d d b d a
## Levels: c d a b

fct_relevel(f1,"a",after=2) #place a en 3ième position 

##  [1] b a c a d c d d d b d a
## Levels: b c a d

fct_inorder(f1) # ordre d'apparition: b apparaît en premier, puis a, etc.

##  [1] b a c a d c d d d b d a
## Levels: b a c d

fct_infreq(f1) # ordre de fréquence: d est le plus fréquent, puis a, etc.

##  [1] b a c a d c d d d b d a
## Levels: d a b c

Agrégation des niveaux rares en un seul niveau

Dans certains cas, les effectifs des différents niveaux peuvent être très désequilibrés. Dans ce cas, il peut être utile de réagréger un certain nombre de niveaux rares en un seul niveau. C'est ce que permet la fonction fct_lump().

vecteur=c(rep("a",9),rep("b",6),rep("c",3),"d","e")[sample(1:20, 20, replace=FALSE)]
f3=factor(vecteur)
f3

##  [1] b a a a b a a a c c b d b e a c b a a b
## Levels: a b c d e

Les effectifs sont répartis comme suit:

table(f3)

## f3
## a b c d e 
## 9 6 3 1 1

La fonction fct_lump peut réagréger les niveaux moins fréquent ("c","d", et "e") en un seul niveau "Other":

fct_lump(f3)

##  [1] b     a     a     a     b     a     a     a     Other Other b     Other b     Other a     Other b     a     a     b    
## Levels: a b Other

Bien entendu, il est possible de paramétrer cette fonction pour définir plus explicitement les seuils de fréquence ou de proportion que l'on utilise pour définir quels niveaux sont considérés comme peu fréquents.

fct_lump(f3, n=3) # niveaux rares= effectif < 3

##  [1] b     a     a     a     b     a     a     a     c     c     b     Other b     Other a     c     b     a     a     b    
## Levels: a b c Other

fct_lump(f3, prop=0.15) # niveaux rares= proportion < 15%

##  [1] b     a     a     a     b     a     a     a     Other Other b     Other b     Other a     Other b     a     a     b    
## Levels: a b Other

fct_lump(f3, other_level="autre") # nom du nouveau niveau

##  [1] b     a     a     a     b     a     a     a     autre autre b     autre b     autre a     autre b     a     a     b    
## Levels: a b autre

Changement d'ordre en vue d'une production graphique

Quand on produit des graphiques impliquant une variable catégorielle, il peut être utile d'ajuster la position/les niveaux des points/barres/boîtes à moustaches de manière à rendre le graphique le plus lisible possible.

Sans fct_reorder():

catdata= read.csv(url("http://perso.ens-lyon.fr/lise.vaudor/Rdata/Graphiques_avec_ggplot2/catdata.csv"), sep=";")
library(ggplot2)
ggplot(catdata, aes(x=haircolor, y=weight)) +
  geom_boxplot(fill="yellow")

Avec fct_reorder():

ggplot(catdata, aes(x=fct_reorder(haircolor,weight), y=weight)) +
  geom_boxplot(fill="yellow")

Les niveaux sont ordonnés par ordre croissant de médiane de weight (median() est la fonction utilisée par défaut par fct_reorder() mais il est possible d'en changer en utilisant l'argument fun=...) .

Autre exemple, les effectifs:

Sans fct_reorder():

ggplot(catdata, aes(x=haircolor)) +
  geom_bar(aes(fill=sex))

Avec fct_reorder():

ggplot(catdata, aes(x=fct_reorder(haircolor, sex, fun=length, .desc=TRUE))) +
  geom_bar(aes(fill=sex))

Les niveaux de haircolor sont réorganisés par ordre (ici, l'ordre décroissant du fait de l'argument .desc=TRUE) de fréquence.