Encodage de Variables Catégorielles : Du Texte aux Nombres pour le Machine Learning

10 min de lecture

Le preprocessing des données est une étape critique en Machine Learning, souvent sous-estimée mais déterminante pour la performance d'un modèle. Une des transformations les plus fondamentales consiste à convertir les variables catégorielles (texte) en variables numériques (nombres). Pourquoi ? Parce que les algorithmes de Machine Learning — qu'il s'agisse de régression linéaire, de forêts aléatoires ou de réseaux de neurones — ne manipulent que des tenseurs numériques. Ils ne peuvent pas "comprendre" directement qu'un arbre est "Mature" ou "Jeune".

Dans ce guide, nous allons analyser en profondeur trois techniques d'encodage appliquées à un dataset réel de 42 588 arbres parisiens (Platanes), en expliquant les fondements théoriques, les pièges à éviter, et les bonnes pratiques industrielles.


Le Dataset : Arbres Remarquables de Paris

Nous travaillons sur un sous-ensemble du patrimoine arboré parisien, filtré sur les Platanes uniquement. Ce dataset contient plusieurs variables catégorielles représentatives des défis courants en Data Science :

  • remarquable : Variable binaire (OUI/NON) indiquant si l'arbre a un intérêt historique ou esthétique exceptionnel
  • stade_de_developpement : Variable ordinale (Jeune → Adulte → Mature) avec une progression logique
  • espece : Variable nominale (Platane, Chêne, Érable, etc.) sans hiérarchie naturelle

Ce cas d'usage est idéal pour illustrer les trois familles d'encodage : binaire, ordinal et nominal.


Problématique Technique : Pourquoi les Catégories Doivent Devenir des Nombres

L'Incompatibilité Fondamentale

Les algorithmes ML reposent sur des opérations mathématiques : multiplication matricielle, calcul de distances euclidiennes, gradients de fonctions de coût. Ces opérations nécessitent des valeurs numériques.

Exemple concret : Si on entraîne un modèle de régression pour prédire la hauteur d'un arbre à partir de son stade de développement, l'algorithme a besoin de calculer :

Hauteur prédite = w₁ × stade + w₂ × circonférence + b

Si stade = "Adulte" (chaîne de caractères), cette équation n'a aucun sens mathématique. Il faut transformer "Adulte" en un nombre (par exemple, 3).

Le Piège de l'Encodage Naïf

La solution la plus simple serait d'assigner arbitrairement :

  • Jeune → 1
  • Adulte → 2
  • Mature → 3

Mais cette approche fonctionne uniquement si l'ordre a du sens (variables ordinales). Pour des catégories nominales comme les espèces d'arbres, cela créerait une hiérarchie artificielle que le modèle pourrait mal interpréter. Par exemple, si Chêne=1, Érable=2, Platane=3, le modèle pourrait "apprendre" que Platane = Chêne + Érable, ce qui est absurde.


Technique 1 : Encodage Binaire Manuel (0/1) — La Variable remarquable

Contexte et Distribution

La variable remarquable est une variable binaire : un arbre est soit remarquable (OUI), soit ordinaire (NON). C'est le cas le plus simple d'encodage catégoriel.

Distribution observée :

  • NON : 42 493 arbres (99.8%)
  • OUI : 95 arbres (0.2%)

Cette distribution déséquilibrée (class imbalance) est typique des problèmes de classification d'événements rares (fraude bancaire, pannes industrielles, etc.).

Méthode d'Encodage

L'encodage manuel est direct et explicite :

df.loc[df.remarquable == "NON", "remarquable"] = 0
df.loc[df.remarquable == "OUI", "remarquable"] = 1

Pourquoi cette méthode ?

  1. Sémantique claire : 0 = absence de propriété, 1 = présence. Cette convention est universelle.
  2. Performance : Pas besoin d'instancier un encodeur complexe pour deux valeurs.
  3. Interprétabilité : Dans un modèle de régression logistique, le coefficient associé aura une interprétation directe (impact sur le log-odds).

Analogie technique : C'est l'équivalent d'un bit en informatique — un interrupteur qui ne peut être que OFF (0) ou ON (1).

Avantages et Limites

