Il 0% ha trovato utile questo documento (0 voti)
253 visualizzazioni185 pagine

Laboratorio Statistica II in R

This document provides an introduction to basic operations in R such as creating vectors, applying functions element-wise to vectors, performing arithmetic operations on vectors, generating sequences, and creating and manipulating matrices and data frames. Key points covered include concatenating vectors, extracting vector lengths, accessing vector elements, applying functions element-wise to vectors, and performing common operations like sorting, reversing, and taking sums or means of vectors. Matrix operations like transposition, selection of elements/rows/columns, and linear algebra functions are also demonstrated.

Caricato da

Franco Terranova
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato DOCX, PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
253 visualizzazioni185 pagine

Laboratorio Statistica II in R

This document provides an introduction to basic operations in R such as creating vectors, applying functions element-wise to vectors, performing arithmetic operations on vectors, generating sequences, and creating and manipulating matrices and data frames. Key points covered include concatenating vectors, extracting vector lengths, accessing vector elements, applying functions element-wise to vectors, and performing common operations like sorting, reversing, and taking sums or means of vectors. Matrix operations like transposition, selection of elements/rows/columns, and linear algebra functions are also demonstrated.

Caricato da

Franco Terranova
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato DOCX, PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 185

Prima Introduzione a R

Il comando base di generazione di un vettore è la concatenazione.

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

Per estrarre la lunghezza del vettore:

length(x)

Per visualizzare parte di un vettore:

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

Conviene utilizzare la parentesi altrimenti l’interpretazione è errata.


Possiamo anche selezionare più elementi non consecutivi:

q[-c(1:95, 99:199)]

Possiamo creare una matrice specificando il numero di righe e colonne:

A = matrix(nrow=4,ncol=6)

La matrice sarà vuota.


La creazione e il suo popolamento possono essere fatti simultaneamente,
z = 1:24
A = matrix(z,4,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)

oppure usare il comando seguente:

A = matrix(z,4,6,byrow=T)

Il comando per la dimensione è un vettore. I singoli valori possono essere ottenuti separatamente,

lenght(A), numero di elementi


dim(A), numero di righe e numero di colonne
nrow(A)
ncol(A)

Come per i vettori, funzioni di variabile reale calcolate su matrici si intendono applicate elemento
per elemento:

A^2, porta al quadrato ogni elemento

La matrice diagonale con diagonale il vettore y:

diag(y)

La trasposta di una matrice:

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)

Il prodotto Q*solve(Q) non restituisce la matrice identica, perché, come per i vettori,


l’operatore * è inteso come prodotto elemento per elemento. Il prodotto tra matrici è
implementato con il simbolo %*%:

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]

Selezione di una riga,

A[,2]

Selezione di una colonna,

A[3,]

Selezione di range di righe e colonne,

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)

La somma e la sottrazione tra matrici avviene come dovrebbe:

A1+A2
A1-A2

Il comando eigen restituisce un output strutturato:

eigen(Q)

Le componenti della struttura possono essere estratte con l’operatore $,

EVV = eigen(Q)
EVV$values
EVV$vectors

L’operatore $ permette di accedere a dei campi della struttura.


Si possono valutare i soli autovalori,

EV = eigen(Q,only.values=T) or eigen(Q)$values

Il commando class ci dice com’è fatto un oggetto:

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

Potrei rivedere P come matrice con il comando:

as.matrix(P)

Aggiungere una colonna al data frame P:

P$"Colonna 5"<-rnorm(5)

Aggiungere una riga al data frame P:

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.

A <- read.csv("04prova.csv",sep=";",dec=",") //separatore di campi e per il decimale


fix(A)

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)

L’opzione pch varia il tipo di rappresentazione del punto-dato e varia da 0 a 25, utilizzando


l’operatore : si assegna una diversa rappresentazione per ogni punto.
type=”b” lascia la rappresentazione del punto-dato e li unisce con delle linee.

plot(X,Y,pch=0:25)

L’opzione pch può anche prendere come valore alcuni caratteri.

plot(X,Y,pch="$")

L’opzione lwd varia lo spessore della linea

plot(X,Y,type="l",lwd=3)

L’opzione col varia il colore, assegnabile con un numero o una stringa.

plot(X,Y,col="red")
plot(X,Y,col=0:7)

I colori di base sono i seguenti

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

L’opzione asp varia il rapporto tra le unità di misura dei due assi.

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

La normale vuole media e deviazione standard.

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.

Z=rnorm(10000) //numeri casuali di media 0 e deviazione standard 1


plot(Z)

Non casualmente i punti si addensano attorno allo 0, che è infatti la media.


Lo stesso, ma con distribuzione uniforme.

plot(matrix(runif(10000),5000,2),pch=".")

Lo stesso, ma con la distribuzione esponenziale.

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.

Alcuni dei principali indicatori statistici:

X=1:100
mean(X)
sd(X)
var(x)
median(X)
quantile(X,c(0,0.25,0.5,0.75,1))

Dovremo cercare di capire se i residui di un modello hanno una certa distribuzione.


Se abbiamo estratto dal problema tutta la parte strutturale, la dipendenza dei fattori di uscita da
quelli di ingresso, le fluttuazioni rimanenti casuali possono assomigliare a una distribuzione nota.
Se entra in gioco il teorema del limite centrale può venir fuori una distribuzione gaussiana.
Può anche accadere che non abbiamo saputo riconoscere qualche fattore significativo oppure non
abbiamo capito qual è il problema nonostante i fattori sono sufficienti (es. uso regressione lineare
ma vi è una dipendenza non lineare) oppure non entra in gioco il teorema del limite centrale, vi è
una causa predominante che rovina l’aspetto dei residui rendendoli non gaussiani.
Cercheremo di capire se una distribuzione assomiglia a qualcosa di gaussiano.
E’ possibile disegnare la funzione di ripartizione empirica (ecdf):
plot(ecdf(rnorm(100)),pch=".")

Abbiamo generato 100 numeri gaussiani e ne abbiamo trovato la ecdf.


Può essere interessante sovrapporre quello che ci aspetteremo di trovare, il valore teorico.

Y=seq(-4,4,0.01)
plot(pnorm(Y),type="l")

Generiamo una sequenza di numeri da -4 a 4 con passo di 0.01.


Se utilizzassi direttamente plot cancelleremmo il disegno precedente.
Per sovrapporre il disegno si usa il comando lines, che disegna su una finestra grafica già esistente.

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)

Possiamo aggiungere la densità empirica.

hist(X,20,freq=FALSE)
lines(density(X), col="red")

Adesso vogliamo sovrapporvi la densità teorica.


Il risultato non è troppo soddisfacente perché i dati son troppo pochi, il teorema del limite
centrale ci dice che il numero dei dati deve essere sufficientemente grande (-> inf).
Sarebbe più conveniente portarci alla stessa media e stessa deviazione standard, non prendiamo
una gaussiana normale ma una gaussiana con media e deviazione standard pari a quella dei nostri
dati, dobbiamo adeguarci alla media e alla deviazione standard dei nostri dati.

hist(X,20,freq=FALSE)
lines(density(X), col="red")
lines(X,dnorm(X,mean(X),sd(X)),col="green3")

La corrispondenza è più precisa all’aumentare del campione

X=sort(rnorm(10000))
hist(X,100,freq=FALSE)
lines(density(X), col="red")
lines(X,dnorm(X,mean(X),sd(X)),col="green3")

Regressione lineare semplice


E’ possibile calcolare la correlazione di dati.

X=1:25
Y=5*X+3
cor(X,Y)
plot(X,Y)

La correlazione è compresa tra [-1, 1] e correlazione grande significa vicina in modulo a 1.


Guardiamo la correlazione e non la covarianza poiché dipende quest’ultima da fattori di scala, se
cambia l’unità di misura cambia la covarianza, cosa che non accade con la correlazione.
La proporzione di varianza spiegata al modello, inoltre, dipende dalla correlazione.
La correlazione dipende anche dal numero di dati.
La correlazione empirica è solo una stima di quella del modello, su un milione di dati l’errore sarà
piccolissimo, mentre su un numero piccolo di dati l’errore sarà considerevole.

La correlazione vale esattamente 1 o -1 solo nel caso di dipendenza lineare.

Y=X^2
cor(X,Y)
round(cor(X,Y),2)
plot(X,Y)

Introduciamo un fattore di aleatorietà:

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)

Se aumentiamo la deviazione standard del termine casuale e aumentiamo il suo peso la


correlazione diminuisce.
Utilizzeremo una soglia di 0.8 come proporzione di varianza spiegata, circa 0.9 di correlazione
come soddisfazione.

Correlazione di un campione casuale


Quando abbiamo un numero basso di campioni la correlazione che otteniamo non è molto
affidabile.
Ci poniamo in questa sezione di quantificare quanto sia una correlazione bassa, ovvero una
correlazione alta, in termini di un confronto con un campione casuale di numerosità n=50.
Prendo 50 individui e creo due serie di dati indipendenti.
Più dati ho e più fedele è il risultato che ottengo, le fluttuazioni rispetto al valore vero sono di
ampiezza assai minore.
Prendo 50 individui, calcolo la correlazione e faccio questo per 1000 volte. (La correlazione avverrà
tra le rispettive colonne)

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

Ci aspetteremo di trovare sempre 0 ma ci sono delle fluttuazioni casuali, in alcuni casi ci


avviciniamo anche a +-0.5.
Se aumentiamo il numero di prove per il teorema del limite centrale la campana si stringe.
N=100000
res=rep(0,N)
for(i in 1:N){
X=rnorm(n)
Y=rnorm(n)
res[i]=cor(X,Y)
}
hist(res,100,xlim=c(-1,1),freq=FALSE)

Va realizzato con un for, poiché con la matrice non entrerebbe in memoria.

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.

Vediamo la regressione lineare semplice.


Si importa una tabella contenente i dati di peso e altezza di 500 individui.

PA=read.csv("05pesoaltezza.csv",header=TRUE)

Esploriamo la tabella, scoprendone innanzitutto la classe,

class(PA)

come sono fatti i primi dati (e le etichette delle colonne),

head(PA)

un breve sommario del suo contenuto.

summary(PA)

E’ importante valutare le correlazioni, prendiamo le sole colonne 2 e 3, perché la prima è


categorica.

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)

Conviene memorizzare il risultato così da utilizzare il commando summary.


Abbiamo delle informazioni sui residui: minimo, tre quartili e il massimo.
L’informazione è rilevante se i residui sono di un ordine di grandezza molto minore di quello dei
dati. La loro ampiezza assume una forte importanza.

Abbiamo poi i coefficienti nella sezione successiva.


L’intercetta non è un valore significativo, se siamo lontani dall’origine è normale che l’intersezione
della retta con l’asse della retta sia alto.
E’ molto più significativo il coefficiente angolare, che ci dà la pendenza della retta e ci dice quanto
una variazione in peso implica una variazione in altezza.
Degli altri valori è rilevante Pr(>ltl), il p-value corrispondente a un test dove l’ipotesi nulla è che il
coefficiente corrispondente sia 0.
Se il coefficiente è 1.42 con un errore di circa 2, potrebbe essere addirittura negativo o circa 0.
In questo caso il test statistico ci dà questa informazione, l’ipotesi nulla qui è che il coefficiente sia
0. Se p-value è basso rigettiamo l’ipotesi nulla, mentre se è grande non ne abbiamo la possibilità.
Possiamo rigettare l’ipotesi che il coefficiente relativo al fattore altezza sia nullo.
L’ultima riga è una legenda, e ci dice che *** significa 0, possiamo rigettare subito l’ipotesi nulla.
Il p-value in basso è un test su tutti i coefficienti.
La riga Multiple R-squared ci dà la varianza spiegata, in questo caso vale 0.85.
Tipicamente la soglia di riferimento è 0.8.
Rappresentiamo il diagramma di dispersione includendo la retta di regressione.

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)

