Accueil des références STT-1100 · Modules 1 à 4 · Copie hors ligne du 2 octobre 2026

Aventure 4 - Nettoyer pour mieux assurer

STT-1100 Introduction à la science des données

Mise en situation

Vous venez tout juste de commencer un stage comme ingénieur·e de données junior dans une grande compagnie d’assurance. Vous travaillez avec Alex, un·e analyste d’affaires expérimenté·e, qui vous a confié une mission importante.

Alex vous a fourni une base de données extraite d’un ancien système d’archives. Cette base contient des informations clés sur les clients, les polices d’assurance et les réclamations. Malheureusement, la base est truffée d’erreurs potentielles : valeurs manquantes, incohérences, erreurs de format, fautes de frappe, valeurs aberrantes et doublons possibles à vérifier.

Votre rôle sera d’assurer la qualité des données pour permettre à Alex de faire une analyse fiable. Certaines erreurs seront corrigées; d’autres seront seulement signalées si l’information disponible ne permet pas de choisir une correction défendable.

Carte de visite

Votre rôle Ingénieur·e de données junior

Interlocuteur Alex, responsable de l’équipe analytique

Organisation et contexte Compagnie d’assurance fictive

Mission Nettoyer une archive client et documenter les décisions de correction

Données dataset_pratique.csv

Livrable donnees_propres.csv, journal_nettoyage.Rdata et rapport Quarto

Objectifs de l’aventure

  • Importer une base de données brute (dataset_pratique.csv).
  • Identifier différents types d’erreurs courantes.
  • Nettoyer et transformer les données avec dplyr, forcats et stringr.
  • Documenter de façon claire toutes les modifications apportées à la base.

Comment réussir cette aventure ?

À la fin de cette aventure, vous devrez :

  1. Fournir une base de données propre (donnees_propres.csv).
  2. Maintenir une liste R structurée appelée journal_nettoyage dans votre script.
  3. Sauvegarder cette liste dans un fichier journal_nettoyage.Rdata.

Pensez à documenter chaque étape de votre nettoyage. Votre rigueur est votre meilleure alliée !

Typologie des erreurs et structure de la liste

Voici un tableau de référence pour les types d’erreurs que vous pouvez rencontrer :

Code Type d’erreur Description Exemple
VM Valeurs manquantes Champs essentiels non remplis Lignes sans valeur pour age ou revenu_annuel
DF Doublons Lignes identiques ou très similaires Deux lignes avec le même id_client
IF Incohérences de format Données du même type mais formats différents date_naissance en formats mixtes (1980-01-01, 01/01/1980)
VA Valeurs aberrantes Valeurs manifestement extrêmes ou irréalistes revenu_annuel = 99,999,999 $
FT Fautes de frappe Erreurs typographiques affectant la cohérence Province inscrite comme “Quebéc”, “quebec”, “Québec”
RC Recodage ou regroupement Catégories similaires à fusionner “sans emploi”, “inactif”, “au chômage”
TY Types mal définis Mauvais type de variable pour les données revenu_annuel enregistré comme texte
LG Erreurs logiques Relations temporelles ou conditionnelles incorrectes Client né en 2022 mais contrat signé en 2020
CI Incohérences inter-variables Données incohérentes entre deux colonnes age = 20 mais years_licensed = 30
RU Données redondantes ou inutiles Colonnes dupliquées ou peu pertinentes Deux colonnes contenant la même information
TR Troncations ou mauvaise fusion Chaînes de texte tronquées ou mal fusionnées Noms de famille coupés comme “Du”
EC Problèmes d’encodage Caractères spéciaux mal encodés “é” au lieu de “é”

Les exemples du tableau sont génériques : toutes ces variables et toutes ces erreurs ne sont pas présentes dans le fichier fourni.

Cette typologie permet de structurer rigoureusement la détection et la documentation des erreurs dans le journal de nettoyage R (liste journal_nettoyage).

