Arbres de décision en R : CART avec rpart et tidymodels
Faites pousser un arbre, lisez les séparations, élaguez-le avec le paramètre de complexité — le modèle interprétable derrière les forêts aléatoires et le boosting
Un guide pratique des arbres de décision (CART) en R pour la classification et la régression. Découvrez comment le partitionnement récursif sépare les données pour maximiser la pureté (Gini), comment lire le diagramme de l’arbre, et élaguez l’arbre avec le paramètre de complexité cp pour éviter le surapprentissage. Traité de deux façons — la route classique rpart() + rpart.plot et le workflow tidymodels moderne (parsnip::decision_tree réglé par validation croisée) — plus l’importance des variables et une note sur l’arbre de régression. Sur les données PimaIndiansDiabetes2 et Boston.
Date de publication
25 juin 2026
Modifié
7 juillet 2026
AstucePoints clés
Un arbre de décision (CART — Classification And Regression Trees) prédit un résultat en posant une séquence de questions oui/non sur les prédicteurs. Il fonctionne à la fois pour la classification (une catégorie) et la régression (un nombre). L’implémentation R est rpart (partitionnement récursif).
L’arbre pousse par partitionnement récursif : à chaque nœud il choisit le prédicteur et le seuil qui séparent les données en sous-groupes les plus purs — mesuré par l’indice de Gini (ou l’entropie) pour la classification, et la somme des carrés des résidus pour la régression.
Un arbre complètement développé surapprend. On l’élague avec le paramètre de complexité cp — une pénalité sur la taille de l’arbre. Un cp plus grand donne un arbre plus petit ; on choisit le meilleur cp par validation croisée.
La force majeure de l’arbre est son interprétabilité : le diagramme est le modèle — vous pouvez lire la règle exacte qui classe n’importe quel patient. rpart.plot le dessine proprement.
Deux routes, même idée : le classique rpart() + rpart.plot(), et le moderne parsnip::decision_tree(cost_complexity = tune()) |> set_engine("rpart") réglé par VC.
Un arbre unique est instable — de petits changements dans les données peuvent le remodeler. C’est exactement cette faiblesse qui explique pourquoi les forêts aléatoires et le boosting moyennent plusieurs arbres.
Vous avez un jeu de données clinique et une question oui/non : un patient sera-t-il positif au diabète, étant donné une poignée de mesures — glucose, IMC, âge, pression artérielle, et ainsi de suite ? Un arbre de décision y répond à la manière dont un clinicien raisonnerait à voix haute : “Le glucose est-il au-dessus de 128 ? Si oui, est-il au-dessus de 166 ? …” — une séquence de questions simples qui achemine chaque patient vers une prédiction.
C’est toute l’idée de CART (Classification And Regression Trees), l’algorithme derrière le paquet rpart de R (Recursive Partitioning And Regression Trees). Un arbre sépare les données encore et encore, choisissant à chaque fois la question qui sépare le mieux les résultats, jusqu’à ce que chaque groupe final soit aussi pur que possible. La même machinerie prédit une catégorie (un arbre de classification) ou un nombre (un arbre de régression).
Les arbres ont une vertu remarquable — ils sont faciles à lire et à expliquer. Le diagramme est le modèle : aucun coefficient à interpréter, juste des règles que vous pouvez suivre du doigt. Le hic, c’est qu’un arbre unique surapprend et est instable, c’est pourquoi nous l’élaguons (ici) et pourquoi les leçons suivantes moyennent plusieurs arbres. Nous construisons l’arbre de deux façons — la route classique rpart avec le diagramme rpart.plot caractéristique, et le moderne workflow tidymodels (parsnip::decision_tree réglé par validation croisée) — puis nous lisons les séparations, élaguons et classons les prédicteurs.
NoteQuand recourir à un arbre de décision
Recourez à un arbre unique quand l’interprétabilité compte avant tout — vous avez besoin d’un modèle qu’un non-spécialiste peut lire et auquel il peut se fier, ou vous voulez voir quelles séparations pilotent la prédiction. Les arbres gèrent aussi les relations non linéaires et les interactions automatiquement, sans aucun besoin de mettre à l’échelle les prédicteurs. Quand la précision prédictive brute compte plus qu’un diagramme lisible, passez aux forêts aléatoires ou au gradient boosting — des ensembles d’arbres qui échangent le diagramme unique contre des prédictions meilleures et plus stables.
Les données
Nous utilisons le jeu de données PimaIndiansDiabetes2 du paquet mlbench : 768 femmes d’origine Pima amérindienne, avec pour objectif de prédire diabetes (un résultat binaire pos/neg) à partir de huit prédicteurs cliniques — le nombre de grossesses (pregnant), le glucose plasmatique, la pressure artérielle, l’épaisseur du pli cutané tricipital (triceps), l’insulin sérique, l’indice de masse corporelle (mass), la fonction pedigree du diabète, et l’age.
Cette version comporte des valeurs manquantes (enregistrées comme NA là où une mesure est invraisemblable, p. ex. un glucose nul). En suivant l’analyse source nous supprimons les lignes incomplètes, laissant 392 enregistrements complets, puis nous séparons en un jeu d’apprentissage (80 %, pour faire pousser et élaguer l’arbre) et un jeu de test (20 %, mis de côté pour le juger honnêtement). Nous stratifions sur diabetes pour que les deux jeux conservent le même équilibre positif/négatif, et nous fixons une graine pour la reproductibilité :
library(rsample)data("PimaIndiansDiabetes2", package ="mlbench")# Drop rows with missing measurementspima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)test_data <-testing(split)c(complete =nrow(pima), train =nrow(train_data), test =nrow(test_data))
complete train test
392 313 79
# Class balance is preserved by the stratified splitprop.table(table(train_data$diabetes))
Environ 313 patients pour apprendre, 79 mis de côté pour être le juge, et 8 prédicteurs sur lesquels séparer. À peu près un tiers des patients sont positifs au diabète — un problème de classification modérément déséquilibré mais exploitable . Notez une chose que les arbres rendent facile : aucune mise à l’échelle nécessaire. Une séparation est « le glucose est-il au-dessus de 128 ? », donc les unités n’ont jamais d’importance — contrairement aux méthodes pénalisées ou fondées sur la distance.
Comment un arbre pousse : le partitionnement récursif
Avant d’ajuster, voici l’algorithme en un paragraphe. Commencez avec toutes les données à la racine. Essayez chaque prédicteur et chaque seuil possible, et choisissez la séparation unique qui rend les deux groupes résultants aussi purs que possible — pour la classification, majoritairement une classe ; pour la régression, aussi proches d’une seule valeur que possible. Puis répétez la procédure à l’intérieur de chaque groupe, et encore, faisant pousser l’arbre vers le bas. Arrêtez quand les groupes sont purs, trop petits pour être séparés, ou qu’une règle d’arrêt se déclenche. Le résultat est un arbre binaire de nœuds de décision (un prédicteur + un seuil), de branches, et de nœuds-feuilles (la prédiction).
Faisons pousser un arbre de classification complètement développé sur les données d’apprentissage et lisons-le. rpart() fait le partitionnement récursif ; method = "class" en fait un arbre de classification :
library(rpart)library(rsample)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)# Grow a (nearly) full classification treeset.seed(123)full_tree <-rpart(diabetes ~ ., data = train_data, method ="class")# The decision rules, as textfull_tree
Lisez la sortie comme l’arbre lui-même. Chaque ligne est un nœud : la séparation qui le définit (glucose >= 128), combien de patients d’apprentissage y tombent (n), combien sont mal classés (loss), la classe prédite (yval), et les probabilités de classe. Un * marque une feuille (une prédiction terminale). La séparation à la racine porte sur glucose — la question la plus informative à elle seule — exactement comme un clinicien s’y attendrait.
Lire l’arbre : le diagramme caractéristique
Le texte est exact mais difficile à parcourir. Le diagramme de l’arbre est la raison pour laquelle les gens aiment CART — il est le modèle, lisible d’un coup d’œil. rpart.plot le dessine proprement, ombrant chaque nœud selon la classe prédite :
library(rpart)library(rpart.plot)library(rsample)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)set.seed(123)full_tree <-rpart(diabetes ~ ., data = train_data, method ="class")rpart.plot(full_tree, type =2, extra =104, box.palette ="Blues",branch.lty =3, shadow.col ="gray85", main ="Fully grown tree (Pima diabetes)")
Lisez-le de haut en bas. Chaque nœud montre la classe prédite, les probabilités de classe (p. ex. .36 .64 = 36 % négatif, 64 % positif), et la part de patients qui l’atteignent. Suivez une branche : oui va à gauche, non va à droite. Un patient avec un glucose en dessous de 128 atterrit dans la grande feuille de gauche et est prédit négatif ; un avec un glucose au-dessus de 166 atterrit tout à droite et est prédit positif avec une grande confiance. Plus le nœud est sombre, plus il est positif — une histoire clinique que vous pouvez voir.
Quelle est la précision de cet arbre complet sur des patients qu’il n’a jamais vus ? Évaluez-le sur le jeu de test mis de côté :
library(rpart)library(rsample)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split); test_data <-testing(split)set.seed(123)full_tree <-rpart(diabetes ~ ., data = train_data, method ="class")# Predict the class on the test set, then the accuracypredicted <-predict(full_tree, test_data, type ="class")mean(predicted == test_data$diabetes)
[1] 0.7594937
Environ 76 % des patients de test sont classés correctement — pas mal. Mais un arbre complètement développé tend à être trop complexe : il continue de séparer pour poursuivre les derniers patients d’apprentissage, ajustant un bruit qui ne se généralisera pas. C’est le surapprentissage, et le remède est l’élagage.
Élagage : le paramètre de complexité cp
Un arbre plus grand ajuste toujours mieux les données d’apprentissage — jusqu’à une feuille par patient — mais passé un certain point il mémorise le bruit et prédit moins bien les données nouvelles. L’élagage ramène l’arbre à un arbre plus simple qui se généralise mieux, échangeant une pincée d’ajustement à l’apprentissage contre de la robustesse.
Dans rpart, l’élagage est gouverné par le paramètre de complexité cp : une séparation n’est conservée que si elle améliore l’ajustement de l’arbre d’au moins cp. Ainsi un cp plus grand donne un arbre plus petit (la valeur par défaut est 0.01). Un cp trop petit surapprend ; trop grand sous-apprend. Nous ne le devinons pas — nous laissons la validation croisée choisir le cp qui donne la meilleure précision hors échantillon. rpart enregistre l’erreur validée croisée pour une séquence de valeurs de cp dans sa cptable :
library(rpart)library(rsample)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)set.seed(123)full_tree <-rpart(diabetes ~ ., data = train_data, method ="class")# Cross-validated error for each candidate cp (xerror = CV error, in relative terms)printcp(full_tree)
Chaque ligne est un arbre candidat. nsplit est son nombre de séparations ; xerror est son erreur validée croisée (relative à la racine) et xstd l’erreur standard. Le meilleur arbre est celui avec le xerror le plus faible — lisez cette valeur de CP sur le tableau. La section suivante laisse tidymodels la trouver et l’appliquer pour nous dans un workflow propre et reproductible.
Le workflow tidymodels : régler cp par validation croisée
La façon moderne et unifiée d’élaguer est le workflow tidymodels. La seule chose qui change par rapport à tout autre modèle est la spécification : parsnip::decision_tree() avec le moteur "rpart". Ses trois leviers correspondent directement à rpart :
cost_complexity = le paramètre de complexité cp (la pénalité d’élagage) — celui que nous réglons
tree_depth = la profondeur maximale (jusqu’à combien de questions de profondeur l’arbre peut aller)
min_n = le nombre minimum d’observations qu’un nœud doit avoir pour être séparé
Nous marquons cost_complexity = tune(), construisons une grille de valeurs candidates de cp, et évaluons chacune par validation croisée à 10 plis sur le jeu d’apprentissage, stratifiée sur le résultat :
library(rsample)library(parsnip)library(workflows)library(tune)library(dials)library(yardstick)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)# A tree whose pruning penalty (cp) will be tunedtree_spec <-decision_tree(cost_complexity =tune(), tree_depth =10, min_n =20) |>set_engine("rpart") |>set_mode("classification")tree_wf <-workflow() |>add_formula(diabetes ~ .) |>add_model(tree_spec)# 10-fold CV on the training set, over a grid of cp valuesset.seed(123)folds <-vfold_cv(train_data, v =10, strata = diabetes)grid <-grid_regular(cost_complexity(range =c(-4, -1)), levels =10) # 10^-4 to 10^-1set.seed(123)tree_tuned <-tune_grid( tree_wf, resamples = folds, grid = grid,metrics =metric_set(accuracy, roc_auc))# The cp with the highest cross-validated accuracybest_cp <-select_best(tree_tuned, metric ="accuracy")best_cp
tune_grid() a validé croisée chaque cp candidat ; select_best() renvoie le gagnant. Ici la pénalité choisie est autour de 0.046 — sensiblement plus grande que la valeur par défaut 0.01, donc la validation croisée nous dit qu’un arbre plus petit, élagué se généralise le mieux.
Voir le compromis : précision vs cp
Le graphique de réglage le plus utile est la précision validée croisée en fonction de cp — tout le compromis biais–variance en une seule image :
library(tune)library(ggplot2)# Collect the CV accuracy for every candidate cpacc_curve <-subset(collect_metrics(tree_tuned), .metric =="accuracy")ggplot(acc_curve, aes(x = cost_complexity, y = mean)) +geom_line(color ="#3a86d4", linewidth =1) +geom_point(color ="#3a86d4", size =1.5) +geom_vline(xintercept = best_cp$cost_complexity, linetype ="dashed", color ="gray40") +scale_x_log10() +labs(x =expression("Complexity parameter "*italic(cp) *" (log scale)"),y ="Cross-validated accuracy",title ="Choosing how hard to prune, by cross-validation",subtitle ="Accuracy peaks at a moderate cp, then falls as the tree is pruned too hard (dashed = best cp)" ) +theme_minimal()
Lisez-le de gauche à droite. Pour un cp petit (gauche), à peine d’élagage — l’arbre est grand et un peu surajusté. À mesure que cp grandit (droite), l’arbre est élagué plus durement ; passé le sommet il est trop petit (sous-ajusté) et la précision chute. La ligne pointillée marque le gagnant de la validation croisée — le point idéal entre un arbre trop développé et un moignon.
Finaliser, tracer l’arbre élagué et l’évaluer
Nous verrouillons le meilleur cp, finalisons le workflow, ajustons sur le jeu d’apprentissage, et dessinons l’arbre élagué — le modèle que vous mettriez réellement en production :
library(rsample)library(parsnip)library(workflows)library(tune)library(dials)library(yardstick)library(rpart.plot)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)tree_spec <-decision_tree(cost_complexity =tune(), tree_depth =10, min_n =20) |>set_engine("rpart") |>set_mode("classification")tree_wf <-workflow() |>add_formula(diabetes ~ .) |>add_model(tree_spec)set.seed(123); folds <-vfold_cv(train_data, v =10, strata = diabetes)grid <-grid_regular(cost_complexity(range =c(-4, -1)), levels =10)set.seed(123)tree_tuned <-tune_grid(tree_wf, resamples = folds, grid = grid,metrics =metric_set(accuracy, roc_auc))best_cp <-select_best(tree_tuned, metric ="accuracy")# Finalize the workflow with the chosen cp, fit on the training datafinal_wf <-finalize_workflow(tree_wf, best_cp)final_fit <-fit(final_wf, data = train_data)# Pull the underlying rpart object and plot itpruned_tree <-extract_fit_engine(final_fit)rpart.plot(pruned_tree, type =2, extra =104, box.palette ="Blues",branch.lty =3, shadow.col ="gray85", main ="Pruned tree (Pima diabetes)")
Bien plus simple que l’arbre complet — juste quelques séparations, toutes lisibles. L’arbre élagué conserve les séparations qui comptent (glucose, puis encore glucose et âge) et abandonne le bruit. Évaluez-le maintenant une seule fois sur le jeu de test intact, la mesure honnête de sa performance sur de nouveaux patients :
library(rsample)library(parsnip)library(workflows)library(tune)library(dials)library(yardstick)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)tree_spec <-decision_tree(cost_complexity =tune(), tree_depth =10, min_n =20) |>set_engine("rpart") |>set_mode("classification")tree_wf <-workflow() |>add_formula(diabetes ~ .) |>add_model(tree_spec)set.seed(123); folds <-vfold_cv(train_data, v =10, strata = diabetes)grid <-grid_regular(cost_complexity(range =c(-4, -1)), levels =10)set.seed(123)tree_tuned <-tune_grid(tree_wf, resamples = folds, grid = grid,metrics =metric_set(accuracy, roc_auc))best_cp <-select_best(tree_tuned, metric ="accuracy")# Finalize, fit on train, score ONCE on the held-out test setfinal_fit <-finalize_workflow(tree_wf, best_cp) |>last_fit(split)collect_metrics(final_fit)
Lisez les chiffres (la performance honnête et hors échantillon de l’arbre élagué) :
Précision ≈ 0.80 — l’arbre élagué appelle correctement le statut de diabète environ 80 % du temps sur des patients qu’il n’a jamais vus. Cela égale ou bat l’arbre complètement développé (~76 %) avec un modèle bien plus simple — exactement à quoi sert l’élagage : même précision (ou meilleure), plus facile à expliquer.
ROC AUC ≈ 0.83 — la probabilité que le modèle classe un patient positif aléatoire au-dessus d’un patient négatif aléatoire. 0.5 est un tirage à pile ou face, 1.0 est parfait ; 0.83 est un classifieur véritablement bon. Parce que l’AUC est sans seuil, elle juge le classement du modèle plutôt que la coupure arbitraire à 0.5.
AstuceÀ retenir, prêt à coller
« Un arbre de classification CART élagué (cp ≈ 0.05, choisi par validation croisée à 10 plis) prédit le statut de diabète sur un jeu de test mis de côté avec environ 80 % de précision et un ROC AUC de 0.83 — égalant l’arbre complètement développé avec un modèle bien plus simple et plus interprétable. »
Sur quels prédicteurs l’arbre s’appuie-t-il ? L’importance des variables
Un arbre classe ses prédicteurs gratuitement : un prédicteur qui produit de grandes séparations pures est important. rpart enregistre cela dans variable.importance — extrayez-le de l’ajustement finalisé :
library(rsample)library(parsnip)library(workflows)library(tune)library(dials)library(yardstick)library(ggplot2)data("PimaIndiansDiabetes2", package ="mlbench")pima <-na.omit(PimaIndiansDiabetes2)set.seed(123)split <-initial_split(pima, prop =0.80, strata = diabetes)train_data <-training(split)tree_spec <-decision_tree(cost_complexity =tune(), tree_depth =10, min_n =20) |>set_engine("rpart") |>set_mode("classification")tree_wf <-workflow() |>add_formula(diabetes ~ .) |>add_model(tree_spec)set.seed(123); folds <-vfold_cv(train_data, v =10, strata = diabetes)grid <-grid_regular(cost_complexity(range =c(-4, -1)), levels =10)set.seed(123)tree_tuned <-tune_grid(tree_wf, resamples = folds, grid = grid,metrics =metric_set(accuracy, roc_auc))best_cp <-select_best(tree_tuned, metric ="accuracy")final_fit <-fit(finalize_workflow(tree_wf, best_cp), data = train_data)# Importance scores from the underlying rpart objectimp <-extract_fit_engine(final_fit)$variable.importanceimp_df <-data.frame(variable =names(imp), importance =as.numeric(imp))ggplot(imp_df, aes(x =reorder(variable, importance), y = importance)) +geom_col(fill ="#3a86d4") +coord_flip() +labs(x =NULL, y ="Variable importance", title ="What the tree splits on most") +theme_minimal()
glucose domine de loin, suivi de age et insulin — les prédicteurs sur lesquels l’arbre s’appuie pour séparer les patients positifs des négatifs, et un classement cliniquement sensé. L’importance compte la contribution d’une variable à travers toutes ses séparations (y compris celles que l’élagage a ensuite retirées), donc un prédicteur peut se classer ici même s’il n’apparaît pas dans le diagramme final.
Arbres de régression : prédire un nombre au lieu d’une classe
Tout ce qui précède prédit une classe. Pour prédire un nombre — un arbre de régression — vous ne changez qu’une seule chose : method = "anova" dans rpart (ou set_mode("regression") dans tidymodels). Au lieu de la pureté de Gini, une séparation de régression minimise la somme des carrés des résidus, et chaque feuille prédit le résultat moyen des patients qui y atterrissent. Voici un arbre de régression rapide sur les données classiques de logement Boston (MASS), prédisant la valeur médiane du logement medv :
library(rpart)library(rpart.plot)library(rsample)data("Boston", package ="MASS")set.seed(123)bsplit <-initial_split(Boston, prop =0.80, strata = medv)btrain <-training(bsplit)btest <-testing(bsplit)# A regression tree: method = "anova" predicts a continuous outcomeset.seed(123)reg_tree <-rpart(medv ~ ., data = btrain, method ="anova")rpart.plot(reg_tree, type =2, box.palette ="Blues", shadow.col ="gray85",main ="Regression tree (Boston home values)")
# Prediction error on the held-out test set (RMSE, in $1000s)reg_pred <-predict(reg_tree, btest)sqrt(mean((btest$medv - reg_pred)^2))
[1] 4.821149
Chaque feuille imprime la valeur médiane prédite (en milliers de dollars) et la part de logements qui l’atteignent. L’arbre sépare d’abord sur le nombre de pièces (rm) et le pourcentage de population défavorisée (lstat) — les moteurs les plus forts du prix. Le RMSE ≈ 4.8 signifie qu’une prédiction typique se trompe d’environ $4,800. Vous régleriez cp ici exactement comme dans le cas de la classification, en choisissant le cp qui minimise le RMSE plutôt que de maximiser la précision.
NoteComment une séparation est choisie : Gini, entropie et RSS (optionnel)
À chaque nœud l’algorithme balaie chaque prédicteur et chaque seuil et conserve la séparation qui améliore le plus la pureté. La mesure d’(im)pureté dépend de la tâche.
Classification. Pour un nœud où une fraction \(p_k\) des observations appartiennent à la classe \(k\), les deux mesures d’impureté courantes sont l’indice de Gini et l’entropie :
Les deux valent 0 quand un nœud est pur (tous d’une seule classe) et augmentent à mesure que les classes se mélangent — Gini culmine à 0.5 pour un nœud à deux classes, l’entropie à \(\log 2\). La séparation choisie est celle qui réduit le plus l’impureté pondérée des nœuds-enfants par rapport au parent (pour l’entropie cette réduction est le gain d’information). rpart utilise Gini par défaut ; les deux divergent rarement en pratique.
Régression. Il n’y a pas de « classe » sur laquelle être pur, donc une séparation minimise plutôt la somme des carrés des résidus — la distance au carré de chaque résultat à la moyenne de son groupe :
\[
RSS = \sum_{i} (y_i - \hat{y}_i)^2,
\]
où \(\hat{y}_i\) est le résultat moyen dans la feuille où tombe l’observation \(i\). La meilleure séparation est le seuil qui rend les résultats des deux groupes-enfants aussi resserrés que possible autour de leurs moyennes. Dans tous les cas l’ arbre pousse en prenant gloutonnement la meilleure séparation locale, puis en répétant — c’est pourquoi un arbre unique est rapide à ajuster mais peut être remodelé par de petits changements dans les données.
Forces et faiblesses : pourquoi les arbres mènent aux forêts
Les arbres de décision gagnent leur place pour une raison : l’interprétabilité. Le diagramme est le modèle — aucun coefficient, aucune mise à l’échelle, juste des règles que n’importe qui peut suivre. Ils capturent les effets non linéaires et les interactions automatiquement, et gèrent des prédicteurs mixtes numériques/catégoriels sans difficulté.
Mais un arbre unique a une vraie faiblesse : il est instable. Le point de séparation exact et la variable choisie à chaque étape dépendent fortement des données d’apprentissage — un échantillon légèrement différent peut changer la première séparation et remodeler tout l’arbre. Un arbre unique est aussi rarement le modèle le plus précis.
Cette instabilité est exactement le problème que résolvent les leçons suivantes. Les forêts aléatoires et le gradient boosting construisent plusieurs arbres et les moyennent, lissant l’instabilité et augmentant la précision — au prix du diagramme unique lisible. Les arbres sont la fondation ; les ensembles sont ce vers quoi vous vous tournez quand la précision l’emporte sur l’interprétabilité.
🟢 Avec un agent IA
Vous avez un problème de classification ou de régression et voulez un modèle que vous pouvez lire ? Demandez à Prova« ajuste un arbre de décision à ces données, élague-le par validation croisée, et montre-moi le diagramme de l’arbre et quelles variables comptent le plus » — elle répond avec du code rpart + tidymodels que vous pouvez exécuter sur vos propres données, puis vous aide à lire les séparations, le graphique de cp, et la précision. The runtime is the judge.Ask Prova →
Problèmes courants
Votre arbre est une feuille unique (aucune séparation). Le cp = 0.01 par défaut de rpart peut tout élaguer sur un jeu de données faible ou minuscule. Abaissez-le — rpart(y ~ ., data, cp = 0.001) — pour faire pousser l’arbre, puis élaguez par validation croisée. Le problème inverse (un arbre géant surajusté) signifie que cp est trop petit.
rpart.plot est introuvable. C’est un paquet distinct de rpart. Installez-le une fois avec install.packages("rpart.plot") et chargez-le avec library(rpart.plot). En solution de repli, les graphiques de base fonctionnent aussi : plot(tree); text(tree).
La précision a l’air correcte mais le modèle rate les positifs. Sur un résultat déséquilibré, la précision peut être élevée tandis que le rappel sur la classe rare est médiocre. Lisez le ROC AUC (sans seuil) à côté de la précision, et voyez l’évaluation des modèles de classification pour la matrice de confusion et un meilleur seuil que 0.5.
Deux exécutions donnent un arbre différent. La séparation des plis de validation croisée est aléatoire, donc le cp choisi (et donc l’arbre élagué) peut changer. Appelez set.seed() avant vfold_cv() / rpart() pour un résultat reproductible — et rappelez-vous qu’un arbre qui se remodèle sous le rééchantillonnage est l’instabilité qui motive les forêts aléatoires.
Vous avez mis à l’échelle ou normalisé les prédicteurs d’abord. Inoffensif, mais inutile — les arbres séparent sur des seuils bruts, donc la mise à l’échelle ne change jamais l’arbre. Réservez step_normalize() aux méthodes pénalisées et fondées sur la distance qui en ont réellement besoin.
Vos séparations semblent biaisées vers les prédicteurs avec beaucoup de catégories ou de valeurs distinctes. La sélection Gini/entropie de CART favorise de telles variables. Pour des séparations choisies par des tests de significativité statistique plutôt que par Gini/entropie, voyez les arbres d’inférence conditionnelle (partykit::ctree()) — une alternative robuste et non biaisée quand les variables diffèrent en échelle ou en nombre de catégories.
Questions fréquentes
NoteComment construire un arbre de décision en R ?
Utilisez le paquet rpart : rpart(outcome ~ ., data = train_data, method = "class") pour un arbre de classification, ou method = "anova" pour un arbre de régression. Tracez-le avec rpart.plot::rpart.plot(model) pour voir les séparations, et prédisez avec predict(model, new_data, type = "class"). Pour l’élaguer et le régler proprement, enveloppez-le dans un workflow tidymodels avec parsnip::decision_tree(cost_complexity = tune()) |> set_engine("rpart").
NoteQue fait rpart en R ?
rpart (Recursive Partitioning And Regression Trees) est l’implémentation R de l’algorithme CART. rpart(y ~ ., data) fait pousser un arbre de décision en séparant répétitivement les données pour maximiser la pureté des groupes résultants — Gini pour la classification, somme des carrés des résidus pour la régression. Il stocke aussi l’ erreur validée croisée pour une séquence de tailles d’arbre (la cptable, consultée avec printcp()), que vous utilisez pour élaguer l’arbre au meilleur paramètre de complexité cp.
NoteQu’est-ce qu’un modèle CART ?
CART signifie Classification And Regression Trees — un algorithme unique qui construit un arbre de décision pour soit un résultat catégoriel (un arbre de classification) soit un résultat continu (un arbre de régression). Il sépare les prédicteurs récursivement pour rendre chaque groupe final aussi pur (classification) ou aussi faible en variance (régression) que possible. En R, CART est implémenté par le paquet rpart, et le même arbre fait pousser les forêts aléatoires et le gradient boosting quand vous en moyennez plusieurs.
NoteComment construire un arbre de classification en R ?
Ajustez-en un avec rpart(outcome ~ ., data = train_data, method = "class"), puis tracez-le avec rpart.plot::rpart.plot(model). Prédisez les classes sur de nouvelles données avec predict(model, test_data, type = "class") et vérifiez la précision avec mean(predicted == test_data$outcome). Élaguez l’arbre en validant croisé le paramètre de complexité cp — le plus simple via un workflow tidymodels decision_tree(cost_complexity = tune()) — afin qu’il se généralise au lieu de surapprendre.
NoteComment construire un arbre de régression en R ?
Un arbre de régression prédit un nombre au lieu d’une classe. Utilisez rpart(outcome ~ ., data = train_data, method = "anova") (ou set_mode("regression") dans tidymodels). Chaque feuille prédit le résultat moyen des observations qui l’atteignent, et les séparations minimisent la somme des carrés des résidus. Jugez-le par le RMSE sur un jeu de test mis de côté — sqrt(mean((truth - predicted)^2)) — et élaguez en choisissant le cp qui minimise le RMSE plutôt que de maximiser la précision.
NoteQu’est-ce que le paramètre de complexité (cp) dans rpart ?
Le paramètre de complexité cp contrôle l’élagage : une séparation n’est conservée que si elle améliore l’ajustement de l’arbre d’ au moins cp. Un cp plus grand donne un arbre plus petit, plus élagué (la valeur par défaut est 0.01) ; trop petit surapprend, trop grand sous-apprend. Vous choisissez le meilleur cp par validation croisée — lisez-le sur printcp(), ou réglez cost_complexity dans un workflow tidymodels à la valeur qui maximise la précision (ou minimise le RMSE).
Testez vos connaissances
ImportantExercice : faire pousser, élaguer et lire un arbre sur les données iris
En utilisant les données intégrées iris, construisez un arbre de classification qui prédit Species à partir des quatre mesures de fleur. Séparez 80/20 stratifié sur Species, faites pousser un arbre avec rpart, tracez-le avec rpart.plot, et rapportez la précision sur le jeu de test. Puis regardez le diagramme : sur quelle mesure unique la racine sépare-t-elle, et quelle espèce est séparée en premier ?
AstuceIndice
La forme reflète la leçon — échangez les données et la formule pour Species ~ ., et utilisez method = "class". Vous aurez besoin de rsample, rpart et rpart.plot. Pour trouver la séparation à la racine, lisez le nœud supérieur du diagramme (ou la première ligne de print(tree)).
AstuceSolution
library(rsample); library(rpart); library(rpart.plot)data("iris")set.seed(123)split <-initial_split(iris, prop =0.80, strata = Species)train_data <-training(split); test_data <-testing(split)set.seed(123)tree <-rpart(Species ~ ., data = train_data, method ="class")rpart.plot(tree, type =2, extra =104, box.palette ="Blues")predicted <-predict(tree, test_data, type ="class")mean(predicted == test_data$Species)
Vous obtiendrez une précision de test autour de 0.90–0.97. La séparation à la racine porte sur Petal.Length (la mesure la plus informative), qui sépare proprement setosa en premier — ses pétales sont minuscules et ne chevauchent pas les deux autres espèces, donc une seule séparation l’isole parfaitement. La frontière plus difficile versicolor/virginica a besoin des séparations plus profondes.
Vérification rapide. Vous faites pousser un arbre complètement développé et un arbre élagué sur les mêmes données. L’arbre complet obtient 99 % de précision sur le jeu d’apprentissage et 74 % sur le jeu de test ; l’arbre élagué obtient 82 % à l’apprentissage et 80 % au test. Lequel mettriez-vous en production, et pourquoi ?
NoteAfficher la réponse
Mettez en production l’arbre élagué. L’écart de 99 % à l’apprentissage contre 74 % au test de l’arbre complet est un surapprentissage d’école — il a mémorisé les données d’apprentissage et échoue sur de nouveaux patients. Les scores d’apprentissage et de test de l’arbre élagué sont proches (82 % contre 80 %), donc il se généralise : sa précision honnête, hors échantillon (80 %) est ce que vous obtiendrez réellement en production, et elle bat le 74 % de l’arbre complet là-dessus. Jugez toujours un arbre sur des données mises de côté, et préférez l’arbre plus simple qui se généralise.
Conclusion
Vous avez construit des arbres de décision de bout en bout sur les données PimaIndiansDiabetes2 et Boston : CART fait pousser un arbre par partitionnement récursif, séparant pour maximiser la pureté (Gini pour la classification, RSS pour la régression). Vous avez lu l’arbre depuis son diagramme rpart.plot — la force majeure du modèle — l’avez élagué avec le paramètre de complexité cp choisi par validation croisée (à la fois la route classique rpart + printcp() et le moderne workflow decision_tree(cost_complexity = tune())), évalué la précision et le ROC AUC, classé les prédicteurs par importance, et vu la variante arbre de régression. La règle pratique : faire pousser, puis élaguer par validation croisée, et préférer l’arbre plus simple qui se généralise. Un arbre unique est interprétable mais instable — c’est exactement pour cela que les forêts aléatoires et le gradient boosting en moyennent plusieurs.
Prouvez que vous savez le faire. Maîtrisez toute la série Apprentissage statistique en R — suivez votre parcours, construisez des projets et obtenez un certificat.
Cette leçon est reproductible : chaque arbre, métrique et figure a été produit par le code montré — copiez n’importe quel bloc et exécutez-le pour les reproduire. The runtime is the judge.
Références
Breiman, L., Friedman, J., Olshen, R., & Stone, C. (1984). Classification and Regression Trees. Wadsworth. (La monographie CART originale.)
James, G., Witten, D., Hastie, T., & Tibshirani, R. (2014). An Introduction to Statistical Learning, with Applications in R. Springer. (Chapitre 8 — Tree-Based Methods.)
Therneau, T., & Atkinson, B. rpart: Recursive Partitioning and Regression Trees. Documentation du paquet R + la vignette « rpart ».
Kuhn, M., & Silge, J. Tidy Modeling with R. Gratuit en ligne (CC BY-NC-SA).
@online{2026,
author = {},
title = {Arbres de décision en R : CART avec rpart et tidymodels},
date = {2026-06-25},
url = {https://www.datanovia.com/learn/machine-learning/statistical-learning/decision-trees},
langid = {fr}
}
Comment un arbre pousse : le partitionnement récursif
Avant d’ajuster, voici l’algorithme en un paragraphe. Commencez avec toutes les données à la racine. Essayez chaque prédicteur et chaque seuil possible, et choisissez la séparation unique qui rend les deux groupes résultants aussi purs que possible — pour la classification, majoritairement une classe ; pour la régression, aussi proches d’une seule valeur que possible. Puis répétez la procédure à l’intérieur de chaque groupe, et encore, faisant pousser l’arbre vers le bas. Arrêtez quand les groupes sont purs, trop petits pour être séparés, ou qu’une règle d’arrêt se déclenche. Le résultat est un arbre binaire de nœuds de décision (un prédicteur + un seuil), de branches, et de nœuds-feuilles (la prédiction).
Faisons pousser un arbre de classification complètement développé sur les données d’apprentissage et lisons-le.
rpart()fait le partitionnement récursif ;method = "class"en fait un arbre de classification :Lisez la sortie comme l’arbre lui-même. Chaque ligne est un nœud : la séparation qui le définit (
glucose >= 128), combien de patients d’apprentissage y tombent (n), combien sont mal classés (loss), la classe prédite (yval), et les probabilités de classe. Un*marque une feuille (une prédiction terminale). La séparation à la racine porte surglucose— la question la plus informative à elle seule — exactement comme un clinicien s’y attendrait.Lire l’arbre : le diagramme caractéristique
Le texte est exact mais difficile à parcourir. Le diagramme de l’arbre est la raison pour laquelle les gens aiment CART — il est le modèle, lisible d’un coup d’œil.
rpart.plotle dessine proprement, ombrant chaque nœud selon la classe prédite :Lisez-le de haut en bas. Chaque nœud montre la classe prédite, les probabilités de classe (p. ex.
.36 .64= 36 % négatif, 64 % positif), et la part de patients qui l’atteignent. Suivez une branche : oui va à gauche, non va à droite. Un patient avec un glucose en dessous de 128 atterrit dans la grande feuille de gauche et est prédit négatif ; un avec un glucose au-dessus de 166 atterrit tout à droite et est prédit positif avec une grande confiance. Plus le nœud est sombre, plus il est positif — une histoire clinique que vous pouvez voir.Quelle est la précision de cet arbre complet sur des patients qu’il n’a jamais vus ? Évaluez-le sur le jeu de test mis de côté :
Environ 76 % des patients de test sont classés correctement — pas mal. Mais un arbre complètement développé tend à être trop complexe : il continue de séparer pour poursuivre les derniers patients d’apprentissage, ajustant un bruit qui ne se généralisera pas. C’est le surapprentissage, et le remède est l’élagage.