Aller au contenu

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.

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èmesExemples
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 formatsources hétérogènes, texte libre, encodage des caractères (UTF-8 ou ISO-8859-1)
Accèsdroits d’utilisation, confidentialité, données incomplètes
Facteur humainerreurs 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.

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.

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.

TypeSous-typeExemples
Numériquecontinu (une infinité de valeurs possibles)vitesse, taille, prix payé
Numériquediscret (valeurs dénombrables)valeur d’un dé, nombre d’enfants
Catégorielnominal (aucun ordre)forme, port d’embarquement
Catégorielordinal (un ordre naturel)petit, moyen, grand ; 1re, 2e, 3e classe
Textelibreavis clients, nom d’un passager
Temporeldate, heuredate d’achat, heure d’une photo

Appliquons cette grille au Titanic :

ColonneSignificationType
PassengerIdnuméro de ligneidentifiant (aucune information)
Survivedsurvie (0 ou 1)catégoriel nominal (c’est la cible)
Pclassclasse du billetcatégoriel ordinal (1re plus luxueuse que 3e)
Sex, Embarkedsexe, port d’embarquement (C, Q, S)catégoriel nominal
Age, Fareâge, prix du billetnumérique continu
SibSp, Parchfrères, sœurs ou conjoints à bord ; parents ou enfants à bordnumérique discret
Name, Ticket, Cabinnom, numéro de billet, code de cabinetexte
variablenumériquecatégorielletextetemporellecontinueAgeFarediscrèteSibSpParchnominaleSurvivedSexEmbarkedordinalePclassNameTicketCabinaucune colonnedans ce fichierPclass : stockée comme un entier,mais catégorie ordonnéePassengerId : identifiant,aucune information
Les colonnes du Titanic rangées par type statistique. Pclass est un entier en mémoire, mais c'est une catégorie ordonnée ; PassengerId ne porte aucune information.

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.

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 colonne
df.isna().sum() # nombre de valeurs manquantes par colonne
df.describe() # moyenne, écart-type, quartiles des colonnes numériques
df["Embarked"].value_counts(dropna=False) # effectif de chaque modalité, manquants compris

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

020406080100part des 891 passagers sans valeur (%)CabinAgeEmbarkedSurvived, Pclass, Sex,SibSp, Parch, Fare…687 manquantes (77,1 %)177 manquantes (19,9 %)2 manquantes (0,2 %)complètes
Trois colonnes, trois ordres de grandeur : Cabin est presque vide, un âge sur cinq manque, Embarked n'a que 2 trous.

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 : histogramme
plt.show()
df["Survived"].value_counts().plot.bar() # variable binaire : diagramme en barres
plt.show()
df.boxplot(column="Fare") # boîte à moustaches : repère les valeurs extrêmes
plt.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é 0
d[["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.

−0,6−0,4−0,20,00,2corrélation de Pearson avec SurvivedSex (homme = 1)Pclass (3e = 3)Age−0,54−0,34−0,08presque nulle…mais pas sans lien
Le sexe et la classe sont nettement liés à la survie ; l'âge a une corrélation presque nulle, ce qui ne veut pas dire qu'il n'a aucun effet.
010203040506070âge (années), tranches de 5 ans0,00,20,40,60,8taux de surviedroite de Pearson (r ≈ −0,16)enfants : ≈ 65 %autres âges : ≈ 35 %
Données simulées comme dans l'exercice 3 : un seuil net à 12 ans, que la droite de Pearson (r ≈ −0,16) résume mal. Un effet en marche d'escalier n'est pas une droite.
  • 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.

Exercice · Typer les variables d'un fichier

facile

Une 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 colonne
trajets["id_trajet"] = trajets["id_trajet"].astype(str) # identifiant, pas un nombre
trajets["ville_depart"] = trajets["ville_depart"].astype("category") # nominale
trajets["depart"] = pd.to_datetime(trajets["depart"]) # temporelle
trajets["note"] = pd.Categorical(trajets["note"], categories=[1, 2, 3, 4, 5], ordered=True) # ordinale
print(trajets.dtypes)
print(trajets["depart"].dt.day_name().tolist()) # ce qu'on extrait d'une date
print(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 : 5

Exercice · Mesurer l'ampleur des valeurs manquantes

facile

Sur 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 : 891−714=177891 - 714 = 177 manquantes, soit 177/891≈19,9 %177 / 891 \approx 19{,}9\,\%.
  • Cabin : 891−204=687891 - 204 = 687 manquantes, soit 687/891≈77,1 %687 / 891 \approx 77{,}1\,\%.
  • Embarked : 891−889=2891 - 889 = 2 manquantes, soit environ 0,2 %0{,}2\,\%.

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 = 891
non_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 nuls
print(min(non_nuls.values())) # 204

Exercice · Interpréter des corrélations

moyen

Un 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
  1. 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.
  2. Sur la classe : il lit le signe à l’envers. Pclass vaut 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, Pclass est ordinale : la corrélation linéaire n’en donne qu’un résumé grossier.

En code

import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
n = 2000
age = rng.uniform(0, 70, n)
# Effet non linéaire simulé : les enfants survivent bien plus, puis plus d'effet
p_survie = np.where(age < 12, 0.65, 0.35)
survie = rng.random(n) < p_survie
print(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 mieux