E’ importante anche la simmetria.


Se i residui sono tutti negativi, le previsioni hanno sovrastimato, mentre se sono tutti positivi, le
previsioni hanno sottostimato, in maniera sistematica.
Ci aspettiamo che siano dispersi intorno allo 0 ma ci deve essere anche una simmetria, almeno a
livello verticale non ci sia una preferenza per l’alto o il basso.
Se ci fosse un’ampia parte vuota si ha un indice di anomalia. Ci aspettiamo una dispersione non
uniforme.
Abbiamo due stime di errore, l’ampiezza con cui si disperdono i dati, intervallo di confidenza
empirico per i valori, e la predizione che ha un errore più ampio perché all’errore di predizione si
somma anche l’incertezza del modello.
Stimare l’errore di predizione si può fare in ambito referenziale, se abbiamo un buon modello sui
residui, se riusciamo ad assimilarli a una buona distribuzione.
I comandi che vedremo ora si basano su un’ipotesi di fondo, quella dei valori gaussiani.
Sarà sensato utilizzarli se le ipotesi implicite sottostante siano plausibili.
Quando le cose vanno bene le stime teoriche ed empiriche saranno pressochè simili.
Possiamo valutare la varianza spiegata al modello direttamente:

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)

Se otteniamo 0.14, come interpretiamo questo valore?


Ci aspettiamo che in verticale i valori si addensino intorno allo 0, quanto rari sono i valori molto
diversi ci viene dato dalla kurtosi.
Una misura quantitativa di presenza più rilevante di valori estremi è la kurtosi (il valore 3 è il valore
di riferimento per una distribuzione normale),

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.

u=sample(nrow(Auto),50) //estraggo 50 righe


AutoTr=Auto[-u,] //tolgo le righe relative alle estrazioni
AutoTs=Auto[u,]
AutoTr.lm=lm(Price~.,data=AutoTr) //il modello viene addestrato su questi
soli dati
Plot(AutoTs[,2:3], col=”blue”, pch=16)
AutoTr.lm.p=predict(AutoTr.lm,AutoTs) //facciamo una predizione sui nuovi dati
AutoTr.lm.p
Abline(Autotr.lm.col=”gray”, lwd=2)
Points(Autots[,2], pa.pt, col=”red”, pch=16)
sqrt(mean((Autoa.lm.p-Autob$Price)^2))
Utilizzare gli stessi dati per la previsione non va bene, abbiamo già fatto il meglio possibile con quei
dati. Dobbiamo utilizzare dati differenti da quelli con i quali abbiamo calibrato il modello. Vediamo
adesso l’esempio su un altro dataset, peso e altezza. L’overfitting si ha nel caso in cui il modello
dipenda troppo dai dati utilizzati per la calibrazione, impara troppo bene sulle fluttuazioni
aleatorie dei dati di training.

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:

Notiamo che sono molto stretti rispetto agli altri.


Calcoliamo gli intervalli di previsione:

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:

Avendo delle informazioni aggiuntive, possiamo migliorare il nostro modello?


Vogliamo vedere se le due popolazioni si distinguono oppure no.
Estraggo la colonna del genere e la trasformo in colori:

Le due popolazioni sono abbastanza distinguibili.


Tracciamo la retta e chiediamoci se cambierebbe qualcosa considerando due modelli differenti.
Dividiamo le tabelle per genere e disegniamo la retta di regressione sul modello

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.

Questa tabella ha come informazione un indice corporeo aggiuntivo.


Se provassimo una regressione lienare con questi dati, avremo una varianza spiegata decisamente
bassa. Il tutto cambierebbe nel caso in cui trovassimo delle sotto-popolazioni.
Regressione non lineare
Proviamo a vedere se c’è una struttura nei residui.
Prendiamo dei dati di uscita con dipendenza non lineare, bensì polinomiale, dai dati di ingresso.

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

Non assomigliano per niente a qualcosa di casuale (terza figura).


Passiamo a una regressione non lineare e un modello in cui prendiamo i quadrati dei fattori di
ingresso. Cerchiamo di prevedere il fattore di uscita con due fattori di ingresso, è la prima
regressione multivariata.

z=x^2
pr=lm(y~x+z)
summary(pr)
plot(predict(pr),resid(pr))

Otteniamo un drastico miglioramento della varianza spiegata, da 0.2 a 0.93.

Ci aspettiamo di rivedere i coefficienti ottimi che abbiamo visto nell’equazioni di y prima.


Il coefficiente di x è 4.67 (vicino a 5), di z è 2.98 (vicino a 3) e l’intercetta è 5, molto diversa
quest’ultima da 2, ma il p-value è infatti molto alto.
Guardando i residui, che però son pochi, notiamo che sono abbastanza vicini a 0, alcuni si
allontanano di più e altri di meno.

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)

E’ una serie storica ma proviamo con una regressione.


Valutiamo un modello lineare per l’andamento dell’indice SMI rispetto al tempo.

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

Un modello non lineare più adeguato potrebbe essere quello logaritmico.


Abbiamo degli indici azionari che hanno tipicamente una struttura moltiplicativa, diversa da quella
lineare che è additiva. Analizziamo un modello non-lineare, passando ai logaritmi dei dati.

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.

Regressione lineare multivariata


Abbiamo diversi fattori di ingresso rennal regressione multivariata. Se alcuni fattori di ingresso
influenzano poco il fattore di uscita, possiamo via via togliere i fattori di ingresso meno significativi.
Consideriamo la tabella Cars93, presente nella libreria MASS, standard di R. Per semplicità
riduciamo la tabella:

library(MASS)
Auto<-Cars93[,-c(1,2,3,4,6,7,8,9,10,11,15,16,18,22,23,24,26,27)]

Esploriamo la struttura del dataset ottenuto,

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)

Con il data frame vengono rappresentate tutte le colonne a coppie.


Abbiamo una collinearità nel caso di fattori di ingresso molto correlati.
Poi quantitativamente,

round(cor(Auto),2)

Ce ne sono alcune sufficientemente alte, ma anche alcune molto basse.


Esploriamo le correlazioni in maniera grafica:
library(corrplot)
corrplot(cor(Auto),"square")

Questo strumento ci da una buona indicazione della correlazione numerica a livello grafico, anche
qui simmetrico, richiedo un pacchetto esterno.

A differenza della regressione semplice lineare dobbiamo occuparci delle collinearità.


Dovremo decidere innanzitutto quale sarà il fattore di uscita.
I fattori di ingresso sono quelli controllabili o oggettivi tipicamente, per esempio le caratteristiche
fisiche misurabili possono essere quelli di ingresso, mentre il prezzo potrebbe essere quello di
uscita.
Il prezzo si correla in modo più sostanziale con HorsePower, dai valori di correlazione precedenti.
Potremmo ad esempio indagare sulla sola dipendenza lineare tra Price e Horsepower

ph.lm=lm(Price~Horsepower,data=Auto)
plot(Auto$Horsepower,Auto$Price)
abline(ph.lm)
summary(ph.lm)$r.squared
La varianza spiegata è bassina.

Studiamo invece la dipendenza di Price da tutti gli altri fattori.

Auto.lm=lm(Price~.,data=Auto) //. = tutti gli altri tranne quello di uscita


summary(Auto.lm)

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

Potrebbe essere un’anomalia.


Esaminiamo la possibile aderenza dei residui a una distribuzione Gaussiana attraverso
l’osservazione della densità empirica,

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

e della distribuzione dei quantili.

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

Procediamo eliminando il fattore Weight che ha il p-value più alto.


Auto.lm2=lm(Price~.-Weight,data=Auto) //tutti tranne weight
summary(Auto.lm2)
r[2,]=c(summary(Auto.lm2)$r.squared,summary(Auto.lm2)$adj.r.squared)

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.

Sembrerebbe che il peso non gioca un ruolo significativo.


Continuiamo eliminando anche il fattore EngineSize, è quello col p-value più alto.

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)

Memorizziamo i risultati per ottenere poi una rappresentazione.


La varianza spiegata è invariata, la varianza spiegata aggiustata è aumentata e quindi bene.
Alcuni p-value sono migliorati, come quello di RPM, e ciò suggerisce che c’era una debole
collinearità tra RPM e EngineSize.
In effetti lo ritroviamo a livello di correlazione nei grafici della correlazione.
Eliminiamo anche il fattore Fuel.tank.capacity.

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)

Stavolta abbiamo una varianza spiegata leggermente diminuita, ma di qualche millesimo, e la


varianza spiegata aggiustata è rimasta pressoché la stessa.
Avevamo a che fare con fattori non essenziali, che potevano essere collineari, ma che
rimuovendoli non tolgono nulla.
Eliminiamo anche il fattore Fuel.tank.capacity, che ha un p-value pari al 28% ma elevato rispetto
agli altri.

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)

La varianza spiegata è diminuita, variando di 0.4%. Possiamo considerarla una variazione.


trascurabile, ancor meno sensibili di varianza spiegata corretta. Il p-value di lenght è aumentato.
Eliminiamo ora il fattore Length.

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)

I p-value significativamente alti non ci sono più, non ci aspettiamo collinearità.


Eliminando un fattore che sembra significativo si ottiene un calo della varianza spiegata dell’1.5%.
Non sostanziale, ma accumulando tutti i cali potrebbe essere rilevante.
Non necessariamente fattori correlati sono collineari, quest’ultima si ha tra fattori
significativamente correlati.
Se eliminiamo dal modello uno dei fattori con un p-value basso, notiamo un forte calo.
Il calo sarebbe stato drammatico se avessimo eliminato Horsepower:
Auto.lm7=lm(Price~.-Weight-EngineSize-Fuel.tank.capacity-Length-RPM-Width,data=Auto)
summary(Auto.lm7)
r[7,]=c(summary(Auto.lm7)$r.squared,summary(Auto.lm7)$adj.r.squared)
r

Raggiungendo un calo del 7% circa.


Esaminiamo infine l’andamento di varianza spiegata e varianza spiegata aggiustata nei modelli
esaminati.

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)

Siamo passati al 98.5%.


Procediamo a confrontare i due modelli attraverso una procedura di autovalutazione. In altre
parole, dividiamo il set di dati in una parte utile a generare i modelli di regressione, ed una parte di
test per verificare la bontà delle previsioni di ogni modello.
Estraiamo per ora un set di dati casualmente e creiamo due tabelle, una per il modello logaritmico
e una per il modello non logaritmico.

testset = sort(sample(1860,200)) //dobbiamo fare la previsione usando il tempo, non possiamo


usare il future perchè quando useremo il modello il future non lo
avremo
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)
summary(esm_train.lm)$r.squared
summary(lesm_train.lm)$r.squared

Nel modello standard abbiamo valori simili a quelli precedenti.


Calcoliamo l’errore per i due modelli.
esm_train.lm.p = predict(esm_train.lm,esm_test)
lesm_train.lm.p = predict(lesm_train.lm,lesm_test)
sqrt(mean((esm_train.lm.p-esm_test$FTSE)^2))
sqrt(mean((lesm_train.lm.p-lesm_test$FTSE)^2))

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

Per confronto, visualizziamo le predizioni insieme ai valori veri.

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.

Set di dati con fattori fortemente allineati


