Garden of KnowledgeApplied Sciences › Computer Science › Software › Data Science
March 22, 2026

Feature Engineering et Prétraitement des Données

Le feature engineering est souvent plus déterminant que le choix de l’algorithme. Un bon feature engineering peut transformer un modèle médiocre en un excellent modèle ; un mauvais peut rendre inutile le meilleur des algorithmes.

Pipeline de prétraitement§

graph LR
    raw[Données brutes] --> audit[Audit qualité]
    audit --> clean[Nettoyage]
    clean --> enc[Encodage]
    enc --> scale[Mise à l'échelle]
    scale --> feat[Feature Engineering]
    feat --> sel[Sélection]
    sel --> model[Modèle]

Audit et compréhension des données§

Avant tout traitement :

import pandas as pd

df = pd.read_csv("data.csv")
df.info()           # Types, valeurs non-nulles
df.describe()       # Statistiques descriptives
df.isnull().sum()   # Valeurs manquantes par colonne
df.duplicated().sum()  # Doublons

Questions clés :

Gestion des valeurs manquantes§

Stratégies§

MéthodeDescriptionQuand l’utiliser
Suppression lignesSupprimer si < 5% manquantPeu de données manquantes
Suppression colonnesSupprimer si > 50-70% manquantColonne quasi-vide
Imputation médianeRemplacer par la médianeNumériques avec outliers
Imputation moyenneRemplacer par la moyenneDistribution symétrique
Imputation modeRemplacer par la valeur la plus fréquenteCatégorielles
Imputation par modèleKNN, régression, MICEDonnées MNAR
Indicateur de manquanceAjouter colonne booléenneSi le fait d’être absent est informatif
from sklearn.impute import SimpleImputer, KNNImputer

# Imputation médiane
imputer = SimpleImputer(strategy='median')
X_filled = imputer.fit_transform(X)

# KNN imputation
knn_imputer = KNNImputer(n_neighbors=5)
X_knn = knn_imputer.fit_transform(X)

Types de manquance :

Gestion des outliers§

Détection§

# Méthode IQR (Interquartile Range)
Q1 = df['feature'].quantile(0.25)
Q3 = df['feature'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df['feature'] < lower) | (df['feature'] > upper)]

# Z-score (>3 ou <-3 = outlier)
from scipy import stats
z_scores = stats.zscore(df['feature'])
outliers_z = df[abs(z_scores) > 3]

Traitement§

OptionDescription
SuppressionSupprimer si erreur de saisie évidente
WinsorisationPlafonner à percentile 1% / 99%
Transformation logCompresser les distributions longues
Traitement séparéModèle différent pour les valeurs extrêmes
ConservationSi l’outlier est réel et informatif

Encodage des variables catégorielles§

One-Hot Encoding§

Crée une colonne binaire par catégorie. Pour K catégories → K-1 colonnes (éviter la multicolinéarité).

pd.get_dummies(df['couleur'], drop_first=True)
# ou
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(drop='first', sparse=False)

Usage : variables nominales (pas d’ordre). Problème : explosion dimensionnelle si beaucoup de catégories.

Label Encoding§

Assigne un entier à chaque catégorie : rouge=0, vert=1, bleu=2.

Usage : variables ordinales (taille: S < M < L < XL) ou arbres de décision (insensibles à l’ordre arbitraire). Attention : induit un ordre artificiel, incompatible avec la régression logistique.

Target Encoding§

Remplace chaque catégorie par la moyenne de la variable cible pour cette catégorie.

# Moyenne de y pour chaque catégorie
target_mean = df.groupby('categorie')['target'].mean()
df['categorie_encoded'] = df['categorie'].map(target_mean)

Usage : haute cardinalité (pays, code postal, ID). Risque : data leakage si mal implémenté → utiliser avec K-Fold.

Frequency Encoding§

Remplace par la fréquence d’apparition. Préserve l’information de rareté.

Mise à l’échelle (Scaling)§

Indispensable pour les algorithmes sensibles à l’échelle : régression, SVM, KNN, réseaux de neurones. Non nécessaire pour les arbres de décision et forêts aléatoires.

graph TD
    dist{Distribution ?}
    dist -->|Gaussienne| std[Standardisation\nz = (x - μ) / σ]
    dist -->|Non-gaussienne| minmax[Min-Max\nx' = (x - min)/(max - min)]
    dist -->|Beaucoup d'outliers| robust[Robust Scaler\n(IQR)]
    dist -->|Loi de puissance| log[Transformation log]
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# Standardisation : moyenne 0, écart-type 1
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)

