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.
Obtenez le livre — Machine Learning Essentials (PDF)

Introduction

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 measurements
pima <- 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 split
prop.table(table(train_data$diabetes))

      neg       pos 
0.6677316 0.3322684 
head(pima, 4)
  pregnant glucose pressure triceps insulin mass pedigree age diabetes
4        1      89       66      23      94 28.1    0.167  21      neg
5        0     137       40      35     168 43.1    2.288  33      pos
7        3      78       50      32      88 31.0    0.248  26      pos
9        2     197       70      45     543 30.5    0.158  53      pos

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 tree
set.seed(123)
full_tree <- rpart(diabetes ~ ., data = train_data, method = "class")

# The decision rules, as text
full_tree
n= 313 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

  1) root 313 104 neg (0.66773163 0.33226837)  
    2) glucose< 127.5 197  30 neg (0.84771574 0.15228426)  
      4) age< 29.5 138  11 neg (0.92028986 0.07971014) *
      5) age>=29.5 59  19 neg (0.67796610 0.32203390)  
       10) insulin< 142.5 37   5 neg (0.86486486 0.13513514) *
       11) insulin>=142.5 22   8 pos (0.36363636 0.63636364) *
    3) glucose>=127.5 116  42 pos (0.36206897 0.63793103)  
      6) glucose< 165.5 80  38 pos (0.47500000 0.52500000)  
       12) age< 23.5 14   1 neg (0.92857143 0.07142857) *
       13) age>=23.5 66  25 pos (0.37878788 0.62121212)  
         26) triceps< 22 8   2 neg (0.75000000 0.25000000) *
         27) triceps>=22 58  19 pos (0.32758621 0.67241379)  
           54) pressure< 77 31  14 pos (0.45161290 0.54838710)  
            108) pressure>=71 15   3 neg (0.80000000 0.20000000) *
            109) pressure< 71 16   2 pos (0.12500000 0.87500000) *
           55) pressure>=77 27   5 pos (0.18518519 0.81481481) *
      7) glucose>=165.5 36   4 pos (0.11111111 0.88888889) *

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)")

A fully grown CART decision-tree diagram for the Pima diabetes data: the root splits on plasma glucose, then branches on further glucose, age, mass and insulin cutoffs down to shaded leaf nodes, each giving the predicted class and the probability of being diabetes-positive.

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 accuracy
predicted <- 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)

Classification tree:
rpart(formula = diabetes ~ ., data = train_data, method = "class")

Variables actually used in tree construction:
[1] age      glucose  insulin  pressure triceps 

Root node error: 104/313 = 0.33227

n= 313 

        CP nsplit rel error  xerror     xstd
1 0.307692      0   1.00000 1.00000 0.080128
2 0.057692      1   0.69231 0.69231 0.071593
3 0.041667      3   0.57692 0.74038 0.073265
4 0.028846      6   0.45192 0.70192 0.071939
5 0.010000      8   0.39423 0.81731 0.075661

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 tuned
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)

# 10-fold CV on the training set, over a grid of cp values
set.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^-1

set.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 accuracy
best_cp <- select_best(tree_tuned, metric = "accuracy")
best_cp
# A tibble: 1 × 2
  cost_complexity .config         
            <dbl> <chr>           
1          0.0464 pre0_mod09_post0

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 cp
acc_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()

Cross-validated accuracy plotted against the complexity parameter cp on a log scale for the Pima diabetes tree: accuracy rises to a peak at a moderate cp then falls as the penalty grows and the tree is pruned too hard, with a dashed line at the chosen cp.

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 data
final_wf  <- finalize_workflow(tree_wf, best_cp)
final_fit <- fit(final_wf, data = train_data)

# Pull the underlying rpart object and plot it
pruned_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)")

A pruned CART decision-tree diagram for the Pima diabetes data: the root splits on plasma glucose below 128, then on glucose below 166 and age below 24, ending in four shaded leaf nodes that give the predicted class and the probability of being diabetes-positive — far simpler than the fully grown tree.

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 set
final_fit <- finalize_workflow(tree_wf, best_cp) |> last_fit(split)
collect_metrics(final_fit)
# A tibble: 3 × 4
  .metric     .estimator .estimate .config        
  <chr>       <chr>          <dbl> <chr>          
1 accuracy    binary         0.797 pre0_mod0_post0
2 roc_auc     binary         0.827 pre0_mod0_post0
3 brier_class binary         0.141 pre0_mod0_post0

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 object
imp <- extract_fit_engine(final_fit)$variable.importance
imp_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()

Horizontal bar chart of decision-tree variable importance for the Pima diabetes data: glucose has by far the largest importance, followed by age and insulin, then pressure, pregnant, mass and pedigree with small contributions.

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 outcome
set.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)")

A regression-tree diagram for the Boston housing data: the tree splits on the number of rooms and the lower-status percentage, with each leaf node giving the predicted median home value in thousands of dollars.

# 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.

À 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 :

\[ Gini = \sum_{k} p_k\,(1 - p_k), \qquad Entropy = -\sum_{k} p_k \,\log(p_k). \]

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, \]

\(\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

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").

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.

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.

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.

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.

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

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 ?

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)).

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 ?

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.

Ensuite, moyennez des arbres pour la stabilité et la précision dans la forêt aléatoire, ou lisez la matrice de confusion complète et la courbe ROC dans l’évaluation des modèles de classification.

Leçons connexes

Vous préférez un livre ? Machine Learning Essentials est disponible en PDF téléchargeable — toutes les leçons de cette série, hors ligne et à vous.

Obtenir le livre (PDF) →Les leçons restent gratuites en ligne.
Cette page vous a-t-elle été utile ?

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.

Commencer gratuitement →

Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.

dès 15 $/mois facturé annuellement

Passer à Pro →

✓ Vous êtes Pro — continuez. The runtime is the judge.

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

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).

Réutilisation

Citation

BibTeX
@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}
}
Veuillez citer ce travail comme suit :
“Arbres de décision en R : CART avec rpart et tidymodels.” 2026. June 25. https://www.datanovia.com/learn/machine-learning/statistical-learning/decision-trees.