Avantages :

  • Simple et lisible
  • Aucune dépendance externe (pas besoin de bibliothèques spécialisées)
  • Économie de mémoire : un seul entier par ligne

Limites :

  • Ne généralise pas aux variables avec plus de deux catégories
  • Nécessite une gestion manuelle des nouvelles données

Technique 2 : Encodage Ordinal — La Variable stade_de_developpement

Théorie des Variables Ordinales

Une variable ordinale est une catégorie où l'ordre relatif a une signification intrinsèque. Le stade de développement d'un arbre suit une progression biologique naturelle :

Ø (manquant) → Jeune → Jeune/Adulte → Adulte → Mature

Cette séquence est non interchangeable : un arbre ne peut pas être "Mature" avant d'avoir été "Jeune". C'est différent d'une variable nominale (espèce) où aucun ordre n'existe.

Analogie : Les niveaux scolaires (Maternelle → Primaire → Collège → Lycée) suivent une progression ordonnée. On ne peut pas être au lycée avant d'avoir terminé le collège.

Implémentation avec OrdinalEncoder

L'utilisation de category_encoders.OrdinalEncoder offre plusieurs avantages sur un mapping manuel :

from category_encoders.ordinal import OrdinalEncoder

mapping = [
    {
        "col": "stade_de_developpement",
        "mapping": {
            np.nan: 0,              # Valeur manquante
            "Jeune (arbre)": 1,
            "Jeune (arbre)Adulte": 2,
            "Adulte": 3,
            "Mature": 4,
        },
    }
]

encoder = OrdinalEncoder(mapping=mapping)
stade = encoder.fit_transform(df[["stade_de_developpement"]])

Gestion des Valeurs Manquantes

Un point critique : la présence de np.nan (valeurs manquantes). Dans ce dataset, certains arbres n'ont pas de stade renseigné. L'encodeur ordinal les mappe explicitement à 0, ce qui permet :

  1. Conservation des données : On ne perd pas les lignes incomplètes
  2. Distinction sémantique : 0 = "information inconnue", différent de 1 = "Jeune"
  3. Compatibilité algorithmique : La plupart des algorithmes ML gèrent les 0, mais pas les NaN

Bonnes pratiques :

  • Toujours vérifier value_counts(dropna=False) avant et après encodage
  • Documenter explicitement la signification de la valeur 0
  • Envisager des stratégies alternatives (imputation par la médiane, modèle prédictif) si les valeurs manquantes représentent >20% des données

Application au DataFrame

Un piège classique pour les débutants est d'oublier d'intégrer l'encodage au DataFrame original :

# ❌ Erreur : l'encodage reste isolé
stade = encoder.fit_transform(df[["stade_de_developpement"]])

# ✅ Correct : intégration au DataFrame
df["stade_de_developpement"] = stade["stade_de_developpement"].copy()

Pourquoi .copy() ? Pour éviter les avertissements SettingWithCopyWarning de Pandas et garantir une copie mémoire indépendante.

Analogie : Remplacer le moteur d'une voiture. Ce n'est pas suffisant de construire un nouveau moteur à côté ; il faut l'installer dans le châssis pour que la voiture fonctionne.


Technique 3 : Encodage Binaire (Binary Encoding) — La Variable espece

Le Défi des Variables Nominales à Haute Cardinalité

La variable espece présente un problème différent : il n'y a aucun ordre naturel entre un Platane, un Chêne ou un Érable. De plus, le dataset complet contient des dizaines voire centaines d'espèces différentes (haute cardinalité).

Pourquoi One-Hot Encoding N'est Pas Optimal Ici

La technique classique pour les variables nominales est le One-Hot Encoding :

  • Chêne → [1, 0, 0]
  • Érable → [0, 1, 0]
  • Platane → [0, 0, 1]

Mais si on a 100 espèces, cela crée 100 colonnes dans le DataFrame, ce qui :

  1. Augmente drastiquement la dimensionnalité (curse of dimensionality)
  2. Crée une matrice creuse (sparse matrix) coûteuse en mémoire
  3. Ralentit l'entraînement des modèles

Binary Encoding : La Solution Compacte