Dans votre script, vous allez construire la liste journal_nettoyage structurée par catégorie :

journal_nettoyage <- list(
  VM = list(),
  DF = list(),
  IF = list(),
  VA = list(),
  FT = list(),
  RC = list(),
  TY = list(),
  LG = list(),
  CI = list(),
  RU = list(),
  TR = list(),
  EC = list()
)
journal_nettoyage
$VM
list()

$DF
list()

$IF
list()

$VA
list()

$FT
list()

$RC
list()

$TY
list()

$LG
list()

$CI
list()

$RU
list()

$TR
list()

$EC
list()

Chaque catégorie (VM, DF, etc.) contient une liste de corrections, où chaque correction est un list() avec :

  • id : valeur(s) de id_variable concernée(s), conservées comme texte; évitez les positions de lignes, qui changent après un tri ou un filtre

  • variables : les variables concernées

  • probleme : nature du problème

  • action : action effectuée

  • justification : pourquoi cette action a été choisie

Cette structure vous aidera à rendre compte de vos modifications de façon propre et professionnelle.

GitHub et rendu

Comme pour les aventures précédentes :

  • Clonez le dépôt GitHub du module 4 depuis l’organisation du cours. Vous pouvez utiliser la cheat sheet du cours si vous avez un trou de mémoire.
  • Travaillez dans RStudio et faites des commits régulièrement pour documenter votre avancement.
  • Votre dépôt doit contenir :
    • le script .qmd de votre aventure, c’est-à-dire un document où vous faites vos tests et construisez votre liste journal_nettoyage ;
    • la liste journal_nettoyage dans un objet .Rdata ;
    • la base de données nettoyée au format .csv.

Bonne chance, et que vos données soient propres !

Travaillez dans votre dépôt personnel aventure-4-<votre-identifiant-GitHub> de l’organisation STT-1100-A26. Le dépôt modèle sert de point de départ. Si votre dépôt personnel n’est pas encore visible, signalez-le à l’équipe enseignante. Ouvrez le projet aventure-4.Rproj, puis complétez defi_04.qmd au fil de l’aventure. Le défi reprend ce même travail.

Importation des données

Avant de nettoyer une base de données, il faut savoir l’importer correctement. Pour cette mission, Alex vous a transmis le fichier dataset_pratique.csv. Ce fichier est séparé par des points-virgules. Il vous recommande de :

  • charger les données dans R avec la fonction read_delim() du package readr,
  • examiner les premières lignes pour repérer les incohérences évidentes,
  • et convertir immédiatement les noms de colonnes en minuscules avec janitor::clean_names() pour faciliter les manipulations à venir.
library(readr)
library(dplyr)
library(janitor)
library(ggplot2)
library(stringr)
library(forcats)

# Importation des données
base <- read_delim(
  "dataset_pratique.csv",
  delim = ";",
  locale = locale(encoding = "UTF-8", decimal_mark = "."),
  col_types = cols(ID_Variable = col_character(), .default = col_guess()),
  trim_ws = TRUE,
  show_col_types = FALSE
)

# Nettoyage des noms de colonnes
base <- base %>%
  clean_names()

# Aperçu des données
head(base)
glimpse(base)

Le fichier contient 101 778 lignes et 23 colonnes. Son encodage est compatible avec UTF-8 : le préciser à l’importation préserve notamment l’accent de « Montréal ». Le séparateur décimal est le point. Le type de ID_Variable est fixé dès l’importation pour préserver les identifiants. Conservez le fichier brut intact.

Version des données : 2026-09-25, avec 101 778 lignes et 23 colonnes avant nettoyage. Des anomalies ont été introduites volontairement pour cet exercice. Si vous aviez téléchargé une ancienne version, remplacez seulement le fichier d’entrée encore intact avant de commencer; n’écrasez pas un travail déjà réalisé.