# Min-Max : [0, 1]
minmax = MinMaxScaler()
X_minmax = minmax.fit_transform(X_train)

# Robust : utilise médiane et IQR
robust = RobustScaler()
X_robust = robust.fit_transform(X_train)

Règle critique : fit uniquement sur le train set, transform sur train et test. Sinon data leakage.

Feature Engineering§

Variables numériques§

Transformations de distribution :

import numpy as np
df['log_revenu'] = np.log1p(df['revenu'])        # log(1+x), gère x=0
df['sqrt_age'] = np.sqrt(df['age'])
df['revenu_carré'] = df['revenu'] ** 2

Interactions :

df['surface_par_piece'] = df['surface'] / df['nb_pieces']
df['age_x_revenu'] = df['age'] * df['revenu']

Discrétisation (Binning) :

df['age_bin'] = pd.cut(df['age'], bins=[0, 18, 35, 60, 100],
                        labels=['mineur', 'jeune', 'adulte', 'senior'])

Variables temporelles§

df['heure'] = df['timestamp'].dt.hour
df['jour_semaine'] = df['timestamp'].dt.dayofweek
df['mois'] = df['timestamp'].dt.month
df['est_weekend'] = df['jour_semaine'].isin([5, 6]).astype(int)
df['trimestre'] = df['timestamp'].dt.quarter

# Encodage cyclique (heure 23 est proche de heure 0)
import numpy as np
df['heure_sin'] = np.sin(2 * np.pi * df['heure'] / 24)
df['heure_cos'] = np.cos(2 * np.pi * df['heure'] / 24)

Variables textuelles§

# Longueur, complexité
df['nb_mots'] = df['texte'].apply(lambda x: len(x.split()))
df['nb_caracteres'] = df['texte'].str.len()
df['ratio_majuscules'] = df['texte'].apply(lambda x: sum(c.isupper() for c in x) / len(x))
df['nb_ponctuation'] = df['texte'].apply(lambda x: sum(c in '.,!?' for c in x))

Sélection de features§

Trop de features → surajustement, coût computationnel, bruit.

Méthodes de filtre (Filter Methods)§

Calculées indépendamment du modèle, rapides.

# Variance faible → feature peu informative
from sklearn.feature_selection import VarianceThreshold
sel = VarianceThreshold(threshold=0.01)

# Corrélation avec la cible
correlations = df.corr()['target'].abs().sort_values(ascending=False)

# Test χ² pour catégorielles
from sklearn.feature_selection import chi2, SelectKBest
selector = SelectKBest(chi2, k=10)

Méthodes wrapper (Wrapper Methods)§

Utilisent le modèle comme oracle, plus précises mais coûteuses.

from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier

rfe = RFE(estimator=RandomForestClassifier(), n_features_to_select=10)
rfe.fit(X_train, y_train)
selected = X.columns[rfe.support_]

Méthodes embedded§

Sélection intégrée à l’entraînement :

# Importance des features (RandomForest, XGBoost)
importances = model.feature_importances_
top_features = pd.Series(importances, index=X.columns).sort_values(ascending=False).head(20)

# Lasso (L1) : annule les coefficients des features inutiles
from sklearn.linear_model import LassoCV
lasso = LassoCV(cv=5)
lasso.fit(X_train, y_train)
selected = X.columns[lasso.coef_ != 0]

Gestion du déséquilibre de classes§

Problème courant en détection de fraude, maladies rares, etc.

graph TD
    imbclass{Déséquilibre ?}
    imbclass -->|Léger < 1:10| weights[Pondération\nclass_weight='balanced']
    imbclass -->|Modéré| smote[Oversampling\nSMOTE]
    imbclass -->|Fort > 1:100| combine[SMOTE + Tomek Links\nou threshold tuning]
from imblearn.over_sampling import SMOTE
from imblearn.combine import SMOTETomek

# SMOTE : génère des exemples synthétiques de la classe minoritaire
smote = SMOTE(sampling_strategy=0.5, random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# Pondération dans le modèle
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(class_weight='balanced')

Attention : appliquer le resampling uniquement sur le train set, jamais sur le test set.

Erreurs courantes (Data Leakage)§

Le data leakage se produit lorsque des informations du futur contaminent l’entraînement.

TypeExempleSolution
Leakage de targetUtiliser une feature calculée à partir de la cibleVérifier les corrélations suspectes > 0.9
Leakage temporelEntraîner sur des données futures par rapport au testToujours faire un split temporel chronologique
Leakage de preprocessingScaler sur train + test avant de splitterToujours splitter avant de scaler
Feature identifiantID client → corrélé à la cible par hasardSupprimer les identifiants
—The Gardener