CI accorgiamo della collinearità se togliere un fattore cambia drasticamente i p-value di altri, quei
fattori che erano superflui diventano essenziali. Se togliendo fattori i p-value rimangano invariati,
quei fattori non erano essenziali per il modello, erano trascurabili e non è un problema di
collinearità.
Le collinearità aumentano la volatilità dell’errore, è opportuno trovarle. Se troviamo un modello
troppo adatto, rischiamo di cadere in overfitting, soprattutto se adottiamo un modello anche
basandoci sul test di validazione. Se facciamo uso di conoscenze a priori, per es. gli indici azionari
crescono moltiplicatamente, quindi l’ipotesi che il modello logaritmico sia migliore è adatta.
Analizziamo il dataset longley, che riporta una serie di dati economici relativi all’economia
statunitense, mediante regressione multivariata, allo scopo di trovare un modello sintetico e di
risolvere gli allineamenti tra fattori, usando il fattore Employed come fattore di uscita.
Il dataset longley è contenuto nella libreria datasets, caricabile in memoria con il
comando library(datasets).

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)

In effetti ci sono forti correlazioni:

plot(longley)
round(cor(longley),2)
corrplot(cor(longley),"number")

Proviamo a ridurre il modello eliminando GNP.deflator (il fattore con il p-value più alto)

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)

Come prima ma rendendo le varianze differenti

X=rnorm(5000,0,6)
Y=rnorm(5000,0,2)
plot(X,Y,pch=".")

Lungo l’ascissa otteniamo una più alta variabilità.

Cambiamo anche le medie e costringiamo il plot a mostrarsi centrato in 0, così notiamo le


differenze.

X=rnorm(5000,10,6)
Y=rnorm(5000,-8,1)
plot(X,Y,pch=".",asp=1,xlim=c(-10,10),ylim=c(-10,10))

Applichiamo una rotazione del problema, per esempio di 30°:

a=pi/6
U=matrix(c(cos(a),-sin(a),sin(a),cos(a)),2,2)

Definiamo la matrice di rotazione U.


P=matrix(c(X,Y),5000,2)
rotP=P%*%U
plot(P,pch=".",col="blue",xlim=c(-10,30),ylim=c(-15,15))
points(rotP,col="red",pch=".")

Ci aspettiamo delle dispersioni verso certe direzioni con gli assi verso cui si disperde con un certo
angolo.

Vediamo cosa accade in 3 dimensioni, rappresentandoli su uno schermo bi-dimensionale.


Generiamo una nube di punti con distribuzione Gaussiana nello spazio, usando un campionamento
Gaussiano con componenti indipendenti e ugual varianza
Per visualizzare i grafici tridimensionali, useremo la libreria rgl.

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)

Il risultato stampato bi-dimensionale, non ci mostra la varianza su un piano. Possiamo ruotare il


RGL device.
Queste dimensioni non sono indipendenti, dobbiamo guardarle tutte, le quantità cambiano
simultaneamente.
Rendendole indipendenti riduciamo il problema a una sequenza di valutazioni uni-dimensionali.
Esaminiamo il campionamento di dati con distribuzione Gaussiana con componenti indipendenti
ma con varianze differenti.

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)

U*D*U^T dovrà essere uguale a Q, quindi sottraiamo Q.

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

Analisi delle componenti principali


Primo esempio con matrice artificiosamente 2-dimensionale, dove abbiamo 5 fattori ma in cui in
realtà la dimensione effettiva è 2.

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)

Ci viene data la deviazione standard, la proporzione di varianza e la proporzione cumulata di


varianza di ogni componente. La prima e la seconda componente insieme hanno come
proporzione cumulativa un valore pari a 1. Abbiamo catturato la natura bidimensionale dei dati
forniti.
plot(pca)

Le prime due componenti principali catturano praticamente tutta la proporzione di varianza.

Il risultato di plot ci dà le varianze. Scopriamo che è 2-dimensionale ma non dà il grafico


cumulativo. Per avere il grafico delle varianze cumulate:

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)

La tabella richiede chiaramente standardizzazione.


Se abbiamo un numero finito di valori nel plot avremo un andamento a righe.

Con il summary scopriamo che alcune componenti raggiungono un elevata proporzione di


varianza, ha senso fare l’analisi.

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)

Stavolta le frecce sono paragonabili.

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

Ruotando su tre, hp tende più sulla prima componente.

Associamo hp alla prima componente e drat alla seconda.


Concludiamo con una possibile interpretazione:
 1ma comp. (mpg, cyl, disp, hp, wt): caratteristiche legate alla potenza, o al consumo.
 2da comp. (drat, qsec, gear, carb): caratteristiche dotazionali (e prestazionali).
Ora possiamo guardare l’orientamento. La prima componente si allinea positivamente con mpg,
viceversa gli altri si orientano opposti alla prima componente (questa componente più si va verso
sx più si va verso veicoli con prestazioni maggiori ma perdono in consumo).
Tutti i veicoli che vanno verso sx tendono ad avere prestazioni migliori, mentre a dx avranno
probabilmente economie di consumo migliori.
Nella seconda componente, il drat, carb e gear si collocano positivi rispetto al secondo asso
mentre qsec si colloca negativamente.
Possiamo considerare positivo avere più marce, carburatori e quoziente mentre qsec si mette
negativamente, che era il tempo necessario a percorrere un quarto di miglio, più questo tempo è
basso e meglio è.
Possiamo tornare al biplot e capire le varie osservazioni. Honda civic collocata a destra, ha un buon
livello di consumo ma scarsa a livello di dotazione.

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

Notiamo ancora un cluster di oggetti:


Notiamo che alcune rimangano lì mentre altre si sono spostate in alto, le terze componenti le sa
distinguere, dovremmo includere anche la terza componente.
La terza componente era caratterizzata da wt, qsec e carb, tutte in negativo per altro.
Indichiamo con colori differenti auto di provenienza differente.
orig=rep(2,32)
orig[c(1:3,19:21)]=3
orig[c(4:7,15:17,22:25,29)]=1
plot(mt.pca$scores,pch=20,col=orig)
text(mt.pca$scores,labels=as.character(row.names(mt)),col=orig,pos=3,cex=.6)

Abbiamo provenienze di tre tipo, statunitense, giapponese ed europea.


Quelle nere e verdi sono abbastanza raccolte, quelle rosse abbastanza sparse.
Il piano principale ce le rivela senza dubbio. Chiediamoci se questo aspetto poteva emergere senza
l’analisi delle componenti principali.
Dovremo capire rispetto a quali fattori proiettare. Il piano principale ci facilita questo compito.

plot(mt,col=orig)
plot(mt[,c(2,4)],col=orig) and so on

Non è detto che emerge analizzando i fattori due a due.


Il nero si stacca in modo significativo in quasi tutti i grafici.
In un problema di questo tipo, a differenza della regressione lineare, in cui non abbiamo un fattore
di uscita, è difficile verificare la validità del modello.
Proviamo a indagare sulla stabilità del risultato. Prendiamo le osservazioni, ne estraggo una e
faccio un modello di PCA su tutte le osservazioni tranne quella estratta.
mt_s=data.frame(scale(mt))
res=rep(0,32)
for(i in 1:32){
mt_r=mt_s[-i,]
mt_r.pca=princomp(mt_r)
mt_p=predict(mt_r.pca,newdata=mt_s[i,])[1:2]
res[i]=mean((mt_p-predict(mt.pca)[i,1:2])^2)
}
sqrt(mean(res))
hist(res)
mt[31,]

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.

Otteniamo 1.02, la cui interpretazione dipende dai nostri valori.


Quest’istogramma rispetta i valori delle discrepanze tra la posizione che avrebbe dovuto vedere la
osservazione sul nuovo sistema di riferimento e quella che effettivamente ha.

Sono tutte piccole tranne una molto grande.


La PCA fatta senza quella osservazione e confrontata con la posizione che avrebbe avuto
includendola, presenta differenze molto differenti.
Cambia in modo significativo il punto di vista giusto in cui guardare. Per tutti i veicoli tranne uno, il
punto di vista rimane lo stesso, quei veicoli sono omogenei agli altri.
Uno non si adegua alla nostra interpretazione, si tratta dell’osservazione 31.
Confrontando i valori con summary(mt) possiamo vedere se ha valori molto diversi dagli altri.
Notiamo che hp realizza il massimo e ce ne sono poche che hanno hp alto.

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.

Analisi sulla produzione agricola


pa<-read.csv("08prodagri.csv",row.names=1)
head(pa)
round(cor(pa),2)

Facciamo l’analisi sulle correlazioni.

Abbiamo una tabella di valori non completamente correlati e scorrelati.


Il fatto che le correlazioni siano basse si riflette sul grafico nel quale non vediamo comportamenti
lineari.
Svolgiamo l’analisi delle componenti principali.
pa.pca=princomp(pa)
summary(pa.pca)

Sembrerebbe fattibile.
Rappresentiamo il piano principale.
biplot(pa.pca)

Troviamo ancora la presenza di alcune componenti che predominano, ortaggi e cereali.


E’ plausibile l’idea che il fattore di scala giochi un ruolo rilevante.
Osserviamo la presenza di frecce più corte e più lunghe, standardizziamo i dati e verifichiamo la
validità dell’analisi.
pa_s.pca=princomp(scale(pa))
summary(pa_s.pca)
screeplot(pa_s.pca)
plot(cumsum(pa_s.pca$sdev^2)/sum(pa_s.pca$sdev^2),type="b",ylim=c(0,1))
segments(1,0.8,7,0.8,col="red")

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:

L’analisi in due dimensioni si porta solo il 67% della varianza.


Otteniamo una situazione più favorevole per l’interpretazione.
biplot(pa_s.pca)

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

Cereali, frutta, agrumi e olivo sono associabili alla seconda componente.


Per la prima componente possiamo associare Tuberi, Vite, Ortaggi.
La seconda componente ha nella direzione positiva cereali e frutta e in quella negativa, agrumi e
olivo, possiamo pensare a una componente che dice che tipo di coltura fare in base al clima.
La terza componente è di più difficile considerazione, anch’essa sente un effetto combinato di un
po' tutti i fattori precedenti con preferenza per cereali, tuberi, frutta.
Le regioni più settentrionali sono in alto, quelle meridionali sono nella parte inferiore, e poi
abbiamo un agglomerato a sinistra che sono i valori più vicini all’origine e forse meno significativi
dal punto di vista del fenomeno.
La loro produzione agricola forse è bassa oppure abbiamo avuto uno schiacciamento dovuto alla
terza componente.
Proviamo a indagare prima e terza componente:
biplot(pa_s.pca,choices=c(1,3))
Tre di loro visti sulla terza componente sono ben staccati, ma rimane un agglomerato che non si
distingue troppo.
Ciò può voler dire che la produzione è più bassa e quindi anche sulla terza componente non
riusciamo a distinguerle.
Non teniamo però conto del fatto che una regione ha territori diversi ecc.
In termini di quantità, la produzione agricola
Anche il biplot (2,3) non ci dice troppo, il solito agglomerato rimane.
biplot(pa_s.pca,choices=c(2,3))

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

Calcoliamo le coordinate delle osservazioni nel nuovo sistema di riferimento.

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

Le correlazioni sono le seguenti:

o meglio ancora, per evidenziare le differenze tra le specie,


plot(ir,pch=19, col=as.numeric(species)+1)

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

Il biplot mostra frecce di lunghezza disomogenea (l’opzione cex è il character expansion factor, qui


usato per rendere i numeri più piccoli e poter osservare le frecce). Risulta opportuno (come
sempre nell’analisi PCA) standardizzare.

L’analisi delle componenti principali continua ad essere buona.


ir_s.pca=princomp(scale(ir))
summary(ir_s.pca)
plot(cumsum(ir_s.pca$sdev^2)/sum(ir_s.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])

Considerando la tabella dei loadings, abbiamo:

