Capitolo 14 Alberi di Classificazione (Classification Trees: CTREE)
14.1 Introduzione
Gli alberi decisionali sono uno degli strumenti comuni per l’analisi esplorativa dei dati e la segmentazione.
Il grande vantaggio legato all’uso degli alberi decisionali è che il loro output è relativamente semplice da comprendere o interpretare.
I metodi basati su ableri possono essere usati sia per problemi di claossificazione che problemi di regressione.
Questi problemi richiedono la stratificazione o segmentazione dello spazio dei predittori
in un numero di regioni semplici. Una previsione per una data osservazione è quindi ottenuta
tipicamente usando la media o la moda delle osservazioni di training nella regione in cui ricade l’osservazione.
Un modo semplice per comprendere gli alberi decisionali è quello di un approccio gerarchico per partizionare i dati di input:
ad ogni nodo (step) si usa una sola variabile indipendente, con uno dei suoi valori, per generare la partizione.
Gli alberi di classificazione (CT) sono usati per prevedere una risposta qualitativa. Nei CT la previsione di una osservazione generalmente corrisponde alla classe più comune nelle osservazioni di training nella regione in cui ricade. Nell’interpretazione dei risultati di un albero di classificazione, usualmente siamo interessati alla previsione di una classe corrispondente alla regione di un particolare nodo terminale, ma anche alle proporzioni nelle numerosità tra le classi delle osservazioni di training che ricadono in quella stessa regione.
La costruzione di un albero segue usualmente un processo in due fasi: crescita (growth) e potatura (pruning).
Per far crescere un albero di classificazione, usualmente si adopera una suddivisione binaria. Per “spezzare” i nodi, si ricerca la “variabilità entro nodo” minima; la “variabilità” è usualmente misuratacon due indici alternativi:
l’indice di Gini, che fornisce una misura dell’incertezza totale tra le \(k\) classi; è spesso indicato anche con il nome di misura di “purezza” (“purity”) del nodo, poiché un valore piccolo indica che il nodo in maniera predominante osservazioni da una sola classe.
la cross-entropia, che, come l’indice di Gini, assume un valore piccolo se il nodo è puro.
Per “potare” l’albero, invece, è usato principalmente il tasso di errata classificazione, se l’obiettivo è quello dell’accuratezza della previsione.
Per migliorare l’accuratezza predittiva, si possono combinare più alberi per produrre una previsione aggregata. “Bagging”, “random forests” e “boosting” sono alcuni approcci che implementano una tale strategia. Il prezzo da pagare per l’accresciuta accuratezza è una perdita in interpretabilità.
Ci sono diversi package R
con funzioni per costruire alberi di classificazione singoli.
Uno dei più popolari è rpart
.
Illustreremo questo package con qualche esempio.
14.2 Esempio: Dati iris
In questo esempio cercheremo di introdurre in maniera semplice il processo di costruzione dell’albero.
L’obiettivo dell’esempio è quello di prevedere ls specie corretta di iris date le misure di
lunghezza e larghezza di petali e sepali.
Cominciamo quindi con un albero “quasi non potato”:
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 ..
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
iris_rp <- rpart(Species ~ ., method = "class", data = iris,
control = rpart.control(minsplit = 4, cp = 0.000001))
Possiamo controllare le regole e le suddivisioni (split) usando l’opzione control
nella chiamata della funzione dell’algoritmo di rpart
.
Per esempio il numero minimo di osservazioni per un nodo perché questo sia considerato per uno split è impostabile tramite
l’uso di minsplit
. Il numero minimo di osservazioni per un nodo foglia (nodo non più suddivisibile) è impostabile tramite il parametro minbucket
.
Ora stampiamo i contenuti dell’albero appena prodotto:
## n= 150
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)
## 2) Petal.Length< 2.45 50 0 setosa (1.00000000 0.00000000 0.00000000) *
## 3) Petal.Length>=2.45 100 50 versicolor (0.00000000 0.50000000 0.50000000)
## 6) Petal.Width< 1.75 54 5 versicolor (0.00000000 0.90740741 0.09259259)
## 12) Petal.Length< 4.95 48 1 versicolor (0.00000000 0.97916667 0.02083333)
## 24) Petal.Width< 1.65 47 0 versicolor (0.00000000 1.00000000 0.00000000) *
## 25) Petal.Width>=1.65 1 0 virginica (0.00000000 0.00000000 1.00000000) *
## 13) Petal.Length>=4.95 6 2 virginica (0.00000000 0.33333333 0.66666667)
## 26) Petal.Width>=1.55 3 1 versicolor (0.00000000 0.66666667 0.33333333) *
## 27) Petal.Width< 1.55 3 0 virginica (0.00000000 0.00000000 1.00000000) *
## 7) Petal.Width>=1.75 46 1 virginica (0.00000000 0.02173913 0.97826087) *
Nell’output qui sopra è mostrato un riepilogo della procedura di splitting:
- Al primo step è valutato l’albero composto dal solo “nodo radice”;
- Al secondo step, la procedura scansiona tutte le variabili ricercando la partizione che riduce al massimo la misura di “variabilità” scelta (indice di Gini o misura entropica) sulla variabile dipendente; a questo punto si usa come prima “diramazione” quella data dal predittore (“feature”) che minimizza la variabilità, con la sua partizione (in questo caso,
Petal.Length
con soglia uguale a 2.45), e il dataset è quindi “spezzato” in due sottoinsiemi; - Al terzo step, per ogni sottoinsieme la procedura scansiona di nuovo tutte le variabili cercado la partizione che minimizza la misura di “variabilià” sulla variabile dipendente, e quindi utilizza il predittore (“feature”) che riduce massimamente la variabilità, con la sua partizione (
Petal.Width
e 1.75); quindi viene di nuovo “spezzato” il sotto-sottoinsieme; - E via di seguito ….
Quello che segue è un riepilogo dettagliato dell’albero:
## Call:
## rpart(formula = Species ~ ., data = iris, method = "class", control = rpart.control(minsplit = 4,
## cp = 1e-06))
## n= 150
##
## CP nsplit rel error xerror xstd
## 1 5.0e-01 0 1.00 1.19 0.04959167
## 2 4.4e-01 1 0.50 0.70 0.06110101
## 3 2.0e-02 2 0.06 0.10 0.03055050
## 4 1.0e-02 3 0.04 0.10 0.03055050
## 5 1.0e-06 5 0.02 0.09 0.02908608
##
## Variable importance
## Petal.Width Petal.Length Sepal.Length Sepal.Width
## 34 32 20 14
##
## Node number 1: 150 observations, complexity param=0.5
## predicted class=setosa expected loss=0.6666667 P(node) =1
## class counts: 50 50 50
## probabilities: 0.333 0.333 0.333
## left son=2 (50 obs) right son=3 (100 obs)
## Primary splits:
## Petal.Length < 2.45 to the left, improve=50.00000, (0 missing)
## Petal.Width < 0.8 to the left, improve=50.00000, (0 missing)
## Sepal.Length < 5.45 to the left, improve=34.16405, (0 missing)
## Sepal.Width < 3.35 to the right, improve=19.03851, (0 missing)
## Surrogate splits:
## Petal.Width < 0.8 to the left, agree=1.000, adj=1.00, (0 split)
## Sepal.Length < 5.45 to the left, agree=0.920, adj=0.76, (0 split)
## Sepal.Width < 3.35 to the right, agree=0.833, adj=0.50, (0 split)
##
## Node number 2: 50 observations
## predicted class=setosa expected loss=0 P(node) =0.3333333
## class counts: 50 0 0
## probabilities: 1.000 0.000 0.000
##
## Node number 3: 100 observations, complexity param=0.44
## predicted class=versicolor expected loss=0.5 P(node) =0.6666667
## class counts: 0 50 50
## probabilities: 0.000 0.500 0.500
## left son=6 (54 obs) right son=7 (46 obs)
## Primary splits:
## Petal.Width < 1.75 to the left, improve=38.969400, (0 missing)
## Petal.Length < 4.75 to the left, improve=37.353540, (0 missing)
## Sepal.Length < 6.15 to the left, improve=10.686870, (0 missing)
## Sepal.Width < 2.45 to the left, improve= 3.555556, (0 missing)
## Surrogate splits:
## Petal.Length < 4.75 to the left, agree=0.91, adj=0.804, (0 split)
## Sepal.Length < 6.15 to the left, agree=0.73, adj=0.413, (0 split)
## Sepal.Width < 2.95 to the left, agree=0.67, adj=0.283, (0 split)
##
## Node number 6: 54 observations, complexity param=0.02
## predicted class=versicolor expected loss=0.09259259 P(node) =0.36
## class counts: 0 49 5
## probabilities: 0.000 0.907 0.093
## left son=12 (48 obs) right son=13 (6 obs)
## Primary splits:
## Petal.Length < 4.95 to the left, improve=4.4490740, (0 missing)
## Sepal.Length < 7.1 to the left, improve=1.6778480, (0 missing)
## Petal.Width < 1.35 to the left, improve=0.9971510, (0 missing)
## Sepal.Width < 2.65 to the right, improve=0.2500139, (0 missing)
##
## Node number 7: 46 observations
## predicted class=virginica expected loss=0.02173913 P(node) =0.3066667
## class counts: 0 1 45
## probabilities: 0.000 0.022 0.978
##
## Node number 12: 48 observations, complexity param=0.01
## predicted class=versicolor expected loss=0.02083333 P(node) =0.32
## class counts: 0 47 1
## probabilities: 0.000 0.979 0.021
## left son=24 (47 obs) right son=25 (1 obs)
## Primary splits:
## Petal.Width < 1.65 to the left, improve=1.95833300, (0 missing)
## Sepal.Length < 4.95 to the right, improve=0.95833330, (0 missing)
## Sepal.Width < 2.55 to the right, improve=0.10119050, (0 missing)
## Petal.Length < 4.45 to the left, improve=0.06359649, (0 missing)
##
## Node number 13: 6 observations, complexity param=0.01
## predicted class=virginica expected loss=0.3333333 P(node) =0.04
## class counts: 0 2 4
## probabilities: 0.000 0.333 0.667
## left son=26 (3 obs) right son=27 (3 obs)
## Primary splits:
## Petal.Width < 1.55 to the right, improve=1.3333330, (0 missing)
## Sepal.Width < 2.65 to the right, improve=0.6666667, (0 missing)
## Petal.Length < 5.35 to the left, improve=0.6666667, (0 missing)
## Sepal.Length < 6.95 to the left, improve=0.2666667, (0 missing)
## Surrogate splits:
## Sepal.Length < 6.5 to the right, agree=0.833, adj=0.667, (0 split)
## Sepal.Width < 2.65 to the right, agree=0.833, adj=0.667, (0 split)
##
## Node number 24: 47 observations
## predicted class=versicolor expected loss=0 P(node) =0.3133333
## class counts: 0 47 0
## probabilities: 0.000 1.000 0.000
##
## Node number 25: 1 observations
## predicted class=virginica expected loss=0 P(node) =0.006666667
## class counts: 0 0 1
## probabilities: 0.000 0.000 1.000
##
## Node number 26: 3 observations
## predicted class=versicolor expected loss=0.3333333 P(node) =0.02
## class counts: 0 2 1
## probabilities: 0.000 0.667 0.333
##
## Node number 27: 3 observations
## predicted class=virginica expected loss=0 P(node) =0.02
## class counts: 0 0 3
## probabilities: 0.000 0.000 1.000
E finalmente produciamo un grafico (ad “albero rovesciato”) che rappresenta l’albero:
Per leggere meglio il grafico qui sopra (dendrogramma) ci sono parecchie opzioni:
plot(iris_rp, uniform = TRUE, compress = TRUE, margin = 0.2, branch = 0.3)
text(iris_rp, use.n = TRUE, digits = 3, cex = 0.6)
Se vogliamo anche vedere le etichette di un oggetto di tipo dendrogramma creato da rpart
, possiamo usare la funzione labels()
:
## [1] "root" "Petal.Length< 2.45" "Petal.Length>=2.45"
## [4] "Petal.Width< 1.75" "Petal.Length< 4.95" "Petal.Width< 1.65"
## [7] "Petal.Width>=1.65" "Petal.Length>=4.95" "Petal.Width>=1.55"
## [10] "Petal.Width< 1.55" "Petal.Width>=1.75"
Per vedere quindi come lo spazio delle “feature” (cioè, dei predittori) è stato partizionato in questo esempio, produciamo il grafico seguente:
library(ggplot2)
ggp <- ggplot(data = iris) +
geom_point(aes(x = Petal.Length, y = Petal.Width, colour = Species)) +
geom_vline(xintercept = 2.45, linetype = 2) +
geom_segment(x = 2.45, y = 1.75, xend = max(iris$Petal.Length)*2,yend = 1.75, linetype = 2) +
geom_segment(x = 4.95, y = min(iris$Petal.Width)*-2, xend = 4.95,yend = 1.75, linetype = 2) +
geom_segment(x = 2.45, y = 1.65, xend = 4.95, yend = 1.65, linetype = 2) +
geom_segment(x = 4.95, y = 1.55, xend = max(iris$Petal.Length)*2,yend = 1.55, lty = 2) +
ggtitle("Partizioni rispetto a Petal.Length e Petal.Width" )
ggp
Ora, un concetto importante nei metodi basati su alberi è quello della potatura (“pruning”): la potatura permette di evitare l’overfitting. E’ importante perché:
- assicura che l’albero sia sufficientemente piccolo da evitare di mettere variabilità casuale nella previsione,
- assicura che l’albero sia sufficientemente grande per evitare di mettere distorsioni sistematiche nelle previsioni.
Per potare l’albero, rpart
usa la misura di complessità \(R(\alpha)\), definita come:
\(R(\alpha) = R + \alpha \cdot T\)
dove
- \(R\) è il “rischio dell’albero” (per la classificazione, il tasso di errata classificazione; per la regressione: RSS);
- \(\alpha\) è il parametro di complessità che altro non è che un termine di penalizzazione che controlla la dimensione dell’albero;
- \(T\) è il numero di split/nodi terminali nell’albero
Quando \(\alpha\) cresce, l’albero con \(R(\alpha)\) minimo è un sottoalbero più piccolo dell’albero originario.
Quando \(\alpha\) cresce, quindi, la sequenza di conseguenti sottoalberi è nidificata.
Quindi, ogni sottoalbero è collegato ad un valore del parametro di complessità \(\alpha\) per cui possiamo accettare il
sottoalbero come quello a “complessità minima”.
Il codice che segue stampa una tabella di potatura ottimale basata sul parametro di complessità:
##
## Classification tree:
## rpart(formula = Species ~ ., data = iris, method = "class", control = rpart.control(minsplit = 4,
## cp = 1e-06))
##
## Variables actually used in tree construction:
## [1] Petal.Length Petal.Width
##
## Root node error: 100/150 = 0.66667
##
## n= 150
##
## CP nsplit rel error xerror xstd
## 1 5.0e-01 0 1.00 1.19 0.049592
## 2 4.4e-01 1 0.50 0.70 0.061101
## 3 2.0e-02 2 0.06 0.10 0.030551
## 4 1.0e-02 3 0.04 0.10 0.030551
## 5 1.0e-06 5 0.02 0.09 0.029086
Nella tabella qui sopra:
CP
è il parametro \(\alpha\);nsplit
è il numero degli split de l miglior albero trovato in base aCP
;rel error
è il valore dell’errore relativo di “resubstitution” (cioè calcolato prevedendo i dati usati per il training dell’albero stesso) dell’albero selezionato rispetto all’albero con “sola radice”.xerror
è il valore di errore di cross-validation, ottenuto spezzando i dati inxval
sottoinsiemi, applicando la procedura di training iterativamente sui dati in cui uno dei sottoinsiemi è rimosso, e quindi applicado la previsione sui dati del sottoinsieme rimosso;xerror
è la media dei valori di errore di crossvalidationxval
. Questa procedura serve per evitare di individuare una configurazione di albero che si adatta “troppo” ai dati correnti e quindi poco generalizzabile (il cosiddetto overfitting).xstd
è la deviazione standard dell’errore stimato tramite cross-validation
L’albero più semplice con il valore di errore di cross-validation più basso (xerror
) o inferiore al minimo + 1 volta xstd
è il numero 3.
L’albero che ritorna il valore minimo di tasso di errore “resubstitution” è l’albero numero 5.
L’albero più grande sarà inevitabilmente sempre quello con il tasso di errore “resubstitution” più basso.
A questo punto ci sono due criteri usati per decidere l’ammontare di potatura da applicare all’albero:
- L’albero con l’errore di cross-validation più basso
- Il più piccolo albero con valore di cross-validation inferiore al minimo valore di errore di cross-validation più 1 volta la sua deviazione standard.
Un grafico del tasso d’errore resubstitution per l’albero si può ottenere con:
ggp <- ggplot(data = data.frame(iris_rp$cptable,Tree.number = 1:nrow(iris_rp$cptable)),
mapping = aes(x = Tree.number, y = rel.error))
ggp <- ggp + geom_line()
ggp <- ggp + geom_point()
ggp
Il grafico del tasso d’errore di cross-validation è invece ottenuto con:
Come abbiamo già notato, la configurazione di albero più semplice con il valore di xerror
“ottimo” (in questo caso il più semplice albero con valore di xerror
inferiore al minimo + 1 volta xsrd
) è la numero 3.
Si noti che i valori in ascissa sono diversi da quelli della tabella CP: i valori di CP sono calcolati come la media geometrica di valori adiacenti della tabella CP.
Il codice che segue mostra come estrarre interattivamente sottoalberi da un albero dato:
plot(iris_rp, uniform = TRUE)
text(iris_rp)
iris_rp1 <- snip.rpart(iris_rp)
plot(iris_rp1)
text(iris_rp1)
Questo invece è il codice per un grafico “veloce” degli errori con legenda:
plotcp(iris_rp)
with(iris_rp, {
lines(cptable[, 2] + 1, cptable[ , 3], type = "b", col = "red")
legend("topright", c("Errore Resub.", "Errore CV", "min(Err CV) + 1se"),
lty = c(1, 1, 2), col = c("red", "black", "black"), bty = "n")
})
E quello che segue è l’albero finale potato per cp=0.02
(la configurazione di albero con valore xerror
“ottimo” dalla tabella dei CP):
iris.pruned <- prune(iris_rp, cp=0.02)
plot(iris.pruned, compress = TRUE, margin = 0.2, branch = 0.3)
text(iris.pruned, use.n = TRUE, digits = 3, cex = 0.8)
Infine, di seguito la partizione dello spazio delle “feature” per l’albero potato:
ggp <- ggplot(data = iris) +
geom_point(aes(x = Petal.Length, y = Petal.Width, colour = Species)) +
geom_vline(xintercept = 2.45, linetype = 2) +
geom_segment(x = 2.45, y = 1.75, xend = max(iris$Petal.Length)*2,yend = 1.75, linetype = 2) +
ggtitle("Partizioni rispetto a Petal.Length e Petal.Width per l'albero potato")
ggp
Le previsioni per nuovi dati a partire da un albero stimato si possono ottenere, come d’uso, con il metodo predict()
:
La corrispettiva matrice di confusione si può generare semplicemente con
##
## iris_pred setosa versicolor virginica
## setosa 50 0 0
## versicolor 0 49 5
## virginica 0 1 45
Oppure usando confusionMatrix()
:
## Confusion Matrix and Statistics
##
## Reference
## Prediction setosa versicolor virginica
## setosa 50 0 0
## versicolor 0 49 5
## virginica 0 1 45
##
## Overall Statistics
##
## Accuracy : 0.96
## 95% CI : (0.915, 0.9852)
## No Information Rate : 0.3333
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.94
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: setosa Class: versicolor Class: virginica
## Sensitivity 1.0000 0.9800 0.9000
## Specificity 1.0000 0.9500 0.9900
## Pos Pred Value 1.0000 0.9074 0.9783
## Neg Pred Value 1.0000 0.9896 0.9519
## Prevalence 0.3333 0.3333 0.3333
## Detection Rate 0.3333 0.3267 0.3000
## Detection Prevalence 0.3333 0.3600 0.3067
## Balanced Accuracy 1.0000 0.9650 0.9450
14.3 Esempio: Dati di credito in Germania
Consideriamo ora il dataset german
, che riguarda dati di credito in Germania.
In questo caso vogliamo prevedere se un cliente andrà in default (Bad
) o pagherà (Good
), sempre con la carta di credito.
Quindi, abbiamo dati di input che contengono sia dati di clienti “buoni” (good) che di clienti “cattivi” (bad). Vogliamo trovare le regole o le condizioni che separano buoni clienti e cattivi clienti.
Le regole dovrebbero aiutare il ricercatore a trovare i segmenti con percentuali sufficientemente alte di buoni clienti.
##
## Good Bad
## 700 300
Prima di iniziare l’analisi spezziamo il campione in due sottoinsiemi, di training e di test, per permettere il test dell’albero:
set.seed(20)
sel <- sample(1:1000, size = 600, replace = FALSE)
train <- german[sel, ]
test <- german[setdiff(1:1000, sel), ]
table(train$Class)
##
## Good Bad
## 413 187
##
## Good Bad
## 287 113
Cominciamo quindi l’analisi costruendo l’albero senza altre specificazioni:
In questo caso il rpart()
applica automaticamente la procedura di crescita e
potatura dell’albero in maniera automatica, utilizzando parametri predefiniti
per le varie fasi.
Ora proviamo a tracciare la curva ROC per l’albero generato:
probs <- predict(modelg0, newdata = test, type = "prob")[,1]
roc(response = (test$Class=="Bad"), predictor = probs, auc = TRUE, ci = TRUE,
plot = TRUE, main = "Curva ROC sui dati di credito in Germania", legacy.axes = TRUE)
## Setting levels: control = FALSE, case = TRUE
## Setting direction: controls > cases
##
## Call:
## roc.default(response = (test$Class == "Bad"), predictor = probs, auc = TRUE, ci = TRUE, plot = TRUE, main = "Curva ROC sui dati di credito in Germania", legacy.axes = TRUE)
##
## Data: probs in 287 controls ((test$Class == "Bad") FALSE) > 113 cases ((test$Class == "Bad") TRUE).
## Area under the curve: 0.6958
## 95% CI: 0.642-0.7496 (DeLong)
Possiamo quindi verificare le performance dell’albero sui dati di training
##
## Good Bad
## Good 355 42
## Bad 58 145
## Confusion Matrix and Statistics
##
## Reference
## Prediction Good Bad
## Good 355 42
## Bad 58 145
##
## Accuracy : 0.8333
## 95% CI : (0.8011, 0.8623)
## No Information Rate : 0.6883
## P-Value [Acc > NIR] : 3.661e-16
##
## Kappa : 0.6204
##
## Mcnemar's Test P-Value : 0.1336
##
## Sensitivity : 0.7754
## Specificity : 0.8596
## Pos Pred Value : 0.7143
## Neg Pred Value : 0.8942
## Prevalence : 0.3117
## Detection Rate : 0.2417
## Detection Prevalence : 0.3383
## Balanced Accuracy : 0.8175
##
## 'Positive' Class : Bad
##
E quindi fare la stessa verifica sui dati di test
##
## Good Bad
## Good 204 59
## Bad 83 54
## Confusion Matrix and Statistics
##
## Reference
## Prediction Good Bad
## Good 204 59
## Bad 83 54
##
## Accuracy : 0.645
## 95% CI : (0.5959, 0.6919)
## No Information Rate : 0.7175
## P-Value [Acc > NIR] : 0.99933
##
## Kappa : 0.1773
##
## Mcnemar's Test P-Value : 0.05359
##
## Sensitivity : 0.4779
## Specificity : 0.7108
## Pos Pred Value : 0.3942
## Neg Pred Value : 0.7757
## Prevalence : 0.2825
## Detection Rate : 0.1350
## Detection Prevalence : 0.3425
## Balanced Accuracy : 0.5943
##
## 'Positive' Class : Bad
##
Come atteso, le performance dell’albero con i dati di test sono scarsi, rispetto ai dati di training. Proveremo ora a potare l’albero a cp = 0.031 (il valore di cp con errore xval minimo nel grafico), per vedere se see si presenta qualche overfitting (sovra adattamento)
Possiamo quindi verificare questo nuovo albero usando il campione di test
##
## Good Bad
## Good 249 81
## Bad 38 32
## Confusion Matrix and Statistics
##
## Reference
## Prediction Good Bad
## Good 249 81
## Bad 38 32
##
## Accuracy : 0.7025
## 95% CI : (0.6551, 0.7469)
## No Information Rate : 0.7175
## P-Value [Acc > NIR] : 0.7659840
##
## Kappa : 0.1704
##
## Mcnemar's Test P-Value : 0.0001181
##
## Sensitivity : 0.2832
## Specificity : 0.8676
## Pos Pred Value : 0.4571
## Neg Pred Value : 0.7545
## Prevalence : 0.2825
## Detection Rate : 0.0800
## Detection Prevalence : 0.1750
## Balanced Accuracy : 0.5754
##
## 'Positive' Class : Bad
##
Riducendo la complessità dell’albero, anche le performances cambiano. Se l’accuratezza globale si riduce solo di poco, anche il numero di clienti cattivi trovati dall’albero si riduce. Inoltre, la specificità cresce mentre la sensibilità cala.
I risultati qui sopra potrebbero dipendere dal fatto che l’albero dà la stessa “importanza” ai clienti buoni e ai clienti cattivi. Ciò non è nella realtà corretto, poiché la perdita legata ad una erra classificazione di un cliente cattivo è maggiore della perdita dovuta all’errata classificazione di un cliente buono.
Per provare a superare questo problema possiamo provare a costruire l’albero dando valori di perdita
diversi ai due tipi di errore di errata classificazione, e quindi dando maggiore “peso”" ai clienti
Bad
mal classificati.
Proviamo quindi creare una matrice di perdita, dove assegnamo una perdita di 5 per clienti
cattivi mal classificati ed una perdita di 1 per clienti buoni mal classificati.
# Resetta previsioni precedenti
train$pred <- NULL
train$pred <- NULL
# Imposta la matrice di perdita
(lmat <- matrix(c(0, 1, 5, 0), byrow = TRUE, nrow = 2))
## [,1] [,2]
## [1,] 0 1
## [2,] 5 0
# Fa crescere l'albero
modelg1 <- rpart(Class ~ ., parms = list(loss = lmat), data = train, cp = 0)
# Tabella CP
printcp(modelg1)
##
## Classification tree:
## rpart(formula = Class ~ ., data = train, parms = list(loss = lmat),
## cp = 0)
##
## Variables actually used in tree construction:
## [1] CreditAmount CreditHistory Duration
## [4] EmployedSince Housing Job
## [7] OtherBebtorsGarants OtherInstallPlans Property
## [10] Purpose RatePercIncome SavingsAccountBonds
## [13] StatusAccount StatusAndSex
##
## Root node error: 413/600 = 0.68833
##
## n= 600
##
## CP nsplit rel error xerror xstd
## 1 0.1670702 0 1.00000 5.0000 0.13735
## 2 0.0520581 1 0.83293 2.2155 0.13349
## 3 0.0351090 3 0.72881 2.7966 0.14184
## 4 0.0290557 5 0.65860 2.6174 0.13980
## 5 0.0242131 6 0.62954 2.5811 0.13935
## 6 0.0169492 8 0.58111 2.6877 0.14070
## 7 0.0145278 9 0.56416 2.6416 0.14008
## 8 0.0112994 11 0.53511 2.6683 0.14031
## 9 0.0096852 14 0.50121 2.4673 0.13715
## 10 0.0072639 19 0.45036 2.4068 0.13627
## 11 0.0024213 21 0.43584 2.3341 0.13514
## 12 0.0012107 23 0.43099 2.2663 0.13385
## 13 0.0000000 27 0.42615 2.2615 0.13350
# Albero
plot(modelg1, uniform = FALSE, compress = TRUE, margin = 0.1, branch = 0.1)
text(modelg1, use.n = TRUE, digits = 3, cex = 0.6)
Notate come l’albero con tasso xerror minimo è quello con parametro CP pari a 0.
Questo significa che non si può pensare di applicare il pruning all’albero senza perdere potenzialmente in
capacità predittiva dell’albero stesso.
## n= 600
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 600 413 Bad (0.68833333 0.31166667)
## 2) StatusAccount=ge.200,noCA 279 175 Good (0.87455197 0.12544803)
## 4) EmployedSince=ge.4y.lt7y,ge.7y 134 45 Good (0.93283582 0.06716418)
## 8) OtherInstallPlans=stores,none 116 20 Good (0.96551724 0.03448276)
## 16) CreditAmount>=1570 77 0 Good (1.00000000 0.00000000) *
## 17) CreditAmount< 1570 39 20 Good (0.89743590 0.10256410)
## 34) SavingsAccountBonds=lt.100,unkown.no 27 5 Good (0.96296296 0.03703704) *
## 35) SavingsAccountBonds=ge.100.lt.500,ge.500.lt.1000,ge.1000 12 9 Bad (0.75000000 0.25000000) *
## 9) OtherInstallPlans=bank 18 13 Bad (0.72222222 0.27777778) *
## 5) EmployedSince=unemployed,lt.1y,ge.1y.lt.4y 145 119 Bad (0.82068966 0.17931034)
## 10) Purpose=car (new),car (used),others,domestic appliances,retraining 56 20 Good (0.92857143 0.07142857)
## 20) Duration< 25.5 48 5 Good (0.97916667 0.02083333) *
## 21) Duration>=25.5 8 5 Bad (0.62500000 0.37500000) *
## 11) Purpose=furniture/equipment,radio/television,repairs,education,business 89 67 Bad (0.75280899 0.24719101)
## 22) CreditAmount< 1840.5 40 25 Good (0.87500000 0.12500000)
## 44) CreditAmount>=1359.5 14 0 Good (1.00000000 0.00000000) *
## 45) CreditAmount< 1359.5 26 21 Bad (0.80769231 0.19230769)
## 90) Property=noRE but building savings agreem/insurance 8 0 Good (1.00000000 0.00000000) *
## 91) Property=real estate,noREorBuild but car or oth not in Savings,unknown/no property 18 13 Bad (0.72222222 0.27777778) *
## 23) CreditAmount>=1840.5 49 32 Bad (0.65306122 0.34693878)
## 46) Purpose=furniture/equipment,radio/television,repairs,education 38 28 Bad (0.73684211 0.26315789)
## 92) StatusAndSex=maleDivorced,maleSingle,maleMarriedWidowed 20 10 Good (0.90000000 0.10000000)
## 184) CreditAmount< 4180.5 13 0 Good (1.00000000 0.00000000) *
## 185) CreditAmount>=4180.5 7 5 Bad (0.71428571 0.28571429) *
## 93) StatusAndSex=femaleDivorcedOrMarried 18 10 Bad (0.55555556 0.44444444) *
## 47) Purpose=business 11 4 Bad (0.36363636 0.63636364) *
## 3) StatusAccount=lt.0,ge.0.lt200 321 169 Bad (0.52647975 0.47352025)
## 6) Duration< 11.5 50 41 Bad (0.82000000 0.18000000)
## 12) StatusAndSex=maleSingle,maleMarriedWidowed 29 0 Good (1.00000000 0.00000000) *
## 13) StatusAndSex=maleDivorced,femaleDivorcedOrMarried 21 12 Bad (0.57142857 0.42857143)
## 26) Purpose=car (new),domestic appliances,retraining 7 0 Good (1.00000000 0.00000000) *
## 27) Purpose=car (used),furniture/equipment,radio/television,education 14 5 Bad (0.35714286 0.64285714) *
## 7) Duration>=11.5 271 128 Bad (0.47232472 0.52767528)
## 14) Purpose=car (used),others,radio/television,retraining,business 123 74 Bad (0.60162602 0.39837398)
## 28) Duration< 33 87 63 Bad (0.72413793 0.27586207)
## 56) OtherBebtorsGarants=co-applicant,guarantor 14 0 Good (1.00000000 0.00000000) *
## 57) OtherBebtorsGarants=none 73 49 Bad (0.67123288 0.32876712)
## 114) EmployedSince=ge.4y.lt7y,ge.7y 26 20 Good (0.84615385 0.15384615)
## 228) Job=unskilled-resident,high-skill 8 0 Good (1.00000000 0.00000000) *
## 229) Job=skilled employee/official 18 14 Bad (0.77777778 0.22222222) *
## 115) EmployedSince=unemployed,lt.1y,ge.1y.lt.4y 47 27 Bad (0.57446809 0.42553191)
## 230) Purpose=car (used),others,retraining 12 10 Good (0.83333333 0.16666667) *
## 231) Purpose=radio/television,business 35 17 Bad (0.48571429 0.51428571)
## 462) SavingsAccountBonds=ge.500.lt.1000,ge.1000,unkown.no 8 5 Good (0.87500000 0.12500000) *
## 463) SavingsAccountBonds=lt.100,ge.100.lt.500 27 10 Bad (0.37037037 0.62962963) *
## 29) Duration>=33 36 11 Bad (0.30555556 0.69444444) *
## 15) Purpose=car (new),furniture/equipment,domestic appliances,repairs,education 148 54 Bad (0.36486486 0.63513514)
## 30) RatePercIncome< 2.5 53 27 Bad (0.50943396 0.49056604)
## 60) Housing=for free 7 5 Good (0.85714286 0.14285714) *
## 61) Housing=rent,own 46 21 Bad (0.45652174 0.54347826) *
## 31) RatePercIncome>=2.5 95 27 Bad (0.28421053 0.71578947)
## 62) CreditHistory=delay in paying in past,critical account/credit in other 34 16 Bad (0.47058824 0.52941176)
## 124) SavingsAccountBonds=ge.500.lt.1000,ge.1000,unkown.no 7 5 Good (0.85714286 0.14285714) *
## 125) SavingsAccountBonds=lt.100 27 10 Bad (0.37037037 0.62962963) *
## 63) CreditHistory=no credits,all credits this bank paid,credits this bank paid back till now 61 11 Bad (0.18032787 0.81967213) *
Adesso quindi verificheremo l’albero usado il campioe di test:
##
## Good Bad
## Good 150 37
## Bad 137 76
## Confusion Matrix and Statistics
##
## Reference
## Prediction Good Bad
## Good 150 37
## Bad 137 76
##
## Accuracy : 0.565
## 95% CI : (0.5148, 0.6142)
## No Information Rate : 0.7175
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.1539
##
## Mcnemar's Test P-Value : 6.135e-14
##
## Sensitivity : 0.6726
## Specificity : 0.5226
## Pos Pred Value : 0.3568
## Neg Pred Value : 0.8021
## Prevalence : 0.2825
## Detection Rate : 0.1900
## Detection Prevalence : 0.5325
## Balanced Accuracy : 0.5976
##
## 'Positive' Class : Bad
##
L’accurateza globale dell’albero è calata ancora, ma la sensibilità è cresciuta in maniera sensibile, al costo di una riduzione in specificità.
Globalmente, quest’ultimo albero “riconosce” molti più clienti “cattivi” rispetto all’albero che non usava la matrice di perdita.