Traitement de données, recoder et filtrer

Core
Manipulation des données
Nettoyage des données
Logique
Apprenez à recoder vos variables avec {dplyr} et comment sélectionner les lignes d’un data frame suivant certains critères
Date de publication

21 février 2025

Objectifs

Dans la session précédente vous avez appris les bases du traitement de données en R avec les fonctions du {tidyverse}, en particulier comment sélectionner et modifier les colonnes d’un data frame. Dans cette session nous allons allez plus loin sur la modification des data frame et apprendre à :

  • Écrire des conditions logiques basiques, ce qui va nous permettre de :
  • Sélectionner des lignes d’un data frame avec filter()
  • Recoder des variables avec case_when()

Mise en place

Prérequis : cette leçon part du principe que vous connaissez les bases de la manipulation de données avec {dplyr}, et en particulier la fonction mutate(). Aller vous rafraîchir si besoin.

Nous utiliserons la liste linéaire avec les données brutes qui peut être téléchargée ici :


Si ce n’est pas déjà fait, enregistrez la dans le sous-dossier approprié de votre projet RStudio puis créez un nouveau script appelé filtrer_recoder.R dans votre sous-dossier R. Ajoutez un en-tête approprié et chargez les paquets suivants : {here}, {rio} et {tidyverse}.

Enfin, ajoutez une section dédiée à l’import des données, utilisez {here} et {rio} pour importer vos données dans R, et assignez-les à un objet que nous appellerons df_brut

Filtrer des données avec des conditions logiques

Nous avons appris précédemment comment comment sélectionner les colonnes d’un data frame. Nous allons à présent apprendre la tâche complémentaire, qui est la sélection des lignes d’un data frame. C’est une tâche particulièrement courante du travail d’épidémiologiste qui permet de sélectionner des observations qui satisfont à certains critères. Le paquet {dplyr} possède bien évidement une fonction pour ça, la fonction filter().

Avant de pouvoir l’utiliser nous allons néanmoins devoir apprendre à écrire des conditions logique, qui sont également un prérequis pour recoder des variables. Les conditions logiques sont des questions (ou tests) auxquelles R va répondre par TRUE ou FALSE (ou NA).

Egalité

La syntaxe de filter() est assez simple :

# NE PAS EXÉCUTER (PSEUDO-CODE)
df_brut |>
  filter([condition_logique])

Cette syntaxe permet de conserver les lignes où condition_logique est vraie. Ici, la condition logique va demander si quelque chose est égale à autre chose. Par exemple, si telle variable est égale à telle valeur (est ce que patient a été hospitalisé ?). En R, nous testons l’égalité avec l’opérateur ==.

En pratique, pour créer un filtre qui ne garde que les patients hospitalisés nous écrivons :

df_brut |>
  filter(hospitalisation == "oui")

Ici, filter() parcourt chaque ligne de notre data frame et teste si la valeur d’hospitalisation dans cette ligne est égale à "oui". La fonction ne renvoie alors que les lignes où la réponse à la question est TRUE [vrai].