La seconda componente è strettamente catturata dal secondo fattore originario. La prima


componente invece racchiude gli altri tre fattori. E’ un caso semplice che non richiede neanche
ulteriori rotazioni.
Vediamo se il piano principale ci dice qualcosa sulle tre specie. Studiamo gli assembramenti,
utilizzando la variabile categoriale.
levels(species)<-c("red","blue","green3")
ir_s.pt <- predict(ir_s.pca)
layout(t(1:2))
plot(ir_s.pt[,c(1,2)],pch=20,asp=1,col=species)
plot(ir_s.pt[,c(1,3)],pch=20,asp=1,col=species)
layout(1)

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)

Una prima analisi delle correlazioni.


library(corrplot)
corrplot(cor(eu),"number")
plot(eu)

I fattori sono di ordini di grandezza un po' differenti.


A guardarla non si vede una netta dipendenza lineare tra I fattori.
L’analisi delle componenti principali:
eu.pca=princomp(eu)
summary(eu.pca)
Per arrivare a circa l’80% è necessario considerare tre componenti principali.
biplot(eu.pca,cex=.2)

Ancora una volta il biplot rivela la necessità della standardizzazione.


eu.pca=princomp(scale(eu))
summary(eu.pca)

Abbiamo bisogno di 5 componenti per un’analisi soddisfacente.


eu.pca=princomp(scale(eu))
summary(eu.pca)
plot(eu.pca)
plot(cumsum(eu.pca$sdev^2)/sum(eu.pca$sdev^2),type="b",ylim=c(0,1))
segments(1,0.8,9,0.8,col="red")
biplot(eu.pca,cex=.4)

Ci limiteremo alle prime e non arriveremo fino a 5.


Notiamo che quasi tutte le frecce sono pressoché allineate in positivo alla seconda componente
principale.
Qui abbiamo la seconda componente ad essere riassuntiva, anche se è la prima componente è
quella che porta la maggior quantità di varianza.
La componente riassuntiva porta meno varianza e quindi rende più debole il ruolo di componente
riassuntiva.
Abbiamo poi alcuni fattori ortogonali alla componente riassuntiva.
Vediamo se possiamo assegnare alle componenti alcuni fattori.
Healthy_years, education e Industrial_porduction sono abbastanza allineati alla prima
componente, i tre verso l’alto con la seconda mentre gli altri sono abbastanza ortogonali.
Se provassimo a racchiudere tutte le componenti più verticali alla seconda componente, questa
componente sembrerebbe legata all’aspetto economico, mentre ignorando Industrial Production
la prima componente sembrerebbe più legata a fattori sociali.
A prima approssimazione le due componenti riescono a distinguere i fattori sociali ed economici.
Vediamo che succede con i loadings,
eu.ld=loadings(eu.pca)
eu.ld

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

Andando a vedere solo due componenti e ruotandole, avremo l’assegnazione originale ma


tenderei a preferire le tre componenti perché le sole prime due portano troppa poca varianza.
varimax(eu.ld[,1:3])

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

Si può tentare di esplorare anche i piani principali successivi.


biplot(eu.pca,choice=c(1,3),cex=.6)
biplot(eu.pca,choice=c(2,3),cex=.6)
Nel piano (1,3) ci sono delle frecce più corte di altre, ma ciò non significa necessariamente una
disparità di scala, bensì alcune componenti possono non avere un ruolo forte rispetto ad altre su
quel piano. Health e Accident sono comunque allineate alla terza componente, nonostante la
rotazione ci aveva dato informazioni contrastanti. Per gli altri non abbiamo molto aiuto.
Uniamo le considerazioni del biplot e dei loading, visto che ci danno conclusioni non univoche.
1: Education, HealthyYears, IndustrialProduction, TaxesTotal (componente sociale)
2: Employment, Exports, TaxesLabour, GenderPaymentGAP (componente economia/lavoro)
3: Health, Accidents (stato di salute legato al lavoro)
Possiamo vedere, esaminando il piano (1,2), come sono collocate le varie nazioni.

Classificazione mediante regressione


Consideriamo la tabella che contiene i dati (opportunamente elaborati) sulle immagini ricavate
dalla scansione di banconote, al fine di riconoscere le banconote false.
Date tutte le caratteristiche dell’immagine, vogliamo capire quali sono più rilevanti per il
problema. Questa fase di feature selection qui è stata già effettuata.
bn=read.csv("11banknote.csv",header=T)
head(bn)
plot(bn,pch=20,col=1+bn$class)
round(cor(bn),2

Notiamo che le correlazioni non sono troppo elevate.


Essendo il problema binario, possiamo tenerci anche la correlazione con la classe.
È importante vedere il grafico distinguendo la classe coi colori. In nessuna delle figure troviamo
una netta distinzione dei campioni. Nessuno dei fattori sembra avere una netta predominanza.

Ai fini dell’implementazione del modello regressivo elementare, rileggiamo la classe in termini


di ±1. Daremo alla classe 1 le predizioni positive e alla classe -1 quelle negative.
bn$class<-2*bn$class-1
summary(bn)

Proviamo una regressione lineare (classica).


bn.lm=lm(class~.,data=bn)
summary(bn.lm)
Abbiamo una varianza spiegata elevata e dei p-value significativi eccetto quello dell’entropia (a
livello regressivo), infatti è quella per cui i due campioni sono sovrapposti di più.
Quante osservazioni del training set sono corrette? Confrontiamo i valori della classe con quelli
previsti, calcolando l’accuratezza, attraverso una classificazione perentoria,
sum((predict(bn.lm)>0)==(bn$class>0))
sum((predict(bn.lm)>0)==(bn$class>0))/length(bn$class)

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

Il risultato non è statisticamente significativo. Ripetiamo più volte l’esperimento su campioni


casuali (ovvero stimiamo l’errore con l’autovalutazione). Ripetiamo l‘esperimento 50 volte.
acc=rep(0,50)
for(i in 1:50){
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)
acc[i]=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.

½ è la soglia neutra, adatta se le classi sono equamente distribuite.


La curva ROC fa quest’operazione tutta in una volta, ci fa vedere come vanno man mano le cose
per tutte le soglie.
Poiché l’output della regressione logistica è in termini di probabilità, possiamo anche vedere la
curva ROC. In una classificazione {0,1} (dove 1 è positivo, e 0 è negativo) la curva ROC rappresenta
la proporzione di risposte corrette rispetto alla soglia di differenziazione. In ascissa c’è
la specificità, ovvero il tasso di negativi corretti, in ordinata c’è la sensibilità, ovvero il tasso di
positivi corretti,

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.

Problemi di convergenza per la regressione logistica


Abbiamo un problema su come la regressione logistica viene calibrata, nel trovare i coefficienti di
regressione. Usare il metodo di regressione non funziona come abbiamo visto e quindi è
necessario ricorrere alla massima verosomiglianza. Se noi prendiamo un problema di regressione
lineare con l’ipotesi di gaussianità dei residui, i coefficienti della regressione lineare sono anche le
stime di massima verosomiglianza.

Determinare le stime di massima verosomiglianza è un problema non lineare complesso, non


abbiamo una funzione convessa. O la soluzione viene imprigionata in qualche minimo locale o
peggio l’algoritmo che trova il minimo può non riuscire a convergere.
Consideriamo la tabella degli indicatori socio-economici di alcuni paesi europei.
eu<-read.csv("10euecosoc.csv",header=T,row.names=1)

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

La regressione logistica ha problemi di convergenza.


glm(classe~.,family=binomial,data=eu)

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.

Possiamo pensare di cambiare la tolleranza come prima strategia.


Per esempio portandola a 1*e^-0.8.
Altra strategia è che alcuni fattori potrebbero non essere molto rilevanti e potremmo pensare di
ridurre il modello. Vi sono diverse strategie di riduzione.
Una possibile strategia è quella di effettuare una riduzione attraverso la correlazione.
Semplifichiamo il modello selezionando i fattori a più alta correlazione con la ``classe’’,
ovvero Employment, Export, TaxesTotal e Accidents
library(corrplot)
corrplot(cor(eu),"number",tl.cex=0.6,number.cex=0.7)
eu.glm=glm(classe~Employment+Export,family=binomial,data=eu)

Un’altra strategia è quella di utilizzare a regressione lineare, guardando il p-value e rigettando


quelli con p-value alto.
Usando la regressione multivariata si mantengono i fattori Employment, TaxesTotal, Accidents.
summary(lm(classe~.,data=eu))
summary(lm(classe~Accidents+Employment+TaxesTotal,data=eu))
eu.lm.glm=glm(classe~Accidents+Employment+TaxesTotal,family=binomial,data=eu)

Altra strategia di riduzione è quella attraverso PCA.


Possiamo guardare i fattori più allineati alla prima componente, può non significare che contenga
la maggiore variabilità ma che la sua variabilità partecipa a quella componente principale.
Standardizziamo i dati, in vista dell’analisi delle componenti principali.
eus<-as.data.frame(scale(eu[,1:10]))
eu.pca=princomp(eus)

Si può anche scegliere di usare un certo numero di componenti principali.


Usando l’analisi delle componenti principali, la prima componente è principalmente allineata
con Education, HealthyYears e IndustrialProduction Il risultato non è entusiasmante.
loadings(eu.pca)
eu.pca1.glm=glm(classe~Education+HealthyYears+IndustrialProduction,family=binomi
al,data=eu)

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)

Possiamo considerare anche altri fattori.


Se l’algoritmo non converge possiamo ridurre il modello o aumentare la tolleranza (riducendo il
valore di epsilon) cercando di variarli il meno possibile.

Analisi discriminante
Consideriamo il dataset relativo ai passeggeri del Titanic.
tit=read.csv("12titanic.csv",header=T,row.names=1)

 pclass: classe del biglietto,


 survived: sopravvivenza,
 sex: sesso,
 age: età in anni,
 sibsp: numero di figli/coniugi a bordo,
 parch: numero di genitori/figli a bordo,
 fare: costo del biglietto
Consideriamo un modello di analisi discriminante lineare allo scopo di prevedere la sopravvivenza
dei passeggeri e dell’equipaggio del Titanic. CV indica se vogliamo usare la cross-validation.
library(MASS)
tit.lda=lda(survived~.,data=tit,CV=F)

Il summary in questo caso non dà informazioni rilevanti.


Non abbiamo specificato la distribuzione a priori, se non è specificata si assume quella uniforme.
Valutiamo l’efficacia del modello.

Questo modello restituisce anche le probabilità delle classificazioni.


tit.lda.p=predict(tit.lda)
tit.lda.post=tit.lda.p$posterior[,2]
head(tit.lda.post)
sum((tit.lda.post>0.5)==(tit$survived>0.5))/length(tit$survived)
s2_confusion(tit$survived,tit.lda.post)

Abbiamo indovinato il 79% dei casi.


I non sopravvissuti sono maggiori e inoltre presentano una maggiore accuratezza.
tit.lda.roc=s2_roc(tit$survived,tit.lda.post)
s2_roc.plot(tit.lda.roc)

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)

Abbiamo quindi la matrice di confusion, la curva ROC e l’AUC.


Esaminiamo anche il risultato dell’analisi discriminante quadratica.
tit.qda=qda(survived~.,data=tit,CV=F)
tit.qda.p=predict(tit.qda)
tit.qda.post=tit.qda.p$posterior[,2]
sum((tit.qda.post>0.5)==(tit$survived>0.5))/length(tit$survived)
s2_confusion(tit$survived,tit.qda.post)

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)

Usiamo adesso il modello di regressione logistica.


