Cours Python · Module 8: Data science

EDA - la reconnaissance avant la chasse

7 min de lecture
Dans cette leçon7

Tu viens de recevoir un nouveau jeu d'observations de la réserve. Avant d'entraîner le moindre modèle dessus, tu dois faire ce que fait tout bon pisteur avant la chasse : reconnaître le terrain. L'EDA (Exploratory Data Analysis), l'analyse exploratoire des données, c'est exactement cette reconnaissance : tu réponds l'une après l'autre à des questions simples jusqu'à comprendre la structure des données, découvrir des motifs et des anomalies. Ce n'est pas entraîner un modèle de machine learning - ça vient après -, ni créer une interface utilisateur, ni déployer quoi que ce soit en production. C'est le repérage qui rend tout le reste possible. Parcourons ces questions une par une.

Question 1 : quelle est la taille du terrain ?

Le premier coup d'oeil porte toujours sur la taille et la forme des données - combien d'observations, combien de colonnes, à quoi ressemblent les premières lignes.

1import pandas as pd
2import numpy as np
3import matplotlib.pyplot as plt
4import seaborn as sns
5
6# 1. Charger les donnees
7df = pd.read_csv('safari_observations.csv')
8
9# 2. Verifier la forme et les infos
10print("Shape:", df.shape)   # (nombre de lignes, nombre de colonnes)
11print(df.head())            # les premieres observations
12print(df.info())            # types des colonnes et valeurs non nulles
13print(df.describe())        # statistiques des colonnes numeriques

Ces quelques lignes sont ton premier rapport de terrain : shape dit à quel point le jeu est grand, head montre ce qui est concrètement enregistré dans chaque colonne, info liste les types et le nombre de valeurs non nulles, et describe sort d'un coup les moyennes, les minimums et les maximums. Retiens cet enchaînement : d'abord charger les données, ensuite vérifier la forme et les infos. Avant même d'aller plus loin, tu sais déjà si tu as cent observations ou un million.

Question 2 : qu'est-ce qui vit ici ? (types de colonnes)

Un modèle ne traite pas les nombres comme les catégories. Tu dois donc séparer les colonnes numériques (population, poids) des colonnes catégorielles (espèce, habitat).

1print(df.dtypes)
2
3categorical = df.select_dtypes(include=['object', 'category']).columns
4numerical = df.select_dtypes(include=[np.number]).columns
5
6for col in categorical:
7    print(f"{col} : {df[col].nunique()} uniques")
8    print(df[col].value_counts().head())

dtypes donne le type brut de chaque colonne, select_dtypes répartit les colonnes en deux groupes, et value_counts compte les occurrences de chaque catégorie. C'est de la vraie reconnaissance : si la colonne espèce contient 3 valeurs, c'est bien une catégorie ; si elle en contient 10 000, c'est plutôt un identifiant, et le modèle ne devrait surtout pas le traiter comme une caractéristique.

Question 3 : où sont les trous ? (données manquantes)

Les vraies données de terrain sont trouées - une partie des observations est incomplète. Il faut savoir où et combien avant que ces trous n'abîment le modèle. Analyser les données manquantes se fait avec df.isnull().sum(), qui compte les valeurs manquantes colonne par colonne.

1missing = df.isnull().sum()
2missing_pct = (missing / len(df)) * 100
3missing_df = pd.DataFrame({'missing': missing, 'percent': missing_pct})
4print(missing_df[missing_df['missing'] > 0].sort_values('missing', ascending=False))
5
6sns.heatmap(df.isnull(), cbar=True, yticklabels=False)
7plt.title('Carte des valeurs manquantes')
8plt.show()

Le tableau dit quelles colonnes sont trouées et à quel point - le pourcentage compte plus que le nombre brut. La carte de chaleur ajoute l'image : si les trous forment des bandes, ce sont peut-être des lots entiers d'observations qui manquent, une piste sérieuse que la collecte a mal tourné, et non un hasard. Méfie-toi des noms qui sonnent juste mais n'existent pas : df.missing() et df.count_nulls() ne sont pas des méthodes pandas, et df.empty répond seulement si le DataFrame est vide, jamais où sont les trous.

Question 4 : qu'est-ce qui est normal, qu'est-ce qui dépasse ? (distributions)

Vient ensuite l'étape analyser les distributions : tu regardes les colonnes numériques une à une, comment les valeurs se répartissent et s'il y a des valeurs aberrantes - des observations qui ne ressemblent pas au reste du troupeau.

1df[numerical].hist(figsize=(15, 10), bins=30, edgecolor='black')
2plt.tight_layout()
3plt.show()
4
5def find_outliers_iqr(df, column):
6    Q1 = df[column].quantile(0.25)
7    Q3 = df[column].quantile(0.75)
8    IQR = Q3 - Q1
9    lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
10    return df[(df[column] < lower) | (df[column] > upper)]
11
12outliers = find_outliers_iqr(df, 'population')
13print(f"Valeurs aberrantes dans population : {len(outliers)}")

