Chargement du fichier.
#changement de dossier
setwd("C:/Users/ricco/Desktop/demo")
#chargement des données
library(readxl)
dfAll <- readxl::read_excel("wave5300.xlsx")
str(dfAll)
## tibble [5,300 × 23] (S3: tbl_df/tbl/data.frame)
## $ CLASSE : chr [1:5300] "A" "A" "A" "A" ...
## $ V1 : num [1:5300] 0.75 1.85 -1.37 -0.29 -1.16 ...
## $ V2 : num [1:5300] 0.05 -0.66 -0.45 -0.28 1.11 ...
## $ V3 : num [1:5300] 2.6 0.7 -1.35 -1.24 1.39 ...
## $ V4 : num [1:5300] 2.28 3.69 2.12 1.65 3.09 ...
## $ V5 : num [1:5300] 2.07 1.5 -0.15 0.94 1.83 ...
## $ V6 : num [1:5300] 3.42 2.42 1.36 1.75 4.76 ...
## $ V7 : num [1:5300] 3.66 2.57 0.27 4.58 3.01 ...
## $ V8 : num [1:5300] 2.65 4.62 -0.26 0.29 3.93 ...
## $ V9 : num [1:5300] 2.3 2.07 -1.19 3.33 2.57 ...
## $ V10 : num [1:5300] 2.54 1.76 1.33 1.73 1.56 ...
## $ V11 : num [1:5300] 1.82 1.13 0.46 2.83 3.37 ...
## $ V12 : num [1:5300] 1.69 3.12 1.41 2.29 1.59 ...
## $ V13 : num [1:5300] 0.82 2.01 2.65 2.32 1.21 ...
## $ V14 : num [1:5300] 1.59 1.43 3.1 1.82 3.39 ...
## $ V15 : num [1:5300] 1.02 2 4.49 0.71 1.64 ...
## $ V16 : num [1:5300] 0.58 3.06 3.96 2.71 2.84 ...
## $ V17 : num [1:5300] -0.37 1.39 3.65 2.61 -0.03 ...
## $ V18 : num [1:5300] 2.76 2.79 2.26 2.43 2.11 ...
## $ V19 : num [1:5300] 0.92 0.34 1.81 0.58 2.11 ...
## $ V20 : num [1:5300] 0.89 0.54 1.04 1.66 0.37 ...
## $ V21 : num [1:5300] -1.13 0.64 -0.41 -0.28 1.02 ...
## $ SAMPLE_STATUS: chr [1:5300] "train" "train" "train" "train" ...
Partition en “train” - “test” basée sur la colonne SAMPLE_STATUS.
#effectifs train-test
table(dfAll$SAMPLE_STATUS)
##
## test train
## 5000 300
#partition apprentissage-test
dfTrain <- dfAll[dfAll$SAMPLE_STATUS == 'train',1:(ncol(dfAll)-1)]
dfTest <- dfAll[dfAll$SAMPLE_STATUS == 'test',1:(ncol(dfAll)-1)]
#vérif.
print(dim(dfTrain))
## [1] 300 22
print(dim(dfTest))
## [1] 5000 22
#construire l'arbre sur l'échantillon TRAIN
#déduit automatique les séquences d'arbres de coût-complexité équivalents
#et mesure automatiquement le taux d'erreur en validation croisée
#!!! attention le rôle de "cp" qui agit aussi durant la phase de growing
#désactivé (cp = 0) ici
set.seed(1)
library(rpart)
arbre_max <- rpart(CLASSE ~ ., data = dfTrain, method='class',
control = rpart.control(minsplit=2,cp=0))
#obtenir le nombre de feuilles - pas vraiment intuitif
#cf. https://cran.r-project.org/web/packages/rpart/rpart.pdf (rpart.object)
print(sum(arbre_max$frame$var == '<leaf>'))
## [1] 37
Package ‘rpart.plot’ pour affichage graphique.
#affichage graphique
library(rpart.plot)
rpart.plot(arbre_max)
Taux d’erreur sur l’échantillon test (proportion des mauvaises affectations).
#erreur en test de l'arbre max
print(errRef <- mean(dfTest$CLASSE != predict(arbre_max,dfTest,type='class')))
## [1] 0.2954
Intervalle de confiance à 95% de l’erreur.
#intervalle de confiance
se <- sqrt(errRef*(1-errRef)/nrow(dfTest))
#borne basse
print(errRef - qnorm(0.975) * se)
## [1] 0.2827544
#borne haute
print(errRef + qnorm(0.975) * se)
## [1] 0.3080456
Séquence d’arbres emboîtés avec le paramètre cp. Attention à l’affichage de l’erreur (normalisée à 1 + erreur en validation croisée).
#séquences d'arbres et performances
printcp(arbre_max)
##
## Classification tree:
## rpart(formula = CLASSE ~ ., data = dfTrain, method = "class",
## control = rpart.control(minsplit = 2, cp = 0))
##
## Variables actually used in tree construction:
## [1] V1 V10 V11 V12 V13 V14 V15 V17 V18 V19 V2 V3 V4 V5 V6 V7 V8 V9
##
## Root node error: 190/300 = 0.63333
##
## n= 300
##
## CP nsplit rel error xerror xstd
## 1 0.3210526 0 1.0000000 1.00000 0.043930
## 2 0.2105263 1 0.6789474 0.82632 0.045531
## 3 0.0552632 2 0.4684211 0.58947 0.044093
## 4 0.0315789 4 0.3578947 0.49474 0.042285
## 5 0.0236842 7 0.2631579 0.47895 0.041906
## 6 0.0210526 9 0.2157895 0.47895 0.041906
## 7 0.0157895 10 0.1947368 0.46316 0.041504
## 8 0.0105263 12 0.1631579 0.46316 0.041504
## 9 0.0078947 19 0.0894737 0.44737 0.041078
## 10 0.0052632 21 0.0736842 0.44737 0.041078
## 11 0.0026316 34 0.0052632 0.46316 0.041504
## 12 0.0000000 36 0.0000000 0.46316 0.041504
Ex. Analyse de l’arbre réduit à la première segemntation c.-à-d. 1 nsplit = arbre avec 2 feuilles.
#utilisation particlière de summary avec cp
summary(arbre_max,cp=0.3)
## Call:
## rpart(formula = CLASSE ~ ., data = dfTrain, method = "class",
## control = rpart.control(minsplit = 2, cp = 0))
## n= 300
##
## CP nsplit rel error xerror xstd
## 1 0.321052632 0 1.000000000 1.0000000 0.04392977
## 2 0.210526316 1 0.678947368 0.8263158 0.04553063
## 3 0.055263158 2 0.468421053 0.5894737 0.04409341
## 4 0.031578947 4 0.357894737 0.4947368 0.04228471
## 5 0.023684211 7 0.263157895 0.4789474 0.04190633
## 6 0.021052632 9 0.215789474 0.4789474 0.04190633
## 7 0.015789474 10 0.194736842 0.4631579 0.04150449
## 8 0.010526316 12 0.163157895 0.4631579 0.04150449
## 9 0.007894737 19 0.089473684 0.4473684 0.04107849
## 10 0.005263158 21 0.073684211 0.4473684 0.04107849
## 11 0.002631579 34 0.005263158 0.4631579 0.04150449
## 12 0.000000000 36 0.000000000 0.4631579 0.04150449
##
## Variable importance
## V14 V11 V7 V4 V13 V10 V12 V15 V6 V16 V5 V8 V2 V9 V17 V18 V3 V1 V19
## 10 9 8 7 7 7 6 6 6 5 5 5 5 4 3 2 2 2 1
##
## Node number 1: 300 observations, complexity param=0.3210526
## predicted class=A expected loss=0.6333333 P(node) =1
## class counts: 110 102 88
## probabilities: 0.367 0.340 0.293
## left son=2 (176 obs) right son=3 (124 obs)
## Primary splits:
## V11 < 3.685 to the left, improve=38.97942, (0 missing)
## V14 < 2.725 to the left, improve=37.74171, (0 missing)
## V13 < 3.45 to the left, improve=32.66288, (0 missing)
## V7 < 1.56 to the right, improve=31.44490, (0 missing)
## V12 < 3.135 to the left, improve=31.10616, (0 missing)
## Surrogate splits:
## V12 < 3.245 to the left, agree=0.760, adj=0.419, (0 split)
## V10 < 3.605 to the left, agree=0.717, adj=0.315, (0 split)
## V4 < -0.045 to the right, agree=0.677, adj=0.218, (0 split)
## V5 < 0.335 to the right, agree=0.660, adj=0.177, (0 split)
## V2 < -0.495 to the right, agree=0.653, adj=0.161, (0 split)
##
## Node number 2: 176 observations
## predicted class=A expected loss=0.3863636 P(node) =0.5866667
## class counts: 108 39 29
## probabilities: 0.614 0.222 0.165
##
## Node number 3: 124 observations
## predicted class=B expected loss=0.4919355 P(node) =0.4133333
## class counts: 2 63 59
## probabilities: 0.016 0.508 0.476
Récupération de la table des cp.
#table des valeurs
tbl <- arbre_max$cptable
print(tbl)
## CP nsplit rel error xerror xstd
## 1 0.321052632 0 1.000000000 1.0000000 0.04392977
## 2 0.210526316 1 0.678947368 0.8263158 0.04553063
## 3 0.055263158 2 0.468421053 0.5894737 0.04409341
## 4 0.031578947 4 0.357894737 0.4947368 0.04228471
## 5 0.023684211 7 0.263157895 0.4789474 0.04190633
## 6 0.021052632 9 0.215789474 0.4789474 0.04190633
## 7 0.015789474 10 0.194736842 0.4631579 0.04150449
## 8 0.010526316 12 0.163157895 0.4631579 0.04150449
## 9 0.007894737 19 0.089473684 0.4473684 0.04107849
## 10 0.005263158 21 0.073684211 0.4473684 0.04107849
## 11 0.002631579 34 0.005263158 0.4631579 0.04150449
## 12 0.000000000 36 0.000000000 0.4631579 0.04150449
Courbe de l’erreur en resubstitution et en validation croisée.
#courbes erreurs en train et en validation croisée
plot(tbl[,'nsplit']+1,tbl[,'rel error'],type='b',col='red',xlab='# feuilles',ylab='Err.')
lines(tbl[,'nsplit']+1,tbl[,'xerror'],type='b',col='green')
legend(x='topright',legend=c('Training set','Cross-val.'),col=c('red','green'),lty=1)
title(main='Erreur relative vs. complexité arbre')
Recherche de la ligne corresp. au min de l’erreur en validation croisée.
#index corresp.au min dans la colonne xerror
idx_opt <- which.min(tbl[,'xerror'])
idx_opt
## 9
## 9
Affichage des infos complètes.
#infos corresp. : valeur de cp., etc.
print(tbl[idx_opt,])
## CP nsplit rel error xerror xstd
## 0.007894737 19.000000000 0.089473684 0.447368421 0.041078489
#obtention de l'arbre associé c.-à-d. qui minimise l'erreur en cross-val.
#on aurait pu prendre toute valeur de cp comprise entre les arbres n°8 (>=) et n°7 (<)
#IL N'EST PAS NECESSAIRE DE REAPPARENDRE A PARTIR DE LA BASE TRAIN !!!
arbre_optimal <- prune(arbre_max,cp=tbl[idx_opt,'CP'])
#nombre de feuilles = 20 (forcément, sinon gros problème)
print(sum(arbre_optimal$frame$var == '<leaf>'))
## [1] 20
#affichage
rpart.plot(arbre_optimal)
Pas nécessaire de relancer l’apprentissage. On dispose de la bonne version de l’arbre que l’on peut appliquer sur l’échantillon test.
#performances en test
print(mean(dfTest$CLASSE != predict(arbre_optimal,dfTest,type='class')))
## [1] 0.2988
#affichage graphique de rpart avec subtilité supplémentaire
plotcp(arbre_max)
Calcul du seuil de l’erreur avec la règle de l’écart-type.
#la valeur de l'écart-type est fournie dans le tableau "cp"
#le seuil peut être obtenu facilement
seuil <- tbl[idx_opt,'xerror'] + 1 * tbl[idx_opt,'xstd']
print(seuil)
## [1] 0.4884469
Recherche de l’arbre corresp.
#numéro du plus petit arbre qui corresp. à cette condition
#ou, la plus grande valeur de CP qui respecte cette condition
#puisque cp est décroissant : première valeur donc
idx_1se <- which(tbl[,'xerror'] <= seuil)[1]
print(idx_1se)
## 5
## 5
#valeur du cp corresp.
print(tbl[idx_1se,'CP'])
## [1] 0.02368421
#arbre corresp. à cette valeur de CP
arbre_1se <- prune(arbre_max,cp=tbl[idx_1se,'CP'])
#nombre de feuilles
print(sum(arbre_1se$frame$var == '<leaf>'))
## [1] 8
#affichage
rpart.plot(arbre_1se)
Et encore une fois, puisqu’on obtient l’arbre sans qu’il soit nécessaire de relancer le processus de modélisation, nous pouvons l’appliquer directement sur l’échantillon test.
#performances en test
print(mean(dfTest$CLASSE != predict(arbre_1se,dfTest,type='class')))
## [1] 0.3182
cf. la vidéo pour scikit-learn/python : https://www.youtube.com/watch?v=if6QEtJP77E
#partitionner train en growing (200) et pruning (100) sets
set.seed(1)
idGrow <- sample(1:nrow(dfTrain),200,replace=FALSE)
#et donc
dfGrow <- dfTrain[idGrow,]
dfPrune <- dfTrain[-idGrow,]
#dimensions
print(dim(dfGrow))
## [1] 200 22
print(dim(dfPrune))
## [1] 100 22
Arbre sans validation croisée (xval = 0).
#arbre sur le growing set (200 obs.)
#noter la configuration du tableau cp maintenant
arbre_growing <- rpart(CLASSE ~ ., data = dfGrow, method='class',
control = rpart.control(minsplit=2,cp=0,xval=0))
printcp(arbre_growing)
##
## Classification tree:
## rpart(formula = CLASSE ~ ., data = dfGrow, method = "class",
## control = rpart.control(minsplit = 2, cp = 0, xval = 0))
##
## Variables actually used in tree construction:
## [1] V1 V10 V11 V12 V13 V14 V18 V2 V3 V4 V5 V8 V9
##
## Root node error: 129/200 = 0.645
##
## n= 200
##
## CP nsplit rel error
## 1 0.3255814 0 1.000000
## 2 0.2248062 1 0.674419
## 3 0.0620155 2 0.449612
## 4 0.0387597 4 0.325581
## 5 0.0348837 5 0.286822
## 6 0.0310078 7 0.217054
## 7 0.0232558 8 0.186047
## 8 0.0155039 9 0.162791
## 9 0.0077519 14 0.085271
## 10 0.0038760 23 0.015504
## 11 0.0000000 27 0.000000
Appliquez les différentes versions de l’arbre, contrôlé par les valeurs de cp, sur le pruning set. Enorme avantage ici, il n’est pas nécessaire de relancer à chaque fois l’apprentissage.
#erreur en pruning
errPruning <- c()
#erreur en pruning
for (cp_value in arbre_growing$cptable[,'CP']){
tmpArbre <- prune(arbre_growing,cp=cp_value)
errPruning <- c(errPruning,mean(dfPrune$CLASSE != predict(tmpArbre,dfPrune,type='class')))
}
#affichage
print(errPruning)
## [1] 0.61 0.56 0.36 0.35 0.31 0.32 0.31 0.31 0.27 0.30 0.31
Normaliser l’erreur sur le pruning set à 1.
#rescaler à 1 pour l'erreur max.
errPruning <- errPruning/errPruning[1]
print(errPruning)
## [1] 1.0000000 0.9180328 0.5901639 0.5737705 0.5081967 0.5245902 0.5081967
## [8] 0.5081967 0.4426230 0.4918033 0.5081967
Courbes sur les 2 échantillons.
plot(arbre_growing$cptable[,'nsplit']+1,arbre_growing$cptable[,'rel error'],
type='b',col='red',xlab='# feuilles',ylab='Err.')
lines(arbre_growing$cptable[,'nsplit']+1,errPruning,type='b',col='green')
legend(x='topright',legend=c('Growing set','Pruning set'),col=c('red','green'),lty=1)
title(main='Erreur relative vs. complexité arbre')
De nouveau ici, en travaillant sur les valeurs de la courbe “pruning set”, on pourra sélectionner l’arbre “optimal” ou appliquer la règle de l’écart-type (laquelle peut être calculée aisément, le taux d’erreur est une proportion).