tit.glm=glm(survived~.,family=binomial,data=tit)
summary(tit.glm)
Questo fornisce un summary interessante visto che sotto vi è il motore della regressione lineare.
I fattori pclass, sex, age sono i più significativi per decidere sulle possibilità di sopravvivenza.
Volendo si possono trascurare i fattori che sembrano meno significativi. Non è ovvio se questo dia
un beneficio visto che è l‘output della regressione lineare sottostante la regressione logistica. La
funzione logistica schiaccia alcuni valori rendendoli paragonabili.
summary(glm(survived~.-parch,family=binomial,data=tit))
summary(glm(survived~.-parch-fare,family=binomial,data=tit))

Esaminiamo accuratezza e matrice di confusione.


tit.glm.p=predict(tit.glm,type="response")
sum((tit.glm.p>0.5)==(tit$survived>0.5))/length(tit$survived)
s2_confusion(tit$survived,tit.glm.p)

Confrontiamo le curve ROC e le aree sotto la curva.


tit.glm.roc=s2_roc(tit$survived,tit.glm.p)
s2_roc.plot(tit.qda.roc,col="blue")
s2_roc.lines(tit.lda.roc,col="green3")
s2_roc.lines(tit.glm.roc,col="red")
s2_auc(tit.glm.roc)

Le curve sono paragonabili e i risultati sono simili.


Si pone in mezzo ai due, tutti e 3 viaggiano sullo stesso tipo di risultato.
I metodi sono assolutamente paragonabili.
A differenza della regressione lineare, qui abbiamo dati categorici e quindi o indoviniamo o
sbagliamo. Con dati numerici conta anche quanto sbagliamo, la previsione numerica permette
anche una stima dell’incertezza o un intervallo di confidenza.
Le probabilità di uscita ci da sì un valore d’incertezza ma è completamente differente dal grado di
incertezza basato sui residui visto.
Stabiliamo attraverso l’autovalutazione il miglior classificatore, valutiamo l’errore dei metodi
mettendoli ala prova su nuovi dati.
l=length(tit$survived)
acc=matrix(0,100,3)
for(i in 1:100){
idx=sample(l,100)
titcv=tit[-idx,]
tit.lda=lda(survived~.,data=titcv)
tit.lda.p=predict(tit.lda,tit[idx,])$posterior[,2]
acc[i,1]=sum((tit.lda.p>0.5)==(tit$survived[idx]>0.5))/100
tit.qda=qda(survived~.,data=titcv)
tit.qda.p=predict(tit.qda,tit[idx,])$posterior[,2]
acc[i,2]=sum((tit.qda.p>0.5)==(tit$survived[idx]>0.5))/100
tit.glm=glm(survived~.,family=binomial,data=titcv)
tit.glm.p=predict(tit.glm,tit[idx,],type="response")
acc[i,3]=sum((tit.glm.p>0.5)==(tit$survived[idx]>0.5))/100
}
# lda
mean(acc[,1])
sd(acc[,1])
# qda
mean(acc[,2])
sd(acc[,2])
# reg. logistica
mean(acc[,3])
sd(acc[,3])

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.

Possiamo anche guardare l’istogramma dell’accuratezza e vedere la differenza.

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.

Inoltre, possiamo capire il numero di estrazioni più stabile.


Notiamo che le accuratezze sono leggermente aumentate.
Se al variare di n raggiungiamo un risultato pressoché costante abbiamo catturato il valore più
stabile.
Al variare di n potremo rappresentare la stima di accuratezza così da capire quanto si stabilizza.
Possiamo anche valutare eventuali altre soglie differenti.
Proviamo a implementare un metodo di previsione che si basa solo sul genere.

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)

Dal summary possiamo notare che il dataset è bilanciato.


Mettendo probabilità a priori sulle classi differenti potremmo avere risultati differenti, come
ricordiamo dalla formula bayesiana.
Usiamo una distribuzione a priori uniforme.

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.

Ci dà un’idea di cosa abbiamo sbagliato.


Una modalità per utilizzare la regressione logistica anche nel caso multi-classe è di confrontare
ogni classe con tutte le altre, perché abbiamo delle probabilità di uscita e conviene, e poi scegliere
per ogni osservazione la classe più probabile.
Valuteremo se l’osservazione appartiene a una classe prefissata, oppure alle altre, quale che esse
siano. Esaminiamo la regressione logistica per classificare setosa.
ir=iris[,1:4]
ir$class<-as.numeric(iris$Species=="setosa")
set.glm=glm(class~.,family=binomial,data=ir)
set.p=predict(set.glm,type="response")

Regressione logistica per classificare versicolor


ir$class<-as.numeric(iris$Species=="versicolor")
ver.glm=glm(class~.,family=binomial,data=ir)
ver.p=predict(ver.glm,type="response")

Regressione logistica per classificare virginica, 1 per virginica e 0 altrimenti.


ir$class<-as.numeric(iris$Species=="virginica")
vir.glm=glm(class~.,family=binomial,data=ir)
vir.p=predict(vir.glm,type="response")

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.

Il risultato non è soddisfacente.


Proviamo con il metodo partition around medoids.
library(cluster)
pm=pam(dati,3)
plot(dati,col=pm$cluster,pch=20)

Questo metodo cattura i 3 macro-gruppi.


Ciò significa che abbiamo bisogno di fare girare k-means più volte come abbiamo visto, e non che
non funziona.
Il problema è che l’algoritmo di k-means spesso cattura minimi locali che non sono minimi assoluti.
La soluzione è far girare l’algoritmo più volte, e scegliere il risultato migliore.
km=kmeans(dati,3,nstart=25)
plot(x,y,col=km$cluster,pch=20)
Anche k-means ha catturato i 3 gruppi.
Implementiamo un confronto quantitativo. Analizziamo una tabella con entrambi i metodi (pam
e kmeans) e valutiamo la variabilità della consistenza numerica del cluster più numeroso.
A=matrix(rnorm(6*234),234,6)
res=rep(0,1000)
for(i in 1:1000){
res[i]=max(kmeans(A,3)$size)
}
mean(res)
sd(res)
for(i in 1:1000){
res[i]=max(pam(A,3)$clusinfo[,1])
}
mean(res)
sd(res)

Esaminiamo la silhouette. Informazioni essenziali:


 il valore medio totale della silhouette,
 la possibilità che singoli individui siano malclassificati.

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

Esaminiamo l’andamento della silhouette media al variare del numero di cluster.

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)

Cerchiamo (eventualmente) una conferma per via della silhouette media globale.

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.

wss=rep(0,10) //prendiamo 10 come limite superiore a caso


for(k in 2:10){
wss[k]=kmeans(us,k,nstart=10)$tot.withinss
}
plot(2:10,wss[2:10],type="b",pch=20)

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)

Esaminiamo la silhouette nei casi k=2,3,4,5.


layout(matrix(1:4,2,2))
plot(silhouette(kmeans(us,2,nstart=10)$cluster,dist(us)))
plot(silhouette(kmeans(us,3,nstart=10)$cluster,dist(us)))
plot(silhouette(kmeans(us,4,nstart=10)$cluster,dist(us)))
plot(silhouette(kmeans(us,5,nstart=10)$cluster,dist(us)))
layout(1)
Le silhouette più alte si hanno per k = 2,4 anche se k = 3 non è molto distante.
k = 2 dà dei buoni risultati, non dà una forte disparità nei valori che invece si ha per k = 4 con quel
0.27.
Notiamo per k = 3, la stessa cosa di k = 4 e un dato misclassificato.
Per k = 5 otteniamo i cluster 5 con valore molto basso e osservazioni con valori negativi, possiamo
metterlo di parte.
Cerchiamo di non avere troppi clusters in relazione al numero di osservazioni per non
frammentare troppo le osservazioni. Un numero basso di cluster porta a un risultato non
scadente se abbiamo clusters vicini.
Tra 3 e 4 è preferibile 4 perché il dato malposto lo è più per 3 che per 4.
Per k = 2 e k = 4 cerchiamo di capire cosa accade.
Non rimane che esaminare come le osservazioni (ovvero gli stati) sono partizionati dall’algoritmo.
k=4 (sinistra)
us.km=kmeans(us,k,nstart=10)
us.pca=princomp(us)
plot(us.pca$scores,col=1+us.km$cluster,pch=20)
points(predict(us.pca,us.km$centers),col=2:(k+1),pch=19)
text(us.pca$scores,labels=as.character(row.names(USArrests)),col=1+us.km$clust,p
os=3)
k=2 (destra)
us.km=kmeans(us,k,nstart=10)
us.pca=princomp(us)
plot(us.pca$scores,col=1+us.km$cluster,pch=20)
points(predict(us.pca,us.km$centers),col=2:(k+1),pch=19)
text(us.pca$scores,labels=as.character(row.names(USArrests)),col=1+us.km$cluster
,pos=3)
Nel caso di k = 4, non vediamo una distinzione geografica.
Proviamo a riconsiderare l’andamento dei fattori alla luce del clustering ottenuto.
parcoord(us,col=us.km$cluster)

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)

Analizzando il grafico, esaminiamo in dettaglio la silhouette nei casi k=2,3,4,5 come prima.


k=4
layout(t(1:2))
plot(pam(us,k))
layout(1)

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.

La PCA ci dice se leggendo i cluster in termini di componenti principali ci fornisce maggiori


informazioni.
L‘interpretazione si basa sui fattori e non sulle osservazioni, come nella PCA.
L’idea è di capire se i cluster hanno alcune caratteristiche relative ai fattori.
Se i fattori son troppi, possiamo pensare di effettuare una riduzione dei fattori.
Metodi gerarchici
Esaminiamo il dataset iris. Possiamo utilizzare differenti distanze per comporre il dendrogramma.
Se x=(x1,x2,…,xp) e y=(y1,y2,…,yp), la distanza del massimo è definita come

La distanza manhattan invece è definita come

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.

ir.hc=hclust(d) # complete linkage


# ir.hc=hclust(d,method="single") # single linkage
# ir.hc=hclust(d,method="average") # average linkage

Rappresentiamo il dendrogramma.
plot(ir.hc,hang=-1,cex=0.3)

Tagliando ad altezze differenti avremmo un numero di clusters differenti.


Valutiamo la silhouette al variare del numero di cluster (attraverso tagli ad altezze opportune
dell’albero).
library(cluster)
as=rep(0,10)
for(i in 2:10){
ir.cut=cutree(ir.hc,i)
as[i]=mean(silhouette(ir.cut,d)[,3])
}

plot(2:10,as[2:10],type="b")

2 e 3 hanno una buona silhouette, mentre poi si va a calare.


Quante specie sono state correttamente individuate?
table(ir.km$cluster,iris$Species)

Vediamo i casi 2,3,4.


ir.cut=cutree(ir.hc,2)
plot(silhouette(ir.cut,dist(ir)),col=heat.colors(3),border=par("fg"))

Neanche il caso 2 è particolarmente soddisfacente, ci sono diversi valori negativi.


Nel caso 4 la situazione non è male.
Con 5 si divide il cluster più grande e così via.

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)

Il dendogramma è completamente diverso dal precedente.


Infine, rappresentiamo graficamente la suddivisione sul piano principale.
ir.pca=princomp(ir)
plot(ir.pca$scores,col=1+ir.cut,pch=20)
points(ir.pca$scores,col=1+as.integer(iris$Species),pch=1)
text(ir.pca$scores,labels=as.character(iris$Species),col=1+as.integer(iris$Speci
es),pos=3,cex=0.5)

I cluster che contengono un singolo elemento non sono mai adatti.


