Prendiamo in esame il dataset TL che raccoglie dati di tipo assicurativo ed iniziamo ad esplorarne le caratteristiche.
data<-read.csv("TL.csv",header=TRUE,row.names=1)
plot(data) #-1 così tolgo la colonna X che mi serve da indice
summary(data)
## AGE MARSTAT EDUCATION NUMHH
## Min. :20.00 Length:275 Min. : 2.00 Min. :1.00
## 1st Qu.:39.00 Class :character 1st Qu.:13.00 1st Qu.:2.00
## Median :47.00 Mode :character Median :16.00 Median :3.00
## Mean :47.83 Mean :14.52 Mean :2.96
## 3rd Qu.:57.00 3rd Qu.:16.50 3rd Qu.:4.00
## Max. :82.00 Max. :17.00 Max. :9.00
## INCOME FACE
## Min. : 260 Min. : 800
## 1st Qu.: 38000 1st Qu.: 50000
## Median : 65000 Median : 150000
## Mean : 208975 Mean : 747581
## 3rd Qu.: 120000 3rd Qu.: 590000
## Max. :10000000 Max. :14000000
Notiamo come non siano tutte di tipo int ma abbiamo anche una variabile (MARSTAT nello specifico) di tipo character. inoltre possiamo notare come le 2 variabili FACE e INCOME siano fortemente concentrate sui primi valori, presentando una skewness positiva.
hist(data$FACE, breaks=20)
hist(data$INCOME, breaks=20)
Per rimediare a ciò possiamo applicare la traformazione logaritmica a queste due variabili e vedere se migliora la loro distribuzione.
data$FACE<-log(data$FACE)
data$INCOME<-log(data$INCOME)
hist(data$FACE, breaks=20)
hist(data$INCOME,breaks=20)
Oltre a ricordare meglio una distribuzione normale, i 2 grafici presentano anche più simmetria tra i valori.
Trasformiamo la variabile MARSTAT in un fattoriale in modo da poter creare un primo grafico di correlazione tra le variabili indipendenti, per avere un’anteprima di cosa aspettarsi con i modelli che eseguiremo successivamente.
library(corrplot)
## corrplot 0.92 loaded
data$MARSTAT<-as.factor(data$MARSTAT)
data$MARSTAT<-as.numeric(data$MARSTAT) #1 married,2 single
corrplot(cor(data),method="number")
Essendo FACE la nostra variabile risposta ci interessa soprasttutto vedere le correlazioni che ha con le altre variabili, in particolare si può notare una correlazione positiva più o meno forte con tutte le variabili tranne che per MARSTAT con la quale è presenta una correlazione negativa (la correlazione con AGE è abbastanza piccola da poter essere omessa).
Attraverso la funzione regsubset() del pacchetto leaps costruiamo i 3 modelli:
library(leaps)
mfor<-regsubsets(FACE~.,data,method = "forward")
mback<-regsubsets(FACE~.,data,method = "backward")
mbest<-regsubsets(FACE~.,data,method = "exhaustive")
back.summary<-summary(mback)
forw.summary<-summary(mfor)
best.summary<-summary(mbest)
summary(mfor)
## Subset selection object
## Call: regsubsets.formula(FACE ~ ., data, method = "forward")
## 5 Variables (and intercept)
## Forced in Forced out
## AGE FALSE FALSE
## MARSTAT FALSE FALSE
## EDUCATION FALSE FALSE
## NUMHH FALSE FALSE
## INCOME FALSE FALSE
## 1 subsets of each size up to 5
## Selection Algorithm: forward
## AGE MARSTAT EDUCATION NUMHH INCOME
## 1 ( 1 ) " " " " " " " " "*"
## 2 ( 1 ) " " " " "*" " " "*"
## 3 ( 1 ) " " " " "*" "*" "*"
## 4 ( 1 ) " " "*" "*" "*" "*"
## 5 ( 1 ) "*" "*" "*" "*" "*"
All’interno del summary è possibile vedere l’ordine di scelta delle variabili con il metodo stepwise, nel caso del metodo forward possiamo vedere che la prima variabile ad essere inserita è EDUCATION, mentre l’ultima è AGE.
Ora facciamo creiamo i plot per le variabili predittive per metterle a confronto.
par(mfrow=c(1,4))
plot(best.summary$rss,type="l")
plot(best.summary$cp,type="l")
plot(best.summary$adjr2,type="l")
plot(best.summary$bic,type="l")
par(mfrow=c(1,4))
plot(forw.summary$rss,type="l")
plot(forw.summary$cp,type="l")
plot(forw.summary$adjr2,type="l")
plot(forw.summary$bic,type="l")
par(mfrow=c(1,4))
plot(back.summary$rss,type="l")
plot(back.summary$cp,type="l")
plot(back.summary$adjr2,type="l")
plot(back.summary$bic,type="l")
I grafici sono sostanzialmente uguali per i 3 metodi quidni possiamo fare un’analisi unica: notiamo come l’RSS sia l’unico che non penalizza per il numero di parametri, infatti dà come miglior modello quello con il maggior numero di parametri,cioè 5; mentre gli altri 3 applicano una penalizzazzione ma mentre Rquadro aggiustato e il CP sono concordi nell’indicare 4 come numero ottimale di parametri, il BIC è più parsimonioso e indica come migliore il modello con soli 3 parametri.
Proviamo ora a scegliere il miglior modello tramite un calcolo diretto dell’errore di valutazione.
Il primo metodo consisterà nel dividere i nostri dati in training set e test set: sul primo fitteremo il modello e sul secondo calcoleremo l’errore di valutazione. Calcoleremo l’errore per ogni numero di predittori possibili, così da poter indicare non solo il migliore tra il metodo di subset ma anche tra i numeri di predittori.
: regsubset() non ha e non eredita la funzione predict(), per ovviare a questo problema si calcoleranno a mano i valori previsti tramite il prodotto tra il vettore dei coefficienti per ogni modello stimato e la matrice dei valori per il set test, per fare ciò verra creata una matrice modello in modo tale da poter calcolare la matrice prodotto tra quest’ultima e il vettore dei coefficienti.
Iniziamo con il bestsubset:
set.seed(3)
data<-read.csv("TL.csv",header=TRUE)
v<-sample(data[,1],200)
data<-data[,-1]
data$FACE<-log(data$FACE)
data$INCOME<-log(data$INCOME)
data$MARSTAT<-as.factor(data$MARSTAT)
data$MARSTAT<-as.numeric(data$MARSTAT) #1 married,2 single
train<-data[v,]
test<-data[-v,]
mbest<-regsubsets(FACE~.,train,method = "exhaustive")
ValError<-c()
test.mat <- model.matrix (FACE~., data = test)
for(i in 1:5){
coefi<-coef(mbest, i)
pred<-test.mat[, names(coefi)] %*% coefi
ValError[i] <- mean((test[,1] - pred)^2)
}
which.min(ValError)
## [1] 3
a<-ValError
Ora lo stepwise foward:
mfor<-regsubsets(FACE~.,train,method = "forward")
for(i in 1:5){
coefi<-coef(mfor, i)
pred<-test.mat[, names(coefi)] %*% coefi
ValError[i] <- mean((test[,1] - pred)^2)
}
which.min(ValError)
## [1] 3
b<-ValError
Ed infine lo stepwise backward:
mback<-regsubsets(FACE~.,train,method = "backward")
for(i in 1:5){
coefi<-coef(mback, i)
pred<-test.mat[, names(coefi)] %*% coefi
ValError[i] <- mean((test[,1] - pred)^2)
}
which.min(ValError)
## [1] 3
c<-ValError
Tutte e 3 indicano come modello ottimale quello che ha al suo interno 3 predittori, possiamo vedere graficamente che sono sostanzialmente uguali.
par(mfrow=c(1,3))
plot(a,type="l",xlab="K",ylab="MSE-Best subset")
plot(b,type="l",xlab="K",ylab="MSE-Stepwise forward")
plot(c,type="l",xlab="K",ylab="MSE-Stepwise backward")
L’analisi appena fatta però è troppo suscettibile alla particolare
partizione fatta, prendendo un’altra divisione dei nostri dati potremmo
trovare risultati diversi da quelli trovati in questo momento. La
cross-validation evita questo problema, sia che si
applichi in modalità Leave One Out Cross Validation sia che la
si applicchi con i fold.
Proveremo inizialmente con un f=10:
prima creo un vettore in cui ripeto i numeri da 1 a 10 per n volte, poi con sample lo ordino in maniera casuale
f<-10
n<-nrow(data)
folds <- sample ( rep (1:f, length = n))
Creiamo la matrice nella quale salvare i risultati delle varie validazioni, nello specifico avremo una matrice 10x5, una riga per ogni fold e una colonna per ogni numero di variabili possibili del modello
cv.errors <- matrix (NA, nrow=f, ncol=5,dimnames = list (NULL , paste (1:5))) #in di,names sepcifico solo il nome delle colonne perchè alle righe viene assegnato in automatico il numero , mentre per le colonne mette V1,V2...
Implementiamo il ciclo di validazione per ogni j-esimo fold
for (j in 1:f){
test.mat <- model.matrix (FACE~., data = data[folds==j,])
mbest<-regsubsets(FACE~.,data=data[folds != j,])
for (i in 1:5){
coefi<-coef(mbest, i)
pred<-test.mat[, names(coefi)] %*% coefi
cv.errors[j, i] <- mean((data$FACE[folds == j]- pred)^2)
}
}
Facciamo la media rispetto alle colonne,quindi rispetto ai folds, in modo tale da trovare quale modello ha il minor MSE test
mean.cv.errors<-c()
for (i in 1:5){
mean.cv.errors[i]<- mean(cv.errors[,i])
}
mean.cv.errors
## [1] 2.747175 2.581845 2.413218 2.408639 2.412599
which.min(mean.cv.errors)
## [1] 4
plot(mean.cv.errors,type="b",xlab="K",ylab="Mean MSE Test")
Il grafico ci indica che lMSE test minore lo sitrova per un K=4, questo risultato ci viene indicato per una cross-validation che utilizza il metodo Best Subset, per completezza rifacciamo il tutto utilizzando il metodo Stepwise forward in modo tale da poter confrontare i dati
cv.errors <- matrix (NA, nrow=f, ncol=5,dimnames = list (NULL , paste (1:5)))
for (j in 1:f){
test.mat <- model.matrix (FACE~., data = data[folds==j,])
mforw<-regsubsets(FACE~.,data=data[folds != j,],method = "forward")
for (i in 1:5){
coefi<-coef(mforw, i)
pred<-test.mat[, names(coefi)] %*% coefi
cv.errors[j, i] <- mean((data$FACE[folds == j]- pred)^2)
}
}
mean.cv.errors<-c()
for (i in 1:5){
mean.cv.errors[i]<- mean(cv.errors[,i])
}
mean.cv.errors
## [1] 2.747175 2.581845 2.413218 2.408639 2.412599
which.min(mean.cv.errors)
## [1] 4
plot(mean.cv.errors,type="b",xlab="K",ylab="Mean MSE Test")
Il risultato è identico.
Useremo il packages glmnet per eseguire entrambi i metodi, nello specifico useremo la funzione glmnet() per creare il modello e cv.glmnet per trovare il valore ottimale di lambda attraverso una cross-validation in maniera automatica senza doverla scrivere a mano.
A differenza di lm() e regsubsets(), la sintassi è differente e in più viene richiesto che la var.risposta sia un vettore e le variabili indipendenti una matrice.
library(glmnet)
## Caricamento del pacchetto richiesto: Matrix
## Loaded glmnet 4.1-6
x<-model.matrix(FACE~.,data = train)
y<-train$FACE
xtest<-model.matrix(FACE~.,data = test)
ytest<-test$FACE
lambda<-cv.glmnet(x,y,alpha=0)
minlambda<-lambda$lambda.min
rmod<-glmnet (x,y, alpha = 0,lambda = minlambda)
ridg.pred<-predict(rmod, s=minlambda, newx=xtest)
mean((ridg.pred - ytest)^2)
## [1] 1.733046
predict(rmod , type = "coefficients", s =minlambda)
## 7 x 1 sparse Matrix of class "dgCMatrix"
## s1
## (Intercept) 4.654078829
## (Intercept) .
## AGE -0.001400292
## MARSTAT -0.438235669
## EDUCATION 0.167316762
## NUMHH 0.254459567
## INCOME 0.424364064
Ovviamente il metodo Ridge non manda nessuna variabile a 0, per questo risultano tutte le variabili indipendenti significative.
Per implementare il Lasso basata ripetere il codice precedente cambiando il parametro alpha da 0 a 1
x<-model.matrix(FACE~.,data = train)
y<-train$FACE
xtest<-model.matrix(FACE~.,data = test)
ytest<-test$FACE
lambda<-cv.glmnet(x,y,alpha=1)
minlambda<-lambda$lambda.min
rmod<-glmnet (x,y, alpha = 1,lambda = minlambda)
ridg.pred<-predict(rmod, s=minlambda, newx=xtest)
mean((ridg.pred - ytest)^2)
## [1] 1.735878
predict(rmod , type = "coefficients", s =minlambda)
## 7 x 1 sparse Matrix of class "dgCMatrix"
## s1
## (Intercept) 3.8340325
## (Intercept) .
## AGE .
## MARSTAT -0.3397008
## EDUCATION 0.1765092
## NUMHH 0.2864429
## INCOME 0.4607279
Il metodo Lasso manda a 0 solo la variabile AGE per il lambda dato (che abbiamo scelto tramite una cross-validation, quindi il migliore), le altre variabili risultano tutte significative per il modello.