Nettoyer les données sous R : 7 situations courantes

nettoyer ses données avec R
Image par mohamed Hassan de Pixabay

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) 

Table des matières

Avant le nettoyage

Afficher les données

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) 
nettoyer les données avec R

Ici, nous pouvons voir :

  • que le dataset contient 9 variables, sans aucune homogénéité dans leur nom : certaines sont totalement en majuscule, d’autres partiellement et d’autres pas du tout
  • la présence d’une variable 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.
  • la présence d’une variable NOM_Prenom qui contient le nom et le prénom attachés ensemble, mais séparé par un underscore

.

Etudiez la structure des données

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 :

  • la variable NOM_Prenom est un facteur, on souhaiterait plutôt une chaîne de caractères
  • la variable gender contient 7 modalités alors qu’on en attend plutôt 2
  • la variable Serum.cholestoral est de type factor alors qu’on s’attendrait à une variable de type numeric ou integer.

Uniformiser le nom des variables

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" 

Nettoyer les modalités des variables catégorielles (levels)

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 :

  • les levels (modalités) manquent d’homogénéité : certaines contiennent une majuscule au début, d’autres non
  • des espaces se sont parfois glissés au début ou à la fin, par exemple :  » male“,”female « .

Dans cette situation, la solution que je préfère consiste à :

    • uniformiser l’emploi des majuscules/minuscule (par exemple en mettant tout en minuscule), avec 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" 

Ajouter des 0 dans un code postal

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..

Séparer une variable en deux variables

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 

Remplacer des underscores par des points, ou inversement

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" 

Remplacer une valeur (numeric ou character) par NA

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 : 

 

nettoyer les données avec R

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 

Transformer une date mal convertie par Excel

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" ... 

Conclusion

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.

Pousruivez votre lecture

9 Responses

  1. 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

    1. 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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Aide mémoire off'R ;)

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.