Può accadere che alcune osservazioni siano molto diverse dalle altre, ma tipicamente ciò emerge
quale che sia il metodo di clustering utilizzato.
Queste osservazioni possono essere riconosciute come anomale.
Se esaminiamo la silhouette media globale qui è molto diversa.
ir.cut=cutree(ir.hc,2)
plot(silhouette(ir.cut,d),col=heat.colors(2),border=par("fg"))

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.

Potremmo pensare anche di cambiare la distanza ad average linkage.


Oppure possiamo cambiare la distanza da usare tra le osservazioni.
Potremmo esaminare la distanza euclidea e prenderne il quadrato che tende ad allontanare valori
lontani e a ravvicinare valori vicini.
d<-dist(ir)^2
d<-dist(ir, method=”maximum”)
d<-dist(ir, method=”manhattan”)

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.

Usiamo innanzitutto metodi a punto prototipo.


Esaminiamo preliminarmente i risultati di partitions around medoids.

2 sembrerebbe essere un buon risultato, ma anche 3 o 4.


Esaminiamo la silhouette per valori di k>2.
k=3
rep.pam=pam(rep,k)
plot(silhouette(rep.pam),col=heat.colors(3),border=par("fg"))

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.

Il metodo single fornisce un output differente, ma che si rivela meno interessante dal punto di


vista del rilevamento di cluster sostanziali.
Lo stato più diverso sembra essere il South Carolina (a sinistra).
table(cutree(rep.hcs,9))

Abbiamo moltissimi casi di clusters singoli.


Se diminuisco, uno a uno si fondono al cluster più grande.

Ogni singola osservazione fa caso a sé.


Esaminiamo la silhouette globale per il metodo average.
as=rep(0,20)
for(i in 2:20){
rep.cut=cutree(rep.hca,i)
as[i]=mean(silhouette(rep.cut,d)[,3])
}
plot(2:20,as[2:20],type="b")
La silhouette parte dall’alto, cala per poi stabilizzarsi per valori grandi. 
Un risultato simile si ottiene per complete, che ci dà un’ottima informazione di stabilità del
problema. Forse i valori 2,3,4 sono i valori più approprati.
Per rappresentare i punti sul piano principale, per semplicità, omettiamo righe e colonne che
presentano valori non disponibili.
nostate=c(2,3,11,31,36,41,50)
rep.pca=princomp(na.omit(rep[-nostate,11:31]))
round(cumsum(rep.pca$sdev^2)/sum(rep.pca$sdev^2),2)
# per k=3,4 poco interessante
k=2
rep.cut=cutree(rep.hca,k)
plot(rep.pca$scores,col=rep.cut[-nostate],pch=20)
Se esaminiamo cosa accade per 3,4,5 e così via abbiamo che uno dei valori si scompone dal cluster
principale.
Potremmo provare a cambiare distanze per vedere se questa situazione permane anche
cambiando distanze oppure possiamo cercare di interpretare il risultato.
Per formulare una interpretazione dei cluster esaminiamo l’andamento dei fattori. Risultati simili
valgono anche per il metodo average:
parcoord(rep,col=rep.cut)

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)

Vedendo per quali indici abbiamo l’appartenenza al primo cluster.

Sono esattamente gli stati del sud-est.


Con due metodi differenti otteniamo all’incirca lo stesso cluster.

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)

Visualizzazione di base di serie storiche. Consideriamo un dataset già visto.


Nel caso di serie storiche multiple. I colori possono permettere di distinguere le serie.
library(datasets)
class(EuStockMarkets)
eu.FTSE = EuStockMarkets[, "FTSE"]
plot(eu.FTSE)
plot(EuStockMarkets)
ts.plot(EuStockMarkets)
ts.plot(EuStockMarkets, col = c("red", "blue", "green3", "black"))

Se una sequenza è già una serie storica, il plot instanzia già il ts.plot.

Andiamo a vedere ‘effetto della funzione di autocorrelazione.


Esaminando la serie da 1 a 100 abbiamo una progressione aritmetica quindi facendo dei lag, le due
serie aritmetiche devono essere linearmente dipendenti una dall’altra, caso in cui la correlazione
deve essere +-1.
Ci aspettiamo di vedere una funzione di autocorrelazione piatta, ma per come viene calcolata da R
si divide per un termine che rende man mano più piccolo il valore.
c = rep(0, 99)
for (i in 0:98) {
c[i + 1] = cor(1:(100 - i), (i + 1):100)
}
c

L’autocorrelazione è sempre 1 in questo caso.


Ma nel caso della acf notiamo comunque un decadimento (dovuto alla modalità con cui il software
calcola la acf):
acf(1:100)
acf(1:100, plot = FALSE)

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

Noi siamo interessati al primo picco positivo a 0, interessante per il problema.

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.

Se intensifichiamo la componente stagionale (aumentando a) otteniamo il grafico a destra.


Il trend spesso nasconde la componente stagionale.
Visualizziamo la funzione di autocorrelazione di campioni aleatori e vediamo cosa fa il rumore:

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.

Introduzione alla decomposizione


Consideriamo la decomposizione di serie storiche generate, per illustrare il risultato della
decomposizione su serie le cui componenti sono già note.
Consideriamo una serie con pura componente trend, senza stagionalità e senza rumore.
T = ts(1:100)
plot(T)
acf(T)
plot(decompose(T)

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.

Otteniamo plottando la decomposizione: la componente osservata, la componente trend estratta,


la componente stagionale estratta e la componente di rumore.
La serie 1:100 è chiaramente trend ma la componente stagionale misteriosamente appare.
Il suo ordine di grandezza è però di 10^-15, la parte di errore random compensa questi valori così
da ottenere rette dritte.
Se paragonabile al rumore, possiamo considerare che non ci sia; viene assorbita dal rumore.
Il comando decompose chiede una serie stagionale, altrimenti non ha senso decomporre e hanno
bisogno di conoscere il periodo della stagionalità.
Se definiamo un periodo fittizio, perché sappiamo che non c’è stagionalità, avremo un valore
molto basso.
Se aggiungiamo del rumore, parte del rumore viene interpretato come trend perché le medie
mobili non filtrano in modo perfetto il rumore e parte di essa viene interpretata come stagionalità
e quel che rimane è rumore.
T = ts(1:100 + 10 * rnorm(100), frequency = 10)
plot(T)
acf(T)
plot(decompose(T))

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

Vediamo una serie con stagionalità, ma senza rumore.


T = ts(1:100 + 10 * sin((pi * 1:100)/5), frequency = 10)
plot(T)
acf(T)
plot(decompose(T))
L’abbiamo decomposta bene, il rumore è di scarso valore in termini di ordine di grandezza.
a frequenza impostata sulla serie cambia la decomposizione. Proviamo con una impostazione
differente (e non adeguata al problema). Notare che la serie non cambia, ma la sua
decomposizione si.
T = ts(1:100 + 10 * sin((pi * 1:100)/5), frequency = 17)
acf(T)
plot(decompose(T))

La acf non richiede un periodo, ma se la ha cambia unità di misura.

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.

Sappiamo che la presenza della stagionalità è certa anche dall’ACF.

Sia l’acf che la decomposizione ci danno la risposta giusta.


La componente stagionale (destra) è più visibile se amplificata.
Ts = 30 * sin((pi * 1:100)/5)
Tr = 5 * rnorm(100)
T = ts(1:100 + Ts + Tr, frequency = 10)
plot(T)
acf(T)
plot(decompose(T))

Confrontiamo le componenti originali (blu) con le componenti estratte da decompose (rosse).


T.d = decompose(T)
layout(c(1:3))
ts.plot(1:100, T.d$trend, col = c("blue", "red"))
ts.plot(Ts, T.d$seasonal, col = c("blue", "red"))
ts.plot(Tr, T.d$random, col = c("blue", "red"))
layout(1)
Sia la serie dei rumori che quelle del trend sono più corte, perché pezzo iniziale e finale sono
scartati per fare la media mobile.
Consideriamo una serie moltiplicativa con stagionalità
Ts = 1 + sin(1:100 * pi/5)
Tr = 1 + rnorm(100)
T = ts(1:100 * Ts * Tr, frequency = 10)
plot(T)
acf(T)
plot(decompose(T))

La stagionalità è comunque visibile. Ma la decomposizione non funziona bene, trend e stagionalità


sono scorretti e il rumore aumenta.
Verifichiamo se il modello moltiplicativo migliora la decomposizione.
T.dm = decompose(T, "multiplicative")
plot(T.dm)
I risultati non sono comunque ottimi, confrontandoli:

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)

Decomposizione di serie storiche


Il fine della decomposizione è capire se vi è una componente stagionale, se vi è un trend e
l’ampiezza del rumore.
Vediamo una serie caratterizzata principalmente da trend.
tp.data <-
read.csv("https://data.giss.nasa.gov/gistemp/graphs/graph_data/Temperature_Anoma
lies_over_Land_and_over_Ocean/graph.csv",
header = F, stringsAsFactors = F)
tp.data <- as.numeric(tp.data[3:141, 4])
tp.data <- ts(tp.data, frequency = 1, start = 1880)
head(tp.data)
ts.plot(tp.data)
tp = ts(tp.data, frequency = 1, start = c(1880))
ts.plot(tp)

Carichiamo i dati come serie storica, proviamo una decomposizione con un periodo fittizio 1.
Dobbiamo capire se la serie presenta stagionalità.

Abbiamo un primo trend discendente e poi un trend ascendente.


Lo vediamo anche dalla acf.
acf(tp)
acf(tp, 60)

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.

Riconsideriamo la serie sugli indici azionari


ftse = EuStockMarkets[, 4]
start(ftse)
end(ftse)
frequency(ftse)
# ts(as.vector(ftse),frequency=260,start=c(1991,130),end=c(1998,169))

Questa è già una serie storica.


start e end ci dicono quando inizia e finisce.
frequency ci dà il periodo dal 130’ di 1991 al 169 del 1998.
Abbiamo 260 che sono i giorni dell’anno al netto di sabato e domenica, giorno in cui la borsa è
aperta.
SI parte dal 130’ giorno di apertura del 1991.
Indaghiamo sulla presenza di stagionalità.
acf(ftse) //non lo vediamo tutto, vediamola più in là almeno fino al periodo
acf(ftse, 300)

Non si capisce se vi è una stagionalità, vediamo al netto del trend.

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

La componente stagionale è ingannevole, basta guardare le scale.


Anche in confronto con la componente di trend.
plot(ftse.d$trend, col = "blue", ylim = c(-500, 5500))
lines(ftse.d$random)
lines(ftse.d$seasonal, col = "red")
Esaminiamo le temperature dell’aria di Nottingham, dal 1920 al 1930
start(nottem)
end(nottem)
frequency(nottem)
plot(nottem)
acf(nottem)

L’acf ci indica stagionalità, e proprio in corrispondenza del periodo c’è un picco.


Lo stesso in due periodi se estendiamo la scala.
La stagionalità è corretta nel periodo, sembra esserci una stagionalità netta.

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

La stagionalità è presente, passiamo alla decomposizione.


Visualizziamo delle bande di confidenza empiriche, ad esempio a ± una deviazione standard
empirica.
Al posto di usare le bande di confidenza empiriche potremmo anche usare i quartili empirici.
Alternativamente si può guardare ai quartili. In tal caso si può usare la rappresentazione standard
del boxplot.
boxplot(t(m_nt), pch = "*")

Ritroviamo la stessa struttura vista prima in ogni caso.


