Laboratorio Statistica II in R
Laboratorio Statistica II in R
x = c(1,2,4)
x
Anche il singolo valore numerico è pensato come vettore di lunghezza 1. In realtà quindi il
comando di concatenazione concatena vettori.
y=c(x,8)
y
length(x)
x[3]
Quando si applica una funzione a un vettore, questa viene applicata a ogni elemento del vettore:
z=1/x
z
Analogamente, il prodotto tra due vettori coincide col prodotto del primo elemento col primo, il
secondo col secondo e così via.
x*z
Problema di questo operatore è che i due vettori devono avere lo stesso numero di elementi. Se z
ha più elementi allora si duplica x e si taglia x in modo che i due vettori abbiano la stessa
lunghezza.
Le operazioni che esso ha eseguito sono le seguenti:
y1=c(y,y): concatenazione di due copie del vettore più corto,
y2=y[1:3]: adattamento della lunghezza,
x*y2: operazione aritmetica coerente.
Se invece la lunghezza di z è un multiplo della lunghezza di x, il software non emette nessun
messaggio di avviso.
Alcune operazioni frequenti:
abs(u)
round(u), il secondo argomento specifica il numero di cifre decimali
length(u)
min(u)
max(u)
sum(u), somma degli elementi del vettore
sort(u)
rev(u), inverte l’ordine degli elementi
rev(sort(u)), ordine decrescente
Possiamo generare sequenze di numeri:
q = 1:10
q = 10:1
seq(from=0, to=1, by=0.01) o seq(0,1,0.01) o 1:100/100
Possiamo utilizzare l’operatore : anche per accedere a più elementi del vettore:
q[99:100] = c(199,200)
NA sta per Non Available, e indica che alcuni elementi non sono disponibili:
q[100]=NA
NA, TRUE e FALSE non possono essere utilizzati come nomi di vettori.
Se vogliamo tutti gli indici tranne alcuni possiamo escluderli con:
q[-(1:10)]
q[-c(1:95, 99:199)]
A = matrix(nrow=4,ncol=6)
Il riempimento avviene per colonne. Se si vuole ottenere il riempimento per righe si può operare
per trasposizione,
B = matrix(z,6,4)
t(B)
A = matrix(z,4,6,byrow=T)
Il comando per la dimensione è un vettore. I singoli valori possono essere ottenuti separatamente,
Come per i vettori, funzioni di variabile reale calcolate su matrici si intendono applicate elemento
per elemento:
diag(y)
t(A)
Il determinante:
det(Q)
Riempimento di una matrice con numeri casuali a partire da una distribuzione gaussiana con
media 0 e varianza non nulla:
Q=matrix(rnorm(25),5,5)
La matrice inversa:
solve(Q)
I = Q %*% solve(Q)
round(I)
Si ha un errore di approssimazione ed eccetto la diagonale, non avremo proprio degli zeri, si può
quindi utilizzare la funzione round.
Selezione di un elemento,
A[2,3]
A[,2]
A[3,]
A[1:2,c(3,5)]
La concatenazione tra matrici restituisce vettori, prende le matrici e le trasforma in vettori per poi
concatenarli:
B=A
c(A,B)
A1+A2
A1-A2
eigen(Q)
EVV = eigen(Q)
EVV$values
EVV$vectors
EV = eigen(Q,only.values=T) or eigen(Q)$values
class(EVV)
class(EV)
Una classe che sarà utile nel seguito è il data frame, a prima vista non molto differente da una
matrice, ma nella quale righe e colonne hanno etichette.
P=data.frame(Q)
row.names(P)
colnames(P)
row.names(P)<-c("Riga 1","Riga 2","Riga 3","Riga 4","Riga 5")
colnames(P)<-c("Colonna 1","Colonna 2","Colonna 3","Colonna 4")
as.matrix(P)
P$"Colonna 5"<-rnorm(5)
nr = data.frame(matrix(rnorm(5),1,5))
colnames(nr)<-c("Colonna 1","Colonna 2","Colonna 3","Colonna 4","Colonna 5")
//stessi nomi di colonne e colonne consistenti
row.names(nr)<-c("Nuova riga")
P <- rbind(P,nr) //altrimenti si ha cbind per le colonne
Se aggiungiamo una o più righe dobbiamo aggiungere righe che hanno la stessa struttura in
termini di colonne, i nomi devono essere gli stessi altrimenti avremo errore.
E’ possibile importare dati da altre sorgenti. In dipendenza del sistema operativo i comandi per
importare i dati possono essere diversi (soprattutto quando si importa dalla clipboard). L’effetto di
alcuni di questi comandi potrebbe non essere quello atteso, e ciò potrebbe dipendere dai fattori
più disparati, come la versione del Software, o l’estensione del file da cui state copiando la tabella.
Per caricare dati dalla clipboard (windows):
A <- read.table("clipboard")
A <- read.table("clipboard",header=T)
Nel caso di uso di file, il software R si aspetta di trovare i file indicati nella cosiddetta working
directory. Il software R ha due comandi per conoscere o assegnare la working directory. La scelta
della working directory dipende dalla piattaforma (Windows, Mac, Linux) e dal programma
(RStudio, RGui, etc.) usati.
I comandi da utilizzare sono:
conoscere: getwd()
assegnare: setwd(⟨percorso completo della cartella⟩)
visualizzarne il contenuto: dir()
Tabella csv senza etichette di riga o colonna e con etichette di riga o colonna, assumendo che la
prima riga della matrice sia il nome delle colonne (header = T di default):
A <- read.csv("01prova.csv",header=F)
fix(A) //Mostra la tabella in un’altra finestra
A <- read.csv("01prova.csv")
fix(A)
Se la prima colonna deve essere usata per i nomi di riga, possiamo specificarlo con row.names
indicando quale colonna utilizzare.
A <- read.csv("03prova.csv",row.names=1)
Se la prima riga (con i nomi delle colonne) contiene un elemento in meno, la prima colonna è
usata per i nomi di riga.
Nel caso il file csv sia in formato locale italiano (ovvero il separatore per il decimale è la virgola, e il
separatore di campi è il punto e virgola). Di default il separatore di campo è la virgola e il
separatore per il decimale è il punto.
Il comando read.csv2 può essere tipicamente utilizzato se il comando read.csv non funziona.
Per leggere tabelle excel c’è bisogno di aggiungere un’estensione e poi è possibile utilizzare il
comando read.xls.
Per la visualizzazione un vettore viene disegnato prendendo come dati per le ascisse gli indici degli
elementi.
plot(X)
Una coppia di vettori viene rappresentata utilizzando il primo vettore per le ascisse e il secondo
vettore per le ordinate.
Y=X^2
plot(X,Y)
Il comando seguente dà lo stesso risultato del precedente. Nel caso la matrice abbia più di due
colonne, vengono utilizzate solo le prime due colonne.
Z=matrix(c(X,Y),26,2)
plot(Z)
Il tipo di rappresentazione dei dati può essere modificata con l’opzione type, che prende i
valori p, l, b, c, o, h, s, S, n (p è il default). Il caso type="n" non disegna nulla.
type=”l” riunisce i punti in modo contigua in modo da creare un grafico a linee.
plot(X,Y,type="l")
Si può variare anche il tipo di linea, con l’opzione lty, che varia da 0 a 7, 2 per esempio dà una linea
tratteggiata.
plot(X,Y,type="l",lty=2)
plot(X,Y,pch=0:25)
plot(X,Y,pch="$")
plot(X,Y,type="l",lwd=3)
plot(X,Y,col="red")
plot(X,Y,col=0:7)
palette()
pie(rep(1,8),col=palette(),labels=palette())
C’è a disposizione un set di colori più ampio, dato dal vettore colours(), di cui diamo un campione
con i comandi seguenti.
colours()[31:60]
hist(1:60,30,col=colours()[31:60])
plot(X,Y,asp=1)
plot(X,Y,asp=0.04)
Se disegnamo una matrice (ma solo come classe data.frame) che abbia più di 2 colonne, otteniamo
una matrice di rappresentazioni grafiche delle colonne accoppiate a due a due.
Z=matrix(c(X,X^2,X^3),26,3)
plot(as.data.frame(Z))
Avremo come ascissa la colonna e come ordinata la riga. Ciò ci da un’idea di come i fattori si
disperdono l’uno dall’altro.
Possiamo riottenere uno dei riquadri selezionando le opportune colonne.
plot(Z[,3],Z[,1])
Nel caso delle matrici, se queste hanno più di due colonne, vengono utilizzate solo le prime due
colonne.
Abbiamo un modello con delle previsioni che possiamo confrontare con i valori che conoscevamo
e il confronto ci fornisce i residui. Studiarli sarà importante per capire se l’analisi sarà stata efficace
e per stimare l’errore delle nostre previsioni.
Visti i residui dobbiamo capire se questi assomigliano a qualcosa, l’errore può assomigliare a tante
piccole cause, come errori fatti nelle misurazioni. Il teorema del limite centrale dice che se
sovrapponiamo le fluttuazioni rispetto al valor medio, se sono tante messe insieme possono
assomigliare a una distribuzione gaussiana. Se i residui non sono gaussiani non significa che
l’analisi non funzioni, può essere che vi sia un fattore dominante che crea dei residui che hanno
una struttura che non abbia a che fare con la gaussianità.
Nella regressione lineare la varianza spiegata vicina a 1 significa un’ottima correlazione lineare.
Dati dei dati vogliamo verificare empiricamente se questi sono gaussiani raccogliendo una serie di
indizi.
La densità gaussiana, densità della distribuzione normale standard può essere disegnata
specificando con norm, d nel caso di densità, p per la funzione di ripartizione, q per i quantili e r
numeri casuali mentre il suffisso norm indica una distribuzione normale.
X=seq(-4,4,0.01)
plot(X, type="l")
dnorm(X, 0, 0.5)
La funzione di ripartizione della distribuzione normale standard, integrale della densità, può essere
ottenuta come segue:
plot(X,pnorm(X),type="l")
Li confronteremo con quelli teorici, ma noteremo che conviene di più utilizzare come confronto le
densità e non le funzioni di ripartizione, tende a mostrare di più le differenze.
La funzione dei quantili della distribuzione normale standard, inversa della funzione di ripartizione:
Y=seq(0,1,0.01)
plot(Y,qnorm(Y),type="l")
Comando per generare 10000 numeri campionati da una distribuzione normale standard.
plot(matrix(runif(10000),5000,2),pch=".")
plot(matrix(rexp(10000,0.3),5000,2),pch=".")
Per le distribuzioni discrete, come la binomiale o quella di Poisson, è più interessante fare
un’istogramma:
hist(rbinom(10000,20,0.3),20)
Dobbiamo specificare come secondo argomento quante prove facciamo e come terzo argomento
la probabilità di successo.
Invece di vedere tutti e i 10000 numeri casuali, conviene vederli aggregati ed è per questo che
utilizziamo il secondo argomento di hist.
Noi analizzeremo però i residui che sono tipicamente continui.
X=1:100
mean(X)
sd(X)
var(x)
median(X)
quantile(X,c(0,0.25,0.5,0.75,1))
Y=seq(-4,4,0.01)
plot(pnorm(Y),type="l")
plot(ecdf(rnorm(100)),pch=".")
lines(Y,pnorm(Y),col="red",type="l")
La corrispondenza è sempre più precisa all’aumentare della numerosità del campione. In realtà è
più efficace confrontare le densità.
Se abbiamo dei numeri che variano molto, fare delle medie di questi numeri tende a smussare
queste variazioni, filtrare il rumore. Tendiamo a vedere meno le irregolarità nelle funzioni di
ripartizione.
Per studiare la densità possiamo innanzitutto ottenere un istogramma e possiamo variare il
numero di classi in cui viene raggruppato, il numero di barre.
Troppe classi tendono a disperdere i dati, mentre troppe poche classi danno poche informazioni.
hist(X,20)
L’istogramma ci dà la numerosità delle classi ma l’unità di misura delle ordinate sono numeri
assoluti, mentre sono più comode le frequenze così da confrontarle con la frequenza teorica che ci
viene data dalla densità teorica.
Con l’opzione freq=FALSE passiamo dalle frequenze assolute alle frequenze relative.
hist(X,20,freq=FALSE)
hist(X,20,freq=FALSE)
lines(density(X), col="red")
hist(X,20,freq=FALSE)
lines(density(X), col="red")
lines(X,dnorm(X,mean(X),sd(X)),col="green3")
X=sort(rnorm(10000))
hist(X,100,freq=FALSE)
lines(density(X), col="red")
lines(X,dnorm(X,mean(X),sd(X)),col="green3")
X=1:25
Y=5*X+3
cor(X,Y)
plot(X,Y)
Y=X^2
cor(X,Y)
round(cor(X,Y),2)
plot(X,Y)
Y=5*X+1+5*rnorm(25)
round(cor(X,Y),2)
plot(X,Y)
Y=5*X+1+50*rnorm(25, 0, 5)
round(cor(X,Y),2)
plot(X,Y)
N=1000
n=50
X=matrix(rnorm(n*N),n,N)
Y=matrix(rnorm(n*N),n,N)
res=diag(cor(X,Y))
hist(res,30,xlim=c(-1,1))
Questa forma è simile a una gaussiana, abbiamo N ripetute prove indipendenti con N elevato,
rispettiamo il teorema del limite centrale.
Più ampio è il campione e più affidabile è la correlazione, nel nostro caso n = 50.
Restringiamo la campana aumentando i numeri casuali che confrontiamo, con n = 100, e la
campana tenderà a restringersi sempre di più. La correlazione tende a fluttuare meno.
Le conclusioni che otteniamo sono tanto più robuste quanto più ampio è il campione.
Portare sempre esempi con tanti campioni.
PA=read.csv("05pesoaltezza.csv",header=TRUE)
class(PA)
head(PA)
summary(PA)
round(cov(PA[,2:3]),2)
round(cor(PA[,2:3]),2)
Ci danno una prima idea di come le colonne della tabella interagiscono tra di loro.
Un secondo passo è dare una rappresentazione grafica. Ricordiamo che la rappresentazione
grafica di un data frame è l’insieme dei grafici di dispersione delle colonne a due a due.
plot(PA)
Diamo una rappresentazione grafica delle prime due colonne con il diagramma di dispersione,
plot(PA$altezza,PA$peso)
Valutiamo il modello di regressione lineare semplice, ovvero determiniamo la retta che meglio si
adegua ai dati. Decidiamo che il fattore di uscita è il peso e il fattore di ingresso è l’altezza.
pa.lm=lm(PA$peso~PA$altezza)
pa.lm=lm(peso~altezza,data=PA)
summary(pa.lm)
plot(PA$altezza,PA$peso)
abline(pa.lm,col="red",lwd=3)
Dobbiamo stimare quanto le nostre previsioni si allontanano dai valori effettivi, studiamo i residui.
Rappresentiamo i residui.
plot(resid(pa.lm), pch=20)
Vogliamo capire quanto casuali siano questi numeri. Dobbiamo guardare la dispersione in
verticale.
Ci aspettiamo che i residui abbiano media nulla, ottimizzando MSE lo abbiamo visto, e che non
solo siano 0 ma che si raccolgano intorno allo 0.
Con questo grafico in ascissa abbiamo l’indice, che non ha un significato, abbiamo raccolto dati in
un certo ordine. La rappresentazione dei residui rispetto alle predizioni è più efficace.
Mettiamo in ascissa le predizioni.
plot(predict(pa.lm),resid(pa.lm))
Adesso non è più significativa solo la dispersione verticale ma anche quella orizzontale.
Vediamo una figura non omogenea, e questo significa che c’è una struttura che non abbiamo
riconosciuto.
Questo grafico ci dice come i residui si collocano rispetto alle predizioni.
Può accadere che alcuni valori sono molto alti rispetto agli altri e quindi quelle predizioni sono
molto più difficili da fare.
Se i residui non assomigliano a valori casuali, il nostro modello non è riuscito a estrarre la struttura
del problema, evidentemente il modello lineare non è adeguato per catturare la dipendenza.
Se la varianza spiegata è molto alta potremmo comunque apprezzare il modello.
Ma se R è basso e i residui non sono casuali il modello non è adatto.
Dobbiamo capire se il modello è riuscito a catturare la struttura del problema, vedendo se i residui
hanno un’apparenza casuale, si distribuisce senza avere preferenze eccetto la concentrazione
eccetto al valor medio.
hist(resid(pa.lm, 30)
1-var(resid(pa.lm))/var(PA$peso)
Nella regressione lineare semplice si può ottenere anche col quadrato della correlazione, ma in
generale vale questa formula.
Vediamo degli indicatori di asimmetria, principalmente riferiti alle distribuzioni gaussiane,
soprattutto la kurtosi.
In generale è meglio riferisci agli istogrammi e ai grafici di dispersione.
Una misura quantitativa di asimmetria è la skewness, la media del momento cubico normalizzato.
pa.lm.res=resid(pa.lm)
mean(((pa.lm.res-mean(pa.lm.res))/sd(pa.lm.res))^3)
mean(((pa.lm.res-mean(pa.lm.res))/sd(pa.lm.res))^4) - 3
Si confronta con il valore 3 perché prendono come riferimento la distribuzione gaussiana nella
quale il momento terzo normalizzato è 0 e il quarto normalizzato vale 3.
Valutiamo graficamente le frequenze e le confrontiamo con la densità di una normale avente le
stesse media e deviazione standard campionarie.
hist(pa.lm.res,30,freq=F)
lines(sort(pa.lm.res),dnorm(sort(pa.lm.res),mean(pa.lm.res),sd(pa.lm.res)),
col="red")
E’ abbastanza aderente. Un’analisi qualitativa di aderenza fra una distribuzione empirica e una
distribuzione teorica è data dal grafico Quantile-Quantile. Se le due distribuzioni sono le stesse,
confrontando i quantili questi devono essere gli stessi.
Ci aspettiamo dati che si avvicinano alla diagonale.
Il comando tipico è qqplot. Nel caso del confronto con i quantili teorici di una normale, c’è il
comando abbreviato qqnorm. Il comando qqline disegna la retta di riferimento per quantili uguali.
qqnorm(pa.lm.res)
qqline(pa.lm.res)
In ascissa abbiamo i quantili teorici di una normale, mentre in ordinata abbiamo i quantili empirici.
I comandi fa delle standardizzazioni a priori, così da avere stessa media e stessa deviazione
standard.
Abbiamo una buona aderenza tra la diagonale e i quantili che a un certo punto si perde, inevitabile
perché le quantità empiriche tendono a rarefarsi in valori grandi e in valori piccoli, non abbiamo
abbastanza dati per valori grandi o piccoli. Più grande sarà il numero di dati che usiamo e più a
lungo ci sarà l’aderenza.
E’ importante di quanto si staccano più che si staccano.
La cosa più importante è vedere se c’è buona aderenza nella parte centrale, poi guardiamo il
distacco inevitabile, e vediamo quanto si staccano.
Qui si staccano a +-2*deviazione standard, più aderenti sono i quantili e meglio è ma il tutto
dipende anche dalla quantità dei dati.
Possiamo usare dei test statistici che ci permettono di valutare l’ipotesi di gaussianità, così da
vedere se i dati provengono da una distribuzione gaussiana. Tra i vari test di normalità, quali ad
esempio il test di Shapiro-Wilk, un test che ha per ipotesi nulla la normalità dei dati. Nell’output il
valore interessante è il p-value, che ci indica (se piccolo) se rigettare l’ipotesi nulla.
shapiro.test(rnorm(100)) //con 1000 dati gaussiani il p-value è alto, non rigettiamo l’ipotesi
shapiro.test(rpois(100)) //rigettiamo l’ipotesi con quella di poisson
shapiro.test(pa.lm.res)
Con i nostri dati otteniamo questo valore vicino a 0.2, valore abbastanza alto e siamo abbastanza
tranquilli di non rigettare l’ipotesi di normalità.
Il test è significativo, essendo un test statistico, se rigettiamo l’ipotesi nulla ma non se non la
rigettiamo.
Alla base di tutti gli elementi che osserviamo nell’analisi dei residui possiamo seguire una certa
direzione. Più gli indizi sono discordanti e più difficile è essere convinti che quella direzione sia
corretta.
E’ il cumulo degli indizi a rafforzare la nostra convinzione.
Possiamo utilizzare i residui per dare una valutazione dell’incertezza intrinseca del problema.
Estraiamo i coefficienti della regressione e i quantili e troviamo un’area del piano entro il quale vi è
il 95% dei dati.
plot(PA[,2:3],col="blue",pch=16)
cfc=coef(pa.lm)
qmin=quantile(pa.lm.res,0.025)
qmax=quantile(pa.lm.res,0.975)
abline(pa.lm)
abline(pa.lm,col="red", lwd=3)
abline(cfc+c(qmin,0),col="blue")
abline(cfc+c(qmax,0),col="blue")
Usiamo uno strumento empirico per avere una stima della variabilità dei valori e usarla come
stima della variabilità di eventuali previsioni.
Lasciamo il coefficiente angolare uguale e spostiamo l’incertezza in base ai quantili empirici dei
residui.
Questa è una stima grossolana ma se non abbiamo un modello che ci permette di fare meglio è
tutto ciò che possiamo fare.
Prendiamo ora degli intervalli di confidenza per questi valori.
abline(confint(pa.lm)[,1],col="green3")
abline(confint(pa.lm)[,2],col="green3")
Abbiamo quindi una rappresentazione delle bande di confidenza empiriche (linee strette) e
parametriche (linee larghe).
In maniera empirica abbiamo stimato quelle strette, mentre abbiamo ottenuto in modo
parametrico quelle larghe, passando dai dati alla distribuzione che supponiamo essere quella
corretta e a partire da quella.
Abbiamo utilizzato i residui per stimare le bande di confidenza, per vedere se questi sono del tutto
casuali come devono essere e per notare eventuali asimmetrie.
Abbiamo ottenuto degli intervalli di confidenza per i coefficienti sotto l’ipotesi gaussiana dei
residui.
Fintanto che possiamo sostenere l’ipotesi di gaussianità dei residui, possiamo dare una fiducia a
questi intervalli di confidenza.
Per la previsione abbiamo bisogno di nuovi dati. Possiamo però estrarre dal set di dati alcune
osservazioni che utilizzeremo per la previsione.
Questa estrazione è senza reinserimento, i numeri non si ripetono.
Calcolando la radice quadrata della media della differenza al quadrato tra le previsioni e il prezzo,
otteniamo lo squarto quadratico.
Ottenendo come valore 5.49, mediamente il modello sbaglia di 5.49€.
L’errore E che abbiamo studiato dipende innanzitutto dall’errore dei dati con i quali abbiamo
calibrato i modelli, un errore dovuto al fatto che il modello lineare potrebbe non essere adatto e
l’errore delle fluttuazioni dei dati che usiamo per il test,
In previsione si fa un errore più largo, si aggiunge l’errore dei nuovi dati, non prevedibile.
Confrontiamo le predizioni ottenute con gli intervalli di confidenza e predizione forniti da R
(ottenuti, ricordiamo, con ipotesi di gaussianità dei residui che su questo dataset sono dubbie).
Usiamo PA al posto di Auto.
Confrontiamoli con quelli che otteniamo non empiricamente.
Calcoliamo gli intervalli di confidenza:
Mentre gli intervalli di confidenza sui valori previsti erano molto stretti, gli intervalli di previsione
tendono abbastanza a catturare gran parte delle previsioni.
La banda piccola è quella degli intervalli di confidenza sui valori, tiene conto solo dell’errore che
proviene dall’addestramento, sono relativi a quanto oscillano i coefficienti e quindi quando il
valore previsto può oscillare.
La banda blu, larga, è una banda di errore di previsione, più larga perché tiene conto del fatto che i
nuovi dati hanno a loro volta un errore.
E’ molto simile a quella di previsione ottenuta empiricamente, questo perché i residui erano
abbastanza gaussiani e abbastanza numerosi.
Riguardando la struttura:
Quando calibro un modello sui soli dati rossi, la retta è leggermente differente.
Lo stesso sui soli dati blu, e le due sono abbastanza parallele.
La retta originaria ha una pendenza differente perché cerca di compensare le due intercette
differenti.
Vediamo ora una tabella con più dati, con una popolazione molto più variegata e meno omogenea.
x=-5:5
y=2+5*x+3*x^2+10*rnorm(11)
lr=lm(y~x)
plot(x,y)
abline(lr)
summary(lr)
Se provo a effettuare una regressione lineare, otteniamo una varianza spiegata bassissima e la
retta di regressione lineare è del tutto non adatta.
Se guardiamo la struttura dei residui:
plot(predict(lr),resid(lr))
z=x^2
pr=lm(y~x+z)
summary(pr)
plot(predict(pr),resid(pr))
Regressione logaritmica
Carichiamo un set di dati standard nell’installazione di R.
library("datasets")
Per maggiori informazioni sui dataset disponibili, si può leggere il risultato del
comando library(help=datasets).
Usiamo il dataset EuStockMarkets, che riporta il valore degli indici azionari del mercato tedesco
(DAX), svizzero (SMI), francese (CAC) e britannico (FTSE)
class(EuStockMarkets)
esm=data.frame(EuStockMarkets)
head(esm)
summary(esm)
t=1:length(esm$SMI)
plot(esm$SMI,pch=".")
smi.lm=lm(esm$SMI~t)
summary(smi.lm)
abline(smi.lm)
Abbiamo un andamento che catturato con una retta non può essere molto ottimale. Il risultato è
quasi un 80% per la varianza spiegata però, probabilmente per il primo andamento che può essere
facilmente catturato.
I residui presentano una chiara struttura, come si vede dal terzo grafico.
plot(predict(smi.lm),resid(smi.lm),pch=".")
plot(log(esm$SMI),pch=".")
lsmi.lm=lm(log(esm$SMI)~t)
abline(lsmi.lm)
La varianza spiegata sale da 0.8 a 0.92, ottenendo una variazione significativa del 12%.
I residui continuano a mantenere una certa struttura, ma ciò è dovuto al fatto che utilizziamo una
regressione per una serie storica.
plot(predict(lsmi.lm),resid(lsmi.lm),pch=".")
summary(lsmi.lm)
Nei residui più o meno la variazione è dell’ordine di 0.3, prima era dell’ordine di 2500, non
possiamo confrontare questi residui perché confrontiamo numeri e i loro logaritmi.
C’è stato un enorme vantaggio in termini di ordine di grandezza dei residui ma il confronto è
difficile.
library(MASS)
Auto<-Cars93[,-c(1,2,3,4,6,7,8,9,10,11,15,16,18,22,23,24,26,27)]
head(Auto)
str(Auto)
Vediamo che vi sono colonne con ordini di grandezza diverse, si potrebbe quindi pensare alla
standardizzazione.
Esploriamo le correlazioni tra i fattori, prima graficamente,
plot(Auto)
round(cor(Auto),2)
Questo strumento ci da una buona indicazione della correlazione numerica a livello grafico, anche
qui simmetrico, richiedo un pacchetto esterno.
ph.lm=lm(Price~Horsepower,data=Auto)
plot(Auto$Horsepower,Auto$Price)
abline(ph.lm)
summary(ph.lm)$r.squared
La varianza spiegata è bassina.
Il p-value sull’intero modello è piccolo. Per quanto riguarda la tabella dei coefficienti, notiamo
alcuni p-value significativamente più alti degli altri.
Se due fattori sono collineari, entrambi portano un contributo alla comprensione della variabilità
che si sovrappone, vi è una ridondanza.
Testiamo i coefficienti di Weight e EngineSize e la loro collinearità.
Non possiamo togliere entrambi o rischiamo di buttar via informazioni rilevanti.
Diamo un’occhiata ai residui del modello e confrontiamo i valori dei residui rispetto alle previsioni.
Auto.lm.r=residuals(Auto.lm)
plot(fitted(Auto.lm),Auto.lm.r)
Abbiamo una struttura che è rimasta. I residui hanno media nulla, si addensano intorno ad esso,
con alcuni valori più ampii (in alto) e qualche piccola asimmetria.
Cerchiamo di scoprire cos’ha di strano il valore in alto.
max(Auto.lm.r)
which(Auto.lm.r==max(Auto.lm.r)) //59
Cars93[59,]
hist(Auto.lm.r,20,freq=F)
lines(density(Auto.lm.r),col="red")
m=mean(Auto.lm.r)
s=sd(Auto.lm.r)
lines(sort(Auto.lm.r),dnorm(sort(Auto.lm.r),m,s))
Anche a livello di istogramma notiamo una certa asimmetria, a destra i valori tendono ad essere
più vicini verso lo 0. Dovremmo calcolare la skewness e scoprire che questa sia un po' negativa.
Sovrapponiamo il grafico della densità gaussiana (nero) e notiamo che le due non sono molto
simili.
A sinistra i valori non sono molto distanti, le due si differenziamo in modo drastico in alto e a
destra.
Se su guadagna in area ovviamente perde a destra, infatti l’area è unitaria. Il picco più elevato
causa la riduzione drastica della parte destra.
Questa analisi suggerisce che la possibilità di avere dati gaussiani sia molto bassa. Queste
informazioni sono indizi e solo se ne accumuliamo diversi abbiamo una conclusione solida.
Vediamo la funzione cumulativa di probabilità,
plot(ecdf(Auto.lm.r),pch=".")
y=seq(m-3*s,m+3*s,6*s/100)
lines(y,pnorm(y,m,s),col="red")
qqnorm(Auto.lm.r)
qqline(Auto.lm.r)
Intorno all’origine abbiamo una certa aderenza e a destra si perde in modo netta, a sinistra in
modo meno netto ma si perde prima rispetto a destra.
Infine osserviamo il risultato del test Shapiro-Wilk.
shapiro.test(Auto.lm.r)
Abbiamo come ipotesi nulla la gaussianità dei dati e avere un p-value basso come in questo caso,
suggerisce il rigetto della gaussianità.
Tutto ciò ci indica che i residui sono probabilmente non gaussiani.
Riduzione del modello
Proviamo ad arrivare ad un modello più sintetico. Iniziamo con il modello originario.
Alcuni valori hanno un p-value piuttosto alto. Potrebbe essere che sono davvero ininfluenti e
quindi eliminarli potrebbe non avere conseguenze negative, oppure possiamo avere fattori
allineati.
Se li togliessimo tutti e due notiamo quella capacità di spiegare che avevano entrambi i fattori.
La linea guida principale consiste nel partire in ordine di p-value ed eliminare quelli con p-value più
alto possibile, riducendo il modello un fattore alla volta.
In presenza di fattori collineari, togliendone più di uno potrebbe causare la perdita di
informazione; togliendone uno alla volta ce ne accorgeremo. Potremo anche partire dal modello
vuoto e man mano arricchirlo, otteniamo la stessa risposta alla fine, ma in caso di tantissimi fattori,
includere tutti i fattori nel modello porterebbe overfitting. Conviene quindi cercare un modello
soddisfacente man mano aggiungendo fattori senza arrivare a regimi di overfitting. Quando
abbiamo più fattori conviene partire dal basso.
r=matrix(ncol=2,nrow=7)
Auto.lm1=lm(Price~.,data=Auto) //consideriamo tutti i fattori
summary(Auto.lm1)
r[1,]=c(summary(Auto.lm1)$r.squared,summary(Auto.lm1)$adj.r.squared) //memorizzo la
varianza e la varianza spiegata
Quando togliamo fattori come abbiamo visto avremo una riduzione di varianza spiegata, dovremo
vedere se la variazione è ampia oppure no.
La varianza spiegata è rimasta la stessa, ma ciò fa sì che la varianza spiegata corretta è aumentata,
indicazione che siamo sulla buona strada. Ciò significa che le informazioni portate dal peso sono
portate anche da altri fattori.
Auto.lm3=lm(Price~.-Weight-EngineSize,data=Auto)
summary(Auto.lm3)
r[3,]=c(summary(Auto.lm3)$r.squared,summary(Auto.lm3)$adj.r.squared)
Auto.lm4=lm(Price~.-Weight-EngineSize-Fuel.tank.capacity,data=Auto)
summary(Auto.lm4)
r[4,]=c(summary(Auto.lm4)$r.squared,summary(Auto.lm4)$adj.r.squared)
Auto.lm4=lm(Price~.-Weight-EngineSize-Fuel.tank.capacity,data=Auto)
summary(Auto.lm4)
r[4,]=c(summary(Auto.lm4)$r.squared,summary(Auto.lm4)$adj.r.squared)
Auto.lm5=lm(Price~.-Weight-EngineSize-Fuel.tank.capacity-Length,data=Auto)
summary(Auto.lm5)
r[5,]=c(summary(Auto.lm5)$r.squared,summary(Auto.lm5)$adj.r.squared)
Abbiamo un altro calo di varianza spiegata, ma anche questo trascurabile. Siamo passati però ad
un modello con 4 fattori partendo d 8.
Ora potremmo anche fermarci, ma vi è un elemento con p-value più alto degli altri ma non alto di
per sè, 0.04. Anche se il valore del p-value non è significativamente alto, proviamo ad eliminare
anche il fattore RPM.
Auto.lm6=lm(Price~.-Weight-EngineSize-Fuel.tank.capacity-Length-RPM,data=Auto)
summary(Auto.lm6)
r[6,]=c(summary(Auto.lm6)$r.squared,summary(Auto.lm6)$adj.r.squared)
ymin=min(r)
ymax=max(r)
plot(r[,1],pch=19,type="b",col="red",ylim=c(ymin,ymax))
lines(r[,2],pch=19,type="b",col="blue")
Questo grafico ci permette di capire quando fermarci nella riduzione del modello. A livello di
varianza spiegata corretta c’è inizialmente addirittura una salita. L’effetto correttivo della
variazione del numero dei fattori è più significativo della variazione di varianza spiegata.
Dopodiché abbiamo un calo, perché sappiamo che la varianza spiegata cala al diminuire del
numero dei fattori (funzione decrescente). Dobbiamo capire il tasso di decrescita e non se cala.
Le pendenze negli ultimi tre punti sono diverse, vi è stato un cambio di pendenza netto che ci dà
informazioni sul tasso di diminuzione.
Osservazioni:
I fattori, a parte RPM, sono tutti (più o meno intensamente) correlati tra loro.
I fattori Wheelbase e Width, pur avendo correlazione 0.81, sono entrambi rimasti nel
modello ridotto.
Osserviamo che i segni delle correlazioni e dei coefficienti non sono necessariamente
concordi. Ad esempio, nel primo modello, i coefficienti e le correlazioni (con Price)
di Enginesize e Width sono discordi. I coefficienti sono negativi ma le correlazioni col prezzo
sono positive. La regressione tiene infatti conto di tutti i fattori e quindi probabilmente vi
sarà la variazione anche di altri fattori.
Facciamo una previsione sul modello di regressione multivariata. Sceglieremo dei dati, li
escluderemo dal set, realizzeremo il modello e utilizzeremo i dati esclusi come set di test.
Discuteremo successivamente come utilizzare il dataset di apprendimento per una stima
dell’errore del modello regressivo (ovvero il metodo di autovalutazione).
u=sample(93,5)
Autoa=Auto[-u,]
Autob=Auto[u,]
Autoa.lm=lm(Price~.,data=Autoa)
Autoa.lm.p=predict(Autoa.lm,Autob)
Autoa.lm.p //guardiamo le previsioni
Autob$Price //guardiamo i valori corretti
sqrt(mean((Autoa.lm.p-Autob$Price)^2))
Estraiamo a caso 5 valori dai 93 complessivi. Vedremo dopo una previsione in modo più
strutturato in modo da vedere se set di dati diversi prevedono in modo differente, guarderemo
infatti la cross-validation.
Utilizzando il comando summary notiamo che il problema non è cambiato di molto, sia R^2 che
R^2adj ma anche i p-value.
L’errore medio è di 4.3 circa, che dipende dall’ordine di grandezza degli oggetti. In questo caso non
abbiamo un ottimo risultato.
Confrontiamo le predizioni ottenute con gli intervalli di confidenza e predizione forniti
da R(ottenuti, ricordiamo, con ipotesi di gaussianità dei residui che su questo dataset sono
dubbie).
Autoa.lm.ci=predict(Autoa.lm,Autob,interval="confidence")
Autoa.lm.pi=predict(Autoa.lm,Autob,interval="prediction")
plot(Autob$Price,pch=19,col="red",ylim=c(min(Autoa.lm.pi[,2]),max(Autoa.lm.pi[,3])))
x=1:5
points(x-0.05,Autoa.lm.ci[,1],pch=19,col="blue")
segments(x-0.05,Autoa.lm.ci[,2],x-0.05,Autoa.lm.ci[,3],col="blue")
points(x+0.05,Autoa.lm.pi[,1],pch=19,col="green3")
segments(x+0.05,Autoa.lm.pi[,2],x+0.05,Autoa.lm.pi[,3],col="green3")
I valori veri sono rappresentati in rosso, gli intervalli di confidenza in blu e l’intervallo di previsione
in verde. Il valore centrale è lo stesso.
Il dato migliore è il secondo o il quarto. Dove abbiamo sbagliato di molto è il terzo, anche se
contenuto nella stima di previsione. Gli intervalli di predizione ci danno una stima ragionevole di
quanto stiamo sbagliando. 5 esempi però non sono significativi da un punto di vista statistico,
necessitiamo una risposta più significativa sulla misura della capacità del modello di predire.
Avrei potuto beccare le migliori previsione e avere una sottostima dell’errore. Eliminiamo ciò
ripetendo il tutto più volte così che tipicamente prendo gruppi di dati che danno una stima fedele
dell’errore, farò una media per filtrare il rumore e arriverà a una stima più affidabile di quella
ottenuta ora.
Potremo confrontare il modello completo con il modello ridotto per vedere se il modello ridotto
guadagna in stabilità. Utilizziamo però un altro set di dati.
Autovalutazione di un modello
Torniamo a stimare l’errore sul modello di base e quello logaritmico per vedere chi prevede
meglio. Scegliamo una piazza d’affari e cerchiamo di prevederla usando le altre.
Torniamo agli indici azionari per studiare un problema di regressione multipla, in vista di svolgere
una autovalutazione del modello.
library(datasets)
esm=EuStockMarkets
plot(esm,pch=".")
esm.lm=lm(FTSE~DAX+SMI+CAC,data=esm)
summary(esm.lm)
Otteniamo una varianza spiegata del 98%.
In realtà avevamo scoperto che il modello che supponeva leggi potenza funzionava meglio e
passiamo dunque ai logaritmi dei dati originali.
lesm=log(esm)
plot(lesm,pch=".")
lesm.lm=lm(FTSE~DAX+SMI+CAC,data=lesm)
summary(lesm.lm)
Quando passiamo al modello logaritmico abbiamo le previsioni dei logaritmi dei valori. Per passare
a qualcosa di confrontabile dobbiamo prendere l’esponenziale di questi valori.
sqrt(mean((esm_train.lm.p - esm_test$FTSE)^2))
sqrt(mean((exp(lesm_train.lm.p) - esm_test$FTSE)^2))
gmin=min(esm_train.lm.p,exp(lesm_train.lm.p),esm_test$FTSE)
gmax=max(esm_train.lm.p,exp(lesm_train.lm.p),esm_test$FTSE)
plot(esm_test$FTSE,pch=20,ylim=c(gmin,gmax))
points(esm_train.lm.p,col="blue",pch=20)
points(exp(lesm_train.lm.p),col="red",pch=20)
legend("topleft",inset=0.02,c("dati","modello lineare",
"modello logaritmico"),col=c("black","blue","red"),
pch=c(19,19),bg="gray",cex=.8)
Entrambe predicono bene, dobbiamo essere sicuri che il risultato del modello logaritmico sia
consistente e non accidentale. Per farlo ripetiamo l’operazione più volte.
Dobbiamo prendere a caso dei valori ma poi anche ripetere prendendone altri, inoltre dovremo
confrontare un modello lineare con un modello non lineare. Prendendo i logaritmi abbiamo
cambiato le scale dei problemi, riduce drasticamente i valori e quindi l’errore diventa
apparentemente minore, ma in realtà confrontiamo valori non omogenei. Il confronto con modelli
non lineari va fatto con grandezze dello stesso tipo. Estraendo un solo set di dati abbiamo una
variabilità nella stima, facendo delle scelte una sola volta introduciamo una distorsione. Il caso
stesso può darci una previsione non rappresentativa dei dati e quindi dobbiamo fare più volte
questa operazione.
n=10
err_lin = rep(0,n) //per i risultati degli errori
err_log = rep(0,n)
for(i in 1:n){
testset <- sort(sample(1860,200))
esm_train <- esm[-testset,]
esm_test <- esm[testset,]
lesm_train <- lesm[-testset,]
lesm_test <- lesm[testset,]
esm_train.lm <- lm(FTSE~.,data=esm_train)
lesm_train.lm <-lm(FTSE~.,data=lesm_train)
esm_train.lm.p = predict(esm_train.lm,esm_test)
lesm_train.lm.p = predict(lesm_train.lm,lesm_test)
err_lin[i]=sqrt(mean((esm_train.lm.p - esm_test$FTSE)^2))
err_log[i]=sqrt(mean((exp(lesm_train.lm.p) - esm_test$FTSE)^2))
}
mean(err_lin)
mean(err_log)
sd(err_lin)
sd(err_log)
gmin=min(err_lin,err_log)
gmax=max(err_lin,err_log)
plot(err_lin,type="b",pch=20,col="blue",ylim=c(gmin,gmax))
points(err_log,type="b",pch=20,col="red")
L’errore medio sul modello lineare diventa 123, mentre su quello logaritmico 109. Le deviazioni
standard sono abbastanza ampie per cui gli intervalli poi si sovrappongono.
Abbiamo un errore più piccolo nella tabella logaritmica. E’ buono che gli errori diventano sempre
più piccoli.
Abbiamo una diversa deviazione standard perché l’errore ha una certa variabilità. L’informazione
data da mean e sd ne dedurremmo che non c’è ragione di ritenere che un errore sulla tabella
logaritmica sia minore dell’errore nella tabella lineare, il grafico ci dice che non è così. Se le medie
non sono troppo differenti e le deviazioni standard sono sufficientemente ampie in modo che le
campane gaussiane si sovrappongono, non c’è ragione di pensare che le popolazioni siano
differenti.
Parametri importanti sono i dati estratti per la prova e il numero di volte che la facciamo. Si usa
circa ¼ per una singola prova per i dati estratti per la prova, se invece la prova è ripetuta si
consiglia circa 1/10. Aumentando il numero di prove diventa più sostanziale l’ipotesi che il modello
logaritmico sia migliore.
Per correttezza va notato che, poiché abbiamo a che fare con serie storiche, l’autovalutazione nel
modo in cui l’abbiamo fatta non è corretta dal punto di vista del significato. In effetti non è
corretto utilizzare il futuro per prevedere il passato. Discuteremo, nella parte dedicata alle serie
storiche, come effettuare in maniera corretta l’autovalutazione per dati temporalmente ordinati.
head(longley)
summary(longley)
La regressione riporta una ottima varianza spiegata, ma coefficienti molto piccoli e p-value
anomali. Prevediamo gli impiegati o non gli impiegati o il GNP, sono i più ragionevoli.
ll1=lm(Employed~GNP.deflator+GNP+Unemployed+Armed.Forces+Population+Year,
data=longley)
summary(ll1)
plot(longley)
round(cor(longley),2)
corrplot(cor(longley),"number")
ll2=lm(Employed~GNP+Unemployed+Armed.Forces+Population+Year,data=longley)
summary(ll2)
La varianza spiegata non è diminuita, quella corretta è aumentata. Proviamo a togliere
anche Population
ll3=lm(Employed~GNP+Unemployed+Armed.Forces+Year,data=longley)
summary(ll3)
Otteniamo la stessa varianza spiegata, ma il p-value del GNP cala drasticamente, a livello da
poterlo considerare utile per il modello, 0.03. A parte GNP abbiamo ora tutti i p-value
significativamente bassi. GNP è diventato importante, sicuramente il GNP-deflator aveva una
dipendenza da GNP.
Proviamo a togliere anche GNP:
ll4=lm(Employed~Unemployed+Armed.Forces+Year,data=longley)
summary(ll4)
La varianza spiegata è rimasta simile, ma il p-value di Armed Forces è aumentato di un fattore 10.
La popolazione cresce in modo molto influente con l’anno, abbiamo però mantenuto l’anno.
Population e Year sono fortemente correlate, la correlazione era di 0.99. Cosa succede se
sostituiamo Year con Population?
ll5=lm(Employed~Unemployed+Armed.Forces+Population,data=longley)
summary(ll5)
La varianza spiegata diminuisce e i valori dei p-value sono stranamente cambiati. Il p-value di forze
armate è salito drasticamente, Population e Armed Forces sembrano avere una forte correlazione.
Ecco perché è stato mantenuto Year, quella parte di informazione di Population era portato anche
da Armed.Forces.
Esempio di regressione multivariata non-lineare
Consideriamo il dataset trees, che contiene dati su circonferenza, altezza e volume di alberi di
ciliegio
head(trees)
summary(trees)
plot(trees)
round(cor(trees),2)
trees.lm=lm(Volume~Girth+Height,data=trees)
summary(trees.lm)
plot(fitted(trees.lm),resid(trees.lm))
hist(resid(trees.lm))
Tra volume e Girth c’è una correlazione significativa, meno tra Height e gli altri parametri.
L’ordine di grandezza dei dati potrebbe essere significativo, height ha un ordine più grande ma non
tale da destare preoccupazioni. Possiamo non standardizzare la tabella.
Dei tre dati il meno misurabile è il volume, la circonferenza media e l’altezza la misuriamo ad
albero piantato mentre il volume no. E’ abbastanza naturale considerare il volume come fattore di
uscita, per misurarlo dovremmo tagliare l’albero.
Il modello è buono, abbiamo un p-value un po' meno basso sull’altezza ma non tale da pensare di
ridurre il modello.
Studiando i residui del modello contro i valori previsti per cogliere eventuali anomalie dei residui
rispetto ai valori, ci aspettiamo che a livello verticale, avendo media nulla, si addensano attorno al
valore medio e poi man mano si vanno a rarefarsi allontanandoci da esso.
Questa figura ci dice però che c’è una asimmetria, abbiamo zone fortemente vuote.
Si può utilizzare anche l’istogramma dei residui.
Notiamo che forse vi è un qualche tipo di caratteristica strutturale che non abbiamo colto.
O abbiamo troppi pochi dati per avere un’analisi completa, oppure questa anomalia è dovuta a
qualche tipo di fenomeno che non abbiamo colto.
Esaminiamo i residui per capire se vi è un qualche tipo di anomalia, vogliamo che assomiglino a
qualcosa di casuale.
Stiamo catturando volumi e lo facciamo con un modello lineare che guarda ad altezza e
circonferenza, ma il volume dipende dall’altezza e dall’area, non dalla circonferenza.
Ci aspettiamo una relazione del tipo V≈H⋅G^2, ovvero logV=logH+2logG+costante.
Per linearizzare il modello passiamo ai logaritmi:
ltrees= log(trees)
plot(ltrees)
round(cor(ltrees),2)
ltrees.lm=lm(Volume~Girth+Height,data=ltrees)
summary(ltrees.lm)
plot(fitted(ltrees.lm),resid(ltrees.lm))
La correlazione con Height è leggermente aumentata. Abbiamo p-value bassi, height è più
rilevanti, ora abbiamo una dipendenza lineare. Se vi è una dipendenza non lineare il modello non
ce lo dice a pieno.
Se guardiamo i residui, tenendo conto del numero basso di osservazioni, notiamo residui
abbastanza accettabili e solo alcuni di essi anomali. L’aspetto ora è più casuale.
Ciò da forza alla nostra ipotesi iniziale sulla struttura del problema.
Vettori gaussiani
Vediamo la rappresentazione di vettori gaussiani nel piano.
Generiamo una nube casuale di punti nel piano con distribuzione Gaussiana.
X=rnorm(5000)
Y=rnorm(5000)
plot(X,Y,pch=".")
Dati due vettori indipendenti, con stessa media e stessa varianza, non otteniamo vettori con scale
differenti su ascisse e ordinata.
Per apprezzare la differenza richiediamo che le unità di misura sui due assi siano le stesse.
plot(X,Y,pch=".",asp=1)
X=rnorm(5000,0,6)
Y=rnorm(5000,0,2)
plot(X,Y,pch=".")
X=rnorm(5000,10,6)
Y=rnorm(5000,-8,1)
plot(X,Y,pch=".",asp=1,xlim=c(-10,10),ylim=c(-10,10))
a=pi/6
U=matrix(c(cos(a),-sin(a),sin(a),cos(a)),2,2)
Ci aspettiamo delle dispersioni verso certe direzioni con gli assi verso cui si disperde con un certo
angolo.
library(rgl)
X=rnorm(5000)
Y=rnorm(5000)
Z=rnorm(5000)
open3d()
plot3d(X,Y,Z)
axes3d(c('x','y','z'),pos=c(0,0,0),labels=FALSE,tick=FALSE,col=2,lwd=3)
X=rnorm(5000,0,6)
Y=rnorm(5000,0,3)
Z=rnorm(5000,0,1)
open3d()
plot3d(X,Y,Z,xlim=c(-20,20),ylim=c(-20,20),zlim=c(-20,20))
axes3d(c('x','y','z'),pos=c(0,0,0),labels=FALSE,tick=FALSE,col=2,lwd=3)
Avremo assi con diversa varianza. Nascondendo l’asse z perdiamo 1/46 della varianza dei dati.
Ruotando la finestra cerchiamo il punto di vista migliore con cui guardare i dati.
Determiniamo la covarianza, ci aspetteremo che sia diagonale perché abbiamo generato
indipendentemente le colonne e con varianze opportune.
A=matrix(c(X,Y,Z),5000,3)
Q=cov(A)
Q
eigen(Q)
Abbiamo un numero vicino a 36, 9, 1 sulla diagonale e 0 altrove. I valori limite li raggiungeremmo
con infiniti valori.
Gli autovalori sono più o meno quelli che ci aspettavamo.
Degli autovettori ci aspettiamo che siano la base canonico (1,0,0), (0,1,0) e (0,0,1). All’incirca siamo
vicini. Se avessimo voluto generare questi numeri data la covarianza, avremmo dovuto caloclare la
radice quadrata di Q.
Diagonalizziamo la matrice Q.
U=eigen(Q)$vectors
U%*%t(U)
round(U%*%t(U),2)
Questa matrice è ortonormale.
D=diag(eigen(Q)$values)
D
U%*%D%*%t(U) - Q
sqQ=U%*%sqrt(D)%*%t(U)
round(sqQ%*%sqQ - Q,6)
La radice quadrata di una matrice diagonale si ottiene come indicato sopra. Se la moltiplichiamo
per sé stessa, ha la caratteristica di essere uguale a Q. L’operazione era corretta.
In questo modo possiamo generare dati con una data covarianza.
Una matrice di covarianza deve essere simmetrica e semidefinita positiva.
Generiamo una matrice simmetrica semidefinita positiva mediante numeri casuali.
A=matrix(rnorm(9),3,3)
A //non è simmetrica e semidefinita positive ancora
Q=A%*%t(A)
eigen(Q)$values
Se la moltiplichiamo per la sua trasposta otteniamo sempre una matrice simmetrica e semidefinita
positiva.
Avremo autovalori reali e positivi a conferma di avere una matrice semidefinita positiva.
Se ne prendiamo la radice quadrata non avremo la matrice A originale, come nei numeri la radice
quadrata non è unica. Quasi sicuro sarà differente.
Q ha inoltre autovalori tutti diversi da 0, otteniamo una matrice non singolare generando una
matrice a caso.
Noi vogliamo generare autovalori e autovettori a caso. La soluzione più semplice è prendere una
matrice a caso e farne il quadrato (analogia numero positivo a caso).
X=rnorm(50)
Y=rnorm(50)
A=matrix(nrow=50,ncol=5)
A[,1]=X
A[,2]=X+0.1*Y
A[,3]=(X+Y)/2
A[,4]=Y+0.1*X
A[,5]=Y
round(cor(A),2)
plot(data.frame(A))
Le correlazioni sono nette. Vediamo se la PCA scopre questo, lo farà specialmente perché le
dipende sono lineari. Abbiamo un comando apposito che ci permette di evitare la
diagonalizzazione.
pca=princomp(A)
summary(pca)
plot(cumsum(pa.pca$sdev^2)/sum(pa.pca$sdev^2),type="b",ylim=c(0,1))
Un grafico importante è quello del piano principale nel quale vediamo le osservazioni ma anche i
fattori originali.
biplot(pca)
In ascissa e ordinate abbiamo le due componenti, vediamo le osservazioni nel nuovo sistema di
riferimento. E’ importante vedere come i vecchi fattori (Var1, .. Var5) si allineanno alle nuove
componenti.
Il terzo era la media fra i due, il secondo e il quarto erano date da un po' e un po'.
Le componenti principali non sono x e y, perché le componenti principali vanno a massimizzare la
varianza di ogni componente, con il vincolo di ortogonalità. X e y sono ortogonali ma non
massimizzano la varianza. La prima componente principale è tutto il terzo fattore (Var3) e un po'
degli altri fattori, mentre nella seconda componente principale il terzo fattore non c’è (allineato
completamente all’ascissa), contribuiscono il primo, secondo, quarto e quinto fattore.
Il primo componente è riassuntivo, tutti i fattori originali hanno una proiezione non banale sulla
componente. La prima componente cattura i comportamenti sia di x che y, mentre la seconda ci
permette di distinguere x da y, non contiene il terzo fattore (solo minima parte trascurabile), ma
contiene una parte sostanziale degli altri. Il primo e il secondo sono allineati in un verso mentre
quarto e quinto in un altro verso.
La seconda componente ci permette di distinguere x da y, ciò che era X si allinea nel verso positivo,
ciò che era Y nel verso negativo (al di sotto delle ascisse).
Finchè questa analisi non è supportata dall’analisi della matrice dei loadings non è sufficiente,
avremo conferma con quest’ultima.
loadings(pca)
Gli spazi vuoti indicano valore abbastanza basso da poter essere ignorato.
Nelle colonne abbiamo le componenti principali, mentre sulle righe quelli originali.
I coefficienti della prima componente sono simili tra loro, ecco perché si può definire riassuntiva.
Nella seconda i primi due hanno coefficiente positivo, gli altri due negativo. Il terzo fattore non
gioca nessun ruolo.
Le varianze spiegate di 3,4,5 sono basse quindi è inutile guardarle.
Possiamo vedere il grafico tra prima e terza componente, tutta la variabilità è sulla prima
componente e quindi vediamo tutto schiacciato sulla prima componente.
biplot(pca,choices=c(1,3))
Come nella regressione lineare, le differenze di scala fra i dati possono falsare il risultato. Stiamo
ripartendo le varianze così da trovare le direzioni che le catturino al massimo rimanendo
ortogonali.
Se un fattore ha una scala maggiore, contiene più varianza possibile e la prima componente
principale si allineerà a quel fattore e non perché è la più significativa ma perché ha più varianza
dal punto di vista quantitativo. La variabilità è amplificata dai valori.
Tipicamente una tabella per la PCA va innanzitutto standardizzata.
Talvolta il risultato può essere nettamente diverso infatti.
Consideriamo il dataset mtcars sulle caratteristiche di alcune auto (dataset standard di R). Prima di
procedere con l’analisi, rimuoviamo due variabili categoriche).
mt=mtcars[,-(8:9)]
head(mt)
str(mt)
plot(mt, pch=16)
biplot(princomp(mt),cex=0.3)
Ci aspetteremo di vedere 9 frecce diverse, le frecce sono proprio proiezioni. Abbiamo due frecce
molto estese che corrispondo a fattori con un’elevata influenza, due fattori hanno una proiezione
ultra-significativa sul problema, segno del fatto che vi è un problema di scala. La variabilità è
catturata in modo enorme da alcuni fattori. Le frecce significative sono displacement e
horsepower, quelli di ordine di grandezza più grande delle altre.
Facciamo un’analisi sulla tabella standardizzata.
mt.pca=princomp(scale(mt))
summary(mt.pca)
Stavolta servono più componenti per avere una proporzione cumulativa elevata.
biplot(mt.pca,cex=0.3)
screeplot(mt.pca)
plot(cumsum(mt.pca$sdev^2)/sum(mt.pca$sdev^2),type="b",ylim=c(0,1))
segments(1,0.8,9,0.8,col="red")
Vedendo le correlazioni possiamo escludere che i fattori siano completamente scorrelati tra di
loro, anche se alcuni hanno una correlazione bassa, nessuno dei due è di quelli più ampi nel biplot.
Escludendo la scorrelazione, potrebbe esserci un problema di scala. Guardando il summary della
tabella notiamo alcune grandezze con una scala differente. Per ovviare al problema possiamo
standardizzare la tabella.
Standardizzando facciamo in modo che ogni colonna abbia varianza 1, ma comunque avremo che
sarà possibile vedere se i fattori siano dispersi o meno, non comprimiamo la variabilità.
Il biplot sarà completamente differente.
E’ quasi sempre opportuno standardizzare nel caso in cui si faccia una PCA.
In alcuni casi potremmo anche standardizzare senza che cambi granché.
E’ possibile vedere quanta informazione è contenuta nelle componenti principali con la varianza
spiegata.
screeplot(mt.pca)
Le prime due componenti sovrastano le altre e quindi è possibile pensare a una riduzione.
La prima componente ha una varianza del 62%, la seconda del 23% e così via (usando summary).
Un grafico più significativo è quello della varianza cumulata.
Qui non possiamo individuare le componenti come riassuntive.
Proviamo ad associare i fattori alle nuove variabili. Dal grafico si potrebbe ipotizzare:
1ma comp.: mpg, cyl, disp, wt
2da comp.: qsec, gear, carb (possiamo decidere di associarli alal seconda)
incerti: hp, drat (dovremo decidere come associarli)
Approfondiamo l’associazione per mezzo dei loadings.
mt.ld=loadings(mt.pca)
mt.ld
Troviamo per ogni componente le proiezioni degli assi. Data una colonna la somma dei loro
quadrati si normalizza a 1, sono sia positivi che negativi ma a livello di associazione il segno non è
significativo ma è relativo al verso rispetto alla componente. Ritornerà rilevante al momento
dell’interpretazione.
Gli spazi vuoti indicano che il valore è così basso da essere vicino a 0.
Più il coefficiente è alto in valore assoluto e più sarà significativo per la componente. Essendo tutto
standardizzato il valore dei coefficienti è significativo.
L’idea non è associare il fattore alla componente con coefficiente più alto possibile, misurano il
peso di come i fattori entrano nella componente principale, ma ben sì il peso che i fattori principali
hanno sulla componente; il che equivale a guardare per colonne e non per righe.
Noi siamo interessati inoltre alle prime componenti, quindi andiamo in ordine.
Integriamo con il contributo delle rotazioni.
1ma comp.: mpg, cyl, disp, wt
2da comp.: qsec, gear, carb
Incerti: hp, drat
Abbiamo una ambiguità relativa a hp e drat, che sono più intermedi tra la prima e la seconda
componente.
Potremo provare a interpretare anche la terza componente ma in questo caso non è la scelta
migliore perché dal grafico abbiamo visto che la terza è poco rilevante. Sarebbe rilevante se
paragonabile alla seconda.
In questo caso conviene provare a fare ulteriori rotazioni di alcune delle componenti principali,
così da massimizzare le varianze dei coefficienti, che vengono spinti quindi a 0 o 1.
La matrice dei loadings diventerà sparsa, con valori vicini a 1 o a 0.
Queste rotazioni perdono alcune delle proprietà delle PCA, la nostra rotazione mantiene
l’ortogonalità ma si perde il fatto che la varianza totale è massima sulla prima componente, di
meno sulla seconda e così via. Non abbiamo più questo ordine.
Proviamo queste rotazioni con l’algoritmo varimax e ruotando sulle prime due componenti:
varimax(mt.ld[,1:2])
Notiamo l’aumento dei numeri di valori a 0.
Avevamo come incerti hp e drat.
1ma comp.: mpg, cyl, disp, drat, wt
2da comp.: hp, qsec, gear, carb
Vediamo che la rotazione suggerisce che hp sia più sulla seconda componente mentre drat più
sulla prima.
Rotazioni successive in questo caso non aiutano.
varimax(mt.ld[,1:3])
L’angolo in alto a sinistra prevede macchine più sportive e con più dotazioni.
C’è un forte assembramento in basso a sinistra, sono auto paragonabili dal nostro punto di vista.
Dobbiamo capire se sono simili perché sono simili o se lo sono sono al livello del piano principale e
altre componenti potrebbero aiutarci a distinguerle.
Per risolvere gli assembramenti guardiamo le componenti successive.
Per vedere i piani successivi:
biplot(mt.pca,col=c("gray","red"), choices=c(1,3))
plot(mt,col=orig)
plot(mt[,c(2,4)],col=orig) and so on
Il punto di vista per le quali facciamo le rotazioni, se togliendo dati, cambia in modo significativo
vuol dire che quel dato cambiava significativamente il punto di vista, altrimenti il dato era
omogeneo agli altri.
Stiamo trovando i dati che in maniera più incisiva intervengono sull’analisi.
Prendo una tabella sulla singola osservazione, ne faccio l’analisi delle componenti principali,
calcolo le coordinate del punto sottratto nel nuovo sistema di riferimento; dove sarebbe se ruoto
col nuovo punto di vista, poi vedo le discrepanze tra la posizione che avrebbe dovuto vedere la
osservazione sul nuovo sistema di riferimento e quella che effettivamente ha.
Se le osservazioni incidono in modo sostanziale sono atipiche rispetto al resto.
Possiamo supporre che abbia hp al limite e altri valori bassi e che questo sia il motivo.
Le altre osservazioni hanno un grado di stabilità, tutte tranne 1.
Le osservazioni che abbiamo ottenuto avremmo potute ottenerle anche senza quell’osservazione.
Con o senza il punto di vista migliore sarebbe stato lo stesso.
Questa non è una validazione del modello, ma se gran parte delle osservazioni risultano anomale
c’è qualcosa che non va, abbiamo un’instabilità di fondo.
Sembrerebbe fattibile.
Rappresentiamo il piano principale.
biplot(pa.pca)
Più componenti abbiamo e più varianza catturiamo ma più sarà difficile esaminare il problema.
Ylim fa sì che il grafico presenti le ordinate tra 0 e 1.
Se non lo usassi, la finestra grafica andrà da circa 0.5, sembrando quasi varianza nulla per la prima
componente, è meglio avere una scala chiara.
Per arrivare all’80% abbiamo bisogno di tre componenti.
Lo vediamo infatti anche dal summary:
Questo è il caso in cui tutte le componenti si allineano più o meno rispetto a una componente, la
prima componente può essere vista come riassuntiva.
Possiamo pensare alla prima componente come riassuntiva e guardare a quella verticale per
distinguere il tipo di coltura. Possiamo associare alla prima componente la produttività, la seconda
permette di distinguere coltivazioni che hanno necessità di climi più caldi/freddi.
Verifichiamo l’interpretazione con le informazioni quantitative. In effetti tutti i fattori
contribuiscono in diverse misure alle componenti principali.
Vediamo la matrice dei loading:
pa_s.ld=loadings(pa_s.pca)
pa_s.ld
Ancora una volta abbiamo regioni che a livello di produzione non sono significative.
Avendo in mente il significato delle prime componenti principali, si può usare l’analisi delle
componenti principali in presenza di nuovi dati per capire che produttività e tipo di coltivazioni
hanno i nuovi individui. Per esempio:
pa_s=data.frame(scale(pa))
pa_sm=pa_s[-4,]
pa_sm.pca=princomp(pa_sm)
predict(pa_sm.pca,newdata=pa_s[4,])[c(1,2)]
predict(pa_s.pca)[4,c(1,2)]
Come possiamo aspettarci la valle d’Aosta ha una scarsa condizione, pensando infatti al territorio è
plausibile. Le nostre considerazioni erano sussistenti.
L’interpretazione delle componenti va fatta con i fattori e non con le osservazioni.
Per scoprire che in alcune regioni non vi siano produttività rilevanti abbiamo dovuto usare tutte e
3 le componenti, non partire da osservazioni a priori.
Una serie di assembramenti, nell’ottica dei dati che abbiamo, possono essere gruppi di
osservazioni simili oppure può avvenire uno schiacciamento.
Ci siamo chiesti se la terza componente principale poteva risolvere questi assembramenti.
Interpretando la terza componente può risolversi l’assembramento, possiamo considerarla tale da
distinguere le scelte in base al tipo di territorio.
La terza componente porta una dote di varianza significativa per il problema e inoltre permette di
capire meglio le osservazioni.
Adesso andremo a considerare un dataset in cui valutiamo gruppi di individui.
library(datasets)
head(iris)
Per ora dimentichiamo la variabile categoriale. La salviamo però in un vettore per uso successivo.
ir=iris[,1:4]
species=factor(t(iris[5]))
C’è una specie che si distingue nettamente dalle altre, eccetto per esempio ne grafico
Sepal.Length/Sepal.Width.
Con l’analisi delle componenti principali notiamo che già la prima componente ci basterebbe a
capire il problema.
ir.pca=princomp(ir)
summary(ir.pca)
plot(ir.pca)
plot(cumsum(ir.pca$sdev^2)/sum(ir.pca$sdev^2),type="b",ylim=c(0,1))
Possiamo pensare che la prima componente cattura tutte le dimensioni del fiore tranne il sepalo,
che invece associamo alla seconda componente.
Interpretazione delle componenti: la prima componente sembra venire dal contributo
(approssimativamente) simile di tre dei quattro fattori, con il ruolo di separare il quarto fattore
dagli altri. La seconda componente principale ha tutti i valori negativi e si potrebbe interpretare
come una misura della dimensione (principalmente dei sepali).
Notiamo un gruppo in verticale completamente allineata alla seconda componente principale,
sembrerebbe che la prima componente ci permette di distinguere in modo netto, questi dati
coprono un range di valori completamente significativo della prima componente principale.
Speriamo che la seconda componente principale ci permetta di distinguere le altre due.
La prima componente può essere considerata come riassuntiva.
biplot(ir_s.pca)
loadings(ir_s.pca)
varimax(loadings(ir_s.pca)[,1:2])
Attraverso la prima e la seconda componente distinguiamo il gruppo a sinistra, ma non bene gli
altri due. Tirando una linea gran parte saranno a destra e a sinistra separatamente ma non tutte.
La prima componente principale permette di distinguere il meglio possibile le due componenti,
mentre la seconda, che era associata alla larghezza del sepalo, non ci aiuta.
Quelle a sinistra tendono ad avere un valore più positivo, un altro gruppo più negativo ma non ci
permette di distinguerle.
Anche con la terza componente non abbiamo molti vantaggi, vediamo una separazione tra rosse e
verdi ma non così netta come lo avevamo usando la seconda componente.
Consideriamo i dati contenuti in un dataset relativo a dati socio-economici di alcuni paesi europei.
eu<-read.csv("10euecosoc.csv",header=TRUE,row.names=1)
Notiamo che Accidents non è cosi associabile alla seconda invece che alla prima.
La seconda conterrò Export, TaxesLabour ed Employment.
Nella prima sicuramente andranno Education, HealthyYears e Industrial Production.
La terza contiene principalmente Health e Accident, con valori di segno opposto.
Ci fermiamo alla terza componente, nonostante la quinta componente ha un fattore con -0.72,
quest’ultima ha una varianza molto bassa.
Va trovato un bilancio tra interpretazione e cattura di varianza sufficientemente alta.
5 componenti sono la metà del totale e sono difficili da gestire, ci spostiamo verso
l’interpretazione perdendo varianza arrivando a conclusioni seppur deboli pur di arrivare a
conclusioni.
Effettuiamo delle rotazioni su un set di componenti principali mantenendo le componenti
ortogonali e massimizzando le varianze.
varimax(eu.ld[,1:2])
Così da capire meglio quali fattori tendono ad allinearsi meglio e a quale componenti.
Ruotiamo le tre componenti e vediamo che questa rotazione fa sì che Health e HealthyYears e
IndustrialProduction tendono a essere catturati dalla prima componente, come avevamo
evidenziato.
Inoltre, per le altre due componenti abbiamo le stesse considerazioni di prima.
varimax(eu.ld[,1:4])
Prendiamo un vettore che contiene vero o falso a ogni elemento a seconda se il valore è positivo o
negativo, e confrontiamolo con ciò che conosco.
Il test risulterà a sua volta un vettore in cui avremo true se la condizione è vera.
True sarà codificato come 1 e 0 come false e quindi se sommo i risultati otterrò la risposta.
Il risultato può essere interpretato dividendo per il totale.
Potremo però avere una certa disparità tra i valori 0 e 1, nell’ottica delle banconote false sbagliare
sulle buone può essere meglio che sbagliare nel caso di banconote fasulle che vengono
riconosciute come buone.
Può risultare interessante scoprire quali errori sono stati compiuti. Visualizziamo gli errori in
una matrice di confusione.
source("s2_cmroc.r")
response=(bn$class>0)
predictor=(predict(bn.lm)>0)
s2_pconfusion(response,predictor)
Abbiamo anche la curva ROC. Possiamo caricare il file r o un pacchetto esterno per ottenere la
matrice.
Quelle fasulle sono trovate tutte, mentre si sbaglia un po' su quelle non fasulle.
Abbiamo una grande accuratezza e sbagliamo anche nel senso giusto.
Dobbiamo vedere se il risultato ottenuto è stabile o abbiamo catturato le particolarità del rumore
nei dati, e siamo andati quindi in overfitting. Vediamo se il modello sa predire dati che non
conosce. Proviamo a verificare la capacità di predizione.
idx=sample(1372,50)
bncv=bn[-idx,]
bncv.lm=lm(class~.,data=bncv)
predictor=(predict(bncv.lm,bn[idx,])>0)
response=(bn$class[idx]>0)
sum(predictor==response)/50
}
mean(acc)
sd(acc)
hist(acc,10)
L’accuratezza viene calcolata solo sui valori di test, dividendo per il numero di dati nel test.
L’accuratezza media e quanto questa varia sono soddisfacenti, ma leggermente meno performanti
da quelle trovate prima.
Notiamo dall’istogramma che talvolta è scesa a valori più bassi.
Proviamo a inserire delle informazioni sbagliate, andando a studiare quanto è robusto il modello,
introducendo informazioni false per studiare l’andamento dell’accuratezza.
L’accuratezza non la calcolerà sui valori sbagliati, vogliamo capire se pur variando delle
informazioni continua a indovinare quelli giusti.
A livello di addestramento del modello fornirò dati errati per capire quanto bene continuerà a
predire.
Ci aspettiamo che cambiando pochi valori il modello continua a essere buono, mentre
cambiandone una gran parte ne sbaglierà molti.
idx=sample(1372,1372)
acc=rep(0,1372)
for(i in 1:1372){
bnf=bn
bnf$class[idx[1:i]]=-bnf$class[idx[1:i]]
bnf.lm=lm(class~.,data=bnf)
acc[i]=sum((predict(bnf.lm)>0)==(bn$class>0))/length(bn$class)
}
plot(acc,type="l")
Prendiamo una permutazione dei valori e cambiamo la classe dei primi i dati, con i variabile da 1 a
1372. Per cambiare i dati in questo caso basta infatti cambiare segno.
Nel valutare le predizioni usiamo i valori originali.
Abbiamo una curva molto netta, il modello continuerà a funzionare molto bene fino a quando
cambiamo molti valori. All’inizio avendo comunque molte osservazioni sbagliate, l’accuratezza
rimane molto ampia. Quando superiamo ½, abbiamo un enorme valore di dati sbagliati e il
modello tenderà di prevedere dall’altra parte perché continua a fare bene. Avviene tra 600 e 800
quindi circa a metà. Ci aspettavamo una curva molto lineare con l’andamento e invece no, è molto
verticale, ciò significa che i marcatori usati sono molto adatti.
Regressione logistica
Consideriamo la regressione logistica sulla stessa tabella. Riprendiamo la tabella originaria con zeri
e uni.
bn=read.csv("11banknote.csv",header=T)
bn.glm=glm(class~.,data=bn,family=binomial)
La regressione logistica ricade nei modelli lineari generalizzati e quindi abbiamo il comando glm.
Poiché generalizzato va specificata la famiglia, noi usiamo Bernoulli che appartiene alla famiglia dei
binomiali. Il fattore di uscita lo modelliamo come una Bernoulli.
Il messaggio di avvertimento indica che questa tabella funziona troppa bene, alcune assegnazioni
erano così nette da avere probabilità nettamente 0 o 1.
Valutiamo la diagnostica: accuratezza e matrice di confusione.
bn.glm.p=predict(bn.glm,type="response")
sum((bn.glm.p>0.5)==(bn$class>0.5))/length(bn$class)
s2_confusion(bn$class,bn.glm.p)
Abbiamo discriminante ½, assegniamo ala classe 0 o 1 a seconda che la probabilità sia minore o
maggiore di 0.5. Usiamo response per farci dare le probabilità di essere nell’una o nell’altra classe.
L’accuratezza è addirittura migliorata con la regressione logistica.
Sbagliamo molto meno, ma sbagliamo dove non vorremmo sbagliare; sono predette buone ma
non lo erano.
Non è detto che in generale la soglia 1/2 sia la migliore.
s2_confusion(bn$class,bn.glm.p,0.45)
s2_confusion(bn$class,bn.glm.p,0.55)
s2_confusion(bn$class,bn.glm.p,0.2)
Con le due soglie specificate continuiamo a sbagliare quelli che non volevamo e abbiamo aggiunto
errori anche dall’altra parte.
Fissandola a 0.2 gli errori che non volevamo sono diminuiti, ma a costo di un errore totale più
ampio.
s2_roc.plot(s2_roc(bn$class,bn.glm.p))
Quella diagonale è la curva di riferimento del tirare a caso.
Abbiamo una curva quasi ottimale, che avrebbe una discesa a 90° in (1,0).
La curva ci permette di valutare i due errori che possiamo fare, abbiamo detto 0 quando era 1 o
viceversa. Il metodo può sbagliare in modo differente.
Notiamo una piccolissima parte di errori nell’angolo in alto a sinistra.
Questa curva, al variare della soglia, ci dà un’idea dell’errore.
L’output della regressione logistica è di tipo probabilistico, ci dà una probabilità e calcolandone il
complemento rispetto a 1 abbiamo la probabilità dell’altra classe.
Esaminando la curva capiamo la bontà del metodo.
L’area sotto la curva ci dà un’idea di massima della bontà del modello.
Aggiungiamo l’informazione della classe, decisa a-priori tra paesi nord-occidentali e paesi sud-
orientali. Vedremo che l’idea a priori della posizione geografica sarà sbagliata.
a=c(0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,1,1,1,1,1)
eu$classe<-a
L’algoritmo di ricerca dei parametri non ha trovato la soluzione con la tolleranza opportuna.
Dobbiamo scegliere un modello più economico. Possiamo utilizzare almeno tre metodi differenti:
scegliere i termini più correlati alla classe (esaminando la matrice di correlazione), che è
categorico ma abbiamo solo due classi e quindi possiamo pensare a un valore numerico
operare una riduzione del modello mediante metodi di regressione multivariata, scartando
i fattori che sembrano essere meno significativi per il problema
utilizzare l’analisi delle componenti principali.
La differenza tra ridurre con PCA rispetto al modello di regressione lineare, è che la regressione
lineare è supervisionata e la riduzione la facciamo in accordo al fattore di uscita mentre la PCA è
neutra rispetto al fattore di uscita. Potremmo anche includere il fattore di uscita nella PCA e
vedere i fattori più ortogonali ad esso. Noi vogliamo buttar via più fattori possibili ma allo stesso
tempo vogliamo trovare un modello di previsione.
Nel fare una riduzione dimensionale per il modello di regressione lineare, dobbiamo capire quali
fattori hanno un p-value alto, non abbiamo elementi per rigettare l’ipotesi che il coefficiente sia 0
ma il p-value alto può anche prevenire da problemi di collinearità.
Se scartiamo un termine non essenziale può essere meno d’impatto di un termine allineato. La
regressione lineare che facciamo dà risposta lineare mentre con la regressione logistica guardiamo
il problema in modo diverso.
La funzione logistica è abbastanza piatta in 0 e 1 e ciò vuol dire che in queste zone i valori sono
indistinguibili, non vede grandi differenze tra 10 e 10^6, i criteri che usavamo in precedenza
potrebbero non essere altrettanto significativi.
A livello di test certe differenze di ordini di grandezze sono più significative nella regressione
lineare che nella regressione logistica.
Riducendo il modello, ai fini della regressione logistica, potrebbe non aiutare; usiamo un punto di
vista di un modello lineare.
Quando facciamo una regressione logistica il problema diventa non lineare per via della forma
della funzione logistica.
Ciò significa che i valori interessanti della funzione logistica, dove questa varia di più, sono quelli
vicino allo 0. Quando la si calcola in valori più grandi, la funzione logistica non varia più.
Tra 5 e 500 i valori sono praticamente gli stessi, mentre gli strumenti prima elencati sono sensibili
a questa differenza; sia il risultato della correlazione, della regressione lineare e della PCA.
Possiamo usare queste strategie ma ciò che otteniamo potrebbe non essere efficace in termini di
regressione logistica tanto quanto lo possa essere nella PCA o nella regressione lineare, per via del
fatto che questa funzione schiaccia le differenze.
Continuiamo adesso usando la riduzione attraverso la correlazione.
eu.glm=glm(classe~Employment+Export,family=binomial,data=eu)
Analisi discriminante
Consideriamo il dataset relativo ai passeggeri del Titanic.
tit=read.csv("12titanic.csv",header=T,row.names=1)
Abbiamo fatto meglio del tirare a caso (diagonale) ma il risultato non è così soddisfacente come
con le banconote.
Useremo la curva per confrontare metodi differenti, preferendo quelli con curva più vicina a quella
perfetta. Avremo una specificità e una sensibilità ottimali.
Un’indicazione numerica ci viene data dall’AUC, l’area sotto la curva, quella delimitata con l’asse
delle ascisse (migliore se più vicina a 1, peggiore se più vicina a 0).
s2_auc(tit.lda.roc)
I risultati sono paragonabili. Fa un po' meglio in termini dei non sopravvissuti ma ne sbaglio alcuni in
più in termini di sopravvissuti.
Confrontiamo le due curve ROC.
tit.qda.roc=s2_roc(tit$survived,tit.qda.post)
s2_roc.plot(tit.qda.roc,col="blue")
s2_roc.lines(tit.lda.roc,col="green3")
Le curve sono paragonabili, ma l’analisi discriminate quadratica fa leggermente meglio in una zona
e l’altra in un’altra.
s2_auc(tit.qda.roc)
Se facciamo troppe iterazioni potremmo avere troppi test simili tra di loro.
Il numero di campioni di test è binominale di 1300 su 100 e quindi la possibilità di estrarre due
campioni di test simili tra loro è bassissima.
Si può prendere anche una soglia diversa da ½ per migliorare la previsione e raggiungere
un’accuratezza migliore. Poi dipende anche se vogliamo catturare al più possibile le banconote
non buone nell’esempio visto precedente, se catturare quelle negative sia più importante che
catturare quelle positive.
A livello di calibrazione qui potremmo vedere quel valore di soglia, nella curva ROC, che fornisce
l’accuratezza migliore.
A livello di soglia, nello scegliere la soglia migliore, ….. 10:31
Variamo quindi la soglia così da capire quella più adatta.
Otteniamo circa la stessa accuratezza e ciò è analogo agli alberi di decisione, in cui guardiamo il
valore dei fattori.
Nel nostro caso abbiamo usato un solo fattore, ma possiamo usare più fattori per fare scelte
dicotomiche.
Classificazione multi-classe
Svolgiamo una analisi discriminante lineare sul dataset iris.
library(MASS)
data(iris)
summary(iris)
iris.lda=lda(Species~.,data=iris,prior=c(1/3,1/3,1/3))
plot(iris.lda,col=as.numeric(iris$Species)+1)
Non possiamo usare nel caso multi-classe la curva ROC, mentre possiamo adattare la matrice di
confusione.
La rappresentazione grafica è sul piano principale, una delle tre è separata dalle altre come
ricordiamo.
Una rappresentazione grafica più chiara.
iris.lda.values=predict(iris.lda)
plot(iris.lda.values$x,pch=20,col=as.numeric(iris$Species)+1)
Valutiamo l’accuratezza.
sum(iris$Species!=iris.lda.values$class)
sum(iris$Species!=iris.lda.values$class)/length(iris$Species)
Proviamo la capacità predittiva, scegliendo tre campioni a caso, uno per specie.
acc=rep(0,30)
l=nrow(iris)
for(i in 1:30){
idx=sample(l,15)
irtrain=iris[-idx,]
irtest=iris[idx,]
irtrain.lda=lda(Species~.,data=irtrain,prior=c(1/3,1/3,1/3))
irtest.pt=predict(irtrain.lda,newdata=irtest)$class
acc[i]=sum(irtest.pt==iris$Species[idx])/15
}
mean(acc)
sd(acc)
hist(acc)
Usiamo 4 fattori e tre classi e quindi il numero di fattori cresce del triplo, però può andare bene.
Estraiamo 15 dati dei 150 complessivi.
Possiamo pensare di prendere il 20% dei dati per vedere se rimane l’aspetto dell’istogramma.
Concludiamo che l’accuratezza che troviamo in ingresso sul metodo è confermata a livello di
autovalidazione.
La classe cui appartengono i campioni scelti e le probabilità di appartenenza alle classi di ogni
campione del test set, può essere ottenuta per ogni set di test:
irtest.pt$class
round(irtest.pt$posterior,2)
Aggiungiamo al grafico il colore della specie ai pallini, e ricolorando le stesse posizioni con un
simbolo diverso le previsioni.
Calibriamo tre regressioni logistiche ognuna delle quali ci dà la probabilità di appartenere ad una
classe invece che a tutte le altre. Scegliamo l’assegnazione alla classe con la probabilità più alta.
Pur avendo una classificazione binaria riusciamo a raggiungere una classificazione multi-classe.
Calcoliamo la risposta finale e l’accuratezza.
probs=cbind(set.p,ver.p,vir.p) //tabella con le 3 colonne delle probabilità
ottenute
l=length(iris$Species)
res=rep(0,l)
for(i in 1:l){
res[i]=which.max(probs[i,]) //indice corrispondente al valore massimo
}
res<-factor(res) //trasformo il risultato in fattori per fare il confronto
levels(res)<-c("setosa","versicolor","virginica")
sum(iris$Species!=res)
sum(iris$Species!=res)/l
Questa accuratezza è in fase di calibrazione quindi chiaramente più eevata. Per verificarlo
dovremmo fare un’autovalidazione.
Se il numero di classi aumenta, il metodo non diventa molto efficace.
Dovremmo addestrare un modello per ogni classe.
Metodi a prototipo
Il metodo k-means può non essere molto robusto nel caso di classi molto diseguali.
x=c(rnorm(3),rnorm(3,1,1),rnorm(20,10,1),rnorm(20,11,1),rnorm(50,100,1),rnorm(50
,101,1))
y=x+rnorm(146)
dati=matrix(c(x,y),146,2)
plot(dati,pch=20)
km=kmeans(dati,3)
plot(x,y,col=km$cluster,pch=20)
Abbiamo tre diversi gruppi di valori con stessa varianza ma media diversa.
In realtà sono 6 gruppi ma le deviazioni standard sono tanto piccole da non poterle notare.
Ci aspettiamo che un algoritmo di clustering catturi i 3 gruppi.
layout(t(1:2))
plot(pm)
layout(1)
Il grafico a destra è la silhouette, mentre quella a sinistra è la rappresentazione dei punti sul piano
principale. Quel messaggio sotto Component 1 si ha perché abbiamo già due dimensioni a priori.
Abbiamo i tre gruppi rappresentati, con un simbolo diverso per i punti a seconda del gruppo a cui
appartiene. La prima componente principale spiega quasi tutto.
Abbiamo tre cluster come si vede dalla silhouette, uno di 6, uno di 40, e uno di 100 elementi.
L’average silhouette width è la silhouette media globale dell’intera partizione, poi abbiamo 3
cluster con numerosità e silhouette media.
Le barre corrispondono alla silhouette di ogni singolo elemento, alcuni sono posizionati meglio nel
cluster rispetto agli altri.
Il terzo cluster sembra caratterizzato da silhouette migliore delle sue osservazioni ma ciò è dovuto
al fatto che è il cluster più lontano dagli altri.
Se provassimo con k = 6, esso partiziona in 6 gruppi ma non lo fa bene. E’ importante guardare le
partizioni prima di partire con una decisione su k, dal plot è evidente siano 4.
Il dataset iris è composta da tre gruppi di 50 individui ognuno di tre diverse specie di iris.
Esaminiamo il problema con il metodo k-means. Il numero ottimale di cluster dovrebbe essere
(presumibilmente) k=3. Per una rappresentazione grafica, usiamo il piano principale.
data("iris")
ir=scale(iris[,1:4])
plot(iris,lower.panel=NULL,pch=20,col=as.numeric(iris$Species)+1)
library(MASS) # contiene parcoord
parcoord(iris[1:4],col=as.numeric(iris$Species)+1)
Ci aspettiamo di trovare i cluster relativi alle tre specie.
Ai fini dell’interpretazione possiamo vedere come i diversi valori dei fattori si associano ai diversi
cluster. I dati sono standardizzati in modo che i minimi e i massimi coincidano. In ascissa abbiamo i
4 fattori e in ordinata i valori per ogni osservazione.
Ogni linea corrisponde a un’osservazione e seguendola scopriamo i suoi valori per ogni fattore.
Da questo grafico riusciamo a distinguere le caratteristiche delle specie, come è evidente dopo
averle colorate.
La specie rossa si caratterizza bene su ogni tipo di fattore, abbiamo valori di Sepal.Length più
piccoli, Sepal.Width più grandi e Petal.Length e Petal.Width molto diversi dagli altri.
Gli altri due si distinguono abbastanza bene ma non su Sepal.Length, vengono distinti bene sul
Petalo e non sul Sepalo. L’interpretazione di questo grafico va fatta in seguito al clustering.
Capiamo come si distinguono i gruppi in termini di fattori.
Esaminiamo il problema con il metodo k-means. Il numero ottimale di cluster dovrebbe essere
(presumibilmente) k=3. Per una rappresentazione grafica, usiamo il piano principale.
ir.km=kmeans(ir,3,nstart=10)
ir.pca=princomp(ir)
plot(ir.pca$scores,col=1+as.integer(ir.km$cluster),pch=20)
points(ir.pca$scores,col=1+as.integer(iris$Species),pch=1)
points(predict(ir.pca,ir.km$centers),col="black",pch=19)
text(ir.pca$scores,labels=as.character(iris$Species),col=1+as.integer(iris$Speci
es),pos=3,cex=0.5)
L’output di k-means non è disegnabile a differenza di quello di k-medoids, lo disegniamo allora noi.
Le specie vere sono cerchiate, i centri sono riempiti di nero e le osservazioni generiche sono invece
riempite del colore del cluster. I colori delle classi non corrispondono a quelli dei cluster, vanno
confrontati con i colori del testo.
Un metodo a punti prototipo non funziona bene in caso di intersezioni non indifferenti.
Conoscendo la risposta, cosa non sempre vera, possiamo individuare degli errori.
Determiniamo quale dovrebbe essere il valore ottimale di cluster, contando la somma delle mutue
distanze tra elementi di uno stesso cluster.
wss=rep(0,10)
for(k in 2:10){
wss[k]=kmeans(ir,k,nstart=10)$tot.withinss
}
plot(2:10,wss[2:10],type="b",pch=20)
Ci aspettiamo che all’aumento di clusters, il singolo cluster perderà punti, quindi questa distanza
tenderà a diminuire.
Questo grafico diminuisce sempre, guardare il minimo non ha senso. Dobbiamo individuare i
cambi significativi di pendenza. Un primo gomito è presente in 3, il secondo in 5. Conviene
esaminare cosa accade intorno a questi punti, nei valori da 2 a 6.
Usando wss abbiamo una prima indicazione che ci dice di esaminare i valori in questo range.
Possiamo fare qualcosa di analogo alla silhouette, guardando la silhouette media globale.
library(cluster)
plot(silhouette(ir.km$cluster,dist(ir)),col=heat.colors(3),border=par("fg"))
as=rep(0,10)
for(k in 2:10){
cl=kmeans(ir,k,nstart=10)$cluster
as[k]=mean(silhouette(cl,dist(ir))[,3]) //vuole la divisione in cluster, e il
tipo di distanza usata, in questo caso euclidea
}
plot(2:10,as[2:10],type="b",pch=20)
Un gomito nettissimo si ha in 3 e in 5 anche qui. Qui, dopo 5 non vi è più variazioni nella silhouette,
le successive variazioni possono essere considerate insignificanti. Tra 2 e 5 invece abbiamo
variazioni non trascurabili.
Considerazioni analoghe possono essere fatte con PAM. Esaminiamo il problema con il
metodo pam.
ir.pam=pam(ir,3)
layout(t(1:2))
plot(ir.pam)
layout(1)
c=rep(0,10)
for(i in 2:10){
c[i]=pam(ir,i)$silinfo$avg.width
}
plot(2:10,c[2:10],type="b",pch=19)
I due grafici sono diversi e ciò significa che le partizioni che vengono da PAM e k-means sono
differenti. Qui abbiamo un gomito in 3 e una seconda variazione in 5 anche qui. Conviene guardare
ancora una volta i valori da 2 a 5. Esaminiamo la silhouette anche per 2, per 3 e 4.
La partizione in due ha il valore di silhouette più ampio possibile e i loro valori sono quasi
paragonabili.
Per due classi viene divisa la terza specie, quella buona. Concludiamo, facendo finta di non sapere
il risultato, che due è un’ottima suddivisione (unire dei sottogruppi può essere conveniente se
sono vicini tra loro). Nel caso di 3 abbiamo una silhouette leggermente più basso e dei valori meno
omogenei per la silhouette media per cluster. I valori negativi in 3,4,5 sono meno presenti in 3 e
quindi è più preferibile.
Torniamo a 3 cluster e vediamo quali dati non sono classificati secondo la loro classe. Ricordiamo
che pam visualizza i cluster sulle prime due componenti principali.
ir.pca=princomp(ir)
plot(ir.pca$scores,col=1+ir.pam$clustering,pch=20)
points(ir.pca$scores,col=1+as.integer(iris$Species),pch=1)
points(predict(ir.pca,ir.pam$medoids),col="black",pch="m")
points(predict(ir.pca,ir.km$centers),col="black",pch="k")
text(ir.pca$scores,labels=as.character(iris$Species),col=1+as.integer(iris$Speci
es),pos=3,cex=0.5)
table(ir.pam$clustering,iris$Species)
Coloriamo secondo i risultati di k-means e PAM e poi rappresentiamo i centri di k-means e PAM. Il
testo rappresenta la specie corretta.
I centri sono abbastanza avvicinati per alcuni clusters ma non lo sono per altri.
Possiamo provare a cambiare distanza, lo si può fare con PAM. La distanza standard usata dal
comando pam è quella euclidea. Proviamo la distanza per mezzo dei valori assoluti (qui
chiamata manhattan). Esaminiamo prima di tutto la silhouette media.
c=rep(0,10)
for(i in 2:10){
c[i]=pam(ir,i,metric="manhattan")$silinfo$avg.width
}
plot(2:10,c[2:10],type="b",pch=19)
Il risultato sembra un pò peggiore rispetto alla distanza euclidea.
Il dataset USArrest contiene statistiche sui crimini nei 50 stati USA nel 1973, insieme al dato sulla
popolazione urbana.
str(USArrests)
head(USArrests)
us=data.frame(scale(USArrests))
plot(us,lower.panel=NULL,pch=20)
round(cor(us),2)
parcoord(us) # inutile...
Determiniamo il numero ottimale di clusters. Osserviamo l’andamento della somma totale dei
quadrati delle distanze di ogni cluster per il metodo k-means.
A seconda del numero di osservazioni possiamo limitare il numero massimo di cluster, non
dobbiamo finire al caso in cui abbiamo un’osservazione per cluster.
Abbiamo un gomito in presenza di 4 clusters. L’andamento della silhouette ci da una risposta
analoga. I valori 2,3,4,5 sembrano essere i valori più interessanti.
as=rep(0,10)
for(k in 2:10){
cl=kmeans(us,k,nstart=10)$cluster
as[k]=mean(silhouette(cl,dist(us))[,3])
}
plot(2:10,as[2:10],type="b",pch=20)
I due gruppi blu e verde per k = 4 hanno valori bassi mentre gli altri due hanno valori più ampii.
Notiamo che il clustering per k = 2 ha senso perché vengono messi insieme a due a due
correttamente.
Verdi e celesti hanno una popolazione urbana significativa ma un livello di criminalità basso.
Il gruppo rosso ha una bassa popolazione urbana, quello nero alta ma entrambi hanno un’alta
incidenza criminale.
Possiamo decidere se mantenere questa distinzione o andare su tre clusters, dove vengono riunite
le prime due, con media popolazione urbana ma alta incidenza criminale. Non ci fa distinguere
quelle con alta incidenza criminale.
Proviamo il metodo pam. Iniziamo dallo studio della silhouette media.
Non è detto che il range di valori da valutare per un metodo sia la stessa di un altro metodo.
c=rep(0,10)
for(i in 2:10){
c[i]=pam(us,i)$silinfo$avg.width
}
plot(2:10,c[2:10],type="b",pch=19)
Abbiamo risultati paragonabili, per k = 3 abbiamo due osservazioni con valori negativi.
Controlliamo se sia possibile anche in questo caso una interpretazione considerando k = 4.
k=4
us.pam=pam(us,k)
parcoord(us,col=as.numeric(us.pam$cluster))
Sembra che l’andamento sia all’incirca lo stesso, un gruppo di cluster con alta popolazione urbana
e scarsa incidenza criminale e poi i due gruppi con alta incidenza di crimini e alta popolazione
urbana.
Dobbiamo capire se abbiamo individuato gli stessi gruppi. Possiamo esaminare entrambi i casi con
k = 3.
A livello numerico, la diagonale rappresenta i valori che entrambi gli algoritmi hanno clusterizzato
allo stesso modo e come hanno inciso le misclassificazioni, notiamo che sono al bordo dei due
gruppi.
Può essere utile anche osservare il piano principale.
us.pca=princomp(us)
plot(us.pca$scores,pch=19,col=us.pam$cluster)
La prima componente sembra essere rappresentative del tipo dei crimini e la seconda legata alla
popolazione urbana.
Possiamo pensare di fare la stessa analisi a coordinate parallele con le componenti principali.
summary(us.pca)
biplot(us.pca)
loadings(us.pca)
Il gruppo blu ha una bassa incidenza di crimini di fronte a un’alta popolazione urbana e così via.
Se i fattori sono troppi è difficile interpretarli, possiamo in tal caso usare le componenti principali e
non tutti i fattori.
A monte deve esserci un’interpretazione delle componenti principali, devono avere un significato.
Usando solo due componenti qui che catturano l’86% della varianza, abbiamo perso la distinzione
di Rape con gli altri due crimini.
I fattori spiegano meno variabilità di quella che avevamo.
ir<-scale(iris[,1:4])
d<-dist(ir) # distanza euclidea
Il dendrogramma può essere ottenuto valutando la distanza tra cluster attraverso il single linkage,
il complete linkage e l’average linkage.
Rappresentiamo il dendrogramma.
plot(ir.hc,hang=-1,cex=0.3)
plot(2:10,as[2:10],type="b")
Potremmo voler cambiare distanza fra clusters, per esempio il single linkage usando ancora la
distanza euclidea.
ir.hc=hclust(d, method=”single”)
plot(ir.hc,hang=-1,cex=0.3)
Con 2 otteniamo un ottimo risultato, con 3 non è male e man mano che andiamo avanti
aumentano i negativi.
La decisione la prendiamo sulla silhouette media, sulla presenza/assenza di clusters troppo singoli
e cerchiamo di capirli. Se ha senso che siano singoli e abbiamo quindi capito che quei dati hanno la
loro peculiarità abbiamo capito come suddividere in diverse parti il set di dati e perché ciò accade.
La silhouette di un cluster è influenzata anche dagli altri cluster, per la presenza di b(o).
La presenza di un cluster isolato potrebbe influenzare la silhouette degli altri cluster.
Oppure potremmo utilizzare la distanza manhattan e la distanza del massimo, dove due
osservazioni sono vicine se lo sono per ogni fattore.
La distanza euclidea e Manhattan vedono invece un risultato complessivo di tutti i fattori.
La differenza tra le due è che in quella euclidea c’è l’effetto del quadrato di enfatizzare distanze
grandi e piccole.
Con la distanza del massimo il risultato della silhouette è completamente diverso.
Bisogna vedere la distanza tra clusters più ideale e poi fare lo stesso con i metodi prototipo e
ottenere un sottoinsieme di metodi utilizzabili.
Un metodo di giudizio che possiamo usare è la possibile interpretazione, una suddivisione diventa
interessante se ha un’interessante suddivisione.
Esaminiamo la tabella che riporta la percentuale di voti ottenuti da candidati repubblicani nelle
elezioni presidenziali americane dal 1856 al 1976 (le elezioni del 1964 sono quelle successive
all’assassinio di Kennedy).
str(votes.repub)
head(votes.repub)
rep=data.frame(scale(votes.repub))
plot(rep,lower.panel=NULL,pch=20) # inutile
library(corrplot)
corrplot(cor(rep,use="na"),tl.cex=0.8) # grafico anni/anni
corrplot(cor(t(rep),use="na"),tl.cex=0.8) # grafico stati/stati
parcoord(rep) # inutile, va vista suddivisa per clusters
Considerando gli anni vediamo come le correlazioni sono man mano maggiori temporalmente, tra
gli anni vicini.
Passiamo ad esaminare la tabella usando metodi di clustering gerarchico. Indagheremo l’uso di tre
metodi differenti per valutare la distanza tra cluster.
d<-dist(rep)
rep.hcc=hclust(d,"complete") # opzione standard
plot(rep.hcc,hang=-1,cex=0.3)
rep.hca=hclust(d,"average")
plot(rep.hca,hang=-1,cex=0.3)
rep.hcs=hclust(d,"single")
plot(rep.hcs,hang=-1,cex=0.3)
Cerchiamo di capire come gli stati si aggregano in relazione al loro comportamento elettorale.
Qualcosa emerge.
Alcuni stati compaiono nelle votazioni più tardi.
Si ha poi una bella differenza tra i clusters.
Il cluster rosso (sopra) tende a mantenere un livello di votazioni alto eccetto nel 1964. L’altro
cluster mantiene un andamento molto basso e alcuni di questi invertono la loro direzione
nell’anno designato.
Il gruppo rosso mantiene un livello alto di voti, mentre quello nero mantiene un livello basso
tranne gli ultimi anni che inverte l’andamento.
Siccome abbiamo utilizzato più di un metodo, abbiamo metodi di tipo prototipo, metodi gerarchici
con differenti distanze tra cluster e osservazioni, cerchiamo di trovare una buona proposta
valutando la coerenza fra i vari metodi (single lo abbiamo scartato)
Ci chiediamo quali elementi appartengono a ogni clusters.
Confrontiamo infine i cluster per i due metodi.
# complete
rep.cut=cutree(rep.hcc,2)
which(rep.cut==1)
# average
rep.cut=cutree(rep.hca,2)
which(rep.cut==1)
Lo stesso vale per PAM. Il fatto che metodi a punti prototipo e gerarchici ci danno risultati simili è
interessante.
Vorremmo in ogni caso pochi valori di silhouette negativi, una silhouette media più o meno alta
con valori di silhouette omogenei per i clusters (non dovuta a un valore di silhouette alto e altri
piccoli).
Il Clustering più opportuno può essere ottenuto considerando le silhouette ma anche
considerando che un cluster è più apprezzabile se ha un forte significato interpretativo, facendo
confronti tra suddivisioni positive.
L’interpretabilità è un fattore che consideriamo su clusters buoni.
Vedere che tante osservazioni tendono a stare insieme per diversi metodi che utilizziamo è un
buon risultato.
Decomposizione di serie storiche
Guardiamo prima la visualizzazione di serie storiche.
s = 1:100 + rnorm(100, 0, 10)
plot(s) // serve a poco
ts.plot(s)
Se una sequenza è già una serie storica, il plot instanzia già il ts.plot.
La funzione di autocorrelazione restituisce una parte dei valori, perché diventa poco significativa
se usiamo un lag elevato nel confrontare la serie.
Con questo output abbiamo un ottimo inizio di avere una serie con trend dominante.
L’autocorrelazione di un campione puramente stagionale (periodo 10, in corrispondenza di 10
dobbiamo avere un picco della funzione di autocorrelazione) ha questo tipico aspetto:
acf(sin(1:100 * pi/5))
A metà periodo abbiamo anche una autocorrelazione negativa, cosa che non accade in tutte le
serie stagionali, nel seno si.
L’autocorrelazione di un campione costituito da trend e stagionalità. Cambiando il parametro si
può enfatizzare l’una o l’altra componente:
a = 1
# a=10 a=15
acf(1:100 + a * sin(1:100 * pi/5))
Moduliamo il trend da 1 a 100 col seno, la parte stagionale modula il trend ma con una
modulazione abbastanza debole.
Apparte il picco in 0 vediamo che tutti i valori sono parecchio bassi. Il rumore fa sì che la funzione
di autocorrelazione sia trascurabile.
Le linee tratteggiate sono i valori limite della funzione di autocorrelazione a livello di un test
statistico.
Si fa un test statistico con ipotesi nulla che la funzione di autocorrelazione che valga 0 e ipotesi
alternativa che non valga 0.
Con una confidenza prefissata del 95% se il valore è all’interno della banda nel test non ci sono
elementi per rigettare l’ipotesi nulla.
Se il valore è più ampio possiamo rigettarla.
Tutti i casi in cui abbiamo rumore ci danno una funzione di autocorrelazione praticamente sempre
all’interno della banda per non rigettare l’ipotesi nulla.
Possiamo pensare che questi valori siano 0.
La funzione di autocorrelazione filtra il rumore, perché ha funzione di autocorrelazione pari a 0
quale che sia il lag, perché abbiamo campioni di rumori indipendenti.
Se aumentiamo a 10.000 dati la banda è molto più stretta, più dati abbiamo e più possiamo
determinare con maggiore esattezza i valori.
Prendiamo 100 numeri gaussiani e cerchiamo una conferma quantitativa. Valutiamo la media e la
deviazione standard per i valori della funzione di autocorrelazione per un campione casuale.
m = rep(0, 21)
s = rep(0, 21)
for (i in 1:1000) {
a = acf(rnorm(100), plot = F)$acf
m <- m + a
s <- s + a^2
}
m <- m/1000
s <- sqrt(s/1000 - m^2)
plot(m[2:21], pch = 20)
plot(s[2:21], pch = 20
Le media fluttuano molto chiaramente intorno allo 0 e quindi ci aspettiamo che la funzione di
correlazione filtri il rumore, buona cosa perché ci permette di identificare ciò che ci interessa.
Se abbiamo dati multi-stagionali, la funzione di autocorrelazione non è più efficace, dovremmo
fare un’analisi spettrale.
Per esempio, se abbiamo una stagionalità settimanale e poi mensile che si sovrappongono.
La decomposizione ci dà errore, perchè ha senso ove ci siano component da estrarre. Se non c’è
una stagionalità da estrarre è inutile la decomposizione.
Se vogliamo registrare una serie e il suo periodo creiamo un oggetto time series.
La decomposizione necessita di un periodo assegnato per la stagionalità. La decomposizione
necessita anche di un numero sufficiente di periodi. Solo, per esempio, aggiungiamo un periodo
fittizio.
T = ts(1:100, frequency = 10)
plot(decompose(T))
decompose(T)
Registriamo la serie storica con periodo 10 e facciamo la decomposizione (stazionaria è quella che
viene fatta con questo comando).
Il risultato dà la componente stagionale, ed è sempre costante perchè stazionaria, poi dà la
componente di trend e quella di rumore (random).
Sia trend che rumore hanno NA alla fine, perché facciamo una media mobile che può essere fatta
solo da un certo punto in poi se si hanno abbastanza dati a destra e a sinistra.
Di default, la decomposizione è additiva.
Se la stagionalità è di ordine inferiore del rumore, possiamo concludere che non c’è o che non è
rilevante.
Ci daranno più informazioni l’autocorrelazione e altro.
Si può riconoscere una serie moltiplicativa guardando che man mano che cresce il rumore si
amplificano le fluttuazioni, si ha un trend modulato da un rumore, consideriamola senza
stagionalità.
Man mano che cresce il trend cresce anche l’ampiezza del rumore, sintomo che abbiamo
decomposto in modo additivo una serie moltiplicativa.
Nel caso di decomposizione moltiplicativa, l’ampiezza del rumore diventa paragonabile a ogni
valore e indipendente dal valore del trend. Questa è una conferma che si tratta di una serie
moltiplicativa.
plot(decompose(T, type = "multiplicative"))
Se ci metto il periodo abbiamo come unità di misura un’unità di misura diversa, tanti lag quanto
c’è il periodo.
La componente di rumore mantiene una componente significativa, avremo struttura
nell’autocorrelazione del rumore.
Aggiungiamo rumore alla serie additiva con stagionalità.
T = ts(1:100 + 10 * sin((pi * 1:100)/5) + 5 * rnorm(100), frequency = 10)
plot(T)
acf(T)
plot(decompose(T))
La acf non cambia, vediamo un po' di stagionalità che emerge chiaramente e varia tra -10 e 10,
l’ordine di grandezza p stato catturato completamente.
Anche il rumore varia nell’ordine di grandezza della stagionalità, ma è corretto.
Anche in questo caso introdurre una periodicità spuria non porta a una decomposizione corretta.
T = ts(1:100 * (1 + sin(1:100 * pi/5)) * (1 + rnorm(100)), frequency = 17)
acf(T)
plot(decompose(T, "multiplicative"))
Consideriamo infine il caso di una serie ottenuta sovrapponendo stagionalità di periodi differenti,
10 e 17 che non sono multipli uno dell’altro e quindi non abbiamo un’unica stagionalità.
Proviamo specificando il periodo 10.
T = ts(sin((pi * 1:100)/5) + sin((2 * pi * 1:100)/17), frequency = 10)
plot(T)
acf(T)
plot(decompose(T))
La acf non riesce a catturare il tutto, a 10 non abbiamo un picco e anche la decomposizione non ha
buoni risultati.
Anche se specificassimo periodo 17 non avremo buoni risultati.
T = ts(sin((pi * 1:100)/5) + sin((2 * pi * 1:100)/17), frequency = 17)
acf(T)
plot(decompose(T)
Carichiamo i dati come serie storica, proviamo una decomposizione con un periodo fittizio 1.
Dobbiamo capire se la serie presenta stagionalità.
Chiediamoci se è presenta stagionalità, osserviamola anche più in là. prendendo fino a 60 quindi
circa metà. Abbiamo 140 dati.
Cala addirittura dentro la banda di confidenza del test di cui si è parlato.
Non emerge apparentemente stagionalità. Impostare lag più lunghi risulta chiaramente inutile.
Non abbiamo neanche un candidato per il periodo della stagionalità.
Il trend potrebbe nascondere una eventuale periodicità. Proviamo a ``osservare’’ la serie al netto
del trend.
plot(diff(tp))
acf(diff(tp))
Con diff ogni elemento è la differenza tra il termine della serie originale e il termine precedente.
Non emerge presenza di stagionalità. Non c’è ragione di decomporre la serie. A livello di
decomposizione abbiamo terminato. Non abbiamo un periodo ben definito.
Sembrerebbe di no, ma prima di esserne sicuri prendiamo ogni anno e li rappresentiamo allineati
sullo stesso grafico per vedere se anno dopo anno vi è una ripetizione dei dati.
Proviamo a confrontare i grafici dei periodi. Otteniamo una matrice nella quale ogni colonna
contiene i dati di un periodo. Avrà 260 righe e 6 colonne.
m_ftse = matrix(ftse[132:(1860 - 169)], 260, 6)
par(bg = "black")
ts.plot(m_ftse, col = heat.colors(6))
Non osserviamo una somiglianza significativa.
Forse è opportuno aggiustarle rispetto al valor medio.
Infatti, se guardiamo a decomposizione non abbiamo risultati significativi.
Il trend non è male ma la stagionalità è di ordine inferiore al rumore, indizio contro la presenza di
stagionalità che sommato agli altri indizi dell’acf e dei periodo uno rispetto all’altro ci fa pensare
ciò.
ts.plot(scale(m_ftse, scale = F), col = heat.colors(6))
par(bg = "white")
ftse.d = decompose(ftse)
plot(ftse.d)
plot(ftse.d$random)
lines(ftse.d$seasonal, col = "red")
Confrontiamo gli andamenti in anni diversi. Non c’è necessità di ricentrare i periodi.
Colori simili sono anni vicini.
par(bg = "black")
m_nt = matrix(nottem, 12, 20)
ts.plot(m_nt, col = heat.colors(20))
Ci sono delle somiglianze e notiamo alte temperature d’estate e basse negli inverni.
Sovrapponiamo l’andamento medio annuale in bianco spesso, ottenuto mediando i dati di ogni
mese.
ts.plot(m_nt, col = heat.colors(20))
lines(rowMeans(m_nt), lwd = 3, col = "white")
par(bg = "white")
In rosso abbiamo il termine della decomposizione, mentre in nero abbiamo l’andamento nero
trovato prima.
Avendoli portati tutti a media 0 notiamo ancora di più la somiglianza ma anche l’effetto di
moltiplicazione dovuto alla presunta natura moltiplicativa della serie. Confermiamo la presenza di
stagionalità, ma valutiamo anche l’amplificazione.
boxplot(t(m_ap), pch = 19)
boxplot(scale(m_ap, scale = F), pch = 19)
Il confronto tra le stagionalità non ha senso, perché sono dati in scale diverse e decentrati (nel
modello moltiplicativo stagionalità e rumore sono amplificati dal trend).
Nella decomposizione additiva la stagionalità è additiva, nella moltiplicativa modula il trend.
Potremmo amplificare la stagionalità in ragione del trend.
Per farlo potremmo prendere il valor medio del trend e lo moltiplicao per la componente
stagionale e lo centro in 0 (togliendo 1), perché ci aspettiamo che nel caso additivo la stagionalità
oscilli attorno a 0 (perché 0 neutro per l’addizione) ma nel caso moltiplicativo l’elemento neutro è
1 e quindi ci aspettiamo che oscilli intorno al valore 1, questo perché in assenza di stagionalità il
trend deve rimanere lo stessp (nel grafico precedente è intorno a 1, vicino a 0 ma se zoomiamo
intorno a 1).
plot(ap.da$seasonal)
lines(mean(ap.dm$trend, na.rm = T) * (ap.dm$seasonal - 1), col = "red")
Il caso additivo ha correlazioni più pronunciate del caso moltiplicativo. (picchi più alti)
Esaminiamo i residui delle decomposizioni additiva e moltiplicativa di AirPassengers. Per semplicità
eliminiamo i termini NA della serie.
ap.dar = as.vector(window(ap.da$random, c(1949, 7), c(1960, 6)))
plot(ap.dar, pch = 20)
ap.dmr = as.vector(window(ap.dm$random, c(1949, 7), c(1960, 6)))
plot(ap.dmr, pch = 20)
Possiamo osservare i residui stessi e al contrario della reg lineare dove era opportuno
rappresentarli rispetto ai valori stimati, qui vogliamo vedere se c’è una struttura temporale, quindi
è interessante rappresentarli rispetto all’indice.
Calcoliamo la varianza spiegata. Emerge un problema: i dati iniziali e finali della serie non sono
disponibili per come decompose valuta il trend (medie mobili). Escludiamo quei valori (lo stesso
dobbiamo fare per la serie originaria).
var(ap.dar)/var(window(AirPassengers, c(1949, 7), c(1960, 6)))
Nel modello moltiplicativo il rumore modula gli altri termini, quindi, non hanno lo stesso ruolo.
Dobbiamo confrontare oggetti omogenei e per farlo prendiamo il logaritmo, così da ottenere una
struttura additiva.
I residui del modello moltiplicativi sono in effetti gli esponenziali dei residui di un modello additivo
sui logaritmi dei dati.
plot(log(ap.dmr), col = "blue", pch = 20)
points(as.vector(na.omit(decompose(log(AirPassengers))$random)), col = "red")
ap.dmrl = log(ap.dmr)
var(ap.dmrl)/var(window(log(AirPassengers), c(1949, 7), c(1960, 6)))
qqnorm(ap.dar)
qqline(ap.dar)
layout(1)
shapiro.test(ap.dar)
Mentre nella regressione lineare l’ordine delle osservazioni è irrilevante, in una serie storica è più
che rilevante e quindi diventa un’informazione sostanziale.
Entrambi i residui sono non particolarmente convincenti dopo l’analisi anche di quelli
moltiplicativi.
Per decidere su quale decomposizione optare, scegliamo quella che ``mostra’’ meno struttura.
Valutiamo numericamente la variabilità della funzione di autocorrelazione.
Usiamo il comando stl per serie con stagionalità non uniforme, serie con comportamento
periodico ma con forma di stagionalità che può cambiare in anni differenti.
Per esempio, riscaldamento con picchi invernali e un comportamento più piatto estivo ma diffuso
l’uso di condizionatori i momenti di alto uso son diventati estivi.
La stagionalità è rimasta ma la forma del comportamento è cambiata negli anni.
Dobbiamo fare medie locali anche a livello di stagionalità.
Il comando stl non prevede struttura moltiplicativa, ma solo additiva. Nel caso si voglia
decomporre un modello moltiplicativo, è opportuno filtrare la serie attraverso la funzione
logaritmo.
Nell’uso di stl si consiglia di settare l’ampiezza della finestra di media per la stagionalità (il
parametro s.window) come un numero dispari e maggiore o uguale a 7.
Esaminiamo il dataset UKDriverDeaths.
Il comportamento medio ha dei picchi nella fine dell’anno ma osserviamo comportamenti più
anomali. I valori più recenti tendono ad avere dei picchi anche prima.
Se colori simili hanno comportamenti simili possiamo fare delle supposizioni relativi a quelli.
Possiamo indagare comportamenti relativi a stagionalità non stazionaria.
All’aumentare del valore di s.window il risultato risulta sempre più simile all’output di decompose.
plot(stl(uk, 7))
plot(stl(uk, 11))
plot(stl(uk, 15))
plot(stl(uk, "periodic"))
stl prende come parametro la seasonal window, facciamo medie locali sulla stagionalità, quindi è
l’ampiezza della finestra per le medie mobili stagionali.
Questo parametro è consigliato di essere dispari per non avere finestre asimmetriche, così sarà
centrata sul valore per cui stiamo mediando, e almeno maggiore o uguale di 7.
Se prendiamo un numero piccolo facciamo la media su valori troppo pochi valori.
Prima estrae la stagionalità e poi il trend.
C’è un trend che tipicamente è più regolare di quello del decompose perché viene estratto dopo la
stagionalità, attraverso quindi due procedure di media.
Le barre finali ci danno un’idea dell’ordine di grandezza.
Abbiamo unità di misura diverse, e le barre su tutti i grafici avrebbero la stessa lunghezza se
rappresentate nella stessa unità di misura.
Se le lunghezze effettive sono simili le unità di misura sono simili a sua volta.
Il trend varia in modo paragonabile al rumore.
Cambiando la finestra abbiamo piccole variazioni, più è ampia la finestra più le stagionalità
tenderanno a essere simili fino al caso limite periodic, in cui assumiamo stagionalità uguali (figura).
La chiamata con s.window="periodic" corrisponde al caso di stagionalità stazionaria.
Se vogliamo decidere se è più opportuna una finestra, potremmo confrontare le decomposizioni
per decidere quella più opportuna.
I residui si confrontano come prima, aggiungendo l’indagine sulle informazioni temporali.
Se la serie è fortemente non stazionaria (stagionalità molto variabile) usare stl ci renderà dei
residui molto meno soggetti alla struttura temporale.
La stl permette anche un buon controllo della media mobile a livello di trend.
C’è anche il comando t.window che anch’esso si può regolare, anch’esso si suggerisce dispari e
non piccolo. Con una finestra più stretta il trend è molto meno frastagliato.
plot(stl(uk, s.window = 7, t.window = 9))
plot(stl(uk, s.window = 7, t.window = 13))
Metodi di Holt-Winters
Useremo l’opportuno metodo in funzione dell’analisi fatta, se riteniamo che non ci sia stagionalità
non usiamo SETS ma SET, viceversa useremo in ogni caso, comunque, un metodo autoregressivo
per capire l’analisi migliore.
Smorzamento esponenziale
Proviamo il metodo di smorzamento esponenziale su una serie con forte trend creata
artificiosamente.
st = ts((1:100) + 10 * rnorm(100), frequency = 10)
se = HoltWinters(st, beta = F, gamma = F)
summary(se)
se
plot(se)
# provare con valore di alpha più opportuno
# se=HoltWinters(st,alpha=?,beta=F,gamma=F)
predict(se, 1)
plot(se, predict(se, 1))
Se non mettiamo i parametri verranno presi quelli ottimi come abbiamo visto.
Mettendo beta e gamma a false, non si usano beta e gamma quindi in questo caso si utilizza lo SE.
Il summary non dice molto, molto di più disegnare la serie.
Non sembra essere utile variare la condizione iniziale. Se la cambiamo le cose non variano
granchè.
Se facciamo un’analisi con lo stesso valore di alpha ma con una condizione iniziale (l.start)
differente da quella iniziale.
plot(tp.se)
points(HoltWinters(tp, alpha = tp.se$alpha, beta = F, gamma = F, l.start = -
0.4)$fitted, col = "blue", type = "l")
E’ inutile prevedere più di un valore perché rimane la stessa previsione con lo SE.
Dobbiamo provare una griglia di valori e visitarli tutti con la freccia dei plot.
Potremmo specificare con xlab alpha e ylab beta così da scegliere quelli opportuni. (meglio / 10)
Dobbiamo scegliere valori che non seguono troppo le fluttuazioni, se pensiamo siano dovute a
rumore.
Se vicino a un range si ha una buona analisi, si può variare nell’intorno di quei valori con decimali
diversi.
Variamo anche la condizione iniziale. Ricordiamo che l.start è la intercetta iniziale, e b.start è la
pendenza iniziale.
plot(HoltWinters(tp, gamma = F, l.start = -0.1))
plot(HoltWinters(tp, gamma = F, l.start = 0, b.start = 0.01))
plot(HoltWinters(tp, gamma = F, l.start = 0.05, b.start = -0.02))
Perchè l’output è:
Avendo scelti valori più conservativi abbiamo meno adattazione a fluttuazioni ma abbiamo la
discesa finale non seguita, va deciso se risulta essere rumorosa o sostanziale.
Con questi valori facciamo un’analisi che va a crescere.
I valori standard erano molto fedeli invece alla serie e l’analisi era discendente.
Con tali valori, osserviamo tutte le componenti (in verde la serie iniziale, in blu la previsione, in
nero le intercette e in rosso i coefficienti angolari).
tp.set = HoltWinters(tp, alpha = 0.2, beta = 0.1, gamma = F, l.start = -0.08,
b.start = -0.005)
ts.plot(tp, tp.set$fitted, col = c("green3", "blue", "black", "red"))
Scelti alpha, beta, gamma e valori iniziali, possiamo fare un confronto tramite cross-validation tra
l’analisi iniziale e quella con valori scelti per vedere quella migliore.
Per sapere alpha,beta,gamma e valori iniziali default possiamo usare tp.set$coefficients,
tp.set$alpha e così via per leggerli.
Passiamo ad esaminare serie con carattere stagionale. Non ci aspettiamo in generale risultati
soddisfacenti.
SE non è soddisfacente, segue la stagionalità (e la scelta della condizione iniziale non aiuta). Anche
SET segue la stagionalità, ma un po’ meno, almeno sui valori ottimali standard.
Esaminiamo la serie sulle temperature includendo la stagionalità.
nt.hw = HoltWinters(nottem)
plot(nt.hw)
Siamo disposti a sbagliare di più all’inizio e far funzionare il tutto bene alla fine.
Più aumentano i parametri più aumentano le decisioni da prendere.
Abbiamo 9 * 9 * 9 grafici da esaminare. Può essere opportuno esaminare solo 3 o 4 valori.
Per alpha, beta e gamma più sono piccoli e più l’analisi tende a essere conservativa (valori,
direzione e stagionalità rispettivamente).
Il comando HoltWinters restituisce tra l’altro la sotto-struttura fitted, dove in particolare level sono
le intercette e xhat sono i valori stimati. Può risultare utile graficamente per calibrare il modello.
plot(nt.hw$fitted)
plot(HoltWinters(nottem, l.start = 48, b.start = -0.21)$fitted)
nt.hw.r = residuals(nt.hw)
plot(nt.hw, predict(nt.hw, 12))
lines(predict(nt.hw, 12) + quantile(nt.hw.r, 0.05), col = "green3")
lines(predict(nt.hw, 12) + quantile(nt.hw.r, 0.95), col = "green3")
Se i residui assomigliano a una distribuzione, ciò può essere utile per una stima parametrica
dell’incertezza usando la distribuzione dei residui.
plot(nt.hw.r, type = "p", pch = 20)
plot(nt.hw$fitted[, 1], nt.hw.r, pch = 20)
Una rappresentazione dei residui può essere vista sia in funzione del tempo e sia in funzione dei
valori stimati.
.acf(nt.hw.r)
Testiamo la capacità previsiva del metodo di smorzamento esponenziale con trend e stagionalità.
In mancanza di dati futuri noti (set di validazione) con i quali confrontare le previsioni,
(eventualmente in caso di confronto tra modelli) possiamo utilizzare una procedura di
autovalidazione. Tale procedura deve essere rispettosa della progressione temporale.
train = window(nottem, end = c(1938, 12))
test = window(nottem, 1939)
nt.hw <- HoltWinters(train)
nt.hw.p = predict(nt.hw, 12)
sqrt(mean((nt.hw.p - test)^2))
I residui del modello additivo hanno nella acf qualche valore sopra la banda in corrispondenza del
periodo.
Valutiamo poi i due modelli in termini di capacità di predizione, confrontando le previsioni dei due
metodi sul periodo successivo.
ap_train = window(ap, end = c(1959, 12)) # Fino al 1959
ap_test = window(ap, 1960) # Ultimo anno
ap.hwa = HoltWinters(ap_train, seasonal = "additive")
ap.hwm = HoltWinters(ap_train, seasonal = "multiplicative")
ap.hwa.p = predict(ap.hwa, 12)
ap.hwm.p = predict(ap.hwm, 12)
sqrt(mean((ap.hwa.p - ap_test)^2))
sqrt(mean((ap.hwm.p - ap_test)^2))
Accorciamo la serie di un periodo e costruiamo l’analisi sulla serie accorciata per poi prevedere
sull’ultimo periodo.
Quando le cose vanno bene entrambe devono darci risultati coerenti.
plot(ap_test)
lines(ap.hwa.p,col=”blue”)
lines(ap.hwm.p,col=”red”)
Possiamo provare a predire un valore più ampio e farlo un periodo alla volta.
Scegliamo un valore, prevediamo questo valore, lo aggiungiamo nella serie di training e
prevediamo il successivo, prevedendo quindi un passo alla volta.
Un maggior numero di previsioni ci permette di fare una media più stabile.
Proviamo un’autovalidazione più robusta.
l = length(ap)
res.hwa = rep(0, 24)
res.hwm = rep(0, 24)
j = 1
for (i in (l - 24):(l - 1)) {
ap_cv = ts(ap[1:i], frequency = 12, start = c(1949, 1))
ap.hwa = HoltWinters(ap_cv, seasonal = "additive")
ap.hwm = HoltWinters(ap_cv, seasonal = "multiplicative")
ap.hwa.p = predict(ap.hwa, 1)
ap.hwm.p = predict(ap.hwm, 1)
res.hwa[j] = ap.hwa.p - ap[i + 1] #previsione – valore effettivo
res.hwm[j] = ap.hwm.p - ap[i + 1] #previsione – valore effettivo
j = j + 1
}
sqrt(mean(res.hwa^2))
sqrt(mean(res.hwm^2))
plot(res.hwa, type = "b", pch = 20, col = "blue")
lines(res.hwm, type = "b", pch = 20, col = "green3")
Prendiamo I primi x valori e prevediamo l’(x+1)-esimo e così via.
Abbiamo ottenuto una stima più precisa.
A livello di validazione sono paragonabili, ma a livello di residui avevamo un aspetto più casuale
nel modello moltiplicativo.
Il modello additivo ci permette una stima più precisa ma il modello moltiplicativo è più casuale;
quindi, la stima dell’incertezza è più affidabile.
Partono tutte da 0 ma da 4 in poi avremo una dipendenza dall’istante precedente (due istanti
precedenti prima) e con del rumore e così via.
Tutte hanno un piccolo rumore.
Nella serie 2b abbiamo una dipendenza con un coefficiente negativo sul termine precedente,
positivo su due termini prima.
Le correlazioni si alternano e la pacf ad 1 lag è negativo e a 2 lag è positiva.
Questa serie dipende dai tre istanti precedenti.
Consideriamo il dataset relativo alle temperature di Nottingham. Valutiamo l’autocorrelazione e
l’autocorrelazione al netto dell’effetto regressivo. Il risultato sembra suggerire una dipendenza che
arriva fino a 7 lag, forse 11.
acf(nottem)
pacf(nottem)
Abbiamo una dipendenza dai valori precedenti ma anche un picco in corrispondenza del periodo.
Proviamo il modello regressivo fatto a mano con dipendenze fino a 13 lag.
Registriamo in una matrice la serie e le sue traslate temporali
L = length(nottem)
l = 13 # numero di lag in ingresso
mnt = matrix(nrow = L - l, ncol = l + 1)
for (i in 1:(l + 1)) {
mnt[, i] = nottem[i:(L - l - 1 + i)]
}
mnt <- data.frame(mnt)
nt.lm <- lm(X14 ~ ., data = mnt) # X14 perché 13 lag in ingresso
summary(nt.lm)
Spostiamo a mano a mano la finestra e X14 sarà la colonna che useremo come fattore di uscita.
Abbiamo una varianza spiegata alta e il primo lag corrisponde a X13 (p-value bassissimo), il
secondo a X12 e così via.
Eliminando ad uno ad uno i fattori con p-value alto (nell’ordine: 9,4,5,8,12,7,11,2), c’è un
guadagno in termini di R2adj (o alla peggio una perdita trascurabile) e un R2 pressoché invariato.
Sottrarre i fattori 1 e 10, seppur di p-value basso, non altera in maniera significativa la varianza
spiegata. Sottrarre invece il terzo o il tredicesimo fattore comporta un calo un po’ più significativo
di varianza spiegata.
nt.lm <- lm(X14 ~ . - X9 - X4 - X5 - X8 - X12 - X7 - X11 - X2 - X6 - X1 -
X10, data = mnt)
summary(nt.lm)
Per confrontare il risultato e la predizione a questo livello naive non possiamo utilizzare predict,
perché alcuni dei fattori in input sono predizioni a tempi precedenti. Noi vogliamo prevedere più
valori e quindi non possiamo usare predict, dobbiamo usare i dati ma anche la prima previsione al
secondo valore. Il predict standard si aspetta dati relativi a tutte le predizioni che vogliamo fare.
Andiamo a prevedere due anni.
anni = 2
L = length(nottem)
pt = rep(0, L + 12 * anni)
pt[1:L] = nottem
for (i in 1:(12 * anni)) {
pt[L + i] = coef(nt.lm) %*% c(1, pt[L + i - 11], pt[L + i - 1])
}
nt.lm.pt = ts(pt, frequency = 12, start = c(1920, 1))
nt.lm.a = window(nottem, c(1921, 2)) - resid(nt.lm)
ts.plot(nottem, nt.lm.a, window(nt.lm.pt, c(1939, 12)), col = c("black",
"blue", "red"))
Con tutti i valori otteniamo una previsione che sembra meno coerente col resto della serie,
Per decidere quale sia la più opportuna, è meglio confrontare a livello di predizione.
Confrontiamo con la previsione di Holt-Winters.
nt.hw = HoltWinters(nottem)
nt.lm.pt = window(nt.lm.pt, c(1939, 12))
nt.hw.pt = predict(nt.hw, 24)
ts.plot(nottem, nt.lm.pt, nt.hw.pt, col = c("black", "red", "blue"))
Anche HW dà un risultato simile al modello ridotto.
Per avere una risposta chiara vanno confrontate le previsioni del modello ridotto e quelle del
modello completo e vedere quale dei due sia preferibile.
Abbiamo però anche HW da confrontare con loro.
Confrontiamo i residui solo di HW e del modello completo.
layout(t(1:2))
# estrazione dei residui
nt.lm.r = resid(nt.lm)
nt.hw.r = resid(nt.hw)
# confronto grafico rispetto al tempo
plot(as.numeric(nt.hw.r), pch = 20)
plot(nt.lm.r, pch = 20)
# confronto grafico rispetto ai valori
plot(nt.hw$fitted[, 1], nt.hw.r, pch = 20)
plot(nt.lm.a, nt.lm.r, pch = 20)
# varianze spiegate
var(nt.hw.r)/var(window(nottem, 1921))
var(nt.lm.r)/var(window(nottem, 1921))
# acf
acf(nt.hw.r)
acf(nt.lm.r)
# pacf
pacf(nt.hw.r)
pacf(nt.lm.r)
# frequenze
hist(nt.hw.r, 20, freq = F)
lines(density(nt.hw.r), col = "blue")
lines(sort(nt.hw.r), dnorm(sort(nt.hw.r), mean(nt.hw.r), sd(nt.hw.r)), col =
"red")
hist(nt.lm.r, 20, freq = F)
lines(density(nt.lm.r), col = "blue")
lines(sort(nt.lm.r), dnorm(sort(nt.lm.r), mean(nt.lm.r), sd(nt.lm.r)), col =
"red")
# quantili
qqnorm(nt.hw.r, pch = 20)
qqline(nt.hw.r)
qqnorm(nt.lm.r, pch = 20)
qqline(nt.lm.r)
# test
shapiro.test(nt.hw.r)
shapiro.test(nt.lm.r)
layout(1)
Eseguiamo una autovalidazione dei due metodi, usando i dati del 2010 come insieme test.
# training e test set
train = window(nottem, end = c(1938, 12))
test = window(nottem, c(1939, 1))
# previsione di Holt-Winters
train.hw = HoltWinters(train)
train.hw.pt = predict(train.hw, 12)
# previsione del modello autoregressivo
train.lm = lm(X14 ~ ., data = mnt)
pt = rep(0, L)
pt[1:(L - l + 1)] = nottem[1:(L - l + 1)]
for (i in 1:12) {
pt[L - l + 1 + i] = coef(train.lm) %*% c(1, rev(pt[L - l + 1 + i - 1:l]))
}
train.lm.pt = window(ts(pt, frequency = 12, start = c(1920, 1)), 1939)
# errore nelle predizioni
sqrt(mean((train.hw.pt - test)^2))
sqrt(mean((train.lm.pt - test)^2))
# confronto grafico
ts.plot(test, train.hw.pt, train.lm.pt, col = c("black", "red", "blue"))
Prenderemo almeno fino a un anno e due mesi prima se volessimo farlo a mano.
Usiamo invece Yule-Walker.
uk.ar = ar(uk)
uk.ar
uk.ar$var/var(uk[15:length(uk)])
Order selected ci dice quanti lag prendere nel modello e quelli sono i coefficienti relativi ai lag
precedenti.
Questa analisi non fornisce direttamente i valori previsti, ma possiamo ottenerli estraendo i
residui.
Abbiamo HW, il metodo autoregressivo, potremmo anche impostarne uno a mano, e tra questi
dobbiamo decidere quello migliore.
Potremmo fare un’analisi dei residui e l’autovalidazione della serie.
Confrontiamo analisi e previsioni con Holt-Winters.
ap.ls.pt = predict(ap.ls, n.ahead = 12, se.fit = FALSE)
ap.ls.r = as.double(na.omit(ap.ls$resid))
# analisi
ap.hw = HoltWinters(ap, seasonal = "m")
ts.plot(ap, ap - ap.ls$resid, ap.hw$fitted[, 1], col = c("black", "blue",
"green3"))
# previsioni
ap.hw.pt = predict(ap.hw, 12)
ts.plot(ap.ls.pt, ap.hw.pt, col = c("blue", "green3"))
lines(ap.ls.pt + quantile(ap.ls.r, 0.975), col = "lightblue")
lines(ap.ls.pt + quantile(ap.ls.r, 0.025), col = "lightblue")
Confrontiamo i due metodi con l’autovalutazione.
train = window(ap, end = c(1959, 12))
test = window(ap, 1960)
apcv.hw.p = predict(HoltWinters(train, seasonal = "m"), 12)
apcv.ls.p = predict(ar(train, method = "ols"), n.ahead = 12, se.fit = FALSE)
ts.plot(test, apcv.hw.p, apcv.ls.p, col = c("black", "red", "blue"))
sqrt(mean((apcv.hw.p - test)^2))
sqrt(mean((apcv.ls.p - test)^2))