Le Binary Encoding représente chaque catégorie en code binaire, comme en informatique :

Espèce A (1)   → [0, 0, 0, 1]
Espèce B (2)   → [0, 0, 1, 0]
Espèce C (3)   → [0, 0, 1, 1]
Espèce D (4)   → [0, 1, 0, 0]
...
Espèce 100 (100) → [1, 1, 0, 0, 1, 0, 0]  # 2^7 = 128 > 100

Avantage majeur : Pour encoder N catégories, on n'a besoin que de log₂(N) colonnes.

  • 100 espèces → 7 colonnes (car 2⁷ = 128)
  • 1000 espèces → 10 colonnes (car 2¹⁰ = 1024)

Versus 100 ou 1000 colonnes avec One-Hot Encoding !

Implémentation

from category_encoders.binary import BinaryEncoder

encoder = BinaryEncoder()
espece_bin = encoder.fit_transform(data[["espece"]])

print(f"Shape: {espece_bin.shape}")
# Résultat : (207641, 10) → 10 colonnes pour toutes les espèces

Note importante : Dans notre notebook, on applique l'encodage sur data (dataset complet), pas sur df (uniquement les Platanes), car df ne contient qu'une seule espèce. Encoder une seule catégorie n'a aucun intérêt pratique — c'est une démonstration technique.

Analogie : Les Codes-Barres

Un code-barres sur un produit de supermarché est composé de barres noires (1) et blanches (0). Il identifie uniquement le produit, sans suggérer qu'un code 12345 est "meilleur" ou "plus grand" qu'un code 67890. C'est exactement ce que fait le Binary Encoding : identifier de manière unique sans créer de hiérarchie artificielle.


Tableau Récapitulatif : Quelle Technique Choisir ?

Type de Variable Exemple Méthode Recommandée Justification
Binaire OUI/NON, Vrai/Faux Encodage manuel (0/1) Simple, sémantique claire, pas de dépendance
Ordinale Jeune → Adulte → Mature OrdinalEncoder Préserve l'ordre naturel, gestion des valeurs manquantes
Nominale (peu de catégories < 10) Couleurs (Rouge, Vert, Bleu) One-Hot Encoding Pas d'ordre, nombre limité de colonnes créées
Nominale (haute cardinalité > 50) Espèces, Villes, Codes postaux BinaryEncoder Compact (log₂ colonnes), évite la matrice creuse, pas de hiérarchie

Pièges Critiques à Éviter

1. Le Double Encodage

Erreur classique observée : Appliquer deux encodages successifs sur la même colonne.

# ❌ Double encodage (bug détecté dans la version initiale du notebook)
for category in df.stade_de_developpement.unique():
    df.loc[df.stade_de_developpement == category, "stade_de_developpement"] = code

encoder = OrdinalEncoder(mapping=mapping)
stade = encoder.fit_transform(df[["stade_de_developpement"]])

Symptôme : Résultats aberrants (valeurs -1.0 ou 0.0 uniquement) parce que l'encodeur cherche les catégories textuelles originales, mais trouve des nombres déjà transformés.

Solution : Supprimer l'encodage manuel et n'utiliser que OrdinalEncoder.

2. Oublier l'Intégration au DataFrame

# ❌ Erreur fréquente
espece_encoded = encoder.fit_transform(df[["espece"]])
# df contient toujours du texte ! Le modèle ne verra jamais les valeurs numériques.

# ✅ Correction
espece_encoded = encoder.fit_transform(df[["espece"]])
df = pd.concat([df, espece_encoded], axis=1)
df.drop("espece", axis=1, inplace=True)

Règle d'or : Toute variable transformée qui doit servir de feature dans un modèle ML DOIT être intégrée au DataFrame final. C'est comme préparer une recette : on ne peut pas donner farine, œufs et sucre dans des bols séparés au four. Il faut tout mélanger dans un seul récipient.

3. Créer une Hiérarchie Artificielle pour les Variables Nominales

# ❌ Mauvaise pratique pour les espèces
df["espece"] = df["espece"].map({"Chêne": 1, "Érable": 2, "Platane": 3})

Le modèle pourrait interpréter mathématiquement que Platane (3) = Chêne (1) + Érable (2), ce qui n'a aucun sens biologique.

