# Exercices
Ces exercices sont conçus pour vous faire pratiquer les concepts de pré-traitement de données avec `pandas`. La difficulté est progressive, allant de l'application directe à des problèmes plus complexes nécessitant une réflexion approfondie.
Pour tous les exercices, vous pouvez simuler le chargement d'un fichier CSV en utilisant `io.StringIO` et une chaîne de caractères Python, comme ceci :
```python
import pandas as pd
import io
csv_data = """col1,col2,col3
1,A,10.5
2,B,12.0
3,A,NaN
4,C,11.2
5,B,13.1
"""
df = pd.read_csv(io.StringIO(csv_data))
```
### Exercice 1 : Inspection Initiale d'un Dataset (Très Basique)
Vous disposez d'un jeu de données de transactions de vente. Votre première tâche est de le charger et d'en obtenir un aperçu rapide.
**Données :**
```csv
ID_Transaction,Produit,Quantite,Prix_Unitaire_HT,Date_Vente,Region
1001,Ordinateur Portable,2,800.0,2023-01-15,Nord
1002,Souris,5,25.0,2023-01-16,Sud
1003,Clavier,1,75.0,2023-01-15,Est
1004,Moniteur,NaN,200.0,2023-01-17,Ouest
1005,Webcam,3,50.0,2023-01-16,Nord
1006,Ordinateur Portable,1,800.0,2023-01-18,Sud
1007,Souris,2,25.0,2023-01-18,Est
1008,Clavier,NaN,75.0,2023-01-19,Ouest
1009,Moniteur,1,200.0,2023-01-19,Nord
1010,Webcam,4,50.0,2023-01-20,Sud
```
**Questions :**
1. Chargez ces données dans un DataFrame `pandas`.
2. Affichez les 5 premières lignes du DataFrame.
3. Affichez un résumé concis du DataFrame, incluant les types de données et les valeurs non nulles.
4. Affichez les dimensions (nombre de lignes et de colonnes) du DataFrame.
### Exercice 2 : Gestion Simple des Valeurs Manquantes (Très Basique)
En reprenant le DataFrame de l'Exercice 1, vous avez remarqué la présence de valeurs manquantes.
**Questions :**
1. Identifiez le nombre de valeurs manquantes par colonne.
2. Supprimez toutes les lignes qui contiennent au moins une valeur manquante.
3. Affichez les dimensions du DataFrame après la suppression pour vérifier le résultat.
### Exercice 3 : Nettoyage et Typage des Données (Normal)
Vous travaillez sur un dataset de clients et leurs achats. Certaines données sont incohérentes ou de types incorrects.
**Données :**
```csv
ID_Client,Nom_Client,Age,Revenu_Annuel,Statut_Fidele,Ville_Residence
1,Alice,30,50000,Oui,Paris
2,Bob,45,75000,Non,Lyon
3,Charlie,NaN,60000,Oui,Marseille
4,David,25,NaN,Non,Paris
5,Eve,35,80000,Oui,Toulouse
6,Frank,50,70000,Non,Lyon
7,Grace,30,50000,Oui,Paris
8,Heidi,28,45000,Non,NaN
9,Ivan,NaN,65000,Oui,Bordeaux
10,Julia,40,90000,Non,Lyon
```
**Questions :**
1. Chargez les données.
2. Remplacez les valeurs manquantes de la colonne `Age` par la moyenne de la colonne.
3. Remplacez les valeurs manquantes de la colonne `Revenu_Annuel` par la médiane de la colonne.
4. Remplacez les valeurs manquantes de la colonne `Ville_Residence` par le mode (valeur la plus fréquente) de la colonne.
5. Convertissez la colonne `Statut_Fidele` en type booléen (True pour 'Oui', False pour 'Non').
6. Affichez `info()` et `describe()` du DataFrame final pour vérifier les changements.
### Exercice 4 : Transformation de Variables Catégorielles (Label Encoding) (Normal)
Pour préparer un modèle de Machine Learning, vous devez convertir une variable catégorielle ordinale en une représentation numérique.
**Données :**
Utilisez les données de l'Exercice 3.
**Questions :**
1. Chargez les données.
2. Identifiez la colonne `Statut_Fidele` (après l'avoir convertie en booléen comme dans l'exercice 3, si ce n'est pas déjà fait).
3. Appliquez un encodage numérique simple (Label Encoding) à la colonne `Statut_Fidele`, où `False` devient `0` et `True` devient `1`.
4. Créez une nouvelle colonne `Statut_Fidele_Encoded` pour stocker le résultat.
5. Affichez les 5 premières lignes du DataFrame avec la nouvelle colonne.
### Exercice 5 : Feature Engineering Simple et Renommage (Normal)
À partir d'un dataset de performances d'étudiants, vous souhaitez créer une nouvelle variable pour l'évaluation globale et améliorer la lisibilité des noms de colonnes.
**Données :**
```csv
ID_Etudiant,Note_Maths,Note_Physique,Note_Chimie,Heures_Etude_Semaine
1,15,14,16,10
2,12,11,13,8
3,18,17,19,15
4,10,9,11,7
5,16,15,17,12
6,14,NaN,15,9
7,17,16,18,13
8,11,10,NaN,6
```
**Questions :**
1. Chargez les données.
2. Créez une nouvelle colonne `Moyenne_Sciences` qui est la moyenne des notes en `Note_Maths`, `Note_Physique` et `Note_Chimie`. Gérez les valeurs manquantes dans le calcul de la moyenne (la moyenne doit être calculée uniquement sur les notes disponibles).
3. Renommez la colonne `Heures_Etude_Semaine` en `Volume_Etude_Hebdomadaire`.
4. Affichez les colonnes `ID_Etudiant`, `Moyenne_Sciences` et `Volume_Etude_Hebdomadaire` pour les 5 premières lignes.
### Exercice 6 : Gestion des Dates (Normal)
Vous disposez d'un journal de connexion d'utilisateurs. Vous souhaitez extraire des informations temporelles pour une analyse ultérieure.
**Données :**
```csv
ID_Utilisateur,Date_Connexion,Heure_Connexion,Duree_Minutes
U001,2023-03-10,09:30:00,45
U002,2023-03-10,10:15:00,60
U003,2023-03-11,14:00:00,30
U004,2023-03-11,11:00:00,90
U005,2023-03-12,08:00:00,120
U006,2023-03-12,16:45:00,75
U007,2023-03-13,09:00:00,50
U008,2023-03-13,10:30:00,40
U009,2023-03-14,13:00:00,80
U010,2023-03-14,17:00:00,100
```
**Questions :**
1. Chargez les données.
2. Combinez les colonnes `Date_Connexion` et `Heure_Connexion` en une seule colonne `Timestamp_Connexion` de type datetime.
3. Créez une nouvelle colonne `Jour_Semaine` qui indique le jour de la semaine (e.g., Lundi, Mardi...).
4. Créez une nouvelle colonne `Heure_Jour` qui indique l'heure de la connexion (juste l'heure, sans les minutes/secondes).
5. Affichez les colonnes `ID_Utilisateur`, `Timestamp_Connexion`, `Jour_Semaine`, `Heure_Jour` pour les 5 premières lignes.
### Exercice 7 : Prétraitement Complet avec One-Hot Encoding (Élaboré)
Vous avez un dataset mixte (numérique et catégoriel) et vous devez le préparer pour un modèle de classification. Cela implique la gestion des valeurs manquantes et l'encodage des variables catégorielles.
**Données :**
```csv
ID_Client,Age,Revenu_Mensuel,Type_Logement,Statut_Professionnel,Score_Credit
C001,35,4500,Proprietaire,Employe,720
C002,28,3000,Locataire,Etudiant,650
C003,50,6000,Proprietaire,Retraite,800
C004,NaN,3800,Locataire,Employe,680
C005,42,NaN,Proprietaire,Independant,750
C006,30,3200,Locataire,Employe,670
C007,55,7000,Proprietaire,Retraite,810
C008,22,2500,Locataire,Etudiant,NaN
C009,48,5500,Proprietaire,Independant,780
C010,33,4000,NaN,Employe,700
```
**Questions :**
1. Chargez les données.
2. Identifiez les colonnes numériques et catégorielles.
3. **Gestion des valeurs manquantes :**
* Pour les colonnes numériques (`Age`, `Revenu_Mensuel`, `Score_Credit`), remplacez les valeurs manquantes par la médiane de chaque colonne respective.
* Pour les colonnes catégorielles (`Type_Logement`, `Statut_Professionnel`), remplacez les valeurs manquantes par le mode (valeur la plus fréquente) de chaque colonne respective.
4. **Encodage des variables catégorielles :**
* Appliquez le One-Hot Encoding aux colonnes `Type_Logement` et `Statut_Professionnel`.
* Assurez-vous de ne pas créer de piège de la variable factice (Dummy Variable Trap) en supprimant une des colonnes encodées par catégorie.
5. Affichez les 5 premières lignes du DataFrame résultant, ainsi que ses dimensions et types de données (`info()`).
> [!note] Piège de la variable factice (Dummy Variable Trap)
> Le piège de la variable factice se produit lorsque vous incluez toutes les $k$ catégories d'une variable nominale encodée en One-Hot Encoding dans un modèle de régression. Cela crée une multicolinéarité parfaite, car la $k$-ième colonne peut être déduite des $k-1$ autres (leur somme est 1). Pour éviter cela, il est courant de supprimer une des colonnes créées par One-Hot Encoding.
### Exercice 8 : Détection et Gestion des Outliers (Élaboré)
Vous examinez un dataset de salaires et souhaitez identifier et potentiellement gérer les valeurs aberrantes qui pourraient fausser votre analyse.
**Données :**
```csv
ID_Employe,Anciennete_Annees,Salaire_Mensuel
E001,5,3000
E002,10,4500
E003,3,2500
E004,15,6000
E005,2,2200
E006,8,4000
E007,20,15000 # Outlier potentiel
E008,6,3200
E009,12,5000
E010,1,2000
E011,18,7000
E012,4,2800
E013,25,20000 # Outlier potentiel
```
**Questions :**
1. Chargez les données.
2. Utilisez la méthode de l'intervalle interquartile (IQR) pour détecter les outliers dans la colonne `Salaire_Mensuel`.
* Calculez le premier quartile ($Q_1$), le troisième quartile ($Q_3$) et l'IQR ($IQR = Q_3 - Q_1$).
* Définissez les bornes inférieure ($LowerBound = Q_1 - 1.5 \times IQR$) et supérieure ($UpperBound = Q_3 + 1.5 \times IQR$) pour la détection des outliers.
3. Identifiez et affichez les lignes du DataFrame qui contiennent des outliers dans la colonne `Salaire_Mensuel`.
4. Créez un nouveau DataFrame où les outliers de `Salaire_Mensuel` sont remplacés par la borne supérieure (capping). Affichez les salaires avant et après le capping pour les lignes concernées.
> [!definition] Intervalle Interquartile (IQR)
> L'IQR est une mesure de la dispersion statistique, égale à la différence entre le troisième quartile ($Q_3$) et le premier quartile ($Q_1$). Il représente la plage des 50% de données centrales.
>
> Les outliers sont souvent définis comme des points de données qui se situent en dehors de l'intervalle $[Q_1 - 1.5 \times IQR, Q_3 + 1.5 \times IQR]$.
### Exercice 9 : Normalisation/Standardisation des Données Numériques (Élaboré)
Pour la modélisation Machine Learning, il est souvent nécessaire de mettre à l'échelle les variables numériques. Vous allez appliquer deux méthodes courantes.
**Données :**
```csv
ID_Client,Age,Revenu_Annuel,Nombre_Achats
1,30,50000,5
2,45,75000,12
3,25,30000,3
4,50,100000,20
5,35,60000,8
```
**Questions :**
1. Chargez les données.
2. Identifiez les colonnes numériques à mettre à l'échelle (`Age`, `Revenu_Annuel`, `Nombre_Achats`).
3. **Normalisation Min-Max :**
* Appliquez la normalisation Min-Max (mise à l'échelle vers l'intervalle $[0, 1]$) à ces colonnes.
* La formule est : $X_{norm} = \frac{X - X_{min}}{X_{max} - X_{min}}$.
* Créez un nouveau DataFrame `df_minmax` avec les colonnes normalisées.
4. **Standardisation (Z-score) :**
* Appliquez la standardisation (mise à l'échelle pour avoir une moyenne de 0 et un écart-type de 1) à ces mêmes colonnes.
* La formule est : $X_{std} = \frac{X - \mu}{\sigma}$.
* Créez un nouveau DataFrame `df_std` avec les colonnes standardisées.
5. Affichez les 5 premières lignes de `df_minmax` et `df_std`. Comparez les valeurs transformées.
> [!definition] Normalisation Min-Max
> La normalisation Min-Max met à l'échelle les valeurs numériques d'une colonne pour qu'elles se situent dans un intervalle spécifié, généralement $[0, 1]$. Elle est sensible aux outliers.
> [!definition] Standardisation (Z--score)
> La standardisation transforme les données de manière à ce qu'elles aient une moyenne de 0 et un écart-type de 1. Elle est moins sensible aux outliers que la normalisation Min-Max et est souvent préférée pour les algorithmes de Machine Learning qui supposent des données distribuées normalement.
### Exercice 10 : Pipeline de Prétraitement pour Analyse (Élaboré)
Vous êtes face à un dataset de feedback client. Votre objectif est de le nettoyer et d'en extraire des informations pour une première analyse.
**Données :**
```csv
ID_Feedback,Date_Soumission,Score_Satisfaction,Commentaire,Type_Produit,Prix_Achat_USD
F001,2023-04-01,4,"Excellent produit, très satisfait !",Électronique,1200
F002,2023-04-02,NaN,"Bon rapport qualité-prix.",Vêtements,50
F003,2023-04-03,5,"Service client impeccable.",Services,NaN
F004,2023-04-04,3,"Fonctionne mais pourrait être mieux.",Électronique,800
F005,2023-04-05,2,"Déçu par la qualité.",Vêtements,70
F006,2023-04-06,5,"Absolument génial !",Électronique,1500
F007,2023-04-07,NaN,"RAS.",Services,100
F008,2023-04-08,4,"Conforme à mes attentes.",Vêtements,NaN
F009,2023-04-09,1,"Très mauvaise expérience.",Électronique,900
F010,2023-04-10,5,"Je recommande vivement !",Services,200
```
**Questions :**
1. Chargez les données.
2. **Nettoyage des noms de colonnes :** Convertissez tous les noms de colonnes en minuscules et remplacez les espaces par des underscores (e.g., `Score_Satisfaction` devient `score_satisfaction`).
3. **Gestion des valeurs manquantes :**
* Pour `score_satisfaction`, remplacez les NaN par la médiane.
* Pour `prix_achat_usd`, remplacez les NaN par la moyenne.
* Pour `type_produit`, remplacez les NaN par le mode.
4. **Transformation de dates :** Convertissez `date_soumission` en type datetime et extrayez le mois de soumission dans une nouvelle colonne `mois_soumission`.
5. **Feature Engineering :** Créez une nouvelle colonne `longueur_commentaire` qui contient le nombre de mots dans chaque `commentaire`.
6. **Encodage catégoriel :** Appliquez l'encodage One-Hot à la colonne `type_produit`. Supprimez une des colonnes encodées pour éviter le piège de la variable factice.
7. **Analyse rapide (après prétraitement) :** Calculez la moyenne du `score_satisfaction` par `type_produit` (utilisez les colonnes encodées pour cela, si besoin, ou la colonne originale si vous l'avez conservée).
---
## Corrigés Détaillés
### Corrigé Exercice 1 : Inspection Initiale d'un Dataset
```python
import pandas as pd
import io
csv_data = """ID_Transaction,Produit,Quantite,Prix_Unitaire_HT,Date_Vente,Region
1001,Ordinateur Portable,2,800.0,2023-01-15,Nord
1002,Souris,5,25.0,2023-01-16,Sud
1003,Clavier,1,75.0,2023-01-15,Est
1004,Moniteur,NaN,200.0,2023-01-17,Ouest
1005,Webcam,3,50.0,2023-01-16,Nord
1006,Ordinateur Portable,1,800.0,2023-01-18,Sud
1007,Souris,2,25.0,2023-01-18,Est
1008,Clavier,NaN,75.0,2023-01-19,Ouest
1009,Moniteur,1,200.0,2023-01-19,Nord
1010,Webcam,4,50.0,2023-01-20,Sud
"""
# 1. Chargez ces données dans un DataFrame pandas.
df_ex1 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame Chargé ---")
print(df_ex1)
# 2. Affichez les 5 premières lignes du DataFrame.
print("\n--- 5 premières lignes ---")
print(df_ex1.head())
# 3. Affichez un résumé concis du DataFrame, incluant les types de données et les valeurs non nulles.
print("\n--- Résumé concis (info()) ---")
df_ex1.info()
# 4. Affichez les dimensions (nombre de lignes et de colonnes) du DataFrame.
print("\n--- Dimensions (shape) ---")
print(f"Le DataFrame a {df_ex1.shape[0]} lignes et {df_ex1.shape[1]} colonnes.")
```
**Explication :**
1. Nous utilisons `pd.read_csv` avec `io.StringIO` pour simuler la lecture d'un fichier CSV à partir d'une chaîne de caractères. C'est une méthode pratique pour les exercices.
2. La méthode `.head()` est essentielle pour avoir un premier aperçu des données. Elle affiche par défaut les 5 premières lignes, ce qui permet de vérifier le chargement et la structure des colonnes.
3. `.info()` est une fonction très utile qui fournit un résumé complet :
* Le nombre d'entrées (lignes).
* Le nombre total de colonnes.
* Pour chaque colonne : son nom, le nombre de valeurs non nulles (`Non-Null Count`), et son type de données (`Dtype`). Cela nous alerte immédiatement sur la présence de valeurs manquantes (si `Non-Null Count` est inférieur au nombre total de lignes) et sur les types de données qui pourraient nécessiter une conversion.
4. L'attribut `.shape` retourne un tuple `(nombre_de_lignes, nombre_de_colonnes)`. C'est rapide et efficace pour connaître la taille du dataset.
### Corrigé Exercice 2 : Gestion Simple des Valeurs Manquantes
```python
import pandas as pd
import io
csv_data = """ID_Transaction,Produit,Quantite,Prix_Unitaire_HT,Date_Vente,Region
1001,Ordinateur Portable,2,800.0,2023-01-15,Nord
1002,Souris,5,25.0,2023-01-16,Sud
1003,Clavier,1,75.0,2023-01-15,Est
1004,Moniteur,NaN,200.0,2023-01-17,Ouest
1005,Webcam,3,50.0,2023-01-16,Nord
1006,Ordinateur Portable,1,800.0,2023-01-18,Sud
1007,Souris,2,25.0,2023-01-18,Est
1008,Clavier,NaN,75.0,2023-01-19,Ouest
1009,Moniteur,1,200.0,2023-01-19,Nord
1010,Webcam,4,50.0,2023-01-20,Sud
"""
df_ex2 = pd.read_csv(io.StringIO(csv_data))
# 1. Identifiez le nombre de valeurs manquantes par colonne.
print("--- Nombre de valeurs manquantes par colonne ---")
print(df_ex2.isnull().sum())
# 2. Supprimez toutes les lignes qui contiennent au moins une valeur manquante.
df_ex2_cleaned = df_ex2.dropna()
print("\n--- DataFrame après suppression des lignes avec NaN ---")
print(df_ex2_cleaned)
# 3. Affichez les dimensions du DataFrame après la suppression pour vérifier le résultat.
print("\n--- Dimensions après suppression ---")
print(f"Le DataFrame a maintenant {df_ex2_cleaned.shape[0]} lignes et {df_ex2_cleaned.shape[1]} colonnes.")
```
**Explication :**
1. `df.isnull()` retourne un DataFrame de booléens de la même taille que `df`, où `True` indique une valeur manquante (NaN, None, NaT).
2. `.sum()` appliqué à ce DataFrame de booléens compte le nombre de `True` par colonne (car `True` est traité comme 1 et `False` comme 0), nous donnant ainsi le nombre de NaN par colonne.
3. `df.dropna()` est la méthode la plus simple pour gérer les valeurs manquantes : elle supprime les lignes (par défaut, `axis=0`) ou les colonnes (`axis=1`) contenant au moins une valeur manquante. Ici, nous l'appliquons pour supprimer les lignes. L'opération ne modifie pas le DataFrame original, elle retourne un nouveau DataFrame.
4. En comparant le `.shape` avant et après `dropna()`, nous pouvons voir combien de lignes ont été supprimées. Dans cet exemple, les lignes 1004 et 1008 contenaient des NaN dans la colonne `Quantite`, elles ont donc été supprimées.
### Corrigé Exercice 3 : Nettoyage et Typage des Données
```python
import pandas as pd
import io
csv_data = """ID_Client,Nom_Client,Age,Revenu_Annuel,Statut_Fidele,Ville_Residence
1,Alice,30,50000,Oui,Paris
2,Bob,45,75000,Non,Lyon
3,Charlie,NaN,60000,Oui,Marseille
4,David,25,NaN,Non,Paris
5,Eve,35,80000,Oui,Toulouse
6,Frank,50,70000,Non,Lyon
7,Grace,30,50000,Oui,Paris
8,Heidi,28,45000,Non,NaN
9,Ivan,NaN,65000,Oui,Bordeaux
10,Julia,40,90000,Non,Lyon
"""
df_ex3 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original ---")
df_ex3.info()
print(df_ex3.head())
# 2. Remplacez les valeurs manquantes de la colonne Age par la moyenne.
# 3. Remplacez les valeurs manquantes de la colonne Revenu_Annuel par la médiane.
df_ex3['Age'].fillna(df_ex3['Age'].mean(), inplace=True)
df_ex3['Revenu_Annuel'].fillna(df_ex3['Revenu_Annuel'].median(), inplace=True)
# 4. Remplacez les valeurs manquantes de la colonne Ville_Residence par le mode.
# Le mode peut retourner plusieurs valeurs s'il y a des ex-aequo, nous prenons la première.
mode_ville = df_ex3['Ville_Residence'].mode()[0]
df_ex3['Ville_Residence'].fillna(mode_ville, inplace=True)
# 5. Convertissez la colonne Statut_Fidele en type booléen.
df_ex3['Statut_Fidele'] = df_ex3['Statut_Fidele'].map({'Oui': True, 'Non': False})
# 6. Affichez info() et describe() du DataFrame final.
print("\n--- DataFrame après nettoyage et typage ---")
df_ex3.info()
print("\n--- Statistiques descriptives ---")
print(df_ex3.describe(include='all')) # include='all' pour voir aussi les colonnes non numériques
```
**Explication :**
1. `df.fillna()` est utilisé pour remplacer les valeurs manquantes.
* Pour les colonnes numériques (`Age`, `Revenu_Annuel`), des mesures de tendance centrale comme la moyenne (`.mean()`) ou la médiane (`.median()`) sont de bonnes options. La médiane est souvent préférée car elle est moins sensible aux outliers.
* Pour les colonnes catégorielles (`Ville_Residence`), le mode (`.mode()`) est la stratégie la plus courante. Puisque `.mode()` peut retourner une série si plusieurs valeurs ont la même fréquence maximale, nous prenons la première avec `[0]`.
* `inplace=True` modifie le DataFrame directement sans avoir besoin de réaffecter la colonne.
2. La conversion de `Statut_Fidele` utilise la méthode `.map()` sur la série. Nous fournissons un dictionnaire qui mappe 'Oui' à `True` et 'Non' à `False`. Pandas gère automatiquement la conversion du type de la colonne en `bool`.
3. `df.info()` confirme que les types de données ont été mis à jour et que le nombre de valeurs non nulles est maintenant égal au nombre total de lignes pour les colonnes traitées.
4. `df.describe(include='all')` fournit des statistiques descriptives pour toutes les colonnes, y compris les catégorielles (fréquence, nombre de valeurs uniques, etc.), ce qui est utile pour vérifier le nettoyage.
### Corrigé Exercice 4 : Transformation de Variables Catégorielles (Label Encoding)
```python
import pandas as pd
import io
csv_data = """ID_Client,Nom_Client,Age,Revenu_Annuel,Statut_Fidele,Ville_Residence
1,Alice,30,50000,Oui,Paris
2,Bob,45,75000,Non,Lyon
3,Charlie,NaN,60000,Oui,Marseille
4,David,25,NaN,Non,Paris
5,Eve,35,80000,Oui,Toulouse
6,Frank,50,70000,Non,Lyon
7,Grace,30,50000,Oui,Paris
8,Heidi,28,45000,Non,NaN
9,Ivan,NaN,65000,Oui,Bordeaux
10,Julia,40,90000,Non,Lyon
"""
df_ex4 = pd.read_csv(io.StringIO(csv_data))
# Pré-traitement de l'exercice 3 pour avoir 'Statut_Fidele' en booléen
df_ex4['Statut_Fidele'] = df_ex4['Statut_Fidele'].map({'Oui': True, 'Non': False})
# 3. Appliquez un encodage numérique simple (Label Encoding).
# 4. Créez une nouvelle colonne Statut_Fidele_Encoded.
# True -> 1, False -> 0 est la conversion par défaut pour les booléens en int.
df_ex4['Statut_Fidele_Encoded'] = df_ex4['Statut_Fidele'].astype(int)
# 5. Affichez les 5 premières lignes du DataFrame avec la nouvelle colonne.
print("--- DataFrame avec Statut_Fidele_Encoded ---")
print(df_ex4[['ID_Client', 'Statut_Fidele', 'Statut_Fidele_Encoded']].head())
print("\n--- Types de données après encodage ---")
print(df_ex4.info())
```
**Explication :**
1. Nous commençons par les données déjà nettoyées et avec `Statut_Fidele` converti en booléen, comme demandé dans l'Exercice 3.
2. Le "Label Encoding" est une technique simple pour convertir des catégories en nombres entiers. Pour une variable binaire comme `Statut_Fidele` (True/False), la conversion en entier (`.astype(int)`) est la plus directe : `True` devient `1` et `False` devient `0`.
3. Cette nouvelle colonne `Statut_Fidele_Encoded` est maintenant prête à être utilisée par des algorithmes de Machine Learning qui nécessitent des entrées numériques.
> [!warning] Quand utiliser Label Encoding ?
> Le Label Encoding est approprié pour les variables catégorielles *ordinales* (où il existe un ordre inhérent entre les catégories, par exemple "Petit", "Moyen", "Grand"). Pour les variables *nominales* (sans ordre, par exemple "Rouge", "Vert", "Bleu"), le Label Encoding peut introduire un ordre artificiel que le modèle pourrait mal interpréter. Dans ce cas, le One-Hot Encoding (voir Exercice 7) est généralement préféré. Pour une variable binaire, les deux approches sont souvent équivalentes ou le Label Encoding est plus compact.
### Corrigé Exercice 5 : Feature Engineering Simple et Renommage
```python
import pandas as pd
import io
csv_data = """ID_Etudiant,Note_Maths,Note_Physique,Note_Chimie,Heures_Etude_Semaine
1,15,14,16,10
2,12,11,13,8
3,18,17,19,15
4,10,9,11,7
5,16,15,17,12
6,14,NaN,15,9
7,17,16,18,13
8,11,10,NaN,6
"""
df_ex5 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original ---")
print(df_ex5)
# 2. Créez une nouvelle colonne Moyenne_Sciences.
# axis=1 pour calculer la moyenne par ligne, skipna=True pour ignorer les NaN.
df_ex5['Moyenne_Sciences'] = df_ex5[['Note_Maths', 'Note_Physique', 'Note_Chimie']].mean(axis=1, skipna=True)
# 3. Renommez la colonne Heures_Etude_Semaine.
df_ex5.rename(columns={'Heures_Etude_Semaine': 'Volume_Etude_Hebdomadaire'}, inplace=True)
# 4. Affichez les colonnes ID_Etudiant, Moyenne_Sciences et Volume_Etude_Hebdomadaire.
print("\n--- DataFrame après Feature Engineering et renommage ---")
print(df_ex5[['ID_Etudiant', 'Moyenne_Sciences', 'Volume_Etude_Hebdomadaire']].head())
```
**Explication :**
1. Le `Feature Engineering` est l'art de créer de nouvelles variables (features) à partir de celles existantes, souvent pour améliorer la performance des modèles. Ici, nous créons `Moyenne_Sciences` en calculant la moyenne de trois colonnes.
2. `df[['Note_Maths', 'Note_Physique', 'Note_Chimie']]` sélectionne les trois colonnes.
3. `.mean(axis=1, skipna=True)` calcule la moyenne *par ligne* (`axis=1`). `skipna=True` est crucial ici : il garantit que la moyenne est calculée uniquement sur les valeurs non manquantes de chaque ligne. Si une note est manquante, elle est ignorée dans le calcul de la moyenne de l'étudiant, plutôt que de donner un NaN pour la moyenne entière.
4. `df.rename(columns={'old_name': 'new_name'}, inplace=True)` est la méthode standard pour renommer une ou plusieurs colonnes. Le dictionnaire mappe les anciens noms aux nouveaux.
### Corrigé Exercice 6 : Gestion des Dates
```python
import pandas as pd
import io
csv_data = """ID_Utilisateur,Date_Connexion,Heure_Connexion,Duree_Minutes
U001,2023-03-10,09:30:00,45
U002,2023-03-10,10:15:00,60
U003,2023-03-11,14:00:00,30
U004,2023-03-11,11:00:00,90
U005,2023-03-12,08:00:00,120
U006,2023-03-12,16:45:00,75
U007,2023-03-13,09:00:00,50
U008,2023-03-13,10:30:00,40
U009,2023-03-14,13:00:00,80
U010,2023-03-14,17:00:00,100
"""
df_ex6 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original ---")
print(df_ex6.info())
# 2. Combinez les colonnes Date_Connexion et Heure_Connexion en Timestamp_Connexion.
df_ex6['Timestamp_Connexion'] = pd.to_datetime(df_ex6['Date_Connexion'] + ' ' + df_ex6['Heure_Connexion'])
# 3. Créez une nouvelle colonne Jour_Semaine.
df_ex6['Jour_Semaine'] = df_ex6['Timestamp_Connexion'].dt.day_name(locale='fr_FR.UTF-8') # Ajout du locale pour les noms de jours en français
# 4. Créez une nouvelle colonne Heure_Jour.
df_ex6['Heure_Jour'] = df_ex6['Timestamp_Connexion'].dt.hour
# 5. Affichez les colonnes demandées.
print("\n--- DataFrame après gestion des dates ---")
print(df_ex6[['ID_Utilisateur', 'Timestamp_Connexion', 'Jour_Semaine', 'Heure_Jour']].head())
print("\n--- Types de données après gestion des dates ---")
print(df_ex6.info())
```
**Explication :**
1. La première étape cruciale est de convertir les chaînes de caractères représentant les dates et heures en objets `datetime` de Pandas. Nous concaténons d'abord les colonnes `Date_Connexion` et `Heure_Connexion` avec un espace, puis utilisons `pd.to_datetime()` pour la conversion. Cela crée une colonne `Timestamp_Connexion` de type `datetime64[ns]`.
2. Une fois une colonne est de type `datetime`, Pandas expose un accesseur `.dt` qui permet d'extraire facilement diverses composantes de la date/heure.
3. `df['Timestamp_Connexion'].dt.day_name()` extrait le nom du jour de la semaine. L'ajout de `locale='fr_FR.UTF-8'` permet d'obtenir les noms des jours en français.
4. `df['Timestamp_Connexion'].dt.hour` extrait l'heure (partie entière) de l'objet datetime.
5. Ces nouvelles colonnes sont très utiles pour l'analyse temporelle ou comme features pour des modèles prédictifs (par exemple, pour détecter des schémas de connexion selon le jour de la semaine ou l'heure de la journée).
### Corrigé Exercice 7 : Prétraitement Complet avec One-Hot Encoding
```python
import pandas as pd
import io
csv_data = """ID_Client,Age,Revenu_Mensuel,Type_Logement,Statut_Professionnel,Score_Credit
C001,35,4500,Proprietaire,Employe,720
C002,28,3000,Locataire,Etudiant,650
C003,50,6000,Proprietaire,Retraite,800
C004,NaN,3800,Locataire,Employe,680
C005,42,NaN,Proprietaire,Independant,750
C006,30,3200,Locataire,Employe,670
C007,55,7000,Proprietaire,Retraite,810
C008,22,2500,Locataire,Etudiant,NaN
C009,48,5500,Proprietaire,Independant,780
C010,33,4000,NaN,Employe,700
"""
df_ex7 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original (info) ---")
df_ex7.info()
# 2. Identifiez les colonnes numériques et catégorielles.
numerical_cols = ['Age', 'Revenu_Mensuel', 'Score_Credit']
categorical_cols = ['Type_Logement', 'Statut_Professionnel']
# 3. Gestion des valeurs manquantes
# Pour les numériques, remplacer par la médiane
for col in numerical_cols:
median_val = df_ex7[col].median()
df_ex7[col].fillna(median_val, inplace=True)
print(f"NaN dans '{col}' remplacés par la médiane ({median_val}).")
# Pour les catégorielles, remplacer par le mode
for col in categorical_cols:
mode_val = df_ex7[col].mode()[0]
df_ex7[col].fillna(mode_val, inplace=True)
print(f"NaN dans '{col}' remplacés par le mode ({mode_val}).")
print("\n--- DataFrame après gestion des NaN (info) ---")
df_ex7.info()
# 4. Encodage des variables catégorielles (One-Hot Encoding)
# Utilisation de pd.get_dummies pour le One-Hot Encoding.
# drop_first=True pour éviter le piège de la variable factice.
df_encoded = pd.get_dummies(df_ex7, columns=categorical_cols, drop_first=True)
print("\n--- DataFrame après One-Hot Encoding (5 premières lignes) ---")
print(df_encoded.head())
print("\n--- DataFrame après One-Hot Encoding (info) ---")
df_encoded.info()
print("\n--- Dimensions du DataFrame final ---")
print(f"Le DataFrame a maintenant {df_encoded.shape[0]} lignes et {df_encoded.shape[1]} colonnes.")
```
**Explication :**
1. Nous commençons par identifier clairement les colonnes numériques et catégorielles, ce qui est une bonne pratique pour appliquer des stratégies de prétraitement différentes.
2. **Gestion des valeurs manquantes :**
* Pour les colonnes numériques, la médiane est choisie car elle est robuste aux outliers, contrairement à la moyenne.
* Pour les colonnes catégorielles, le mode est la méthode standard pour imputer les valeurs manquantes.
* Nous utilisons des boucles pour appliquer ces opérations à plusieurs colonnes de manière efficace.
3. **One-Hot Encoding :**
* `pd.get_dummies()` est la fonction `pandas` dédiée au One-Hot Encoding. Elle prend le DataFrame, la liste des colonnes à encoder, et retourne un nouveau DataFrame avec les colonnes originales supprimées et les nouvelles colonnes binaires ajoutées.
* L'argument `drop_first=True` est crucial pour éviter le piège de la variable factice (Dummy Variable Trap). Pour chaque variable catégorielle, il supprime la première colonne binaire créée. Par exemple, si `Type_Logement` avait les catégories "Proprietaire", "Locataire", "Autre", `get_dummies` créerait `Type_Logement_Proprietaire`, `Type_Logement_Locataire`, `Type_Logement_Autre`. Avec `drop_first=True`, une de ces colonnes (par défaut la première, alphabétiquement ou par ordre d'apparition) est supprimée, par exemple `Type_Logement_Autre`, et les deux autres suffisent à représenter toutes les catégories.
4. Le `info()` final montre que les colonnes catégorielles originales ont disparu, remplacées par de nouvelles colonnes numériques binaires, et que le nombre de colonnes a augmenté.
### Corrigé Exercice 8 : Détection et Gestion des Outliers
```python
import pandas as pd
import io
csv_data = """ID_Employe,Anciennete_Annees,Salaire_Mensuel
E001,5,3000
E002,10,4500
E003,3,2500
E004,15,6000
E005,2,2200
E006,8,4000
E007,20,15000
E008,6,3200
E009,12,5000
E010,1,2000
E011,18,7000
E012,4,2800
E013,25,20000
"""
df_ex8 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original ---")
print(df_ex8)
# 2. Utilisez la méthode de l'intervalle interquartile (IQR) pour détecter les outliers.
Q1 = df_ex8['Salaire_Mensuel'].quantile(0.25)
Q3 = df_ex8['Salaire_Mensuel'].quantile(0.75)
IQR = Q3 - Q1
LowerBound = Q1 - 1.5 * IQR
UpperBound = Q3 + 1.5 * IQR
print(f"\nQ1 (25ème percentile) : {Q1}")
print(f"Q3 (75ème percentile) : {Q3}")
print(f"IQR : {IQR}")
print(f"Borne Inférieure (LowerBound) : {LowerBound}")
print(f"Borne Supérieure (UpperBound) : {UpperBound}")
# 3. Identifiez et affichez les lignes du DataFrame qui contiennent des outliers.
outliers = df_ex8[(df_ex8['Salaire_Mensuel'] < LowerBound) | (df_ex8['Salaire_Mensuel'] > UpperBound)]
print("\n--- Outliers détectés dans 'Salaire_Mensuel' ---")
print(outliers)
# 4. Créez un nouveau DataFrame où les outliers de Salaire_Mensuel sont remplacés par la borne supérieure (capping).
df_ex8_capped = df_ex8.copy() # Créer une copie pour ne pas modifier l'original
df_ex8_capped['Salaire_Mensuel'] = df_ex8_capped['Salaire_Mensuel'].apply(lambda x: UpperBound if x > UpperBound else x)
# Si on voulait aussi gérer les outliers inférieurs:
# df_ex8_capped['Salaire_Mensuel'] = df_ex8_capped['Salaire_Mensuel'].apply(lambda x: LowerBound if x < LowerBound else x)
print("\n--- Salaires après Capping (comparaison avec original) ---")
# Afficher les lignes qui étaient des outliers pour voir le changement
print(pd.concat([df_ex8.loc[outliers.index], df_ex8_capped.loc[outliers.index]], axis=1, keys=['Original', 'Capped']))
```
**Explication :**
1. **Calcul de l'IQR :**
* `df['col'].quantile(0.25)` calcule le premier quartile ($Q_1$), qui est la valeur en dessous de laquelle se trouvent 25% des données.
* `df['col'].quantile(0.75)` calcule le troisième quartile ($Q_3$), sous lequel se trouvent 75% des données.
* L'IQR est simplement la différence $Q_3 - Q_1$.
* Les bornes sont définies par $Q_1 - 1.5 \times IQR$ et $Q_3 + 1.5 \times IQR$. Le facteur 1.5 est une convention couramment utilisée, mais il peut être ajusté en fonction du domaine.
2. **Identification des outliers :** Nous utilisons un filtrage booléen pour sélectionner les lignes où `Salaire_Mensuel` est inférieur à la borne inférieure OU supérieur à la borne supérieure.
3. **Gestion des outliers (Capping) :**
* Nous créons une copie du DataFrame pour ne pas modifier l'original.
* La méthode `.apply()` avec une fonction `lambda` est utilisée pour parcourir chaque valeur de la colonne `Salaire_Mensuel`. Si une valeur est supérieure à `UpperBound`, elle est remplacée par `UpperBound`. C'est une technique appelée "capping" ou "winsorization", qui réduit l'impact des outliers sans les supprimer complètement.
* Une autre stratégie pourrait être de supprimer les lignes d'outliers (`df.drop(outliers.index)`) ou de les remplacer par la médiane/moyenne, mais le capping est souvent préféré pour conserver la taille du dataset et l'information contenue dans les valeurs "extrêmes" (même si elles sont limitées).
### Corrigé Exercice 9 : Normalisation/Standardisation des Données Numériques
```python
import pandas as pd
import io
from sklearn.preprocessing import MinMaxScaler, StandardScaler
csv_data = """ID_Client,Age,Revenu_Annuel,Nombre_Achats
1,30,50000,5
2,45,75000,12
3,25,30000,3
4,50,100000,20
5,35,60000,8
"""
df_ex9 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original ---")
print(df_ex9)
print("\n--- Statistiques descriptives originales ---")
print(df_ex9.describe())
# 2. Identifiez les colonnes numériques à mettre à l'échelle.
cols_to_scale = ['Age', 'Revenu_Annuel', 'Nombre_Achats']
# 3. Normalisation Min-Max
minmax_scaler = MinMaxScaler()
# Fit et transform sur les colonnes sélectionnées
df_minmax_scaled_data = minmax_scaler.fit_transform(df_ex9[cols_to_scale])
# Créer un nouveau DataFrame pour les résultats Min-Max
df_minmax = df_ex9.copy()
df_minmax[cols_to_scale] = df_minmax_scaled_data
print("\n--- DataFrame après Normalisation Min-Max (5 premières lignes) ---")
print(df_minmax.head())
print("\n--- Statistiques descriptives après Normalisation Min-Max ---")
print(df_minmax[cols_to_scale].describe())
# 4. Standardisation (Z-score)
standard_scaler = StandardScaler()
# Fit et transform sur les colonnes sélectionnées
df_std_scaled_data = standard_scaler.fit_transform(df_ex9[cols_to_scale])
# Créer un nouveau DataFrame pour les résultats Standardisation
df_std = df_ex9.copy()
df_std[cols_to_scale] = df_std_scaled_data
print("\n--- DataFrame après Standardisation (5 premières lignes) ---")
print(df_std.head())
print("\n--- Statistiques descriptives après Standardisation ---")
print(df_std[cols_to_scale].describe())
```
**Explication :**
1. Nous importons `MinMaxScaler` et `StandardScaler` de `sklearn.preprocessing`, qui sont les outils standards pour ces opérations en Python.
2. **Normalisation Min-Max :**
* Nous instancions `MinMaxScaler()`.
* `scaler.fit_transform(data)` calcule les valeurs min et max pour chaque colonne (`fit`) puis applique la transformation (`transform`). Le résultat est un tableau NumPy.
* Nous réaffectons ces valeurs transformées aux colonnes correspondantes dans une copie du DataFrame.
* Le `describe()` des colonnes transformées montre que toutes les valeurs sont maintenant entre 0 et 1.
3. **Standardisation (Z-score) :**
* Le processus est similaire avec `StandardScaler()`.
* `scaler.fit_transform(data)` calcule la moyenne ($\mu$) et l'écart-type ($\sigma$) de chaque colonne, puis applique la formule $X_{std} = \frac{X - \mu}{\sigma}$.
* Le `describe()` des colonnes standardisées montre que les moyennes sont très proches de 0 et les écarts-types très proches de 1.
4. **Comparaison :**
* Min-Max est utile lorsque vous avez besoin que les valeurs soient dans une plage fixe (par exemple, pour les réseaux de neurones qui attendent des entrées entre 0 et 1).
* Standardisation est généralement préférée pour la plupart des algorithmes de Machine Learning (régression linéaire, SVM, k-means, PCA) car elle gère mieux les distributions non gaussiennes et les outliers (par rapport à Min-Max pur) en centrant les données.
> [!tip] Quand utiliser quoi ?
> * **Min-Max Scaling** : Utile si vous savez que vos données ne suivront pas une distribution gaussienne et que vous voulez des valeurs dans une plage spécifique (ex: [0,1]). Sensible aux outliers.
> * **Standard Scaling** : Préféré pour la plupart des algorithmes de ML, surtout ceux basés sur des distances (SVM, K-NN, K-Means) ou qui supposent une distribution normale (Régression Linéaire, LDA). Moins sensible aux outliers que Min-Max.
### Corrigé Exercice 10 : Pipeline de Prétraitement pour Analyse
```python
import pandas as pd
import io
csv_data = """ID_Feedback,Date_Soumission,Score_Satisfaction,Commentaire,Type_Produit,Prix_Achat_USD
F001,2023-04-01,4,"Excellent produit, très satisfait !",Électronique,1200
F002,2023-04-02,NaN,"Bon rapport qualité-prix.",Vêtements,50
F003,2023-04-03,5,"Service client impeccable.",Services,NaN
F004,2023-04-04,3,"Fonctionne mais pourrait être mieux.",Électronique,800
F005,2023-04-05,2,"Déçu par la qualité.",Vêtements,70
F006,2023-04-06,5,"Absolument génial !",Électronique,1500
F007,2023-04-07,NaN,"RAS.",Services,100
F008,2023-04-08,4,"Conforme à mes attentes.",Vêtements,NaN
F009,2023-04-09,1,"Très mauvaise expérience.",Électronique,900
F010,2023-04-10,5,"Je recommande vivement !",Services,200
"""
df_ex10 = pd.read_csv(io.StringIO(csv_data))
print("--- DataFrame original (info) ---")
df_ex10.info()
print("\n--- DataFrame original (head) ---")
print(df_ex10.head())
# 2. Nettoyage des noms de colonnes
df_ex10.columns = df_ex10.columns.str.lower().str.replace(' ', '_')
print("\n--- Noms de colonnes après nettoyage ---")
print(df_ex10.columns)
# 3. Gestion des valeurs manquantes
# score_satisfaction (numérique) par la médiane
df_ex10['score_satisfaction'].fillna(df_ex10['score_satisfaction'].median(), inplace=True)
print(f"NaN dans 'score_satisfaction' remplacés par la médiane ({df_ex10['score_satisfaction'].median()}).")
# prix_achat_usd (numérique) par la moyenne
df_ex10['prix_achat_usd'].fillna(df_ex10['prix_achat_usd'].mean(), inplace=True)
print(f"NaN dans 'prix_achat_usd' remplacés par la moyenne ({df_ex10['prix_achat_usd'].mean()}).")
# type_produit (catégorielle) par le mode
mode_type_produit = df_ex10['type_produit'].mode()[0]
df_ex10['type_produit'].fillna(mode_type_produit, inplace=True)
print(f"NaN dans 'type_produit' remplacés par le mode ({mode_type_produit}).")
print("\n--- DataFrame après gestion des NaN (info) ---")
df_ex10.info()
# 4. Transformation de dates
df_ex10['date_soumission'] = pd.to_datetime(df_ex10['date_soumission'])
df_ex10['mois_soumission'] = df_ex10['date_soumission'].dt.month
print("\n--- DataFrame après transformation de dates (head) ---")
print(df_ex10[['date_soumission', 'mois_soumission']].head())
# 5. Feature Engineering: longueur_commentaire
df_ex10['longueur_commentaire'] = df_ex10['commentaire'].apply(lambda x: len(str(x).split()))
print("\n--- DataFrame avec longueur_commentaire (head) ---")
print(df_ex10[['commentaire', 'longueur_commentaire']].head())
# 6. Encodage catégoriel (One-Hot) pour 'type_produit'
df_ex10_encoded = pd.get_dummies(df_ex10, columns=['type_produit'], drop_first=True)
print("\n--- DataFrame après One-Hot Encoding (head) ---")
print(df_ex10_encoded.head())
print("\n--- DataFrame après One-Hot Encoding (info) ---")
df_ex10_encoded.info()
# 7. Analyse rapide (après prétraitement): moyenne du score_satisfaction par type_produit
# Utilisons le DataFrame original pour cette analyse pour plus de clarté
print("\n--- Moyenne du score_satisfaction par type_produit ---")
# On peut utiliser le df_ex10 après imputation du mode pour type_produit
print(df_ex10.groupby('type_produit')['score_satisfaction'].mean().reset_index())
# Si on voulait utiliser les colonnes encodées pour une analyse future, il faudrait réassembler
# ou faire une jointure, mais pour une simple agrégation, la colonne originale est plus directe.
```
**Explication :**
1. **Nettoyage des noms de colonnes :** Une pratique courante est de standardiser les noms de colonnes. `.str.lower()` convertit en minuscules, et `.str.replace(' ', '_')` remplace les espaces par des underscores, rendant les noms plus faciles à manipuler en Python.
2. **Gestion des valeurs manquantes :**
* Stratégies différentes sont appliquées en fonction du type de la colonne : médiane pour les numériques (robustesse aux outliers), moyenne pour les numériques (simple et efficace si pas d'outliers extrêmes), et mode pour les catégorielles.
3. **Transformation de dates :** La colonne `date_soumission` est convertie en type `datetime` pour permettre l'extraction de composantes temporelles comme le mois (`.dt.month`). C'est une forme de `Feature Engineering` pour les données temporelles.
4. **Feature Engineering :** `longueur_commentaire` est créée pour quantifier la verbosité des commentaires. C'est une feature textuelle simple qui pourrait être utile pour comprendre la relation entre la longueur du feedback et la satisfaction. `.apply(lambda x: len(str(x).split()))` compte les mots. `str(x)` est utilisé pour s'assurer que même si un commentaire est NaN, il est traité comme une chaîne vide pour éviter une erreur.
5. **Encodage catégoriel :** `pd.get_dummies()` est utilisé pour le One-Hot Encoding de `type_produit`, avec `drop_first=True` pour éviter la multicolinéarité. Le DataFrame résultant `df_ex10_encoded` est prêt pour des modèles de Machine Learning.
6. **Analyse rapide :** Après tout ce prétraitement, nous pouvons effectuer une agrégation simple (`.groupby().mean()`) pour obtenir un aperçu de la satisfaction moyenne par type de produit. Cela montre l'utilité du prétraitement : les données sont maintenant propres et prêtes pour des analyses plus poussées.
> [!note] L'importance du pipeline
> Cet exercice illustre un mini-pipeline de prétraitement. Dans la réalité, ces étapes sont souvent enchaînées de manière structurée, parfois à l'aide de bibliothèques comme `scikit-learn` pour créer de véritables pipelines de transformation, ce qui est une compétence avancée très appréciée en Data Science.