Si vous utilisez read_csv() ici, tout le fichier sera lu comme une seule colonne. C’est un bon réflexe de vérifier ncol(base) juste après l’importation.

Alex : « Cette base est un vrai casse-tête ! J’ai besoin que tu la rendes exploitable rapidement. Tu devrais commencer par repérer ce qui cloche dans les noms, les formats ou les valeurs. »


Exploration initiale des problèmes potentiels

Format des variables

Maintenant que vous avez importé la base, il est temps de faire une première exploration pour identifier les erreurs potentielles. Voici quelques étapes clés à suivre :

# Dimensions de la base
nrow(base)
ncol(base)

# Variables disponibles
names(base)

# type des variables
glimpse(base)

Alex : « Tu verras, certains types des variables ne font aucun sens… Garde une trace de tout ce que tu trouves bizarre pour qu’on décide ensemble quoi en faire. »

L’une des fonctions les plus utiles ici est glimpse() (du package dplyr). Elle affiche un aperçu des premières valeurs de chaque variable ainsi que leur type : par exemple chr pour une chaîne de caractères (character), dbl pour un nombre décimal (double), int pour un entier, ou encore lgl pour un booléen.

Voici un rappel des principaux types de données en R :

  • character (chr) : texte

  • numeric / double (dbl) : nombres réels

  • integer (int) : nombres entiers

  • logical (lgl) : booléens (TRUE, FALSE)

  • factor : variable catégorielle à niveaux définis

  • Date : date au format standard R

Pour transformer une variable, vous pouvez utiliser les fonctions suivantes :

as.character(base$colonne)
as.numeric(base$colonne)
as.integer(base$colonne)
as.logical(base$colonne)
as.factor(base$colonne)

Pensez à toujours vérifier le résultat de la transformation avec glimpse() ou summary().

Alex : « Quand on regarde les types de variables avec glimpse(), il faut garder en tête qu’il y a des standards en science des données. En général, les variables contenant du texte devraient être de type character ou factor si elles prennent un nombre limité de valeurs (par exemple, une colonne sexe ou province). Les variables contenant des nombres devraient normalement être de type dbl (pour les décimaux) ou int (pour les entiers), selon le cas.

Un piège courant, c’est les identifiants ! Même s’ils ont l’air d’être des nombres, comme une colonne ID_client ou numero_contrat, ce ne sont pas des quantités sur lesquelles on va faire des calculs. Ce sont des étiquettes uniques. On devrait donc les convertir en character. Ça évite qu’un identifiant comme 0012 soit transformé en 12 par erreur, ou que R pense qu’on veut faire une moyenne avec ça…

Bref, vérifiez bien chaque type. Posez-vous la question : est-ce que cette variable est du texte ? Est-ce que je vais calculer dessus ? Est-ce que ce sont des catégories ? »

Vérification des doublons

Un des premiers réflexes à avoir dans toute opération de nettoyage : vérifier les doublons. Il peut s’agir :

  • de doublons complets (lignes identiques sur toutes les colonnes),
  • de doublons partiels (même identifiant, mais des valeurs légèrement différentes ailleurs).

Commencez par détecter les doublons complets avec :

Et pour les identifier :

base[duplicated(base), ]

Vous pouvez ensuite les retirer :

ids_doublons <- base$id_variable[duplicated(base)]
base <- base %>% distinct()

Si vous voulez repérer les doublons sur une ou plusieurs colonnes clés (ex. : id_variable), utilisez :

base %>%
  group_by(id_variable) %>%
  filter(n() > 1)

Alex : « Deux clients avec le même numéro, c’est louche. Regarde ce qui se passe. Supprimer aveuglément n’est pas toujours la bonne solution. Note bien ce que tu fais dans le journal_nettoyage ! »

Et bien sûr, si vous intervenez, n’oubliez pas de l’indiquer dans la section DF de votre journal_nettoyage. Si vous ne trouvez aucun doublon, vous pouvez simplement noter dans votre texte que la vérification a été faite et qu’aucune suppression n’était nécessaire.

