# 🧠 Workshop Machine Learning — Session 1 ## **Prétraitement des Données (Data
Sciences **Formateur :** Dr. OM HENI Nizar | **Date :** 01/11/2025 --- ## 📋
Preprocessing)** **Durée :** 60 minutes | **Public :** Étudiants 5ème année Ingénieur Data
Objectifs
pédagogiques de la session À l'issue de cette session, les participants seront capables de : -
Diagnostiquer et traiter les problèmes de qualité des données - Appliquer les techniques
appropriées d'imputation des valeurs manquantes - Détecter et gérer les outliers selon le
contexte métier - Choisir et appliquer les bonnes méthodes de normalisation/standardisation -
reproductible --- ## 🗺️
Encoder efficacement les variables catégorielles - Structurer un pipeline de preprocessing
Plan détaillé de la session 1. **Introduction** - Importance du
preprocessing dans le pipeline ML 2. **Théorie : Nettoyage des données** - Valeurs
manquantes, doublons, outliers 3. **Pratique : Exploration et nettoyage** - Hands-on sur
🎯
dataset Kaggle 4. **Q&A et transition** - Questions et préparation session suivante --- #
Introduction : Le preprocessing, fondation du ML ##
1️⃣
Pourquoi le preprocessing est-il critique
? Le preprocessing représente **60-80% du temps** d'un projet ML en entreprise. Sans cette
étape : - **Garbage In, Garbage Out** : modèles biaisés et peu fiables - **Convergence
difficile** : algorithmes instables ou qui n'apprennent pas - **Performances dégradées** :
production ## 📊
métriques artificiellement mauvaises - **Déploiement impossible** : modèles non-robustes en
Impact quantifiable **Étude de cas réel :** Modèle de prédiction de prix
immobilier - **Sans preprocessing :** RMSE = 45,000€, R² = 0.65 - **Avec preprocessing :**
RMSE = 28,000€, R² = 0.87 - **Gain :** +34% d'amélioration des performances ##
pipeline ML complet ``` Données brutes → Preprocessing → Feature Engineering → Modeling
🔄 Le
Production variable fiable features optimal valides robuste ``` --- # 2️⃣
→ Evaluation → Deployment ↑ ↑ ↑ ↑ ↑ ↑ Qualité Transformation Création Algorithme Métriques
Théorie : Nettoyage et
### 🔍
transformation des données ## 2.1 Gestion des valeurs manquantes - Approche stratégique
Diagnostic des valeurs manquantes Les valeurs manquantes ne sont **jamais
aléatoires** en pratique : | Type de manque | Description | Stratégie | |----------------|-------------|----
-------| | **MCAR** (Missing Completely At Random) | Absence totalement aléatoire |
Suppression acceptable | | **MAR** (Missing At Random) | Dépend d'autres variables
valeur manquante elle-même | Modélisation explicite | ### 📈
observées | Imputation sophistiquée | | **MNAR** (Missing Not At Random) | Dépend de la
Seuils de décision pratiques
```python # Règles métier pour la gestion des valeurs manquantes missing_percentage =
([Link]().sum() / len(df)) * 100 def missing_strategy(missing_pct): if missing_pct < 5: return
"Suppression sûre" elif missing_pct < 15: return "Imputation simple (médiane/mode)" elif
suppression de la variable" ``` ### 🛠️
missing_pct < 40: return "Imputation avancée (KNN/MICE)" else: return "Considérer
Techniques d'imputation avancées **1. Imputation
statistique classique** ```python from [Link] import SimpleImputer # Variables
numériques : médiane (robuste aux outliers) num_imputer = SimpleImputer(strategy='median')
df[num_cols] = num_imputer.fit_transform(df[num_cols]) # Variables catégorielles : mode
cat_imputer = SimpleImputer(strategy='most_frequent') df[cat_cols] =
cat_imputer.fit_transform(df[cat_cols]) ``` **2. Imputation par proximité (KNN)** ```python from
[Link] import KNNImputer # Utilise les K plus proches voisins pour imputer
knn_imputer = KNNImputer(n_neighbors=5) df_imputed =
knn_imputer.fit_transform(df_numeric) ``` **3. Imputation itérative (MICE)** ```python from
[Link] import enable_iterative_imputer from [Link] import
IterativeImputer # Modélise chaque variable avec valeurs manquantes mice_imputer =
et traitement des outliers ### 🎯
IterativeImputer(random_state=42) df_mice = mice_imputer.fit_transform(df) ``` ## 2.2 Détection
Définition contextuelle des outliers Un outlier n'est **jamais
absolu** - il dépend du contexte métier : - **Revenue = 1M€** → Normal pour un CEO, aberrant
pour un stagiaire - **Température = 45°C** → Normal au Sahara, aberrant à Paris ###
Méthodes de détection **1. Méthode IQR (Interquartile Range) - La plus robuste** ```python def
📊
detect_outliers_iqr(df, column): Q1 = df[column].quantile(0.25) Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers =
df[(df[column] < lower_bound) | (df[column] > upper_bound)] return outliers, lower_bound,
upper_bound ``` **2. Z-Score (distribution normale)** ```python from scipy import stats def
detect_outliers_zscore(df, column, threshold=3): z_scores = [Link]([Link](df[column]))
outliers = df[z_scores > threshold] return outliers ``` **3. Isolation Forest (multivarié)** ```python
from [Link] import IsolationForest iso_forest = IsolationForest(contamination=0.1,
== -1 ``` ### 🎯
random_state=42) outliers = iso_forest.fit_predict(df[numeric_columns]) df['is_outlier'] = outliers
Stratégies de traitement | Contexte | Action recommandée | Justification | |-------
---|-------------------|---------------| | **Erreur de saisie** | Correction ou suppression | Données
invalides | | **Valeur extrême légitime** | Conservation | Information précieuse | | **Anomalie
détectable** | Traitement spécial | Cas particulier | | **Bruit de mesure** | Winsorization
(écrêtage) | Réduction impact | ```python # Winsorization - écrêtage aux percentiles from
[0.05, 0.05]) ``` ## 2.3 Normalisation et standardisation - Choix technique ### ⚖️
[Link] import winsorize df['variable_winsorized'] = winsorize(df['variable'], limits=
Comparaison
des méthodes | Méthode | Formule | Résultat | Usage optimal | |---------|---------|----------|------------
---| | **StandardScaler** | (x - μ) / σ | μ=0, σ=1 | SVM, Regression, PCA | | **MinMaxScaler** | (x
- min) / (max - min) | [0,1] | Neural Networks, KNN | | **RobustScaler** | (x - médiane) / IQR |
Distribution normale | Données asymétriques | ### 🔧
Centré robuste | Données avec outliers | | **PowerTransformer** | Box-Cox/Yeo-Johnson |
Implémentation pratique ```python from
[Link] import StandardScaler, MinMaxScaler, RobustScaler, PowerTransformer
# Pipeline de normalisation def create_scaler_pipeline(method='standard'): scalers = {
'standard': StandardScaler(), 'minmax': MinMaxScaler(), 'robust': RobustScaler(), 'power':
PowerTransformer(method='yeo-johnson') } return scalers[method] # Application avec
sauvegarde des paramètres scaler = create_scaler_pipeline('standard') X_train_scaled =
### ⚠️ Piège fréquent - Data Leakage ```python # ❌
scaler.fit_transform(X_train) X_test_scaled = [Link](X_test) # JAMAIS fit sur test ! ```
INCORRECT - Fuite de données scaler =
train_test_split(X_all_scaled) # ✅
StandardScaler() X_all_scaled = scaler.fit_transform(X_all) X_train, X_test =
CORRECT - Pas de fuite X_train, X_test =
train_test_split(X_all) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train)
🏷️
X_test_scaled = [Link](X_test) ``` ## 2.4 Encodage des variables catégorielles ###
Stratégies d'encodage par type de variable **Variables ordinales** (ordre important) :
```python # Exemple : Niveau d'éducation education_order = ['Primaire', 'Collège', 'Lycée',
'Licence', 'Master', 'Doctorat'] df['education_encoded'] = df['education'].map( {level: i for i, level in
enumerate(education_order)} ) ``` **Variables nominales** (pas d'ordre) : ```python # One-Hot
Encoding avec gestion des catégories rares from [Link] import OneHotEncoder
# Grouper les catégories rares (< 5% des observations) def group_rare_categories(series,
threshold=0.05): value_counts = series.value_counts(normalize=True) rare_categories =
value_counts[value_counts < threshold].index series_cleaned = [Link](rare_categories,
'Other') return series_cleaned # Application df['city_cleaned'] = group_rare_categories(df['city'])
encoder = OneHotEncoder(drop='first', sparse_output=False, handle_unknown='ignore')
city_encoded = encoder.fit_transform(df[['city_cleaned']]) ``` **Variables haute cardinalité** (> 10
catégories) : ```python # Target Encoding avec validation croisée pour éviter l'overfitting from
category_encoders import TargetEncoder target_encoder = TargetEncoder(cols=
X_test_encoded = target_encoder.transform(X_test) ``` --- #
Prices Kaggle ##
3️⃣
['high_cardinality_var']) X_train_encoded = target_encoder.fit_transform(X_train, y_train)
🎯 Atelier Pratique : Dataset House
Objectifs de l'atelier 1. **Diagnostic complet** du dataset (qualité,
distribution, corrélations) 2. **Nettoyage méthodique** (valeurs manquantes, outliers, doublons)
reproductible** avec sauvegarde des transformateurs ## 📊
3. **Transformation appropriée** (encoding, scaling, feature creation) 4. **Pipeline
Dataset : House Prices - Advanced
Regression Techniques **Contexte :** Prédire le prix de vente de maisons à Ames, Iowa (USA) -
**79 variables explicatives** (23 nominales, 23 ordinales, 14 discrètes, 20 continues) - **1460
observations d'entraînement** - **Variable cible :** SalePrice (prix de vente en dollars) ###
Phase 1 : Exploration diagnostique ```python import pandas as pd import numpy as np import
🔍
[Link] as plt import seaborn as sns from sklearn.model_selection import
train_test_split from [Link] import StandardScaler, LabelEncoder,
OneHotEncoder from [Link] import SimpleImputer # Chargement et première
exploration df = pd.read_csv('[Link]') print(f"Dataset shape: {[Link]}") print(f"Memory
usage: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB") # Vue d'ensemble de la qualité
des données missing_summary = [Link]({ 'Column': [Link], 'Missing_Count':
[Link]().sum(), 'Missing_Percentage': ([Link]().sum() / len(df)) * 100, 'Dtype': [Link] })
missing_summary = missing_summary[missing_summary['Missing_Count'] >
0].sort_values('Missing_Percentage', ascending=False) print(missing_summary) # Analyse de la
variable cible [Link](figsize=(12, 4)) [Link](1, 2, 1) [Link](df['SalePrice'], kde=True)
[Link]('Distribution du prix de vente') [Link](1, 2, 2) [Link](y=df['SalePrice'])
[Link]('Détection d\'outliers sur SalePrice') plt.tight_layout() [Link]() # Statistiques
descriptives par type de variable numeric_cols = df.select_dtypes(include=['int64',
'float64']).[Link]() categorical_cols = df.select_dtypes(include=['object']).[Link]()
{len(categorical_cols)}") ``` ### 🧹
print(f"Variables numériques : {len(numeric_cols)}") print(f"Variables catégorielles :
Phase 2 : Nettoyage méthodique ```python # 1. Gestion
stratégique des valeurs manquantes def analyze_missing_patterns(df): """Analyse les patterns
de valeurs manquantes""" missing_matrix = [Link]() # Variables avec beaucoup de manquant
(> 40%) - Candidates à la suppression high_missing =
missing_summary[missing_summary['Missing_Percentage'] > 40]['Column'].tolist()
print(f"Variables avec >40% de manquant : {high_missing}") # Variables avec pattern logique de
manquant logical_missing = ['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu']
print(f"Variables où 'manquant' = 'absent' : {logical_missing}") return high_missing,
logical_missing high_missing_vars, logical_missing_vars = analyze_missing_patterns(df) #
Stratégie de nettoyage appliquée df_clean = [Link]() # Suppression des variables trop
incomplètes df_clean = df_clean.drop(columns=high_missing_vars) # Remplacement logique
pour les variables où "manquant" signifie "absent" for col in logical_missing_vars: if col in
df_clean.columns: df_clean[col] = df_clean[col].fillna('None') # Imputation pour les variables
numériques restantes remaining_numeric_missing =
df_clean[numeric_cols].columns[df_clean[numeric_cols].isnull().any()].tolist() for col in
remaining_numeric_missing: if col != 'SalePrice': # Ne pas imputer la cible ! # Utiliser médiane
pour robustesse aux outliers df_clean[col] = df_clean[col].fillna(df_clean[col].median()) #
Imputation pour variables catégorielles remaining_categorical_missing =
df_clean[categorical_cols].columns[df_clean[categorical_cols].isnull().any()].tolist() for col in
remaining_categorical_missing: df_clean[col] = df_clean[col].fillna(df_clean[col].mode()[0])
print(f"Valeurs manquantes après nettoyage : {df_clean.isnull().sum().sum()}") ``` ```python # 2.
Détection et traitement des outliers def detect_and_treat_outliers(df, target_col='SalePrice'):
"""Détection contextuelle des outliers""" # Outliers sur la variable cible Q1 =
df[target_col].quantile(0.25) Q3 = df[target_col].quantile(0.75) IQR = Q3 - Q1
outlier_threshold_low = Q1 - 1.5 * IQR outlier_threshold_high = Q3 + 1.5 * IQR outliers_target =
df[(df[target_col] < outlier_threshold_low) | (df[target_col] > outlier_threshold_high)]
print(f"Outliers détectés sur {target_col} : {len(outliers_target)}
({len(outliers_target)/len(df)*100:.1f}%)") # Analyse contextuelle des outliers if
len(outliers_target) > 0: print("Caractéristiques des maisons 'outliers' :")
print(outliers_target[['GrLivArea', 'TotalBsmtSF', 'OverallQual', target_col]].describe()) # Décision
: conserver les outliers légitimes, supprimer les erreurs évidentes # Exemple : maisons avec
surface habitable énorme mais prix très bas suspicious_outliers = df[ (df['GrLivArea'] > 4000) &
(df[target_col] < 200000) ] print(f"Outliers suspects (grande surface, prix bas) :
{len(suspicious_outliers)}") # Suppression des outliers suspects uniquement df_clean =
[Link](suspicious_outliers.index) return df_clean, outliers_target df_clean, outliers_info =
``` ###🔧
detect_and_treat_outliers(df_clean) print(f"Dataset après traitement outliers : {df_clean.shape}")
Phase 3 : Transformation et encodage ```python # 3. Pipeline de preprocessing
complet from [Link] import StandardScaler, OneHotEncoder from
[Link] import ColumnTransformer from [Link] import Pipeline # Mise à jour
des listes de colonnes après nettoyage numeric_cols = df_clean.select_dtypes(include=['int64',
'float64']).[Link]() categorical_cols = df_clean.select_dtypes(include=
['object']).[Link]() # Retirer la variable cible des features if 'SalePrice' in numeric_cols:
numeric_cols.remove('SalePrice') # Séparation X et y X = df_clean.drop('SalePrice', axis=1) y =
df_clean['SalePrice'] # Split train/test AVANT preprocessing pour éviter le data leakage X_train,
X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42,
stratify=[Link](y, q=5, labels=False) ) # Pipeline de preprocessing numeric_transformer =
Pipeline(steps=[ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ])
categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='constant',
fill_value='missing')), ('onehot', OneHotEncoder(drop='first', handle_unknown='ignore',
sparse_output=False)) ]) # ColumnTransformer pour appliquer les bonnes transformations
preprocessor = ColumnTransformer( transformers=[ ('num', numeric_transformer,
numeric_cols), ('cat', categorical_transformer, categorical_cols) ]) # Application du
preprocessing X_train_processed = preprocessor.fit_transform(X_train) X_test_processed =
[Link](X_test) print(f"Shape après preprocessing :") print(f"X_train :
{X_train_processed.shape}") print(f"X_test : {X_test_processed.shape}") # Sauvegarde du
preprocessor pour réutilisation import joblib [Link](preprocessor,
'house_prices_preprocessor.pkl') print("Preprocessor sauvegardé !") ``` ## 📊 Livrables de
l'atelier 1. **Notebook complet** : `Session1_Preprocessing_HousePrices.ipynb` 2. **Dataset
nettoyé** : X_train_processed, X_test_processed 3. **Preprocessor sauvegardé** :
transformation --- # 🎯 ✅
`house_prices_preprocessor.pkl` 4. **Rapport de nettoyage** : Statistiques avant/après
Récapitulatif et points clés ## Check-list du preprocessing réussi - [ ]
**Diagnostic complet** : valeurs manquantes, outliers, distributions - [ ] **Stratégie
contextualisée** : pas de règles automatiques aveugles - [ ] **Pipeline reproductible** :
uniquement - [ ] **Documentation** : choix justifiés et tracés ## 🎪
transformations sauvegardées et versionnées - [ ] **Prévention du data leakage** : fit sur train
Erreurs fréquentes à éviter 1.
**Data leakage** : fit des transformations sur tout le dataset 2. **Suppression systématique** :
outliers peuvent être informatifs 3. **Imputation aveugle** : ne pas tenir compte du contexte
explosion** : variables catégorielles haute cardinalité ## 🎨
métier 4. **Oubli de sauvegarde** : impossibilité de reproduire en production 5. **One-Hot
Prochaine session : Feature
Engineering Le preprocessing a préparé des données **propres et cohérentes**. La session
**Preview** : Polynomial features, interactions, feature selection, PCA... --- ##
complémentaires ###
📚
suivante créera des **features intelligentes** pour maximiser les performances des modèles.
📖 Ressources
Documentation officielle - [Scikit-learn Preprocessing]([Link]
([Link] ### 🎓
[Link]/stable/modules/[Link]) - [Pandas User Guide]
Approfondissement - **Kaggle Learn** :
[Data Cleaning Course]([Link] - **DataCamp** : [Data
learning-in-python) ### 🛠️
Preprocessing in Python]([Link]
Outils avancés - **Pandas Profiling** : EDA automatique - **Great
preprocessing --- **🎯
Expectations** : Tests de qualité des données - **Optuna** : Optimisation des pipelines de
Fin de la Session 1 - Préparation Session 2 : Feature Engineering**