Feature engineering en R avec recipes : encoder, imputer, transformer
Normaliser, encoder en variables indicatrices, imputer les valeurs manquantes et transformer l’asymétrie — un prétraitement qui se branche directement sur votre workflow de modélisation
Un guide pratique du feature engineering et du prétraitement des données en R avec le package recipes. Construisez une recipe, faites-en le prep() et le bake(), et appliquez les étapes essentielles — normalisation, encodage en variables indicatrices des catégories, imputation des données manquantes (y compris l’imputation multiple), transformations de l’asymétrie, PCA, et suppression des prédicteurs à variance quasi nulle — puis branchez la recipe sur un workflow tidymodels.
Date de publication
25 juin 2026
Modifié
7 juillet 2026
AstuceL’essentiel à retenir
Le feature engineering est le prétraitement qui transforme des colonnes brutes en prédicteurs prêts pour la modélisation — souvent il compte davantage pour la précision que le choix du modèle lui-même.
Le package recipes déclare le prétraitement comme une spécification réutilisable : recipe() définit les étapes, prep() les apprend à partir des données d’entraînement, bake() les applique — et à l’intérieur d’un workflow tout se fait automatiquement.
Étapes essentielles :step_normalize() (centrer + réduire), step_dummy() (encoder les catégories), step_impute_*() (combler les valeurs manquantes), step_YeoJohnson()/step_log() (corriger l’asymétrie), step_pca() (compresser les prédicteurs corrélés), step_nzv()/step_zv() (supprimer les prédicteurs inutiles).
L’imputation est intégrée : step_impute_median() pour un remplissage rapide, step_impute_knn() ou step_impute_bag() pour une estimation fondée sur un modèle, dans l’esprit de l’imputation multiple, qui exploite les autres prédicteurs.
Ajustez sur l’entraînement, appliquez au test. Une recipe apprend chaque statistique (moyennes, médianes, niveaux des variables indicatrices) à partir du seul jeu d’entraînement — ainsi le prétraitement ne laisse jamais fuiter d’information du test.
Les recipes se branchent directement sur le workflow tidymodels : construisez la recipe une fois, faites-en add_recipe(), c’est terminé.
Un modèle ne vaut que ce que valent les prédicteurs que vous lui fournissez. Les données réelles arrivent en désordre : des variables à des échelles radicalement différentes, des colonnes catégorielles qu’un modèle ne sait pas lire, des cellules manquantes, des distributions asymétriques, et des colonnes qui ne portent aucun signal. Le feature engineering est l’art de corriger tout cela — et en pratique il fait davantage bouger l’aiguille de la précision que de remplacer un algorithme par un autre.
Le package recipes (qui fait partie de tidymodels) rend le feature engineering reproductible et sans fuite. Au lieu de modifier votre data frame à la main — au risque que votre jeu de test soit préparé différemment de votre jeu d’entraînement — vous écrivez une recipe : une liste déclarative d’opérations step_*(). La recipe apprend ses paramètres (les moyennes à soustraire, la médiane à imputer, les niveaux de facteur à encoder) à partir des seules données d’entraînement, puis applique exactement ceux-ci à toute nouvelle donnée. Cette seule discipline évite le bug silencieux le plus courant en ML appliqué : la fuite d’information du jeu de test vers le prétraitement.
Utilisez une recipe chaque fois que vous prétraitez pour un modèle — c’est-à-dire presque toujours. Cette leçon parcourt les étapes essentielles que vous emploierez au quotidien, puis branche la recipe finale sur le workflow de modélisation que vous connaissez déjà.
Les données (et quelques valeurs manquantes)
Nous utiliserons les données de logement Boston du package MASS — 506 quartiers, la variable cible medv (valeur médiane du logement, en milliers de dollars), et un mélange de prédicteurs numériques. Pour illustrer l’encodage, nous ajouterons une colonne catégorielle synthétique (river_side), et pour illustrer l’imputation, nous retirerons quelques valeurs de crim (criminalité par habitant) afin d’avoir de réelles données manquantes à combler :
data("Boston", package ="MASS")boston <- Boston# A categorical predictor to demonstrate encodingboston$river_side <-factor(ifelse(boston$chas ==1, "river", "inland"))boston$chas <-NULL# Introduce a few missing values in crim to demonstrate imputationset.seed(123)boston$crim[sample(nrow(boston), 15)] <-NA# Confirm the categorical column and the 15 missing crime valuesstr(boston[, c("crim", "rm", "lstat", "river_side", "medv")])
Nous avons une variable cible numérique, plusieurs prédicteurs numériques à des échelles différentes, un facteur, et 15 valeurs de criminalité manquantes — un petit désordre réaliste à nettoyer.
Pourquoi une recipe ? prep() et bake()
Une recipe comporte trois moments. recipe() déclare la variable cible, les prédicteurs et les étapes (rien ne s’exécute encore). prep() estime les étapes à partir des données d’entraînement (calcule les moyennes, apprend les niveaux des variables indicatrices, ajuste les modèles d’imputation). bake() applique la recipe préparée à un jeu de données — entraînement, test, ou données entièrement nouvelles :
library(recipes)data("Boston", package ="MASS")boston <- Bostonboston$river_side <-factor(ifelse(boston$chas ==1, "river", "inland")); boston$chas <-NULLset.seed(123); boston$crim[sample(nrow(boston), 15)] <-NA# Declare a small recipe: impute, then normalize the numeric predictorsrec <-recipe(medv ~ ., data = boston) |>step_impute_median(crim) |>step_normalize(all_numeric_predictors())# Learn the steps from the data, then apply themprepped <-prep(rec, training = boston)baked <-bake(prepped, new_data =NULL) # NULL = return the prepped TRAINING datahead(baked[, c("crim", "rm", "lstat", "medv")], 4)
crim n’a désormais plus de valeurs manquantes (comblées par la médiane) et les prédicteurs sont centrés et réduits. Le point clé : chaque statistique — la médiane, les moyennes, les écarts-types — a été apprise dans prep() à partir des données d’entraînement, et bake() appliquerait ces mêmes valeurs à toute nouvelle donnée, sans recalcul. À l’intérieur d’un workflow vous appelez rarement prep()/bake() vous-même — fit() et predict() le font pour vous — mais le voir une fois rend concrète la garantie d’absence de fuite.
Étape 1 — Normaliser les prédicteurs numériques
Beaucoup de modèles sont sensibles à l’échelle : les méthodes fondées sur la distance (kNN, SVM), la régression pénalisée (lasso/ridge), et la PCA supposent toutes des unités comparables. step_normalize() centre chaque prédicteur sur une moyenne de 0 et le réduit à un écart-type de 1 :
library(recipes)data("Boston", package ="MASS")boston <- Bostonrec <-recipe(medv ~ ., data = boston) |>step_normalize(all_numeric_predictors())baked <-prep(rec) |>bake(new_data =NULL)# Each predictor now has mean ~0 and sd ~1round(sapply(baked[, c("crim", "rm", "tax", "lstat")], function(x) c(mean =mean(x), sd =sd(x))), 2)
crim rm tax lstat
mean 0 0 0 0
sd 1 1 1 1
Utilisez step_center() / step_scale() si vous ne voulez qu’un seul des deux. Pour les modèles à base d’arbres (arbres de décision, forêts aléatoires, boosting), la mise à l’échelle est inutile — ils sont invariants à un rééchelonnement monotone — donc vous pouvez la sauter dans ce cas.
Étape 2 — Encoder les prédicteurs catégoriels
La plupart des modèles ne peuvent pas consommer un facteur directement ; il leur faut des nombres. step_dummy() transforme un facteur à k niveaux en k−1 colonnes indicatrices binaires (un niveau servant de référence) :
library(recipes)data("Boston", package ="MASS")boston <- Bostonboston$river_side <-factor(ifelse(boston$chas ==1, "river", "inland")); boston$chas <-NULLrec <-recipe(medv ~ ., data = boston) |>step_dummy(all_nominal_predictors())baked <-prep(rec) |>bake(new_data =NULL)# The factor became a 0/1 indicator columntable(baked$river_side_river)
0 1
471 35
all_nominal_predictors() saisit d’un coup tous les prédicteurs de type facteur/caractère. Quelques compagnons utiles à connaître pour les catégorielles plus délicates :
step_other(threshold = 0.05) — regroupe les niveaux rares dans une unique catégorie "other" avant l’encodage en variables indicatrices (vous évite de créer des dizaines de colonnes indicatrices presque vides).
step_novel() — réserve un niveau pour les catégories qui n’apparaissent que dans les nouvelles données, afin que la prédiction ne plante pas sur un niveau jamais vu.
Les colonnes à forte cardinalité (code postal, identifiant produit — des centaines de niveaux) explosent en des centaines de variables indicatrices. Pour celles-ci, l’encodage par effet ou par vraisemblance (remplacer chaque niveau par un résumé de la variable cible) via la fonction step_lencode_*() du package embed est bien plus compact. C’est l’approche moderne, celle des manuels de feature engineering, pour les prédicteurs à nombreuses catégories.
Vous n’obtenez presque jamais des données complètes, et la plupart des modèles refusent de tourner avec des NA. Supprimer des lignes gaspille des données et peut biaiser le résultat. L’imputation comble les trous en s’appuyant sur le reste des données — et recipes vous offre toute une échelle, du rapide au sophistiqué :
library(recipes)data("Boston", package ="MASS")boston <- Bostonset.seed(123); boston$crim[sample(nrow(boston), 15)] <-NA# Median imputation: fast, robust to outliers, learned from training datarec_median <-recipe(medv ~ ., data = boston) |>step_impute_median(crim)baked <-prep(rec_median) |>bake(new_data =NULL)sum(is.na(baked$crim)) # all 15 gaps filled
[1] 0
L’échelle de l’imputation :
step_impute_median() / step_impute_mean() — comblent avec une seule valeur de résumé. Rapide et adapté à quelques valeurs manquantes ; la médiane résiste aux valeurs aberrantes. L’inconvénient : elle ignore les autres prédicteurs et réduit la variance.
step_impute_mode() — l’équivalent catégoriel (le niveau le plus fréquent).
step_impute_knn() — comble chaque trou à partir des k lignes les plus similaires (en utilisant les autres prédicteurs). C’est une estimation fondée sur un modèle, dans l’esprit de l’imputation multiple : elle emprunte de l’information à travers les colonnes au lieu de se réduire à un seul nombre, ce qui préserve bien mieux les relations.
step_impute_bag() — impute avec un modèle d’arbres baggés ajusté sur les autres prédicteurs ; l’option intégrée la plus flexible, idéale quand le caractère manquant dépend d’autres variables.
library(recipes)data("Boston", package ="MASS")boston <- Bostonset.seed(123); boston$crim[sample(nrow(boston), 15)] <-NA# kNN imputation: uses the OTHER predictors to estimate each missing crime valuerec_knn <-recipe(medv ~ ., data = boston) |>step_impute_knn(crim, neighbors =5)baked <-prep(rec_knn) |>bake(new_data =NULL)sum(is.na(baked$crim))
[1] 0
Note« L’imputation multiple en R » — la version honnête
L’imputation multiple classique (le package mice) crée plusieurs jeux de données complets pour propager l’incertitude d’imputation dans l’inférence — le bon outil quand vous avez besoin d’erreurs standard correctes. Pour la modélisation prédictive, l’objectif est différent : un bon remplissage unique de chaque trou, appris sans laisser fuiter le jeu de test. step_impute_knn() / step_impute_bag() font exactement cela, fondés sur un modèle et à l’intérieur du workflow — le cousin pratique et orienté prédiction de l’imputation multiple. Choisissez mice quand vous avez besoin d’inférence ; choisissez une étape de recipe quand vous avez besoin de prédictions.
Étape 4 — Transformer les prédicteurs asymétriques
Les prédicteurs asymétriques peuvent nuire aux modèles qui supposent des entrées à peu près symétriques. step_YeoJohnson() trouve une transformation de puissance qui rend un prédicteur plus symétrique (elle fonctionne sur les valeurs nulles et négatives, contrairement à un log simple) ; step_log() est le choix simple pour les colonnes strictement positives, asymétriques à droite :
library(recipes)library(ggplot2)data("Boston", package ="MASS")boston <- Boston# Apply a Yeo-Johnson transform to the heavily right-skewed crime variablerec <-recipe(medv ~ ., data = boston) |>step_YeoJohnson(crim)baked <-prep(rec) |>bake(new_data =NULL)# Before vs after, on a common layout (base-R reshape, no tidyverse)before <-data.frame(value = boston$crim, when ="Before (raw crim)")after <-data.frame(value = baked$crim, when ="After (Yeo-Johnson)")both <-rbind(before, after)both$when <-factor(both$when, levels =c("Before (raw crim)", "After (Yeo-Johnson)"))ggplot(both, aes(x = value)) +geom_histogram(bins =30, fill ="#3a86d4", color ="white") +facet_wrap(~ when, scales ="free") +labs(x =NULL, y ="Count") +theme_minimal()
La variable de criminalité brute est fortement asymétrique à droite (une longue traîne de secteurs à forte criminalité) ; après la transformation elle est bien plus symétrique — plus facile à exploiter pour un modèle linéaire ou pénalisé. tidymodels propose aussi step_BoxCox() (valeurs positives uniquement) et une famille apparentée pour d’autres distributions.
Étape 5 — Compresser les prédicteurs corrélés avec la PCA
Lorsque les prédicteurs sont fortement corrélés, step_pca() les remplace par une poignée de composantes principales non corrélées qui retiennent l’essentiel de la variation — des entrées moins nombreuses et plus propres, et un remède à la multicolinéarité. Normalisez toujours d’abord, car la PCA est sensible à l’échelle :
library(recipes)data("Boston", package ="MASS")boston <- Bostonrec <-recipe(medv ~ ., data = boston) |>step_normalize(all_numeric_predictors()) |>step_pca(all_numeric_predictors(), num_comp =3)baked <-prep(rec) |>bake(new_data =NULL)# The numeric predictors are now 3 principal componentshead(baked[, c("PC1", "PC2", "PC3", "medv")], 4)
Les 13 prédicteurs numériques se ramènent à 3 composantes. Pour comprendre ce que fait la PCA — saturations, variance expliquée, biplots — voyez la leçon dédiée Analyse en composantes principales ; ici c’est une étape de prétraitement parmi d’autres.
Étape 6 — Supprimer les prédicteurs inutiles
Certaines colonnes ne portent aucun signal. step_zv() supprime les prédicteurs à variance nulle (une unique valeur constante — inutile et source d’erreurs dans certains modèles) ; step_nzv() supprime les prédicteurs à variance quasi nulle (presque constants, par exemple un indicateur valant 1 dans 0.2 % des lignes) :
library(recipes)data("Boston", package ="MASS")boston <- Boston# Add a constant column to show what step_zv removesboston$constant_col <-1rec <-recipe(medv ~ ., data = boston) |>step_zv(all_predictors()) |>step_nzv(all_predictors())baked <-prep(rec) |>bake(new_data =NULL)# constant_col is gone"constant_col"%in%names(baked)
[1] FALSE
C’est une assurance bon marché : exécutez-les tôt pour que les étapes ultérieures et le modèle ne trébuchent jamais sur une colonne morte.
Tout assembler — une recipe dans un workflow
Le bénéfice : empilez les étapes dont vous avez besoin dans une seule recipe et confiez-la au workflow que vous connaissez déjà. L’ordre compte — imputez avant de normaliser, encodez avant d’ajuster :
library(recipes)library(parsnip)library(workflows)library(rsample)library(yardstick)data("Boston", package ="MASS")boston <- Bostonboston$river_side <-factor(ifelse(boston$chas ==1, "river", "inland")); boston$chas <-NULLset.seed(123); boston$crim[sample(nrow(boston), 15)] <-NAset.seed(123)split <-initial_split(boston, prop =0.8, strata = medv)train_data <-training(split); test_data <-testing(split)# A full preprocessing recipe, then a linear model — all in one workflowfe_rec <-recipe(medv ~ ., data = train_data) |>step_impute_knn(crim) |># fill missing crime, model-basedstep_YeoJohnson(crim) |># de-skew itstep_dummy(all_nominal_predictors()) |># encode river_sidestep_normalize(all_numeric_predictors()) |># put predictors on a common scalestep_nzv(all_predictors()) # drop dead columnswf <-workflow() |>add_recipe(fe_rec) |>add_model(linear_reg() |>set_engine("lm") |>set_mode("regression"))fit_wf <-fit(wf, data = train_data)results <-cbind(predict(fit_wf, new_data = test_data), medv = test_data$medv)metric_set(rmse, rsq)(results, truth = medv, estimate = .pred)
# A tibble: 2 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 rmse standard 4.98
2 rsq standard 0.701
Une seule recipe a géré l’imputation, l’asymétrie, l’encodage, la mise à l’échelle et la suppression des colonnes mortes — et parce qu’elle vit à l’intérieur du workflow, le prétraitement identique a été appliqué automatiquement au jeu de test lorsque nous avons appelé predict(). C’est le feature engineering bien fait : déclaratif, réutilisable et sans fuite.
NoteL’ordre des étapes compte (facultatif)
Les étapes d’une recipe s’exécutent dans l’ordre où vous les écrivez, et certaines dépendent des précédentes :
Imputez d’abord les valeurs manquantes — les étapes ultérieures (normaliser, PCA) ne peuvent pas calculer de statistiques sur des NA.
Transformez l’asymétrie (step_YeoJohnson) avant de normaliser, pour que la mise à l’échelle agisse sur la version symétrique.
Encodez les facteurs (step_dummy) avant les étapes qui exigent une matrice entièrement numérique (PCA, glmnet).
Normalisez avant step_pca (la PCA est sensible à l’échelle).
Supprimez tôt les colonnes à variance nulle / quasi nulle pour que rien en aval ne s’étrangle sur une colonne morte.
Une règle commode : imputation → transformations → encodage → mise à l’échelle → réduction de dimension → filtrage. En cas de doute, faites le prep() de la recipe et le bake() de quelques lignes pour voir ce que chaque étape a produit.
🟢 Avec un agent IA
Bloqué sur vos propres données ? Demandez à Prova« quelle recipe de prétraitement dois-je construire pour ce jeu de données ? » — elle répond avec du code recipe() + step_*() que vous pouvez exécuter sur votre jeu de données, puis vous aide à ordonner les étapes et à corriger les fuites. The runtime is the judge.Ask Prova →
Problèmes courants
prep() échoue parce qu’une étape a rencontré des NA. Une étape qui exige des données complètes (par exemple step_pca, step_normalize) s’est exécutée avant l’imputation. Déplacez step_impute_*() en tête de la recipe.
step_dummy() « new levels in test data ». Le jeu de test comporte une catégorie que le jeu d’entraînement n’a jamais vue. Ajoutez step_novel()avantstep_dummy() pour réserver une place aux niveaux jamais vus.
Vous avez normalisé, et votre modèle à base d’arbres ne s’est pas amélioré. Les modèles à base d’arbres (rpart, ranger, xgboost) sont invariants à l’échelle — normaliser ne les aidera pas (ni ne leur nuira). Gardez step_normalize() pour les modèles fondés sur la distance et les modèles pénalisés.
bake(new_data = NULL) vous a déconcerté.new_data = NULL est l’idiome pour « rends-moi les données d’entraînement sur lesquelles cette recipe a été préparée. » Passez un véritable data frame pour traiter de nouvelles données.
Questions fréquentes
NoteQu’est-ce que le feature engineering en R ?
Le feature engineering consiste à préparer vos colonnes brutes pour qu’un modèle puisse bien les exploiter — mettre les nombres à l’échelle, encoder les catégories, combler les valeurs manquantes, corriger l’asymétrie et supprimer les prédicteurs inutiles. En R, l’outil moderne est le package recipes, qui vous permet de déclarer toutes ces étapes une fois et de les appliquer à l’identique aux données d’entraînement et de test.
NoteQuelle est la différence entre prep() et bake() ?
prep()apprend les paramètres d’une recipe à partir des données d’entraînement (les moyennes à soustraire, les niveaux des variables indicatrices, les modèles d’imputation) ; bake()applique la recipe préparée à un jeu de données. À l’intérieur d’un workflow tidymodels vous les appelez rarement directement — fit() fait le prep et predict() le bake — mais ensemble c’est ce qui garantit que le jeu de test est prétraité exactement comme le jeu d’entraînement.
NoteComment faire de l’imputation multiple en R ?
Pour l’inférence (des erreurs standard correctes), la réponse classique est le package mice, qui construit plusieurs jeux de données complétés. Pour la modélisation prédictive, utilisez une étape de recipe — step_impute_knn() ou step_impute_bag() estime chaque valeur manquante à partir des autres prédicteurs, fondée sur un modèle et sans laisser fuiter le jeu de test. Toutes deux vont au-delà d’un simple remplissage par moyenne/médiane ; choisissez celle qui correspond à votre objectif.
NoteComment encoder une variable catégorielle pour un modèle en R ?
Ajoutez step_dummy(all_nominal_predictors()) à votre recipe — elle transforme chaque facteur en colonnes indicatrices 0/1. Pour une colonne comportant de nombreux niveaux rares, regroupez-les d’abord avec step_other() ; pour les colonnes à très forte cardinalité (des centaines de niveaux), utilisez l’encodage par effet/vraisemblance via le package embed plutôt que des dizaines de variables indicatrices.
NoteFaut-il mettre mes données à l’échelle avant ou après la séparation entre entraînement et test ?
Après — et à l’intérieur d’une recipe. Si vous mettez à l’échelle tout le jeu de données avant la séparation, les moyennes et les écarts-types sont calculés en utilisant les lignes de test, ce qui laisse fuiter de l’information et gonfle votre estimation de performance. Séparez d’abord, placez step_normalize() dans la recipe, et elle apprendra les statistiques à partir du seul entraînement.
Testez vos connaissances
ImportantExercice : construire une recipe de prétraitement robuste
En partant de Boston (MASS) avec quelques NA introduits dans lstat, écrivez une recipe qui (1) impute les valeurs manquantes en utilisant les autres prédicteurs, (2) atténue l’asymétrie de crim et lstat, (3) normalise tous les prédicteurs numériques, et (4) supprime toute colonne à variance quasi nulle. Faites-en le prep et confirmez qu’il ne reste aucune valeur manquante.
AstuceIndice
Ordonnez les étapes imputation → transformation → normalisation → filtrage. Utilisez step_impute_knn(), step_YeoJohnson(), step_normalize(), et step_nzv().
La recipe impute, atténue l’asymétrie, met à l’échelle et filtre en une seule passe déclarative — prête à être déposée dans un workflow.
Vérification rapide. Vous ajoutez step_pca() à une recipe mais les composantes semblent dominées par une seule variable. Quelle étape de prétraitement avez-vous oubliée, et pourquoi est-ce important ?
NoteAfficher la réponse
Vous avez oublié step_normalize()avantstep_pca(). La PCA est sensible à l’échelle — un prédicteur mesuré dans de grandes unités (par exemple tax) domine la variance et accapare la première composante. Normalisez les prédicteurs d’abord pour que chacun contribue sur un pied d’égalité.
Conclusion
Vous disposez désormais de la boîte à outils essentielle du feature engineering : déclarer le prétraitement sous forme de recipe, l’apprendre avec prep() et l’appliquer avec bake(), et choisir le bon step_*() — normaliser, encoder, imputer, transformer, compresser, filtrer — pour chaque type de désordre. Surtout, une recipe apprend chaque statistique à partir des seules données d’entraînement et la réapplique à travers le workflow, de sorte que votre prétraitement est reproductible et sans fuite.
Prouvez que vous savez le faire. Maîtrisez toute la série Modélisation prédictive en R — suivez votre parcours, construisez des projets et obtenez un certificat.
Cette leçon est reproductible : chaque sortie et chaque figure a été produite par le code montré — copiez n’importe quel bloc et exécutez-le pour les reproduire. The runtime is the judge.
Pour aller plus loin
Kuhn, M., & Johnson, K. Feature Engineering and Selection — le traitement de référence de l’encodage, de l’ingénierie numérique et de la gestion des données manquantes (gratuit en ligne).
Kuhn, M., & Silge, J. Tidy Modeling with R — le chapitre sur recipes et le workflow plus large (CC BY-NC-SA).