if (length(ids_doublons) > 0) {
  journal_nettoyage$DF <- append(journal_nettoyage$DF, list(
    list(
      id = ids_doublons,
      variables = "Toutes les colonnes",
      probleme = "Doublons complets confirmés",
      action = "Suppression des occurrences supplémentaires",
      justification = "Conserver une occurrence de chaque ligne identique"
    )
  ))
}

Nettoyage des facteurs avec forcats

On continue l’exploration avec un type de variable souvent négligé… mais qui peut faire dérailler toute une analyse : les facteurs.

En R, les facteurs sont utilisés pour représenter des catégories. Par exemple, dans la colonne vehicle_type, chaque valeur correspond à un type de véhicule : "CAR", "TRUCK", "VAN", "ANIMAL", etc.

Pour voir l’ensemble des niveaux d’un facteur, vous pouvez utiliser la fonction levels() :

base <- base %>%
  mutate(vehicle_type = as_factor(vehicle_type))

levels(base$vehicle_type)

Bien sûr, il faut que votre variable soit de type factor pour que cette fonction fonctionne. Si ce n’est pas le cas, vous pouvez la convertir avec as.factor() (voir section précédente).

Le problème ?
Quand on regarde les niveaux d’un facteur, on remarque souvent des doublons déguisés ("car" vs "CAR"), des niveaux aberrants ("ANIMAL") ou des catégories très rares qui ne méritent peut-être pas leur propre niveau.

Quelques outils utiles (avec forcats)

Pour nettoyer tout ça, voici quelques fonctions clés du package forcats :

Étapes proposées

Prenez le temps de :

  1. Lister les variables de type facteur avec glimpse() ou select(where(is.factor)).
  2. Explorer les niveaux avec fct_count().
  3. Identifier les incohérences, comme :
    • mêmes valeurs avec casse différente ("CAR" vs "car")
    • fautes de frappe ou variantes de saisie
    • niveaux aberrants ("ANIMAL" dans vehicle_type)

Alex : « Faites attention à ces valeurs étranges. Si une valeur n’a aucun sens dans le contexte (par exemple "ANIMAL" dans une colonne sur les types de véhicules), ne cherchez pas à deviner. Mettez-la à NA. On préfère une donnée manquante qu’une mauvaise information. »

Exemple à adapter

lignes_animal <- base %>%
  filter(as.character(vehicle_type) == "ANIMAL") %>%
  select(id_variable, vehicle_type, vehicle_make, vehicle_model)

ids_casse <- base$id_variable[
  !is.na(base$vehicle_type) &
    as.character(base$vehicle_type) != str_to_title(as.character(base$vehicle_type))
]

# Harmoniser les minuscules/majuscules
base <- base %>%
  mutate(
    vehicle_type = vehicle_type %>%
      str_to_title() %>%                 # "CAR" -> "Car", "TRUCK" -> "Truck"
      na_if("Animal") %>%                # niveau aberrant dans ce contexte
      as_factor()
  )

Les lignes concernées ont été conservées avant la correction. Vous pouvez les consulter :

lignes_animal

Il ne faudra pas oublier de documenter ces modifications dans votre liste journal_nettoyage ! Par exemple, pour la variable vehicle_type ci-dessus, vous pourriez ajouter :

journal_nettoyage$RC <- append(journal_nettoyage$RC, list(
  list(
    id = lignes_animal$id_variable,
    variables = "vehicle_type",
    probleme = "Niveau aberrant dans une variable de type de véhicule",
    action = "Remplacement de 'ANIMAL' par NA",
    justification = "La valeur ne décrit pas un type de véhicule exploitable"
  )
))

Documentez aussi l’harmonisation de la casse, qui concerne toutes les lignes modifiées et pas seulement la valeur ANIMAL :

