Explorez vos données avant de les modéliser
Vous recevez un fichier de 891 lignes et 12 colonnes décrivant les passagers du Titanic, et l’on vous demande de prédire qui a survécu. La tentation est grande de lancer tout de suite un algorithme. Mauvaise idée : sans regarder les données, vous ne saurez pas qu’un cinquième des âges manque, que la colonne des cabines est presque vide, ou que le numéro de passager n’a aucun sens pour la survie.
Ce chapitre vous apprend à faire connaissance avec un jeu de données : d’où il vient, de quels types sont ses variables, comment il est structuré, et ce que révèle une première exploration. Nous suivrons le Titanic, un jeu de données public très utilisé en enseignement, tout au long de la partie.
Des données propres n’existent (presque) pas
Section intitulée « Des données propres n’existent (presque) pas »Commençons par deux mots de vocabulaire. Une variable est une caractéristique mesurée ou observée : la taille, l’âge, la couleur des yeux. Une donnée est la valeur d’une variable pour un individu à un moment donné : « 176 cm, mesuré le 6 juin ». Un tableau de 3 dates et 4 variables contient donc 12 données.
Dans un monde idéal, toutes ces données seraient justes, complètes et bien rangées. En pratique, les problèmes sont nombreux, et il est utile de les classer pour ne rien oublier :
| Famille de problèmes | Exemples |
|---|---|
| Qualité | valeurs manquantes, erronées, dupliquées, dates incohérentes, valeurs aberrantes |
| Biais | échantillon non représentatif (biais de sélection) |
| Temporalité | données obsolètes, fréquence de collecte trop rare ou irrégulière |
| Structure et format | sources hétérogènes, texte libre, encodage des caractères (UTF-8 ou ISO-8859-1) |
| Accès | droits d’utilisation, confidentialité, données incomplètes |
| Facteur humain | erreurs de saisie, observateurs qui ne notent pas de la même façon |
Le prétraitement (preprocessing) a un objectif simple : rendre les données aussi propres que possible, et surtout exploitables par l’algorithme qui viendra ensuite. C’est souvent l’étape la plus longue d’un projet, et celle qui a le plus d’effet sur le résultat.
Connaître la source des données
Section intitulée « Connaître la source des données »Sans données, pas d’apprentissage ; et la qualité des données dépend directement de leur collecte. Les données peuvent venir :
- de capteurs : électrocardiogramme, station météo, objets connectés ;
- d’humains : formulaires, sondages, enquêtes, avec leurs erreurs de saisie ;
- de machines : journaux de serveurs (logs), transactions bancaires ;
- du web, par extraction automatique de pages (web scraping).
Elles sont ensuite stockées dans une base relationnelle (PostgreSQL, MySQL), une base NoSQL orientée documents (MongoDB), derrière une API (celle d’un service météo, par exemple), dans des fichiers plats (CSV, JSON, XML), ou proviennent de portails publics (Kaggle, UCI, open data) et des systèmes internes d’une entreprise (CRM, ERP).
Pourquoi s’en soucier ? Pour trois raisons. La source permet d’évaluer la qualité (un capteur a une précision connue, un formulaire libre beaucoup moins). Elle conditionne la légalité de l’usage (droits, RGPD, données personnelles). Et elle oriente le prétraitement : on ne nettoie pas un flux de capteurs comme un sondage.
Identifiez le type de chaque variable
Section intitulée « Identifiez le type de chaque variable »Le type d’une variable décide de presque tout : les prétraitements possibles, les variables qu’on peut construire à partir d’elle et les algorithmes qui l’acceptent.
| Type | Sous-type | Exemples |
|---|---|---|
| Numérique | continu (une infinité de valeurs possibles) | vitesse, taille, prix payé |
| Numérique | discret (valeurs dénombrables) | valeur d’un dé, nombre d’enfants |
| Catégoriel | nominal (aucun ordre) | forme, port d’embarquement |
| Catégoriel | ordinal (un ordre naturel) | petit, moyen, grand ; 1re, 2e, 3e classe |
| Texte | libre | avis clients, nom d’un passager |
| Temporel | date, heure | date d’achat, heure d’une photo |
Appliquons cette grille au Titanic :
| Colonne | Signification | Type |
|---|---|---|
PassengerId | numéro de ligne | identifiant (aucune information) |
Survived | survie (0 ou 1) | catégoriel nominal (c’est la cible) |
Pclass | classe du billet | catégoriel ordinal (1re plus luxueuse que 3e) |
Sex, Embarked | sexe, port d’embarquement (C, Q, S) | catégoriel nominal |
Age, Fare | âge, prix du billet | numérique continu |
SibSp, Parch | frères, sœurs ou conjoints à bord ; parents ou enfants à bord | numérique discret |
Name, Ticket, Cabin | nom, numéro de billet, code de cabine | texte |
Reconnaissez la structure des données
Section intitulée « Reconnaissez la structure des données »Au-delà du type de chaque colonne, il y a la forme d’ensemble des données.
Les données structurées ont un format prédéfini, le plus souvent un tableau : une ligne par exemple, une colonne par variable, et chaque variable a un sens à elle seule. Elles sont faciles à interroger et demandent peu de préparation, mais elles sont rigides à la saisie. Régressions, arbres de décision et la plupart des méthodes de ce cours travaillent sur elles.
Les données non structurées n’ont pas de format fixe : images, sons, textes. Elles sont riches, mais un pixel ou un mot isolé n’a pas de sens seul ; on les traite plutôt avec des réseaux de neurones (convolutifs pour les images, récurrents ou transformers pour les séquences).
Entre les deux, on rencontre des structures usuelles :
- les tableaux (le Titanic) ;
- les images : une grille de pixels, une matrice de valeurs entre 0 et 255 en niveaux de gris, trois matrices (rouge, vert, bleu) en couleur ;
- les séries temporelles : des mesures à intervalles réguliers, comme la consommation électrique heure par heure ;
- les textes : des suites de mots ou de caractères ;
- les graphes : des entités reliées entre elles, comme un réseau social ou un graphe de connaissances.
La structure choisit pour vous une bonne partie du nettoyage, des transformations et des algorithmes. Dans ce cours, nous travaillerons surtout sur des tableaux, et, au dernier chapitre, sur des coordonnées GPS et des tags textuels.
Menez l’analyse exploratoire
Section intitulée « Menez l’analyse exploratoire »L’analyse exploratoire des données (exploratory data analysis, EDA) a deux objectifs : comprendre la distribution de chaque variable et repérer les relations entre variables. Au passage, elle fait apparaître les problèmes : valeurs manquantes, aberrantes, incohérentes. Ses outils sont simples : statistiques descriptives (moyenne, médiane, écart-type, quartiles) et graphiques (histogrammes, boîtes à moustaches, nuages de points).
Avec pandas, quatre lignes suffisent pour une première vue d’ensemble :
import pandas as pd
df = pd.read_csv("titanic.csv")
df.info() # nombre de lignes, types Python et valeurs non nulles par colonnedf.isna().sum() # nombre de valeurs manquantes par colonnedf.describe() # moyenne, écart-type, quartiles des colonnes numériquesdf["Embarked"].value_counts(dropna=False) # effectif de chaque modalité, manquants comprisSur le Titanic, le bilan est instructif : 891 passagers et 12 colonnes. Age n’a que 714 valeurs renseignées, il en manque donc 177, soit environ 20 %. Cabin en a 687 manquantes (77 %) et Embarked seulement 2. Ce simple comptage dicte déjà trois stratégies différentes, que nous verrons au chapitre suivant.
On regarde ensuite chaque variable, avec le graphique adapté à son type :
import matplotlib.pyplot as plt
df["Age"].plot.hist(bins=30, alpha=0.7) # variable continue : histogrammeplt.show()df["Survived"].value_counts().plot.bar() # variable binaire : diagramme en barresplt.show()df.boxplot(column="Fare") # boîte à moustaches : repère les valeurs extrêmesplt.show()Enfin, on étudie le lien entre chaque variable et la cible. Le coefficient de corrélation de Pearson est un premier indicateur :
d = df.copy()d["Sex"] = (d["Sex"] == "male").astype(int) # homme codé 1, femme codé 0d[["Survived", "Sex", "Pclass", "Age"]].corr()["Survived"]On obtient environ −0,54 pour le sexe, −0,34 pour la classe et −0,08 pour l’âge. Le signe se lit avec le codage : être un homme (codé 1) va avec une survie plus faible, et un numéro de classe élevé (3e classe) aussi. L’âge semble presque sans lien.
En résumé
Section intitulée « En résumé »- Des données réelles souffrent de problèmes de qualité, de biais, de temporalité, de format, d’accès et d’erreurs humaines ; le prétraitement les rend exploitables.
- Connaître la source des données permet d’en juger la qualité et la légalité, et d’adapter le nettoyage.
- Chaque variable a un type (numérique continu ou discret, catégoriel nominal ou ordinal, texte, temporel) qui ne se déduit pas de son type informatique.
- Les données sont structurées (tableaux) ou non structurées (images, textes, sons), avec des structures intermédiaires comme les séries temporelles et les graphes.
- L’analyse exploratoire combine statistiques descriptives et graphiques ; sur le Titanic, elle révèle 20 % d’âges et 77 % de cabines manquants.
- Une corrélation faible n’exclut pas un lien non linéaire : regardez toujours les graphiques.
Exercices
Section intitulée « Exercices »Exercice · Typer les variables d'un fichier
facileUne application de covoiturage vous fournit un tableau avec les colonnes suivantes : identifiant du trajet, ville de départ, date et heure de départ, nombre de places proposées, prix par passager (en euros), note du conducteur (de 1 à 5 étoiles), commentaire libre du dernier passager. Donnez le type de chaque colonne.
Voir la solution
- Identifiant du trajet : identifiant, sans information utile pour un modèle.
- Ville de départ : catégorielle nominale (aucun ordre entre les villes).
- Date et heure de départ : temporelle ; on pourra en extraire le jour de la semaine, l’heure, le mois.
- Nombre de places : numérique discrète.
- Prix par passager : numérique continue.
- Note du conducteur : catégorielle ordinale (5 étoiles vaut mieux que 4). On la traite parfois comme numérique, mais l’écart entre 1 et 2 étoiles n’est pas forcément le même qu’entre 4 et 5.
- Commentaire : texte libre, qui demandera un traitement spécifique (nettoyage, vectorisation).
En code
import pandas as pd
trajets = pd.DataFrame({ "id_trajet": [101, 102, 103], "ville_depart": ["Rennes", "Nantes", "Rennes"], "depart": ["2024-05-03 08:15", "2024-05-04 17:40", "2024-05-06 07:05"], "nb_places": [3, 2, 4], "prix": [12.5, 9.0, 15.0], "note": [4, 5, 3], "commentaire": ["Très ponctuel", "Voiture propre", "Un peu de retard"],})# Typage « à la main », selon le sens de chaque colonnetrajets["id_trajet"] = trajets["id_trajet"].astype(str) # identifiant, pas un nombretrajets["ville_depart"] = trajets["ville_depart"].astype("category") # nominaletrajets["depart"] = pd.to_datetime(trajets["depart"]) # temporelletrajets["note"] = pd.Categorical(trajets["note"], categories=[1, 2, 3, 4, 5], ordered=True) # ordinaleprint(trajets.dtypes)print(trajets["depart"].dt.day_name().tolist()) # ce qu'on extrait d'une dateprint(trajets["note"].max()) # l'ordre des notes est connu# id_trajet: str, ville_depart: category, depart: datetime64, nb_places: int64,# prix: float64, note: category (ordonnée), commentaire: str# ['Friday', 'Saturday', 'Monday'] ; note max : 5trajets <- data.frame( id_trajet = c("101", "102", "103"), # identifiant, gardé en texte ville_depart = factor(c("Rennes", "Nantes", "Rennes")), # nominale depart = as.POSIXct(c("2024-05-03 08:15", "2024-05-04 17:40", "2024-05-06 07:05"), tz = "UTC"), # temporelle nb_places = c(3L, 2L, 4L), # numérique discrète prix = c(12.5, 9.0, 15.0), # numérique continue note = factor(c(4, 5, 3), levels = 1:5, ordered = TRUE), # ordinale commentaire = c("Très ponctuel", "Voiture propre", "Un peu de retard"))str(trajets) # chr, Factor, POSIXct, int, num, Ord.factor 1 < 2 < ... < 5, chrformat(trajets$depart, "%u") # jour de la semaine (1 = lundi) : "5" "6" "1"max(trajets$note) # 5 : l'ordre des notes est connuExercice · Mesurer l'ampleur des valeurs manquantes
facileSur les 891 passagers du Titanic, df.info() indique 714 valeurs non nulles pour Age, 204 pour Cabin et 889 pour Embarked. Calculez le pourcentage de valeurs manquantes de chaque colonne. Si vous supprimiez toutes les lignes où il manque au moins une de ces trois valeurs, combien de passagers resteraient au plus ?
Voir la solution
Age: manquantes, soit .Cabin: manquantes, soit .Embarked: manquantes, soit environ .
Une ligne complète doit au minimum avoir une cabine renseignée : il resterait donc au plus 204 passagers, soit moins d’un quart du jeu de données (et en réalité un peu moins, car certains passagers avec cabine n’ont pas d’âge). Supprimer les lignes incomplètes serait ici désastreux ; il faudra traiter chaque colonne à part.
En code
n = 891non_nuls = {"Age": 714, "Cabin": 204, "Embarked": 889}
for colonne, k in non_nuls.items(): manquants = n - k print(f"{colonne}: {manquants} manquantes, {100 * manquants / n:.1f} %")# Age: 177 manquantes, 19.9 %# Cabin: 687 manquantes, 77.1 %# Embarked: 2 manquantes, 0.2 %
# Une ligne complète doit avoir les trois valeurs : au plus le minimum des non nulsprint(min(non_nuls.values())) # 204n <- 891non_nuls <- c(Age = 714, Cabin = 204, Embarked = 889)
manquants <- n - non_nulsround(100 * manquants / n, 1) # Age 19.9 Cabin 77.1 Embarked 0.2manquants # 177 687 2
# Une ligne complète doit avoir les trois valeurs : au plus le minimum des non nulsmin(non_nuls) # 204Exercice · Interpréter des corrélations
moyenUn collègue affirme : « La corrélation entre l’âge et la survie vaut −0,08, donc l’âge ne sert à rien, je le supprime. Et la classe a une corrélation de −0,34 : plus on paie cher, moins on survit. » Relevez ses deux erreurs.
Voir la solution
- Sur l’âge : une corrélation de Pearson proche de 0 signifie seulement qu’il n’y a pas de lien linéaire. Or l’effet de l’âge est non linéaire : les enfants ont eu un taux de survie nettement plus élevé, puis l’effet s’estompe. Un modèle comme un arbre de décision peut exploiter ce seuil. Il faut regarder la survie par tranche d’âge avant de conclure.
- Sur la classe : il lit le signe à l’envers.
Pclassvaut 1 pour la classe la plus chère et 3 pour la moins chère. Une corrélation négative signifie que plus le numéro de classe est grand (donc moins le billet est cher), plus la survie est faible. Les passagers de 1re classe ont en fait mieux survécu. Au passage,Pclassest ordinale : la corrélation linéaire n’en donne qu’un résumé grossier.
En code
import numpy as npimport pandas as pd
rng = np.random.default_rng(0)n = 2000age = rng.uniform(0, 70, n)# Effet non linéaire simulé : les enfants survivent bien plus, puis plus d'effetp_survie = np.where(age < 12, 0.65, 0.35)survie = rng.random(n) < p_survieprint(round(np.corrcoef(age, survie)[0, 1], 2)) # -0.16 : lien linéaire faible
tranches = pd.cut(age, [0, 12, 30, 50, 70])print(pd.Series(survie).groupby(tranches, observed=True).mean().round(2))# (0, 12] 0.67 ; (12, 30] 0.34 ; (30, 50] 0.34 ; (50, 70] 0.35 : un seuil net
# Lecture du signe avec Pclass : 1 = classe chère, 3 = bon marchéclasse = np.array([1, 1, 2, 3, 3, 3])survie_cl = np.array([1, 1, 1, 0, 0, 1])print(round(np.corrcoef(classe, survie_cl)[0, 1], 2)) # -0.66 : la 1re classe survit mieuxset.seed(0)n <- 2000age <- runif(n, 0, 70)# Effet non linéaire simulé : les enfants survivent bien plus, puis plus d'effetp_survie <- ifelse(age < 12, 0.65, 0.35)survie <- runif(n) < p_survieround(cor(age, survie), 2) # -0.14 : lien linéaire faible
tranches <- cut(age, c(0, 12, 30, 50, 70))round(tapply(survie, tranches, mean), 2) # 0.64 0.36 0.36 0.38 : un seuil net à 12 ans
# Lecture du signe avec Pclass : 1 = classe chère, 3 = bon marchéclasse <- c(1, 1, 2, 3, 3, 3)survie_cl <- c(1, 1, 1, 0, 0, 1)round(cor(classe, survie_cl), 2) # -0.66 : la 1re classe survit mieux