Decomponiamo la serie.
nt.d = decompose(nottem)
plot(nt.d)
Abbiamo un trend che viene filtrato all’inizio e quindi presenta un po' di rumore in più.
Poi viene catturata la stagionalità, che viene filtrata due volte e quindi ha un andamento più
robusto.
Confrontiamo l’andamento medio ricavato prima con la stagionalità estratta dal decompose.
plot(nt.m, pch = 20, type = "b", ylim = range(c(nt.m - nt.sd, nt.m + nt.sd)))
arrows(1:12, nt.m - nt.sd, 1:12, nt.m + nt.sd, length = 0.02, angle = 90,
code = 3, col = "green3")
points(nt.m + nt.sd, type = "b", pch = 20, col = "gray")
points(nt.m - nt.sd, type = "b", pch = 20, col = "gray")
lines(nt.d$figure + mean(nottem), col = "red")

In rosso abbiamo il termine della decomposizione, mentre in nero abbiamo l’andamento nero
trovato prima.

Vediamo un problema in cui bisogna decidere la giusta decomposizione da fare.


Esaminiamo la serie riguardante i passeggeri di voli internazionali tra il 1949 e il 1960.
start(AirPassengers)
end(AirPassengers)
frequency(AirPassengers)
plot(AirPassengers)

Abbiamo un trend di crescita e sembra esserci una stagionalità, vediamo se è stagionalità o


rumore.
Al crescere del valore della serie aumenta anche la variabilità, tipico comportamento da serie
moltiplicativa.
Valutiamo la stagionalità, evidente e non mascherata dal trend.
acf(AirPassengers, 30)
acf(diff(AirPassengers), 30)

C’è un picco al periodo 12, al secondo periodo e così via.


La stessa conferma otteniamo con diff. A mezzo periodo non c’è il picco negativo.
La periodicità sembra esserci, chiaro andamento temporale dovuto al trend.
Per valutare al netto del trend calcoliamo ogni anno la media dei valori e la sottraiamo.
par(bg = "black")
m_ap = matrix(AirPassengers, 12, 12)
ts.plot(m_ap, col = heat.colors(12))
ts.plot(scale(m_ap, scale = F), col = heat.colors(20))
lines(rowMeans(scale(m_ap, scale = F)), lwd = 3, col = "white")
par(bg = "white")

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)

Analogo risultato dato dal boxplot. Iniziamo a decomporre esaminando la decomposizione


additiva.
ap.da = decompose(AirPassengers)
plot(ap.da)
Osserviamo dei coni nei residui, valori piccoli nel mezzo che si amplificano poi.
Sbagliamo a sinistra e a destra, il rumore contiene troppa struttura.
Se prendiamo la funzione di autocorrelazione della componente random vediamo una struttura
stagionale.

Vediamo la decomposizione moltiplicativa.


ap.dm = decompose(AirPassengers, type = "multiplicative")
plot(ap.dm)

La componente dei residui ha una struttura meno pronunciata.


Il trend nei due casi è sempre lo stesso, come sappiamo che viene preso allo stesso modo, non va
evidenziato.

plot(ap.da$trend, col = "blue")


lines(ap.dm$trend, col = "red")

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

Notiamo ora due valori paragonabili.


Analoghe considerazioni si possono fare per la componente rumore.
plot(ap.da$random)
lines(mean(ap.dm$trend, na.rm = TRUE) * (ap.dm$random - 1), col = "red")
Rispetto al modello additivo le ampiezze sono rimaste comparabili, ma il rumore ora ha
meno struttura.

Dobbiamo decidere quale decomposizione funziona meglio.


Un primo indizio riguarda i residui, che devono essere abbastanza casuali e devono aver perso la
loro natura di serie.
Trend, serie e stagionalità sono una serie storica mentre i residui devono aver perso la natura
temporale, non devono essere per esempio maggiori ad Agosto e minori a Dicembre.
Possiamo utilizzare la funzione acf come nuovo strumento per cercare struttura nei residui.
Nel caso additivo e moltiplicativo rimane una struttura temporale.

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.

Nel caso additivo abbiamo una chiara struttura temporale rimasta.

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

var(ap.dmr)/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)))

Rimane comunque bassa.


Analisi delle frequenze e confronto con la distribuzione normale, vediamoli solo per dar.
Sovrapponiamo la densità gaussiana su quella empirica e non è troppo distante.
layout(t(1:2))
hist(ap.dar, 20, freq = F)
lines(density(ap.dar), col = "blue")
lines(sort(ap.dar), dnorm(sort(ap.dar), mean(ap.dar), sd(ap.dar)), col = "red")

qqnorm(ap.dar)
qqline(ap.dar)
layout(1)
shapiro.test(ap.dar)

I residui non sono gaussiani ma neanche troppo distanti dall’esserlo.


A noi interessa per lo più se sono casuali più che gaussiani.
Da una parte abbiamo residui non troppo male, dall’altra parte sia la rappresentazione grafica e
sia l’acf ci dicono di avere una struttura temporale netta.
L’indice sull’asse delle ascisse veicola un’informazione importante, se li permutassimo otterremo
una struttura casuale.
plot(sample(1:length(ap.dar)), ap.dar, pch = 20)

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.

Abbiamo un trend iniziale positivo e poi in calo.


La funzione di acf ci informa della presenza di stagionalità.

Confrontiamo i diversi periodi.


m_uk = matrix(uk, 12, 16)
par(bg = "black")
ts.plot(m_uk, col = heat.colors(16))
ts.plot(scale(m_uk, scale = F), col = heat.colors(16))
par(bg = "white")

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

Possiamo confrontare il trend di stl con il trend di decompose.


plot(decompose(uk)$trend, col = "blue")
lines(stl(uk, 7)$time.series[, 2], col = "red")

Confrontiamo la stagionalità di stl con la stagionalità di decompose


plot(decompose(uk)$seasonal, col = "blue")
lines(stl(uk, 7)$time.series[, 1], col = "red")

Dobbiamo capire stagionalità stazionaria o non stazionaria e decomposizione additiva o


moltiplicativa.
E’ molto più importante la seconda cosa, per esempio considerando il metodo di HW.

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.

In nero abbiamo la serie originale, in rosso la previsione.

Se aumentiamo alpha, tendermo a seguirla di più.

Con un valore molto piccolo sarà all’incirca il valore medio.


Passiamo a esaminare la serie esempio sulle anomalie di temperatura, che contiene dati con trend
dominante. ( ci vorrebbe SET ma proviamo SE)
tp.data <- read.csv("13anomalie.csv")
tp = ts(tp.data, frequency = 1, start = 1880)
tp.se = HoltWinters(tp, beta = F, gamma = F)
tp.se$alpha
plot(tp.se)

alpha ottimale è 0.8, infatti segue abbastanza bene la serie.


Se riteniamo che le fluttuazioni siano rumore e vogliamo catturare il trend, potremmo pensare di
ridurre alpha così da seguire meno la serie.
Proviamo a variare il valore del parametro α∈{1,0.9,0.7,0.6,0.5,0.4,0.3,0.2,0.1}
plot(tp.se)
points(HoltWinters(tp, alpha = 1, beta = F, gamma = F)$fitted, col = "blue",
type = "l")
points(HoltWinters(tp, alpha = 0.9, beta = F, gamma = F)$fitted, col = "blue",
type = "l")

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

Rapidamente le due analisi con lo stesso alpha vanno a coincidere.


Cambiare la condizione iniziale è ininfluente.
Per fare una predizione con smorzamento esponenziale:
plot(tp.se, predict(tp.se, 1))
plot(tp.se, predict(tp.se, 12)) # inutile prevedere 12 valori, ne basta uno
predict(tp.se, 1)

E’ inutile prevedere più di un valore perché rimane la stessa previsione con lo SE.

Smorzamento esponenziale con trend


Includiamo il trend nella analisi. Annulliamo solo gamma.
tp.set = HoltWinters(tp, gamma = F)
tp.set
plot(tp.set)
tp.set$alpha
tp.set$beta

L’analisi segue abbastanza bene la serie, alpha = 1 e beta è quasi a 0.


E’ una serie molto fedele, da un punto di vista di coefficiente angolare è molto conservativa.
Potremmo cercare un valore migliore, dobbiamo variare due parametri.
Decidiamo di visitare i diversi valori di alpha.
Proviamo a variare direttamente i valori dei parametri. Prima α∈{0.9,0.8,0.7,0.6,0.5} con lo stesso
beta, e per poi fissare quelli di β∈{0.1,0.2,0.3,0.4,0.5}.
plot(tp.set)
points(HoltWinters(tp, alpha = 0.9, beta = tp.set$beta, gamma = F)$fitted,
col = "blue", type = "l")
plot(tp.set)
points(HoltWinters(tp, alpha = tp.set$alpha, beta = 0.1, gamma = F)$fitted,
col = "blue", type = "l")

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

Proviamo una ``mini’’ regressione lineare per valutare questi valori.


x = 1:20
coefficients(lm(tp[1:20] ~ x))
plot(HoltWinters(tp, gamma = F, l.start = -0.08, b.start = -0.005))

Perchè l’output è:

Abbiamo ancora la possibilità di calibrare α e β.


tp.set = HoltWinters(tp, gamma = F, l.start = -0.08, b.start = -0.005)
c(tp.set$alpha, tp.set$beta)
plot(HoltWinters(tp, gamma = F, l.start = -0.08, b.start = -0.005, alpha = 0.2,
beta = 0.1))

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)

I valori ottimali sono:

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.

Scopriamo che i valori ottimali non sono troppo male.


Se abbiamo osservato che il rumore era poco intenso conviene seguire la serie.
In basso troviamo una sovrastima in alcuni casi e in alto una sottostima, non è quindi perfetta.
Possiamo anche qui cambiare le condizioni iniziali e in questo caso fanno drastiche differenze.
Abbiamo quindi 5 parametri opportuni, e sono troppi.
Per i valori iniziali possiamo usare una regressione lineare:

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)

Esploriamo i risultati confrontandoli con quelli della decomposizione.


nt.stl = stl(nottem, "periodic")
# confronto grafico (delle intercette) con il trend di stl
ts.plot(nt.stl$time.series[, 2], nt.hw$fitted[, 2], col = c("black", "red"))
# confronto grafico con la stagionalità di stl
ts.plot(nt.stl$time.series[, 1], nt.hw$fitted[, 4], col = c("black", "red"))

Proviamo a ottenere una previsione.


Nello SE abbiamo come previsione 1 valore, nello SET abbiamo intercetta e direzione, due
informazioni e quindi possiamo anche avere la previsione di 2 valori. Abbiamo come previsione
una linea con lo stesso coefficiente angolare.
Trovata l’analisi migliore possiamo fare la previsione.
Con Holt Winters prevediamo l’intero periodo:
layout(t(1:2))
plot(nt.hw, predict(nt.hw, 12), main = "Previsione a 12 mesi")
plot(nt.hw, predict(nt.hw, 24), main = "Previsione a 24 mesi")
layout(1)
Se prevediamo un periodo più lungo, non otteniamo nessuna informazione interessante se non
una ripetizione. E’ inutile fare previsioni troppo a lungo tempo.
Quanto è fedele questa previsione?
Come prima approssimazione usiamo i residui di HW.
Calcoliamo l’incertezza per via non parametrica.

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)

Ci aspettiamo che le barre siano all’interno di questa banda.


C’è un valore leggermente più grande in corrispondenza dei periodi, ma l’acf dice che non sembra
esserci una struttura significativa.
hist(nt.hw.r, 20, freq = F)
lines(density(nt.hw.r))
lines(sort(nt.hw.r), dnorm(sort(nt.hw.r), mean(nt.hw.r), sd(nt.hw.r)), col =
"red")
qqnorm(nt.hw.r, pch = 20)
qqline(nt.hw.r)
shapiro.test(nt.hw.r)

Non c’è un’aderenza particolarmente significativa, ma neanche male.


C’è una debole evidenza

Possiamo anche calcolare:


var(window(nottem, c(1921, 1)))