journal_nettoyage$IF <- append(journal_nettoyage$IF, list(
  list(
    id = ids_casse,
    variables = "vehicle_type",
    probleme = "Présentation des libellés à harmoniser",
    action = "Harmonisation de la casse avec str_to_title()",
    justification = "Convention de présentation sans changer le sens des catégories valides"
  )
))

Ce ne sont que des exemples. À vous d’explorer la base de données et de choisir ce qui est cohérent.

Autres catégories et caractères inattendus

Explorez aussi les modalités avant tout recodage :

base %>% count(multi_product, sort = TRUE)
base %>% count(marital_status, sort = TRUE)
base %>% count(vehicle_use, sort = TRUE)

La modalité Maybe est-elle compatible avec une variable attendue comme binaire ? Certains libellés contiennent-ils des caractères inattendus ? Distinguez une correction de texte défendable d’une valeur dont le sens reste incertain. Ne transformez pas automatiquement Maybe en No ou Yes.

Recette de nettoyage - Approfondissement

Bravo ! Vous avez déjà corrigé les types de variables et nettoyé les facteurs les plus visibles. Maintenant, on pousse le nettoyage plus loin, en croisant statistiques, relations logiques et comportements aberrants. Voici votre recette de nettoyage avancé.

Étape 1 – Statistiques descriptives sur les variables numériques

Tout comme on a vérifié les niveaux des facteurs, on doit vérifier si certaines valeurs numériques sont aberrantes.

Prenez chaque variable numérique et résumez-la avec summary() ou un boxplot pour identifier les extrêmes.

Exemple : commute_distance

summary(base$commute_distance)
ggplot(base, aes(x = commute_distance)) +
  geom_histogram(bins = 50, fill = "blue", alpha = 0.7) +
  labs(title = "Distribution de la distance de trajet domicile-travail",
       x = "Distance (km)", y = "Fréquence")

Posez-vous la question : une distance de 1 500 km pour se rendre au travail est-elle plausible dans ce contexte ? Est-ce que certaines valeurs sont manquantes ou négatives ? Ne déduisez pas automatiquement que la bonne valeur serait 150 : une correction exige une justification.

Si vous intervenez, n’oubliez pas de justifier dans journal_nettoyage, en utilisant le code VA (valeurs aberrantes) ou VM (valeurs manquantes).

Étape 2 – Combinaisons incohérentes de deux facteurs

Il peut exister des relations logiques entre deux variables catégoriques. Le trimestre (quarter) et la saison (season) peuvent être comparés, mais ils ne se correspondent pas un à un : un trimestre peut chevaucher deux saisons. Vérifiez la date et la convention utilisée avant de conclure à une erreur.

Exemple : quarter et season

table(base$quarter, base$season)

Alex : « Si tu vois quarter = "Q2" avec season = "Winter", tu devrais tiquer… Ce genre d’incohérence mérite d’être noté, même si tu ne sais pas quoi corriger. »

Vous pouvez aussi visualiser avec un graphique à barres croisé :

ggplot(base, aes(x = quarter, fill = season)) +
  geom_bar(position = "fill")

Si vous corrigez, utilisez le code CI (incohérences inter-variables).

Étape 3 – Statistiques groupées : facteur + numérique

Autre vérification : est-ce que certaines valeurs numériques sont incohérentes pour certains groupes ?

Exemple : age selon generation

base %>%
  group_by(generation) %>%
  summarise(min = min(age, na.rm = TRUE),
            max = max(age, na.rm = TRUE),
            mean = mean(age, na.rm = TRUE))

ggplot(base, aes(x = age, fill = generation)) +
  geom_histogram(position = "identity", alpha = 0.6, bins = 40)
  • Est-ce qu’un âge de 18 ans est cohérent avec la catégorie Traditionalist et l’année de naissance ?
  • Est-ce que les bornes d’âge de chaque génération semblent raisonnables ?