Solution : Utiliser One-Hot ou Binary Encoding pour les variables nominales.


Bonnes Pratiques Industrielles

Vérification Systématique Après Transformation

Toujours inspecter les résultats avec value_counts() :

print("Avant encodage :")
print(df.stade_de_developpement.value_counts(dropna=False))

# ... encodage ...

print("Après encodage :")
print(df.stade_de_developpement.value_counts())

Cela permet de détecter immédiatement les anomalies (valeurs inattendues, perte de données).

Sauvegarde des Encodeurs pour la Production

En production, les nouvelles données doivent être encodées exactement de la même manière que les données d'entraînement. Il faut donc sauvegarder les encodeurs :

import joblib

# Après entraînement
joblib.dump(encoder, "ordinal_encoder_stade.pkl")

# En production
encoder = joblib.load("ordinal_encoder_stade.pkl")
new_data_encoded = encoder.transform(new_data[["stade_de_developpement"]])

Pipeline Scikit-Learn

Pour éviter les oublis et garantir la reproductibilité, intégrer les encodeurs dans un pipeline :

from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestRegressor

pipeline = Pipeline([
    ("encoder", OrdinalEncoder(mapping=mapping)),
    ("model", RandomForestRegressor())
])

pipeline.fit(X_train, y_train)
# L'encodage est automatiquement appliqué à chaque prédiction
predictions = pipeline.predict(X_test)

Cas Particulier : Pourquoi espece N'Est Pas Appliquée à Notre DataFrame ?

Dans notre notebook, l'encodage de la variable espece est appliqué sur le dataset complet (data), mais pas intégré au DataFrame filtré df. Pourquoi ?

Raison pédagogique : df ne contient que des Platanes (une seule espèce). Encoder une seule catégorie créerait une colonne constante (toujours la même valeur), ce qui est :

  1. Inutile pour le modèle : Aucune variance, aucune information discriminante
  2. Redondant : Le modèle apprendrait que cette colonne n'apporte rien

C'est une démonstration technique de la méthode Binary Encoding sur le dataset complet (plusieurs espèces), mais pas une étape réelle de preprocessing pour notre sous-ensemble.

Dans un projet réel avec plusieurs espèces dans df, il faudrait absolument appliquer l'encodage et l'intégrer au DataFrame.


Conclusion : L'Art du Preprocessing Catégoriel

Le choix de la technique d'encodage catégoriel n'est pas anodin. Il impacte directement :

  • La performance du modèle : Un mauvais encodage (hiérarchie artificielle) introduit du biais
  • Le temps de calcul : La dimensionnalité excessive ralentit l'entraînement
  • L'interprétabilité : Comprendre la signification des coefficients appris

Les trois principes directeurs sont :

  1. Respecter la nature de la variable : Binaire, ordinale ou nominale ?
  2. Économiser les dimensions : Préférer Binary Encoding à One-Hot pour la haute cardinalité
  3. Vérifier et valider : Toujours inspecter les distributions avant/après encodage

Ce guide, basé sur un dataset réel de 42 588 arbres parisiens, fournit un framework décisionnel réutilisable pour tout projet de Data Science nécessitant le preprocessing de variables catégorielles.


Ressources Complémentaires

Bibliothèques Utilisées

  • Category Encoders : Bibliothèque complète d'encodeurs (Ordinal, Binary, Target, etc.)
  • Pandas : Manipulation de DataFrames
  • NumPy : Opérations numériques et gestion des valeurs manquantes

Techniques Avancées Non Couvertes

  • Target Encoding : Encoder une catégorie par la moyenne de la variable cible (risque de fuite de données)
  • Frequency Encoding : Remplacer chaque catégorie par sa fréquence d'apparition
  • Embeddings : Représentation vectorielle apprise (utilisée en Deep Learning)

Dataset Source

Le dataset des arbres de Paris est fourni par OpenClassrooms dans le cadre du cours "Initiez-vous au Machine Learning". Il contient plus de 200 000 observations avec des métadonnées sur l'espèce, la localisation, les dimensions et l'état sanitaire des arbres.

Clément R
~
Partager :