Calcoliamo l’incertezza per via parametrica.


plot(nt.hw, predict(nt.hw, 12))
lines(predict(nt.hw, 12) + qnorm(0.05, mean(nt.hw.r), sd(nt.hw.r)), col =
"blue")
lines(predict(nt.hw, 12) + qnorm(0.95, mean(nt.hw.r), sd(nt.hw.r)), col =
"blue")
Visualizziamo le incertezze al 90% sovrapposte (in verde a destra).
plot(nt.hw, predict(nt.hw, 12))
lines(predict(nt.hw, 12) + qnorm(0.05, mean(nt.hw.r), sd(nt.hw.r)), col =
"blue")
lines(predict(nt.hw, 12) + qnorm(0.95, mean(nt.hw.r), sd(nt.hw.r)), col =
"blue")
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")

Una nota a margine: il comando predict per Holt-Winters prevede l’opzione prediction.interval.


L’interpretazione del risultato è sensata solo se si sa cosa significhi e come sia stato ottenuto. Non
ha senso usarlo altrimenti. Ad esempio,
ts.plot(predict(nt.hw, 12, prediction.interval = T, level = 0.9))
lines(predict(nt.hw, 12) + qnorm(0.05, mean(nt.hw.r), sd(nt.hw.r)), col =
"blue")
lines(predict(nt.hw, 12) + qnorm(0.95, mean(nt.hw.r), sd(nt.hw.r)), col =
"blue")
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")
Nel caso dei parametri alpha, beta e gamma per sceglierli rischiamo di esplorare troppi valori.
Possiamo partire dai valori portati dal sw e esploriamo una zona di parametri che rende i
parametri meno conservativi (più ampii) ma senza esplorarli troppo.

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

Esaminiamo le differenze graficamente.


ts.plot(test, nt.hw.p, col = c("black", "red"))
Vediamo un altro dataset e un confronto tra due modelli diversi.
Consideriamo la serie sull’andamento dei passeggeri di voli internazionali tra il 1949 e il 1960.
Valutiamo la differenza tra l’analisi e la predizione del metodo di Holt-Winter con modello
stagionale additivo e moltiplicativo.
La ACF mostra una chiarissima stagionalità.
Usiamo HW con trend e stagionalità.
ap = AirPassengers
ap.hwa = HoltWinters(ap, seasonal = "additive")
ap.hwm = HoltWinters(ap, seasonal = "multiplicative")
ts.plot(ap, ap.hwa$fitted[, 1], ap.hwm$fitted[, 1], col = c("black", "blue",
"red"))

Riscontriamo una maggiore aderenza del modello moltiplicativo.


Valutiamo i due modelli prima in termini di residui.
layout(t(1:2))
# estrazione dei residui
ap.hwa.r = resid(ap.hwa)
ap.hwm.r = resid(ap.hwm)
# proporzione di varianza non spiegata
var(ap.hwa.r)/var(window(ap, 1950))
var(ap.hwm.r)/var(window(ap, 1950))
# rappresentazione grafica rispetto al tempo
plot(ap.hwa.r, type = "p", pch = 20)
plot(ap.hwm.r, type = "p", pch = 20)
# rappresentazione grafica rispetto ai valori stimati
plot(as.numeric(ap.hwa$fitted[, 1]), as.numeric(ap.hwa.r), type = "p", pch = 20)
plot(as.numeric(ap.hwm$fitted[, 1]), as.numeric(ap.hwm.r), type = "p", pch = 20)
# autocorrelazione
acf(ap.hwa.r)
acf(ap.hwm.r)
# densità empiriche
hist(ap.hwa.r, 20, freq = F)
lines(density(ap.hwa.r), col = "blue")
lines(sort(ap.hwa.r), dnorm(sort(ap.hwa.r), mean(ap.hwa.r), sd(ap.hwa.r)),
col = "red")
hist(ap.hwm.r, 20, freq = F)
lines(density(ap.hwm.r), col = "blue")
lines(sort(ap.hwm.r), dnorm(sort(ap.hwm.r), mean(ap.hwm.r), sd(ap.hwm.r)),
col = "red")
# grafico quantile-quantile
qqnorm(ap.hwa.r, pch = 20)
qqline(ap.hwa.r)
qqnorm(ap.hwm.r, pch = 20)
qqline(ap.hwm.r)
# test
shapiro.test(ap.hwa.r)
shapiro.test(ap.hwm.r)
layout(1)

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.

Metodi regressivi per serie storiche


Riusciamo a prevedere il futuro quando i dati non sono troppo differenti dal passato.
Quali sono i momenti passati più opportuni da osservare possono essere ricavati con la PACF.
La funzione di autocorrelazione parziale calcola la funzione di autocorrelazione escludendo
l’effetto regressivo dei termini precedenti.
L’acf della serie da 1 a 100 dà il risultato a sinistra:
La pacf dà il risultato a destra.
La pacf parte da 1 ed è alto per quel valore, 0 per il resto.
Analogamente, se prendiamo una serie stagionale con periodo 20, la pacf presenta una
correlazione col periodo, ma una volta estratto i valori dovrebbero calare.
I valori sono zero tranne due valori passati.

I valori precedenti hanno un’influenza e poi man mano si smorzano.


La pacf del rumore è invece del tutto casuale, oscillando tutto dentro la banda di irrilevanza.
Ci aspettiamo una pacf non nulla proprio nei valori dai quali la serie dipende.
Esaminiamo un esempio più elaborato. Tipicamente, una serie storica con carattere autoregressivo
che dipende dai primi pistanti precedenti avrà una pacf nulla ovunque tranne che in
corrispondenza delle dipendenze, e una acf che decade a zero rapidamente.
U1 = rep(0, 200)
U2a = rep(0, 200)
U2b = rep(0, 200)
U3 = rep(0, 200)
for (i in 4:200) {
U1[i] = 0.5 * U1[i - 2] + rnorm(1) # AR(1)
U2a[i] = 0.2 * U2a[i - 1] + 0.5 * U2a[i - 2] + rnorm(1) # AR(2)
U2b[i] = -0.2 * U2b[i - 1] + 0.5 * U2b[i - 2] + rnorm(1) # AR(2)
U3[i] = -0.2 * U3[i - 1] + 0.5 * U3[i - 2] + 0.55 * U3[i - 3] + rnorm(1) #
AR(3)
}
layout(matrix(c(1, 2, 1, 3), 2, 2))
# AR(1)
ts.plot(U1)
acf(U1, 60)
pacf(U1)
# AR(2)
ts.plot(U2a)
acf(U2a, 60)
pacf(U2a)
# AR(2) (alternato)
ts.plot(U2b)
acf(U2b, 60)
pacf(U2b)
# AR(3)
ts.plot(U3)
acf(U3, 60)
pacf(U3)
layout(1)

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.

La prima prevede una dipendenza da due istanti prima.


L’acf tende a smorzarsi, mentre la pacf identifica immediatamente la dipendenza da due istanti
prima.
La seconda serie dipende dai due istanti precedenti, infatti la pacf presenta ai lag 1 e 2 valori
ampii.

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

I primi valori saranno la serie stessa.


Rappresentiamo la serie originale, l’analisi e la previsione del modello regressivo solo nella parte
interessante.
La predizione precedente è stata ottenuta con la seguente modalità. Il modello regressivo fornisce
i coefficienti (b,a1,a2,…,aℓ), dove ℓ è il numero di lag in ingresso, che a parte l’intercetta
corrispondono alle colonne X1, X2, …, Xℓ.
La previsione è 
xL+i=b+a1xL+i−ℓ+a2xL+i−ℓ−1+⋯+aℓxL+i−1,
ed il generico termine corrispondente al coefficiente am (m=1,2,…,ℓ) è xL+i−(l−m+1). Nella
previsione, dunque, devono apparire i fattori corrispondenti alle colonne che sono state incluse
nel modello. Ad esempio, nella predizione precedente abbiamo incluso le colonne X13 e X3. Il
termine per X3 (ℓ=13 e m=3)è pt[L+i-11], il termine per X13 (ℓ=13, m=13) è pt[L+i-1].
Proviamo con il modello completo.
nt.lm <- lm(X14 ~ ., data = mnt)
anni = 2
pt = rep(0, L + 12 * anni)
pt[1:L] = nottem
for (i in 1:(12 * anni)) {
pt[L + i] = coef(nt.lm) %*% c(1, rev(pt[L + i - 1:l]))
}
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"))

Potremmo anche confrontarci il modello ridotto.


Il regressivo completo funziona molto male (rosso), perché abbiamo sicuramente colonne allineate
e ciò crea una predizione più instabile.
Probabilmente HW funziona meglio e lo abbiamo visto anche in termini di errore confrontandolo
col modello ridotto.
Abbiamo ottenuto un modello più robusto con la riduzione, escludendo alcuni lag della serie
stessa, e possiamo dire ciò perché abbiamo fatto la validazione.
I fattoi da includere li troviamo con la pacf mentre quelli da escludere con il solito metodo dei p-
value.
Potremmo aver ridotto troppo il modello, ci sta che potremmo reintrodurre qualche fattore e il
bilancio tra stabilità che perdiamo e guadagno di spiegazione che perdiamo possa portare un
vantaggio.
Attraverso la validazione cerchiamo di capire qual è il modello migliore.
Tra i tre osserviamo che HW ci dà il risultato migliore.

Autoregressione con il metodo Yule-Walker


Esistono algoritmi già codificati in R che permettono di fare modelli regressivi.
Consideriamo il dataset sugli autisti vittime di incidenti stradali in UK. Studiamo la stagionalità e le
autodipendenze lineari. Emerge una dipendenza fino al lag 14.
uk = UKDriverDeaths
acf(uk)
pacf(uk)

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.

Se alla serie originaria sottraiamo i residui dell’analisi, otteniamo l’analisi stessa.


ts.plot(uk, uk - uk.ar$resid, col = c("black", "red"))
Possiamo fare una previsione e per farlo possiamo usare predict.
Predizione con il modello regressivo.
uk.ar.pt = predict(uk.ar, n.ahead = 12, se.fit = FALSE)
plot(uk.ar.pt)
ts.plot(uk,uk.ar$resid, uk.ar.pt, col=c(“black”,”blue”,”red”)

Autoregressione con il metodo dei minimi quadrati


Questo metodo funziona bene per serie stazionarie. Esiste un metodo che dovrebbe dare risultati
più affidabile anche per serie non stazionarie.
Questo metodo considera anche serie non stazionarie. Esaminiamo la serie sull’andamento dei
passeggeri di voli internazionali
ap = AirPassengers
acf(ap)
pacf(ap)

La pacf è pronunciata a 1,2,9,13 lag.


ap.ls = ar(ap, method = "ols")
ap.ls$order
var(na.omit(ap.ls$resid))/var(ap[15:144])
L’algoritmo seleziona però altri lag, forse non si basa solo sulla pacf, per noi è comunque affidabile.
Rappresentiamo l’analisi:
ts.plot(ap, ap - ap.ls$resid, col = c("black", "blue"))

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

Da un punto di vista quantitativo l’auto-regressione da un risultato migliore.


Per avere un risultato più stabile avremmo dovuto prevedere le cose passo-passo.
Potremmo usare dei residui attraverso quantili empirici per vedere l’errore sulla previsione.
Se i residui assomigliano una distribuzione nota, possiamo usare direttamente la distribuzione
nota e quindi stima parametrica o non dell’incertezza.
Prendiamo il metodo che prevede meglio con la valutazione e data la previsione associamo anche
una stima di massima dell’errore della previsione attraverso i quantili dei residui del metodo che
abbiamo scelto.

Potrebbero piacerti anche