Les histogrammes montrent la forme de chaque distribution d'un seul regard. La fonction find_outliers_iqr applique la méthode IQR (écart interquartile) : on prend le premier quartile Q1 et le troisième Q3, et tout ce qui tombe à plus de 1,5 fois l'IQR en dessous ou au-dessus est signalé. C'est la méthode courante précisément parce qu'elle s'appuie sur les quartiles : une moyenne arithmétique serait elle-même tirée par les valeurs extrêmes que tu cherches à repérer. Attention : aberrant ne veut pas toujours dire faux. Un éléphant dans des données de poids dépasse naturellement les autres - à toi de décider si c'est une erreur à retirer ou un animal réel et important.

Question 5 : qu'est-ce qui bouge ensemble ? (corrélations)

Dernière étape du repérage numérique : analyser les corrélations, c'est-à-dire repérer les caractéristiques qui bougent ensemble - quand l'une monte, l'autre aussi. Cela indique ce qui aidera à prédire, et quelles colonnes racontent en réalité la même chose.

1correlation = df[numerical].corr()
2
3sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0, fmt='.2f')
4plt.title('Matrice de correlation')
5plt.show()
6
7corr_pairs = correlation.unstack().sort_values(ascending=False)
8print(corr_pairs[corr_pairs < 1].head(10))

La carte des corrélations colore les paires de caractéristiques : une valeur proche de 1 (ou de -1) signale un lien fort, une valeur proche de 0 aucun lien. unstack remet ces paires à plat pour lire directement le classement des liens les plus forts. À quoi bon le savoir ? Deux caractéristiques presque identiques - corrélation de 0,98 - portent la même information, et en garder une seule suffit généralement. À l'inverse, une caractéristique fortement liée à ce que tu veux prédire est ton meilleur candidat pour le modèle.

Question 6 : qu'est-ce qui distingue les groupes ?

La corrélation relie des nombres à des nombres. Mais la question la plus intéressante est souvent celle-ci : est-ce qu'une catégorie influence un nombre - la population change-t-elle selon l'habitat ? Le boxplot par catégorie est fait pour ça.

1for cat in categorical:
2    for num in numerical:
3        sns.boxplot(data=df, x=cat, y=num)
4        plt.title(f'{num} par {cat}')
5        plt.xticks(rotation=45)
6        plt.show()

Chaque boxplot compare la distribution d'une variable numérique groupe par groupe. Si les boîtes des différents habitats se placent à des hauteurs franchement différentes, c'est le signe que l'habitat explique vraiment la population - et qu'il vaut la peine de donner cette catégorie au modèle. Si les boîtes se chevauchent, la catégorie n'apporte pas grand-chose.

La reconnaissance tenue dans une seule fonction

Puisque tu poses les mêmes questions à chaque nouveau jeu de données, autant emballer ce premier rapport dans une fonction que tu lances juste après le chargement. Elle vérifie au passage les doublons avec df.duplicated().sum() : des lignes enregistrées deux fois fausseraient toutes les statistiques qui suivent.

1def eda_summary(df):
2    print("=" * 50)
3    print("RAPPORT EDA")
4    print("=" * 50)
5    print(f"Lignes : {df.shape[0]}")
6    print(f"Colonnes : {df.shape[1]}")
7    print(f"Donnees manquantes : {df.isnull().sum().sum()}")
8    print(f"Doublons : {df.duplicated().sum()}")
9    print(df.dtypes.value_counts())
10    print(df.describe())
11
12eda_summary(df)

Cette fonction rassemble les réponses aux questions 1 à 3 en un rapport express - taille, types, trous, doublons - pour que chaque nouveau jeu commence par la même reconnaissance éprouvée. Note le double sum() dans df.isnull().sum().sum() : le premier compte les manques par colonne, le second additionne ces comptes en un total unique.

De cette leçon, retiens l'ordre plutôt que les commandes : charger les données → vérifier la forme et les infos → analyser les données manquantes → analyser les distributions → analyser les corrélations. Tu n'entraînes un modèle qu'une fois cette reconnaissance terminée - sinon, tu chasses à l'aveugle sur un terrain que tu ne connais pas.

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quel est l'objectif principal de l'analyse exploratoire des données (EDA) ?

  2. 2. Comment vérifie-t-on le nombre de valeurs manquantes dans chaque colonne d'un DataFrame ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri vertical

    Remets dans le bon ordre les étapes d'un déroulé d'EDA typique :

  • Éditeur de code

    Utilise df.head(), df.info() et df.describe() pour explorer les données du Safari.

  • Clique dans l'ordre

    Clique sur les boutons pour vérifier le nombre de lignes en double :

Articles utiles