Nettoyer des données est souvent une étape un peu casse-tête, dans laquelle on peut perdre beaucoup de temps.
Dans cet article je vous présente quelques situations que je rencontre régulièrement et les solutions que j’emploie.
Pour l’illustrer j’ai créé un petit jeu de données (dirty_data.csv), qui condense ses situations.
Comme d’habitude, je travaille en projet R, avec un dossier data qui contient ce dirty_data.csv.
dd <- read.csv2("data/dirty_data.csv", stringsAsFactors = TRUE)
Une fois les données importées, la première chose que vous pouvez faire, c’est de les afficher dans le tableur R, comme ceci :
View(dd)
Ici, nous pouvons voir :
Code.POStal
et lorsque le code postal commence par le chiffre zéro, ce zéro n’est pas présent. Par exemple sur la première ligne on a 4100 à la place de 04100.NOM_Prenom
qui contient le nom et le prénom attachés ensemble, mais séparé par un underscore.
La seconde chose à faire est d’étudier la structure des données, en employant la fonction str()
:
str(dd)
## 'data.frame': 10 obs. of 9 variables:
## $ NOM_Prenom : Factor w/ 10 levels "Adjani_isabelle",..: 8 10 9 4 1 2 7 6 3 5
## $ Code.POStal : int 4100 37610 45960 25000 6000 38000 84320 13290 5000 77560
## $ Age : int 63 67 67 37 41 56 62 57 63 53
## $ GENDER : Factor w/ 7 levels " male","female",..: 6 7 5 1 2 5 3 4 5 5
## $ chest_pain : int 1 4 4 3 2 2 4 4 4 4
## $ Resting_blood_PRESSURE: int 145 160 120 130 130 120 140 120 130 140
## $ Serum.cholestoral : Factor w/ 10 levels "203","204","229",..: 4 10 3 6 2 5 8 9 7 1
## $ MAX.HEART.RATE : int 150 108 129 187 172 178 160 163 147 155
## $ disease : Factor w/ 2 levels "no","yes": 1 2 2 1 1 1 2 1 2 2
Si nous regardons les sorties attentivement, nous pouvons voir des éléments a priori, non conformes à nos attentes :
NOM_Prenom
est un facteur, on souhaiterait plutôt une chaîne de caractèresgender
contient 7 modalités alors qu’on en attend plutôt 2Serum.cholestoral
est de type factor
alors qu’on s’attendrait à une variable de type numeric
ou integer
.Une solution simple et efficace est d’employer la fonction clean_names()
du package janitor
.
#install.packages("janitor")
library(janitor)
## Warning: package 'janitor' was built under R version 4.0.5
dd2 <- dd # creation d'une copie de dd
dd2<- clean_names(dd2)
str(dd2)
## 'data.frame': 10 obs. of 9 variables:
## $ nom_prenom : Factor w/ 10 levels "Adjani_isabelle",..: 8 10 9 4 1 2 7 6 3 5
## $ code_po_stal : int 4100 37610 45960 25000 6000 38000 84320 13290 5000 77560
## $ age : int 63 67 67 37 41 56 62 57 63 53
## $ gender : Factor w/ 7 levels " male","female",..: 6 7 5 1 2 5 3 4 5 5
## $ chest_pain : int 1 4 4 3 2 2 4 4 4 4
## $ resting_blood_pressure: int 145 160 120 130 130 120 140 120 130 140
## $ serum_cholestoral : Factor w/ 10 levels "203","204","229",..: 4 10 3 6 2 5 8 9 7 1
## $ max_heart_rate : int 150 108 129 187 172 178 160 163 147 155
## $ disease : Factor w/ 2 levels "no","yes": 1 2 2 1 1 1 2 1 2 2
Ici, je vous montre l’utilisation basique de cette fonction clean_names()
, mais il existe de nombreuses d’options possibles.
Tout n’est pas parfait, par exemple Code.POStal
est devenu code_po_stal
, mais cela est facile à modifier. Par exemple, en employant la fonction rename()
du package dplyr :
library(tidyverse)
dd2 <- dd2 %>%
rename(code_postal=code_po_stal)
names(dd2)
## [1] "nom_prenom" "code_postal" "age"
## [4] "gender" "chest_pain" "resting_blood_pressure"
## [7] "serum_cholestoral" "max_heart_rate" "disease"
J’utilise aussi, assez souvent, ces trois fonctions du package stringr
:
str_to_lower()
str_to_upper()
str_to_title()
dd3 <- dd2
# Tout en majuscule
names(dd3) <- str_to_upper(names(dd2))
names(dd3)
## [1] "NOM_PRENOM" "CODE_POSTAL" "AGE"
## [4] "GENDER" "CHEST_PAIN" "RESTING_BLOOD_PRESSURE"
## [7] "SERUM_CHOLESTORAL" "MAX_HEART_RATE" "DISEASE"
# Tout en minuscule
names(dd3) <- str_to_lower(names(dd3))
names(dd3)
## [1] "nom_prenom" "code_postal" "age"
## [4] "gender" "chest_pain" "resting_blood_pressure"
## [7] "serum_cholestoral" "max_heart_rate" "disease"
# Avec une première majuscule
names(dd3) <- str_to_title(names(dd3))
names(dd3)
## [1] "Nom_prenom" "Code_postal" "Age"
## [4] "Gender" "Chest_pain" "Resting_blood_pressure"
## [7] "Serum_cholestoral" "Max_heart_rate" "Disease"
Nous allons nettoyer les modalités de la variable gender
qui contient 7 levels à la place des 2 attendus :
levels(dd2$gender)
## [1] " male" "female" "Female" "female " "male" "Male" "male "
En étudiant la sortie nous pouvons voir que :
Dans cette situation, la solution que je préfère consiste à :
str_to_lower()
supprimer les espaces avec str_trim()
repasser la variable en facteurÀ noter, que nous n’avons pas besoin de passer la variable en chaînes de caractères, avant d’employer les fonctionsstr_to_lower(), et str_trim().
dd3 <- dd2
dd3$gender <- str_to_lower(dd3$gender)
dd3$gender
## [1] "male" "male " "male" " male" "female" "male" "female"
## [8] "female " "male" "male"
dd3$gender <- str_trim(dd3$gender, side="both")
dd3$gender
## [1] "male" "male" "male" "male" "female" "male" "female" "female"
## [9] "male" "male"
dd3$gender <- as.factor(dd3$gender)
levels(dd3$gender)
## [1] "female" "male"
Nous allons maintenant nous occuper du code postal qui, lorsque celui-ci commence par le chiffre zéro, ne contient pas ce zéro. C’est le cas, trois fois ici, pour 4100
au lieu de 04100
, 6000
au lieu de 06000
, et 5000
au lieu de 05000.
Pour cela, nous allons employer la fonction str_pad()
, comme ceci:
dd3$code_postal
## [1] 4100 37610 45960 25000 6000 38000 84320 13290 5000 77560
dd3$code_postal <- str_pad(dd3$code_postal, 5, "left", "0")
dd3$code_postal
## [1] "04100" "37610" "45960" "25000" "06000" "38000" "84320" "13290" "05000"
## [10] "77560"
class(dd3$code_postal)
## [1] "character"
À noter ici que la variable code_postal
a été transformé en chaînes de caractères.
Ça fonctionne aussi avec n’importe quel numéro d’identification, comme un numéro de tel, un numéro de Siret, etc..
Si nous souhaitons, à partir de la variable nom_prenom
créer une variable nom
puis une variable prenom
, nous pouvons employer la fonction separate(
du package tidyr)
dd3 <- dd3 %>%
separate(nom_prenom,c("nom", "prenom"), sep="_")
dd3
## nom prenom code_postal age gender chest_pain resting_blood_pressure
## 1 Verdi Giuseppe 04100 63 male 1 145
## 2 WAGNER Richard 37610 67 male 4 160
## 3 Vivaldi ANTONIO 45960 67 male 4 120
## 4 BIZET GEORGES 25000 37 male 3 130
## 5 Adjani isabelle 06000 41 female 2 130
## 6 Aznavour CHARLES 38000 56 male 2 120
## 7 LUCIANI clara 84320 62 female 4 140
## 8 gall france 13290 57 female 4 120
## 9 Biolay benjamin 05000 63 male 4 130
## 10 CABREL Francis 77560 53 male 4 140
## serum_cholestoral max_heart_rate disease
## 1 233 150 no
## 2 non renseigne 108 yes
## 3 229 129 yes
## 4 250 187 no
## 5 204 172 no
## 6 236 178 no
## 7 268 160 yes
## 8 354 163 no
## 9 254 147 yes
## 10 203 155 yes
Nous pouvons ensuite uniformiser l’emploi des majuscules et minuscules, comme cela, par exemple :
dd3 <- dd3 %>%
mutate(nom=str_to_title(nom),
prenom=str_to_title(prenom))
dd3
## nom prenom code_postal age gender chest_pain resting_blood_pressure
## 1 Verdi Giuseppe 04100 63 male 1 145
## 2 Wagner Richard 37610 67 male 4 160
## 3 Vivaldi Antonio 45960 67 male 4 120
## 4 Bizet Georges 25000 37 male 3 130
## 5 Adjani Isabelle 06000 41 female 2 130
## 6 Aznavour Charles 38000 56 male 2 120
## 7 Luciani Clara 84320 62 female 4 140
## 8 Gall France 13290 57 female 4 120
## 9 Biolay Benjamin 05000 63 male 4 130
## 10 Cabrel Francis 77560 53 male 4 140
## serum_cholestoral max_heart_rate disease
## 1 233 150 no
## 2 non renseigne 108 yes
## 3 229 129 yes
## 4 250 187 no
## 5 204 172 no
## 6 236 178 no
## 7 268 160 yes
## 8 354 163 no
## 9 254 147 yes
## 10 203 155 yes
L’emploi de la fonction clean_names
du package janitor
a uniformisé le nom des variables et les points (dans le nom des variables) a été remplacé par des underscores
names(dd)
## [1] "NOM_Prenom" "Code.POStal" "Age"
## [4] "GENDER" "chest_pain" "Resting_blood_PRESSURE"
## [7] "Serum.cholestoral" "MAX.HEART.RATE" "disease"
names(dd2)
## [1] "nom_prenom" "code_postal" "age"
## [4] "gender" "chest_pain" "resting_blood_pressure"
## [7] "serum_cholestoral" "max_heart_rate" "disease"
Si vous souhaitez remplacer un caractère par un autre, ici les underscores par des points, vous pouvez employer les fonctions str_replace()
, et str_replace_all()
dd4 <- dd3
names(dd3) <- str_replace(names(dd3),"_","." )
names(dd3)
## [1] "nom" "prenom" "code.postal"
## [4] "age" "gender" "chest.pain"
## [7] "resting.blood_pressure" "serum.cholestoral" "max.heart_rate"
## [10] "disease"
Avec la fonction str_replace()
seul le premier underscore a été remplacé (cf resting.blood_pressure
, par exemple). Pour remplacer tous les underscores, nous devons employer str_replace_all()
:
names(dd4) <- str_replace_all(names(dd4),"_","." )
names(dd4)
## [1] "nom" "prenom" "code.postal"
## [4] "age" "gender" "chest.pain"
## [7] "resting.blood.pressure" "serum.cholestoral" "max.heart.rate"
## [10] "disease"
La variable serum_cholestoral
est étonnamment de type factor
, alors qu’on s’attendrait à une variable de type numeric
. Cela est dû à la valeur “non renseigne” présent en ligne 2 :
Dans cette situation, on peut commencer par remplacer ce “non renseigne” par un “NA” (character), puis transformer la variable en numeric :
dd4 <- dd2
dd4$serum_cholestoral <- str_replace(dd4$serum_cholestoral, "non renseigne", "NA")
dd4$serum_cholestoral <- as.numeric(dd4$serum_cholestoral)
## Warning: NAs introduits lors de la conversion automatique
str(dd4)
## 'data.frame': 10 obs. of 9 variables:
## $ nom_prenom : Factor w/ 10 levels "Adjani_isabelle",..: 8 10 9 4 1 2 7 6 3 5
## $ code_postal : int 4100 37610 45960 25000 6000 38000 84320 13290 5000 77560
## $ age : int 63 67 67 37 41 56 62 57 63 53
## $ gender : Factor w/ 7 levels " male","female",..: 6 7 5 1 2 5 3 4 5 5
## $ chest_pain : int 1 4 4 3 2 2 4 4 4 4
## $ resting_blood_pressure: int 145 160 120 130 130 120 140 120 130 140
## $ serum_cholestoral : num 233 NA 229 250 204 236 268 354 254 203
## $ max_heart_rate : int 150 108 129 187 172 178 160 163 147 155
## $ disease : Factor w/ 2 levels "no","yes": 1 2 2 1 1 1 2 1 2 2
Très très récemment, j’ai voulu importer un jeu de données contenu dans un classeur Excel, en employant la fonction xlsx2dfs()
du package xlsx2dfs
. Cette fonction permet d’importer toutes les feuilles d’un classeur en une seule fois, et de les stocker sous forme de liste.
Mon jeu de données comportait une variable Date, avec par exemple 19/01/2021. Mais après l’importation, sous R, je me suis retrouvée avec des valeurs comme 44215.
A cette occasion, j’ai découvert la génialissime fonction excel_numeric_to_date()
du package janitor
.
Pour reproduire cet exemple vous pouvez télécharger les données (`wrong_date.csv`) en cliquant sur le bouton ci-dessous :
library(xlsx2dfs)
## Loading required package: openxlsx
alldata <- xlsx2dfs("data/wrong_date.xlsx", rowNames=FALSE, colNames=TRUE)
wd <- alldata[[1]]
str(wd)
## 'data.frame': 11 obs. of 3 variables:
## $ Date : num 44215 44215 44215 44215 44215 ...
## $ Patient: chr "ID_1" "ID_2" "ID_3" "ID_4" ...
## $ age : num 12 24 69 32 85 41 20 36 25 98 ...
library(janitor)
wd$Date <- excel_numeric_to_date(wd$Date)
str(wd$Date)
## Date[1:11], format: "2021-01-19" "2021-01-19" "2021-01-19" "2021-01-19" "2021-01-19" ...
J’espère que cet article vous apportera quelques solutions à vos problèmes de nettoyage de données, ou pour le moins, vous donnera des pistes. Si vous avez rencontré d’autres situations, n’hésitez pas à les partager en commentaire ! Et vos solutions avez si vous en avez !
Si cet article vous a plu, ou vous a été utile, vous pouvez le partager, et soutenir le blog en réalisant un don libre sur la page Tipeee.
Enregistrez vous pour recevoir gratuitement mes fiches « aide mémoire » (ou cheat sheets) qui vous permettront de réaliser facilement les principales analyses biostatistiques avec le logiciel R et pour être informés des mises à jour du site.
9 Responses
Bonjour Claire,
Abonné aux nouvelles de votre blog, j’en apprécie la clarté et l’approche. Cette nouvelle publication n’y fait pas défaut et je vous en remercie.
Je travaille à la préparation de jeux de données cliniques pour un hôpital. Ces données sont soit consommées directement pour des études médico-économiques, soit travaillées par des équipes de recherches disposant de techniques statistiques avancés. L’un des jeux de données concerne l’anesthésie. Il couvre les phase préparatoire (consultation d’anesthésie), l’anesthésie en elle même et la phase d’hospitalisation. Il comprend actuellement 100.000 enregistrements et 800 variables.
L’une des difficultés que nous rencontrons dans son élaboration porte sur sa validation. Par exemple, une même information peut être saisie plusieurs fois et se retrouver dans différentes variables, laquelle choisir ? Ou encore, une information est issue d’une collecte automatisée (équipement biomédical par exemple). Est elle fiable (à priori oui) ? La notion de fiabilité de la donnée, découlant du processus de collecte me parait intéressante, pour la validation des données et pour la robustesse de leur interprétation. Or la validation de suppose de décrire et de comprendre le processus métier produisant les données.
Une méthode décrivant les points à documenter, à vérifier, d’un point de vue métier, puis d’un point de vue statistique me serait utile. Les quelques références que j’ai trouvées portent uniquement sur des méthodes de validations statistiques (https://cran.r-project.org/web/packages/validate/vignettes/JSS_3483.pdf ou https://ec.europa.eu/eurostat/cros/system/files/methodology_for_data_validation_v1.0_rev-2016-06_final.pdf) mais je n’en ai pas vu portant sur la description du processus de collecte et son analyse pour estimer la fiabilité des données.
Si ce sujet vous intéressait, je serais très preneur de votre avis et recommandations.
Au plaisir de vous lire
Bernard Trillat
Bonjour Bernard,
Je n’ai jamais été confronté à une telle quantité de données à valider ! Je n’ai donc pas de pistes à vous indiquer sur la validation « métier ».
Le sujet est très intéressant, mais il est « immense »….
En tout cas, merci pour le partage des références, je suis sûre qu’elles seront très utiles à d’autres.
Est-ce que vous utilisez ce package « validate » ?
Bonne continuation.
Merci beaucoup.
Cordialement
juste pour le fun
dd %>%
rename_with(str_to_lower) %>%
clean_names()
bonne journée
Merci Xavier !
« Super article qui aide beaucoup pour la pratique! Aymeric Inpong »
Bonjour
Je suis totalement ravi de votre présentation
Je souhaiterais que vous m’aidiez à apprendre comment le high frequency check dans Rstudio
Merci
Bonjour,
vous trouverez des informations ici : https://unhcr.github.io/HighFrequencyChecks/docs/index.html
Bonne continuation
Super tuto