S’il y a recodage ou regroupement à faire, utilisez le code RC.

Étape 4 – Liens logiques entre deux variables numériques

Ici, on vérifie que les relations numériques ont du sens.

Exemple : age vs years_licensed

ggplot(base, aes(x = age, y = years_licensed)) +
  geom_point(alpha = 0.3)
  • Est-ce qu’il y a des points au-dessus d’une ligne où years_licensed = age ?
  • Peut-on avoir plus d’années de permis que d’âge ?

Alex : « Tu n’es pas obligé de corriger ici. Par contre, si tu remarques quelque chose d’étrange, prends deux minutes pour l’écrire dans le journal_nettoyage. Juste pour montrer que tu l’as vu, réfléchi, et que tu as pris une décision. C’est ça être rigoureux. »

Utilisez ici le code LG pour une erreur logique.

Étape 5 – Vérifications avancées (facultatives mais pros !)

Voici quelques autres choses à garder à l’œil :

  • Codes postaux incomplets ou trop longs : la colonne fsa_code devrait contenir trois caractères.
  • Années de véhicule improbables : une valeur comme 14 dans vehicle_year n’a pas le même sens que 2014.
  • Variables redondantes : deux colonnes qui disent la même chose.
  • Colonnes potentiellement inutiles : colonnes vides ou avec une seule modalité (RU). Conservez les identifiants nécessaires à la traçabilité.
  • Problèmes de format de texte : accents, caractères spéciaux (stringr::str_detect)
  • Colonnes fusionnées à la hâte : chaînes comme "Smith, John" dans une seule cellule au lieu de deux (TR)
  • Format de dates mélangés : cette vérification est facultative ici; n’imposez pas une conversion sans avoir identifié le format.

Tout au long de votre nettoyage, documentez vos décisions dans la liste journal_nettoyage. Le but n’est pas de tout corriger, mais de montrer que vous avez su repérer les problèmes, réfléchir, et intervenir quand nécessaire.

Travail réalisé

À la fin de l’aventure, vérifiez que votre travail comprend les éléments suivants. Il ne s’agit pas d’un portfolio séparé.

  • le fichier journal_nettoyage.Rdata;
  • le fichier donnees_propres.csv;
  • au moins trois corrections distinctes et cinq entrées de journal, incluant au besoin un signalement justifié;
  • une courte explication de la façon de reproduire le nettoyage.

Mission accomplie !

Bravo d’être allé·e jusqu’au bout de cette aventure !
Vous avez maintenant acquis une solide méthodologie pour nettoyer des données de manière rigoureuse et professionnelle. Vous avez :

  • identifié et corrigé des erreurs de format, de type et de cohérence ;
  • utilisé les outils de dplyr, stringr, forcats et ggplot2 pour explorer les données sous toutes leurs coutures ;
  • documenté chaque intervention dans une structure claire et transparente avec la liste journal_nettoyage.

Avant de terminer, n’oubliez pas de pousser sur GitHub les trois éléments suivants :

  1. Le script .qmd de votre aventure (là où vous avez effectué vos tests, analyses et nettoyage)
  2. La liste journal_nettoyage enregistrée dans un objet .Rdata
  3. La base de données nettoyée au format .csv

Dans les exemples de l’aventure, les corrections sont appliquées à base. À la fin, donnez le nom donnees_propres au résultat et sauvegardez les deux livrables. Si vous avez travaillé directement sur donnees_propres, ne le remplacez pas par une ancienne version de base.

donnees_propres <- base
write_csv(donnees_propres, "donnees_propres.csv")
save(journal_nettoyage, file = "journal_nettoyage.Rdata")

Alex : « Vous venez de faire ce que peu de gens font bien : nettoyer des données proprement, en gardant une trace de vos décisions. C’est ce qui distingue un·e vrai·e professionnel·le de quelqu’un qui bidouille. Chapeau ! »

GPT STT-1100