Traitement de données, recoder et filtrer
{dplyr}
et comment sélectionner les lignes d’un data frame suivant certains critères
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],
2],
[condition 3]) [condition
Par exemple, nous pourrions sélectionner tous les patients hospitalisés de moins de cinq ans :
|>
df_brut filter(age < 5,
== "oui") hospitalisation
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(
~ [valeur_si_condition_1_est TRUE],
[condition_1] ~ [valeur_si_condition_2_est TRUE],
[condition_2] = [valeur_par_défaut])) .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)
~ [valeur si condition est VRAIE] # Les crochets sont là pour la lisibilité [condition]
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]
< 110 ~ "MAS" pb
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(
< 110 ~ "MAS",
pb < 125 ~ "MAM",
pb .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
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é ?
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",
== "female" ~ "Femme",
sexe == "femme" ~ "Femme",
sexe == "h" ~ "Homme",
sexe == "male" ~ "Homme",
sexe == "homme" ~ "Homme",
sexe .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)
%in% [vector_des_options] [valeur]
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(
%in% c("f", "female", "femme") ~ "Femme",
sexe %in% c("h", "male", "homme") ~ "Homme",
sexe .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
etunite_age
- Renommer les variables suivantes :
-
age
devientage_ans
-
sous_prefecture
devientprefecture
-
village_commune
devientvillage
-
nom_structure_sante
devientstructure
-
- Ajouter une variable
age_ans
avec l’âge du patient en années - Mettre à jour
region
etprefecture
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 sontInconnu
) - Recoder le sexe pour n’avoir que les valeurs :
Femme
,Homme
etInconnu
- 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
.
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 !