Filtrez vos données pour ne conserver que les patients qui avaient de la fièvre (c’est à dire les patients contenant la valeur "Yes" dans la colonne fievre. Le début de la colonne fievre dans la sortie filtrée est :

  fievre
1    Yes
2    Yes
3    Yes
4    Yes
5    Yes
6    Yes

Inspectez la sortie et df_brut. Pourquoi df_brut contient-il encore les patients qui n’avaient pas de fièvre ?

Inégalité

Parfois, nous préférons tester l’inégalité plutôt que l’égalité ; pour examiner les patients qui ne se sont pas rétablis, par exemple, qu’ils soient décédés ou sorti contre avis médical. Dans ce cas nous utiliserons l’opérateur !=, ce qui donne ce code :

df_brut |>
  filter(issue != 'gueri') # Garde les lignes avec patients NON guéris

Filtrez votre data frame pour ne montrer que les patients qui n’ont pas de carte confirmant leur statut vaccinal. Le début de la colonne filtrée ressemble à :

  statut_vaccinal
1             Non
2             Non
3             Non
4             Non
5             Non
6             Non

Astuce : Rappelez-vous que vous pouvez utiliser count() pour vérifier les modalités de statut_vaccinal.

Supérieur à / Inférieur à

Dans le cas des variables numériques, on sera souvent intéressé par savoir si une valeur est supérieure ou inférieure à un seuil. Par exemple, quels sont les patients de moins de 5 ans. Ici, nous utiliserons les opérateurs < et > pour évaluer si une variable est inférieure à ou supérieure à une valeur donnée, respectivement.

Nous pouvons par exemple filtrer les patients de moins de 60 mois :

df_brut |>
  filter(age < 60)

Affichez un data frame ne contenant que les patients souffrant de malnutrition aiguë sévère. Le début de la colonne concernée est :

    pb
1  244
2  232
3  123
4  210
5   80
6  220
7  152
8  155
9  232
10 135

Ecrivez un autre filtre qui sélectionne les patients âgés de plus de 15 ans. L’en-tête de votre colonne d’âge doit ressembler à ceci :

  age
1 348
2 348
3 312
4 432
5 444
6 324

Si nous ne voulons pas l’égalité stricte nous pouvons ajouter un signe égal aux opérateurs précédents, ce qui donne <= pour “inférieur ou égal à” et >= pour “supérieur ou égal à”. Attention, le = doit venir après les opérateurs < et >, pas avant.

Pour filtrer les patients avec 10 ans ou moins :

df_brut |>
  filter(age <= 120)

Sélectionnez tous les patients avec un état nutritionnel normal, c’est-à-dire les patients dont le PB est supérieur ou égal à 125mm. L’en-tête du pb devrait ressembler à ceci :

    pb
1  244
2  232
3  210
4  220
5  152
6  155
7  232
8  135
9  146
10 202

Conditions multiples

Il est possible de combiner plusieurs conditions logiques dans un même filtre ! Il suffit de séparer plusieurs conditions logiques par une virgule.

# NE PAS EXÉCUTER (PSEUDO-CODE)
df_brut |>
  filter([condition 1],
         [condition 2],
         [condition 3])

Par exemple, nous pourrions sélectionner tous les patients hospitalisés de moins de cinq ans :

df_brut |>
  filter(age < 5,
         hospitalisation == "oui")

Créez un filtre qui sélectionne tous les patients de la sous-préfecture de Koumra hospitalisés et sévèrement malnutris Cela donne :

    id sous_prefecture hospitalisation  pb
1 8624          KOUMRA             oui 103
2 8939          KOUMRA             oui  67
3 9957          KOUMRA             oui  71

Indice : if faut une condition sur le statut d’hospitalisation, une sur la sous-préfecture et une sur le PB.

Résumé des conditions logiques

Nous avons fait le tour des conditions logiques les plus basiques en R. Les voici rassemblées dans un tableau pour références futures :

Condition R
A identique à B ? A == B
A pas identique à B ? A != B
A supérieur à B ? A > B
A supérieur ou égal à B ? A >= B
A inférieur à B ? A < B
A inférieur ou égal à B ? A <= B

Recoder des variables avec case_when()

L’utilité des conditions logiques dans le traitement de données va bien plus loin que la sélection de lignes ! Elles sont par exemple très utiles quand nous voulons recoder des variables. Nous utiliserons les conditions logiques à l’intérieur de la fonction case_when() (également du paquet {dplyr}) pour recoder les variables.

La fonction case_when() est un peu plus complexe que ce que l’on a vu jusqu’à présent, mais très puissante (et va donc vous être très utile). Nous allons décomposer sa syntaxe pas à pas.

Vous utiliserez presque toujours case_when() dans un mutate() pour recoder une variable existante ou en créer une nouvelle, avec cette syntaxe :

# NE PAS EXÉCUTER (PSEUDO-CODE)
df_brut |>
  mutate(nouvelle_colonne = case_when(
    [condition_1] ~ [valeur_si_condition_1_est TRUE],
    [condition_2] ~ [valeur_si_condition_2_est TRUE],
    .défaut = [valeur_par_défaut]))

Décomposons-la commande.

A l’exception de la dernière ligne, chaque ligne à l’intérieur de la fonction case_when() a le format suivant :

# NE PAS EXÉCUTER (PSEUDO-CODE)
[condition] ~ [valeur si condition est VRAIE]  # Les crochets sont là pour la lisibilité

Ainsi, pour recoder les patients avec un PB inférieur à 110mm comme "MAS", nous écrivons la commande suivante dans notre case_when() :

# NE PAS EXÉCUTER (PSEUDO-CODE)
# [condition] ~ [valeur si VRAIE]
   pb < 110   ~ "MAS"

Il y a en général plus d’une condition ! Dans notre exemple, une autre condition logique testerait si le patient est modérément malnutri avec l’instruction pb < 125 ~ "MAM".

La dernière ligne de notre pseudo code contient l’argument .default et sert à fournir la valeur à utiliser lorsqu’aucune des conditions n’est remplie. Dans notre exemple, ça pourrait être "Normal".

Pour résumer, pour résumer, pour créer une variable contenant le statut nutritionnel à partir du PB :

df_brut |>
  mutate(malnut = case_when(
    pb < 110 ~ "MAS",
    pb < 125 ~ "MAM",
    .default = "Normal"))

Exécutez le code ci-dessus pour créer une variable malnut contenant le statut nutritionnel des patients. Le haut des deux colonnes concernées renvoie :

   pb malnut
1 244 Normal
2 232 Normal
3 123    MAM
4 210 Normal
5  80    MAS
6 220 Normal
Important

L’ordre des conditions logiques est important ! case_when() teste les conditions dans l’ordre que vous lui donnez et attribue une valeur dès qu’une condition est TRUE.

Ainsi, dans l’exemple ci-dessus, case_when() pose ces questions suivantes dans l’ordre :

  • Est-ce que pb < 110 pour ce patient ? Si oui, attribuer la valeur "MAS"
  • Si le patient n’est pas MAS, est-ce que pb < 125 ? Si oui, attribuer la valeur "MAM"
  • Si aucune des conditions précédentes n’est vraie, attribuer la valeur "Normal"

Intervertissez l’ordre des deux premières conditions dans le case_when()précédent (pb < 125 testé en premier). Le haut des deux colonnes concernées est maintenant :

   pb malnut
1 244 Normal
2 232 Normal
3 123    MAM
4 210 Normal
5  80    MAM
6 220 Normal

Vous pouvez enregistrer le data frame crée dans un objet temporaire temp pour l’inspecter plus facilement. Où sont les patients MAS ? Comprenez-vous ce qui s’est passé ?

Note

L’argument .default dans case_when() n’est pas obligatoire. Si vous ne l’incluez pas, case_when() utilisera la valeur NA par défaut.

Dans notre exemple, nous avons utilisé case_when() pour créer une variable catégorique (le statut nutritionnel) à partir d’une variable continue (le PB). Un autre exemple typique et similaire est de créer une colonne contenant les classes d’âge.

Utilisez case_when() pour créer une variable groupe_age avec les catégories suivantes :

  • "< 5 Ans"
  • "5 - 15 Ans"
  • "> 15 Ans".
  • si l’âge est manquant, attribuer la valeur "Inconnu".

Faites attention à l’ordre ! L’en-tête des colonnes concernées doit ressembler à ceci :

   age  age_group
1   36    < 5 Ans
2    5    < 5 Ans
3  156 5 - 15 Ans
4    8    < 5 Ans
5    7    < 5 Ans
6    4    < 5 Ans
7    2    < 5 Ans
8   48    < 5 Ans
9  156 5 - 15 Ans
10 348   > 15 Ans

L’opérateur %in%

Nous savons maintenant recoder les variables en catégories, ce qui vous arrivera très souvent en épidémiologie. Un autre cas d’usage majeur est d’utiliser case_when() pour standardiser les valeurs d’une variable.

Utilisez count() pour inspecter les variables catégorielles de votre jeu de données. Lesquelles devraient être standardisées ?

Vous avez dû voir que la variable sexe présente quelques problèmes d’encodage. Par exemple, les patientes sont codées comme f, female et femme. Utilisons case_when() pour recoder cette variable. Ici, nous ne créerons pas une nouvelle variable, mais remplacerons la variable existante :

df_brut |>
  mutate(sexe = case_when(sexe == "f"      ~ "Femme",
                          sexe == "female" ~ "Femme",
                          sexe == "femme"  ~ "Femme",
                          sexe == "h"      ~ "Homme",
                          sexe == "male"   ~ "Homme",
                          sexe == "homme"  ~ "Homme",
                          .default = "Inconnu"))

Ce code fonctionne correctement mais est terriblement répétitif et verbeux. Heureusement il y a un raccourci pour lister toutes les options à réaffecter à “Femme” (et celles à “Homme”), l’opérateur %in% ! L’opérateur %in% permet de tester la condition “est ce que la valeur existe dans ce vecteur ?”.

# NE PAS EXÉCUTER (PSEUDO-CODE)
[valeur] %in% [vector_des_options]

Ainsi, par exemple, nous pourrions vérifier si la valeur “f” est dans les options “f” et “femme” :

"f" %in% c("f", "femme")

Exécutez l’instruction ci-dessus. Quel est le type de données de votre résultat ?

La commande renvoie un bolléen, c’est-à-dire un résultat logique. C’est donc une condition logique valide à utiliser dans un case_when() (ou un filter()) ! On peut donc simplifier notre code :

df_brut |>
  mutate(sexe = case_when(
    sexe %in% c("f", "female", "femme") ~ "Femme",
    sexe %in% c("h", "male", "homme") ~ "Homme",
    .default = "Inconnu"))

C’est plus court comme ça…

Utilisez case_when() et l’opérateur %in% pour créer une nouvelle colonne vacc_status_strict qui a la valeur :

  • "Oui" si le statut vaccinal est confirmé
  • "Non" pour les cas non vaccinés
  • "Non vérifié" sinon.

La tête de la nouvelle colonne ressemble à ceci :

  statut_vaccinal statut_vaccinal_strict
1            <NA>            Non vérifié
2             Non                    Non
3      Oui - oral            Non vérifié
4             Non                    Non
5             Non                    Non
6             Non                    Non

Pipeline de nettoyage des données

Maintenant que vous savez utiliser les conditions logiques pour recoder colonnes avec case_when(), nous pouvons reprendre le pipeline de nettoyage que nous avions commencé dans la session précédente.

Reprenez, le code de la session précédente, amendez-le et complétez le pour créer un gros pipeline de nettoyage des données, qui crée un data frame df_linelist en effectuant les opérations suivantes :

  • Supprimer les variables nom_complet et unite_age
  • Renommer les variables suivantes :
    • age devient age_ans
    • sous_prefecture devient prefecture
    • village_commune devient village
    • nom_structure_sante devient structure
  • Ajouter une variable age_ans avec l’âge du patient en années
  • Mettre à jour region et prefecture pour utiliser la casse de titre
  • Mettre à jour toutes les colonnes contenant des dates pour utiliser le type Date
  • Créer une nouvelle variable groupe_age avec les groupes < 6 mois, 6 - 11 mois, 12 - 59 mois, 5 - 15 ans et > 15 ans (les patients dont l’âge est inconnu sont Inconnu)
  • Recoder le sexe pour n’avoir que les valeurs : Femme, Homme et Inconnu
  • Supprimer toutes les lignes en double

Le début de vos données finales devrait ressembler à ceci :

  id  sexe age_mois  region prefecture        village date_debut
1  1 Femme       36 Mandoul   Moissala Sangana Koïtan 2022-08-13
2  2 Femme        5 Mandoul   Moissala      Mousdan 1 2022-08-18
3  3 Femme      156 Mandoul   Moissala     Djaroua Ii 2022-08-17
4  6 Homme        8 Mandoul   Moissala     Monakoumba 2022-08-22
5  7 Homme        7 Mandoul   Moissala      Tétindaya 2022-08-30
6 10 Homme        4 Mandoul   Moissala      Danamadja 2022-08-30
  date_consultation hospitalisation date_admission
1        2022-08-14             oui     2022-08-14
2        2022-08-25             oui     2022-08-25
3        2022-08-20            <NA>           <NA>
4        2022-08-25             non           <NA>
5        2022-09-02             non           <NA>
6        2022-09-02             oui     2022-09-02
                        structure tdr_paludisme fievre eruption toux
1 Hôpital du District de Moissala       negatif     No     <NA>  Yes
2 Hôpital du District de Moissala       negatif     No       No  Yes
3                      CS Silambi       negatif    Yes     <NA>   No
4 Hôpital du District de Moissala       negatif     No       No   No
5                      CS Silambi       negatif   <NA>       No  Yes
6                    Moissala Est       negatif    Yes       No   No
  yeux_rouges pneumonie encephalite  pb statut_vaccinal doses_vaccin issue
1          No        No          No 244            <NA>         <NA> gueri
2          No      <NA>          No 232             Non         <NA>  <NA>
3          No        No        <NA> 123      Oui - oral         <NA> gueri
4        <NA>        No          No 210             Non         <NA> gueri
5         Yes        No          No  80             Non         <NA> gueri
6        <NA>        No          No 220             Non         <NA> gueri
  date_issue    age_ans   groupe_age
1 2022-08-18  3.0000000 12 - 59 mois
2 2022-08-28  0.4166667     < 6 mois
3       <NA> 13.0000000   5 - 15 ans
4       <NA>  0.6666667  6 - 11 mois
5       <NA>  0.5833333  6 - 11 mois
6 2022-09-03  0.3333333     < 6 mois

Top ! Nous pouvons maintenant exporter ce data frame (presque) propre hors de R. Pour cela nous utiliserons la fonction export() de {rio} (et notre fidèle compagnon, la fonction here() de {here} pour gérer les chemins d’accès) :

df |>
  export(here('data', 'clean', 'measles_linelist_clean.xlsx'))

Notez ici que nous plaçons nos données dans le sous-dossier clean dans data.

Astuce

Enregistrer les données au format .xlsx est utile pour pouvoir les ouvrir dans Excel pour les inspecter ou les partager. Cependant, nous préférerons souvent utiliser un fichier avec l’extension .rds. Ce type de fichier est spécifique à R et est plus robuste aux problèmes liés à l’encodage ou au formatage des dates que les fichiers de type .xlsx ou .csv.

Pour exporter votre data frame vers un fichier .rds, il suffit de modifier l’extension :

df |>
  export(here('data', 'clean', 'measles_linelist_clean.rds')) # TADAM !

C’est fini !

Bravo. Lors des deux dernières sessions vous avez appris à utiliser les fonctions qui forment le socle du traitement de données, mais aussi les conditions logiques et comment organiser votre code en un pipeline de nettoyage !

Aller plus loin

Exercices supplémentaires