The French version of this document is no longer maintained: be sure to check the more up-to-date English version.

Examinons, critiquons et interprétons les résultats d'une régression

Tests et intervalles de confiance
Comparaisons de modèles (Anova)
Quelques graphiques pour explorer une régression
Problèmes de la régression
A FAIRE

A FAIRE : regrouper ce chapitre avec le suivant ???

Tests et intervalles de confiance

Après ce tour d'horizon des différentes régressions, revenons à la régression linéaire.

Tests

La commande summary permettait de faire un test T de Student sur les coefficients -- qui répond à la question << ce coefficient est-il significativement non nul ? >>.

> x <- rnorm(100)
> y <- 1 + 2*x + .3*rnorm(100)
> summary(lm(y~x))
...
Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  1.08440    0.03224   33.64   <2e-16 ***
x            2.04051    0.03027   67.42   <2e-16 ***

Dans le même exemple, on peut regarder si le coefficient constant est 1. (Quand on écrit une formule pour décrire un modèle, certaines opérations sont interprétées différemment (en particulier * et ^) : pour être sûr qu'elles seront interprétées comme des opérations arithmétiques sur les variables, on peut les entourer de I(...). Ici, ça n'est pas nécessaire.)

> x <- rnorm(100)
> y <- 1 + 2*x + .3*rnorm(100)
> summary(lm(I(y-1)~x))

Call:
lm(formula = I(y - 1) ~ x)

Residuals:
     Min       1Q   Median       3Q      Max
-0.84692 -0.24891  0.02781  0.20486  0.60522

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.01294    0.02856  -0.453    0.651
x            1.96405    0.02851  68.898   <2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.2855 on 98 degrees of freedom
Multiple R-Squared: 0.9798,     Adjusted R-squared: 0.9796
F-statistic:  4747 on 1 and 98 DF,  p-value: < 2.2e-16

Sous l'hypothèse que ce coefficient est 1, regardons si le coefficient directer est nul.

> summary(lm(I(y-1)~0+x))

Call:
lm(formula = I(y - 1) ~ 0 + x)

Residuals:
     Min       1Q   Median       3Q      Max
-0.85962 -0.26189  0.01480  0.19184  0.59227

Coefficients:
  Estimate Std. Error t value Pr(>|t|)
x  1.96378    0.02839   69.18   <2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.2844 on 99 degrees of freedom
Multiple R-Squared: 0.9797,     Adjusted R-squared: 0.9795
F-statistic:  4786 on 1 and 99 DF,  p-value: < 2.2e-16

Autre méthode :

> x <- rnorm(100)
> y <- 1 + 2*x + .3*rnorm(100)
> a <- rep(1,length(x))
> summary(lm(y~offset(a)-1+x))

Call:
lm(formula = y ~ offset(a) - 1 + x)

Residuals:
     Min       1Q   Median       3Q      Max
-0.92812 -0.09901  0.09515  0.28893  0.99363

Coefficients:
  Estimate Std. Error t value Pr(>|t|)
x  2.04219    0.03114   65.58   <2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.3317 on 99 degrees of freedom
Multiple R-Squared: 0.9816,     Adjusted R-squared: 0.9815
F-statistic:  5293 on 1 and 99 DF,  p-value: < 2.2e-16

Regardons s'il est égal à deux.

> summary(lm(I(y-1-2*x)~0+x))

Call:
lm(formula = I(y - 1 - 2 * x) ~ 0 + x)

Residuals:
     Min       1Q   Median       3Q      Max
-0.85962 -0.26189  0.01480  0.19184  0.59227

Coefficients:
  Estimate Std. Error t value Pr(>|t|)
x -0.03622    0.02839  -1.276    0.205

Residual standard error: 0.2844 on 99 degrees of freedom
Multiple R-Squared: 0.01618,    Adjusted R-squared: 0.006244
F-statistic: 1.628 on 1 and 99 DF,  p-value: 0.2049

Autre méthode :

> summary(lm(y~offset(1+2*x)+0+x))
Call:
lm(formula = y ~ offset(1 + 2 * x) + 0 + x)

Residuals:
     Min       1Q   Median       3Q      Max
-0.92812 -0.09901  0.09515  0.28893  0.99363

Coefficients:
  Estimate Std. Error t value Pr(>|t|)
x  0.04219    0.03114   1.355    0.179

Residual standard error: 0.3317 on 99 degrees of freedom
Multiple R-Squared: 0.9816,     Adjusted R-squared: 0.9815
F-statistic:  5293 on 1 and 99 DF,  p-value: < 2.2e-16

De manière plus générale, on peut utiliser la commande offset lors d'une régression multilinéaire, lorsqu'on connait l'un des coefficients de manière exacte.

Autre méthode

x <- rnorm(100)
y <- 1 + 2*x + .3*rnorm(100)
library(car)
linear.hypothesis( lm(y~x), matrix(c(1,0,0,1), 2, 2), c(1,2) )

Cela regarde si

[ 1  0 ]   [ premier coefficient  ]   [ 1 ]
[      ] * [                      ] = [   ]
[ 0  1 ]   [ deuxième coefficient ]   [ 2 ].

On obtient :

F-Test
SS = 0.04165324     SSE = 9.724817     F = 0.2098763  Df = 2 and 98     p = 0.8110479

Intervalles de confiance et de prédiction

On peut calculer des intervalles de confiance des paramètres.

> library(MASS)
> n <- 100
> x <- rnorm(n)
> y <- 1 - 2*x + rnorm(n)
> r <- lm(y~x)
> r$coefficients
(Intercept)           x
  0.9569173  -2.1296830
> confint(r)
                 2.5 %    97.5 %
(Intercept)  0.7622321  1.151603
x           -2.3023449 -1.957021

On peut aussi chercher des intervalles de confiance des valeurs prédites. Il peut s'agir d'un intervale de confiance de aX+b (bande de confiance) ou, ce n'est pas pareil, de E[Y|X=x] (bande de prédiction).

x <- runif(20)
y <- 1-2*x+.1*rnorm(20)
res <- lm(y~x)
plot(y~x)
new <- data.frame( x=seq(0,1,length=21) )
p <- predict(res, new)
points( p ~ new$x, type='l' )
p <- predict(res, new, interval='confidence')
points( p[,2] ~ new$x, type='l', col="green" )
points( p[,3] ~ new$x, type='l', col="green" )
p <- predict(res, new, interval='prediction')
points( p[,2] ~ new$x, type='l', col="red" )
points( p[,3] ~ new$x, type='l', col="red" )
title(main="Bandes de confiance et de prédiction")
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("Bande de confiance", "Bande de prédiction"),
        lwd=1, lty=1, col=c("green", "red") )

*

Si on s'éloigne, les intervalles grandissent démesurément.

plot(y~x, xlim=c(-1,2), ylim=c(-3,3))
new <- data.frame( x=seq(-2,3,length=200) )
p <- predict(res, new)
points( p ~ new$x, type='l' )
p <- predict(res, new, interval='confidence')
points( p[,2] ~ new$x, type='l', col="green" )
points( p[,3] ~ new$x, type='l', col="green" )
p <- predict(res, new, interval='prediction')
points( p[,2] ~ new$x, type='l', col="red" )
points( p[,3] ~ new$x, type='l', col="red" )
title(main="Bandes de confiance et de prédiction")
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("Bande de confiance", "Bande de prédiction"),
        lwd=1, lty=1, col=c("green", "red") )

*

plot(y~x, xlim=c(-5,6), ylim=c(-11,11))
new <- data.frame( x=seq(-5,6,length=200) )
p <- predict(res, new)
points( p ~ new$x, type='l' )
p <- predict(res, new, interval='confidence')
points( p[,2] ~ new$x, type='l', col="green" )
points( p[,3] ~ new$x, type='l', col="green" )
p <- predict(res, new, interval='prediction')
points( p[,2] ~ new$x, type='l', col="red" )
points( p[,3] ~ new$x, type='l', col="red" )
title(main="Bandes de confiance et de prédiction")
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("Bande de confiance", "Bande de prédiction"),
        lwd=1, lty=1, col=c("green", "red") )

*

Voici d'autres manières de représenter les intervalles de confiance et de prédiction.

N <- 100
n <- 20
x <- runif(N, min=-1, max=1)
y <- 1 - 2*x + rnorm(N, sd=abs(x))
res <- lm(y~x)
plot(y~x)
x0 <- seq(-1,1,length=n)
new <- data.frame( x=x0 )
p <- predict(res, new)
points( p ~ x0, type='l' )
p <- predict(res, new, interval='prediction')
segments( x0, p[,2], x0, p[,3], col='red')
p <- predict(res, new, interval='confidence')
segments( x0, p[,2], x0, p[,3], col='green', lwd=3 )

*

mySegments <- function(a,b,c,d,...) {
  u <- par('usr')
  e <- (u[2]-u[1])/100
  segments(a,b,c,d,...)
  segments(a+e,b,a-e,b,...)
  segments(c+e,d,c-e,d,...)
}
plot(y~x)
p <- predict(res, new)
points( p ~ x0, type='l' )
p <- predict(res, new, interval='prediction')
mySegments( x0, p[,2], x0, p[,3], col='red')
p <- predict(res, new, interval='confidence')
mySegments( x0, p[,2], x0, p[,3], col='green', lwd=3 )

*

Test sur un couple de variables (ellipses)

On peut chercher des intervalles de confiance pour un paramètre isolé -- on obtient un segment -- ou pour plusieurs paramètres en même temps -- on obtient un ellipsoïde plein : si on peut le tracer, il apporte plus d'information. On parle alors de région de confiance.

(L'ellipse qu'on obtient est penchée, car elle s'obtient à l'aide de la matrice de corrélation des deux variables : on diagonalise cette matrice dans une base othonormale et les vecteurs propres nous donnent les axes.)

library(ellipse)
my.confidence.region <- function (g, a=2, b=3) {
  e <- ellipse(g,c(a,b))
  plot(e,
       type="l",
       xlim=c( min(c(0,e[,1])), max(c(0,e[,1])) ),
       ylim=c( min(c(0,e[,2])), max(c(0,e[,2])) ),
      )
  x <- g$coef[a]
  y <- g$coef[b]
  points(x,y,pch=18)
  cf <- summary(g)$coefficients
  ia <- cf[a,2]*qt(.975,g$df.residual)
  ib <- cf[b,2]*qt(.975,g$df.residual)
  abline(v=c(x+ia,x-ia),lty=2)
  abline(h=c(y+ib,y-ib),lty=2)
  points(0,0)
  abline(v=0,lty="F848")
  abline(h=0,lty="F848")
}

n <- 20
x1 <- rnorm(n)
x2 <- rnorm(n)
x3 <- rnorm(n)
y <- x1+x2+x3+rnorm(n)
g <- lm(y~x1+x2+x3)
my.confidence.region(g)

*

n <- 20
x <- rnorm(n)
x1 <- x+.2*rnorm(n)
x2 <- x+.2*rnorm(n)
y <- x1+x2+rnorm(n)
g <- lm(y~x1+x2)
my.confidence.region(g)

*

La probabilité que le point se trouve dans la zône coloriée est la même dans les trois cas :

my.confidence.region <- function (g, a=2, b=3, which=0, col='pink') {
  e <- ellipse(g,c(a,b))
  x <- g$coef[a]
  y <- g$coef[b]
  cf <- summary(g)$coefficients
  ia <- cf[a,2]*qt(.975,g$df.residual)
  ib <- cf[b,2]*qt(.975,g$df.residual)
  xmin <- min(c(0,e[,1]))
  xmax <- max(c(0,e[,1]))
  ymin <- min(c(0,e[,2]))
  ymax <- max(c(0,e[,2]))
  plot(e,
       type="l",
       xlim=c(xmin,xmax),
       ylim=c(ymin,ymax),
      )
  if(which==1){ polygon(e,col=col) }
  else if(which==2){ rect(x-ia,par('usr')[3],x+ia,par('usr')[4],col=col,border=col) }
  else if(which==3){ rect(par('usr')[1],y-ib,par('usr')[2],y+ib,col=col,border=col) }
  lines(e)
  points(x,y,pch=18)
  abline(v=c(x+ia,x-ia),lty=2)
  abline(h=c(y+ib,y-ib),lty=2)
  points(0,0)
  abline(v=0,lty="F848")
  abline(h=0,lty="F848")
}
my.confidence.region(g, which=1)

*

my.confidence.region(g, which=2)

*

my.confidence.region(g, which=3)

*

Les dangers des tests multiples

Lors d'une régression multiple, on s'efforce de prendre le moins de variables prédictives possibles, tout en ayant une prédiction correcte. Pour choisir ou éliminer des variables, on peut être tenté de faire plein de tests statistiques.

C'est une mauvaise idée.

En effet, à chaque test, on a un certain risque de commettre une erreur -- et ces risques d'erreur s'accumulent.

Néanmoins, on le fait souvent -- on a rarement le choix. On peut soit partir d'un modèle sans aucune variable, ajouter la variable Xi qui a la plus forte corrélation avec Y, puis ajouter successivement les variables qui procurent la plus grande augmentation de R^2, en s'arrétant lorsqu'on atteint une F-valeur préalablement fixée ; soit partir d'un modèle avec toutes les variables, et retirer successivement celles qui réduisent le moins R^2, en s'arrétant lorsqu'on atteint une F-valeur préalablement fixée.

A FAIRE : tukey, etc. (c'est dans le chapitre sur l'ANOVA).

Comparaisons de modèles (Anova)

Régression et sommes de carrés

Quand on écrit les résultats d'une régression ou d'une analyse de la variance, on se retrouve souvent avec un tableau rempli de << sommes de carrés >>.

RSS (Residual Sum of Squares) : c'est la quantité que l'on cherche à minimiser lors de la régression. On dispose d'une variable prédictive, X, et on cherche à prédire les valeurs d'une autre variable, Y. On écrit

hat Yi = b0 + b1 Xi

et on cherche les valeurs de b0 et b1 qui minimisent

RSS = Somme( (Yi - hat Yi)^2 ).

TSS (Total sum of squares) : c'est la quantité qu'on cherche à minimiser lorsqu'on définit la moyenne de Y.

TSS = Somme( (Yi - bar Y)^2 )

ESS (Explained Sum of Squares) : c'est la différence entre les valeurs précédentes. On peut aussi l'écrire comme une somme de carrés.

ESS = Somme( ( hat Yi - bar Y )^2 )

R-square : c'est le coefficient de détermination (ou pourcentage de variance de Y expliquée par X). Plus il est proche de 1, plus la régression explique les variations de Y.

R^2 = ESS/TSS.

On peut interpréter graphiquement le coefficient de détermination. Les données sont réparties autour d'une bande de hauteur RSS autour de la droite de régression, alors que la hauteur du dessin est TSS. On a alors R^2=1-RSS/TSS.

n <- 20000
x <- runif(n)
y <- 4 - 8*x + rnorm(n)
plot(y~x, pch='.')
abline(lm(y~x), col='red')
arrows( .1, -6, .1, 6, code=3, lwd=3, col='blue' )
arrows( .9, -3.2-2, .9, -3.2+2, code=3, lwd=3, col='blue' )
text( .1, 6, "TSS", adj=c(0,0), cex=2, col='blue' )  
text( .9, -3.2+2, "RSS", adj=c(1,0), cex=2, col='blue' )

*

Lecture du résultat d'une régression

Nous avons vu trois manières d'afficher les résultats d'une régression : avec les commandes print, summary et anova. La dernière ligne du résultat de la commande summary fait une comparaison entre le modèle et un modèle nul.

> x <- rnorm(100)
> y <- 1 + 2*x + .3*rnorm(100)
> summary(lm(y~x))

Call:
lm(formula = y ~ x)

Residuals:
     Min       1Q   Median       3Q      Max
-0.84692 -0.24891  0.02781  0.20486  0.60522

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  0.98706    0.02856   34.56   <2e-16 ***
x            1.96405    0.02851   68.90   <2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.2855 on 98 degrees of freedom
Multiple R-Squared: 0.9798,     Adjusted R-squared: 0.9796
F-statistic:  4747 on 1 and 98 DF,  p-value: < 2.2e-16

Le résultat de la commande anova explique d'où viennent ces chiffres : on nous donne les sommes de carrés, leur moyenne (on les divise par le nombre de degrés de liberté), leur quotient (F value), et la probabilité que leur quotient soit aussi élevé si le coefficient directeur de la droite est nul (c'est donc la p-valeur d'un test dont l'hypothèse nulle est l'annulation de ce coefficient directeur).

> anova(lm(y~x))
Analysis of Variance Table

Response: y
          Df Sum Sq Mean Sq F value    Pr(>F)
x          1 386.97  386.97    4747 < 2.2e-16 ***
Residuals 98   7.99    0.08
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

De manière plus générale, la commande anova permet de comparer des modèles.

Comparaison de modèles

Lors d'une régression multiple, on s'efforce de prendre le moins de variables possibles. On est ainsi amené à comparer des modèles : par exemple, comparer un modèle avec plein de variables et un autre avec moins de variables.

La commande anova permet d'effectuer ce genre de comparaison.

data(trees)
r1 <- lm(Volume ~ Girth, data=trees)
r2 <- lm(Volume ~ Girth + Height, data=trees)
anova(r1,r2)

Le résultat,

Analysis of Variance Table

Model 1: Volume ~ Girth
Model 2: Volume ~ Girth + Height
  Res.Df    RSS Df Sum of Sq      F  Pr(>F)
1     29 524.30
2     28 421.92  1    102.38 6.7943 0.01449 *
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

nous dit que les deux modèles sont sensiblement différents avec un risque d'erreur inférieur à 2%.

Voici d'autres exemples.

x1 <- rnorm(100)
x2 <- rnorm(100)
x3 <- rnorm(100)
b <- .1* rnorm(100)

y <- 1 + x1 + x2 + x3 + b 
r1 <- lm( y ~ x1 )
r2 <- lm( y ~ x1 + x2 + x3 )
anova(r1,r2) # p-valeur = 2e-16

y <- 1 + x1
r1 <- lm( y ~ x1 )
r2 <- lm( y ~ x1 + x2 + x3 )
anova(r1,r2) # p-valeur = 0.25

y <- 1 + x1 + .02*x2 - .02*x3 + b
r1 <- lm( y ~ x1 )
r2 <- lm( y ~ x1 + x2 + x3 )
anova(r1,r2) # p-valeur = 0.10

On peut comparer plus de 2 modèles (chaque modèle est inclus dans le suivant).

y <- 1 + x1 + x2 + x3 + b 
r1 <- lm( y ~ x1 )
r2 <- lm( y ~ x1 + x2 )
r3 <- lm( y ~ x1 + x2 + x3 )
anova(r1,r2,r3) # p-valeurs = 2e-16 (toutes les deux)

Si, lors de la comparaison de deux modèles, on trouve une p-valeur importante, i.e., si les deux modèles ne sont pas significativement différents, on peut abandonnera le plus complexe pour retenir le plus simple.

Lien entre anova et la régression

On peut présenter les calculs que l'on fait dans une régression linéaire dans un tableau d'analyse de variance. Et le principe des calculs est en fait le même : on cherche à exprimer la variance de Y comme somme de la variance d'une expression linéaire en X et d'une variance résiduelle, en minimisant cette variance résiduelle.

x <- runif(10)
y <- 1 + x + .2*rnorm(10)
anova(lm(y~x))

La table d'analyse de variance nous dit qu'effectivement, Y dépend de X (avec un risque d'erreur inférieur à 1%).

Analysis of Variance Table

Response: y
          Df  Sum Sq Mean Sq F value   Pr(>F)
x          1 0.85633 0.85633  20.258 0.002000 **
Residuals  8 0.33817 0.04227

Ca marche toujours avec plusieurs variables.

x <- runif(10)
y <- runif(10)
z <- 1 + x - y + .2*rnorm(10)
anova(lm(z~x+y))

La table d'analyse de variance nous dit que z dépend de x et de y, avec un risque d'erreur inférieur à 1%.

Analysis of Variance Table

Response: z
          Df  Sum Sq Mean Sq F value    Pr(>F)
x          1 2.33719 2.33719  45.294 0.0002699 ***
y          1 0.73721 0.73721  14.287 0.0068940 **
Residuals  7 0.36120 0.05160

On peut remarquer que le résultat dépend de l'ordre des paramètres.

>   anova(lm(z~y+x))
Analysis of Variance Table

Response: z
          Df  Sum Sq Mean Sq F value   Pr(>F)
y          1 2.42444 2.42444  46.985 0.000241 ***
x          1 0.64996 0.64996  12.596 0.009355 **
Residuals  7 0.36120 0.05160

Dans certains cas on peut même avoir des résultats contradictoires : selon l'ordre des variables indépendantes, on peut tantôt trouver que z dépend de x, tantôt qu'il n'en dépend pas.

> x <- runif(10)
> y <- runif(10)
> z <- 1 + x + 5*y + .2*rnorm(10)

> anova(lm(z~x+y))
Analysis of Variance Table
Response: z
          Df Sum Sq Mean Sq  F value    Pr(>F)
x          1 0.0104  0.0104   0.1402    0.7192
y          1 7.5763  7.5763 102.1495 1.994e-05 ***
Residuals  7 0.5192  0.0742

> anova(lm(z~y+x))
Analysis of Variance Table
Response: z
          Df Sum Sq Mean Sq F value    Pr(>F)
y          1 7.1666  7.1666  96.626 2.395e-05 ***
x          1 0.4201  0.4201   5.664   0.04889 *
Residuals  7 0.5192  0.0742

Quelques graphiques pour explorer une régression

Résidus

Les résidus sont les différences entre les valeurs prédites et les valeurs observées.

Résidus et "bruit" (dans le modèle)

Ca n'est pas exactement le bruit qui apparait dans le modèle (Y = a + b X + bruit). La simulation suivante permet d'estimer la variance des résidus : on trouve 0.008893307 alors que la variance du bruit était 0.01. En effet, le bruit est la différence entre les valeurs observées et le modèle réel, alors que les résidus sont la différence entre le modèle estimé d'après l'échantillon et les valeurs observées.

a <- 1
b <- -2
s <- .1
n <- 10
N <- 1e6
v <- NULL
for (i in 1:N) {
  x <- rnorm(n)
  y <- 1-2*x+s*rnorm(n)
  v <- append(v, var(lm(y~x)$res))
}
mean(v)

On peut montrer que la variance du résidu de l'observation i est :

sigma^2 * ( 1 - h_i )

où sigma^2 est la variance du bruit et h_i l'"effet de levier" ("leverage", en anglais) de l'observation i (le i-ième terme diagonal de t(X)%*%X).

Résidus studentisés (ou standardisés)

Il s'agit des résidus normalisés. Pour estimer la variance, on prend tout l'échantillon.

?rstandard

Résidus Jackknife (ou studentisés)

Il s'agit encore des résidus normalisés, mais cette fois-ci, pour estimer la variance, on prend l'échantillon sans l'observation courrante.

?rstudent

Graphiques des résidus

Considérons l'exemple suivant.

n <- 100
x1 <- rnorm(n)
x2 <- rnorm(n)
x3 <- x1^2+rnorm(n)
x4 <- 1/(1+x2^2)+.2*rnorm(n)
y <- 1+x1-x2+x3-x4+.1*rnorm(n)
pairs(cbind(x1,x2,x3,x4,y))

*

Les résidus constituent une variable statistique comme une autre : on peut les observer à l'aide de boite à moustaches, histogramme, qqplot, etc. On peut aussi les représenter en fonction des valeurs prédites, en fonction de chacune des cariables prédictives, ou en fonction du numéro de l'observation.

r <- lm(y~x1+x2+x3+x4)  
boxplot(r$res, horizontal=T)

*

hist(r$res)

*

plot(r$res, main='Résidus')

*

plot(rstandard(r), main='Résidus standardisés')

*

plot(rstudent(r), main="Résidus studentisés")

*

plot(r$res ~ r$fitted.values, main="Résidus et valeurs prédites")    
abline(h=0, lty=3)

*

op <- par(mfrow=c(2,2))
plot(r$res ~ x1)    
abline(h=0, lty=3)
plot(r$res ~ x2)    
abline(h=0, lty=3)
plot(r$res ~ x3)    
abline(h=0, lty=3)
plot(r$res ~ x4)    
abline(h=0, lty=3)
par(op)

*

n <- 100
x1 <- rnorm(n)
x2 <- 1:n
y <- rnorm(1)
for (i in 2:n) {
  y <- c(y, y[i-1] + rnorm(1))
}
y <- x1 + y
r <- lm(y~x1+x2) # Ou simplement : lm(y~x1)
op <- par(mfrow=c(2,1))
plot( r$res ~ x1 )
plot( r$res ~ x2 )
par(op)

*

C'est généralement une mauvaise idée de représenter les résidus en fonction des valeurs effectivement observées, car le terme de bruit est présent sur les deux axes, et on représente presque le bruit en fonction de lui-même...

n <- 100
x <- rnorm(n)
y <- 1-x+rnorm(n)
r <- lm(y~x)
plot(r$res ~ y)    
abline(h=0, lty=3)
abline(lm(r$res~y),col='red')
title(main='Not a good idea...')

*

Partial regression plot (or added variable plot)

On a une variable dépendante Y et deux variables indépendantes X1 et X2. On peut étudier l'effet de X1 sur Y après s'être débarassé de l'effet (linéaire) de X2. Pour cela, on fait une régression de Y sur X2, une régression de X1 sur X2, et on trace les résidus de la première en fonction de ceux de la seconde.

Ces graphiques permettent, par exemple, de repérer les points influents.

partial.regression.plot <- function (y, x, n, ...) {
  m <- as.matrix(x[,-n])
  y1 <- lm(y ~ m)$res
  x1 <- lm(x[,n] ~ m)$res
  plot( y1 ~ x1, ... )
  abline(lm(y1~x1), col='red')
}

n <- 100
x1 <- rnorm(n)
x2 <- rnorm(n)
x3 <- x1+x2+rnorm(n)
x <- cbind(x1,x2,x3)
y <- x1+x2+x3+rnorm(n)
op <- par(mfrow=c(2,2))
partial.regression.plot(y, x, 1)
partial.regression.plot(y, x, 2)
partial.regression.plot(y, x, 3)
par(op)

*

Il y a déjà une commande av.plot dans la bibliothèque car qui fait ce genre de chose.

library(car)
av.plots(lm(y~x1+x2+x3),ask=F)

*

La commande leverage.plots, toujours dans la bibliothèque car, en est une généralisation.

?leverage.plots

Partial residual plots

Ca ressemble beaucoup à la régression partielle. On représente Y, auquel on a retiré l'effet des X_j (j différent de i), en fonction de X_i. C'est plus efficace que la régression partielle pour repérer la non-linéarité (mais la régression partielle permet mieux de repérer les points atypiques ou influents).

my.partial.residual.plot <- function (y, x, i, ...) {
  r <- lm(y~x)
  xi <- x[,i]
  # Y, auquel on a retiré l'effet des X_j
  yi <- r$residuals + r$coefficients[i] * x[,i]
  plot( yi ~ xi, ... )  
}
n <- 100
x1 <- rnorm(n)
x2 <- rnorm(n)
x3 <- x1+x2+rnorm(n)
x <- cbind(x1,x2,x3)
y <- x1+x2+x3+rnorm(n)
op <- par(mfrow=c(2,2))
my.partial.residual.plot(y, x, 1)
my.partial.residual.plot(y, x, 2)
my.partial.residual.plot(y, x, 3)
par(op)

*

Les bibliothèques car ou Design permettent de faire ces graphiques diretement.

library(car)
?cr.plots
?ceres.plots

library(Design)
?plot.lrm.partial
?lrm

Problèmes de la régression

Dans cette partie, nous énumérons certains différents problèmes que l'on peut rencontrer lors d'une régression et expliquons comment les repérer à l'aide de graphiques. Souvent, on peut résoudre ces problèmes en transformant les variables, en changeant le modèle ou en utilisant un autre type de régression.

Graphiques

La commande plot produit quatre graphiques permettant d'évaluer la qualité et la pertinence d'une régression.

data(crabs)
n <- length(crabs$RW)
r <- lm(FL~RW, data=crabs)
op <- par(mfrow=c(2,2))
plot(r)
par(op)

*

Ces graphiques restent valables en dimensions supérieures. Voici l'exemple du manuel (on prédit une variable à partir de quatre autres).

data(LifeCycleSavings)
plot(LifeCycleSavings)

*

op <- par(mfrow = c(2, 2), oma = c(0, 0, 2, 0))
plot(lm.SR <- lm(sr ~ pop15 + pop75 + dpi + ddpi, data = LifeCycleSavings))
par(op)

*

## 4 plots on 1 page; allow room for printing model formula in outer margin:
op <- par(mfrow = c(2, 2), oma = c(0, 0, 2, 0))
#plot(lm.SR)
#plot(lm.SR, id.n = NULL)               # no id's
#plot(lm.SR, id.n = 5, labels.id = NULL)# 5 id numbers
plot(lm.SR, panel = panel.smooth)
## Gives a smoother curve
#plot(lm.SR, panel = function(x,y) panel.smooth(x, y, span = 1))
par(op)

*

Surmodélisation ("overfit", surapprentissage)

Il est possible que la régression colle trop près aux données, au point que le modèle en devienne irréaliste. La situation n'est pas toujours aussi flagrante que dans cet exemple. Néanmoins, si on est amené à choisir un modèle non linéaire, il faut pouvoir le justifier. En particulier, il faut comparer le nombre de paramètres à estimer avec le nombre d'observations...

n <- 10
x <- seq(0,1,length=n)
y <- 1-2*x+.3*rnorm(n)
plot(spline(x, y, n = 10*n), col = 'red', type='l', lwd=3)
points(y~x, pch=16, lwd=3, cex=2)
abline(lm(y~x))
title(main='Overfit')

*

C'est essentiellement du bon sens. On peut aussi comparer (dans le cas d'une régression linéaire) le coefficient de détermination (qui est proche de 1 dans ce cas) et le coefficient de détermination ajusté (qui tient compte de ce problème).

> summary(lm(y~poly(x,n-1)))
Call:
lm(formula = y ~ poly(x, n - 1))

Residuals:
ALL 10 residuals are 0: no residual degrees of freedom!

Coefficients:
                Estimate Std. Error t value Pr(>|t|)
(Intercept)      0.01196
poly(x, n - 1)1 -1.94091
poly(x, n - 1)2 -0.02303
poly(x, n - 1)3 -0.08663
poly(x, n - 1)4 -0.06938
poly(x, n - 1)5 -0.34501
poly(x, n - 1)6 -0.51048
poly(x, n - 1)7 -0.28479
poly(x, n - 1)8 -0.22273
poly(x, n - 1)9  0.39983

Residual standard error: NaN on 0 degrees of freedom
Multiple R-Squared:     1,      Adjusted R-squared:   NaN
F-statistic:   NaN on 9 and 0 DF,  p-value: NA

> summary(lm(y~poly(x,n-2)))
Call:
lm(formula = y ~ poly(x, n - 2))

Residuals:
        1         2         3         4         5         6         7         8         9        10
-0.001813  0.016320 -0.065278  0.152316 -0.228473  0.228473 -0.152316  0.065278 -0.016320  0.001813

Coefficients:
                Estimate Std. Error t value Pr(>|t|)
(Intercept)      0.01196    0.12644   0.095    0.940
poly(x, n - 2)1 -1.94091    0.39983  -4.854    0.129
poly(x, n - 2)2 -0.02303    0.39983  -0.058    0.963
poly(x, n - 2)3 -0.08663    0.39983  -0.217    0.864
poly(x, n - 2)4 -0.06938    0.39983  -0.174    0.891
poly(x, n - 2)5 -0.34501    0.39983  -0.863    0.547
poly(x, n - 2)6 -0.51048    0.39983  -1.277    0.423
poly(x, n - 2)7 -0.28479    0.39983  -0.712    0.606
poly(x, n - 2)8 -0.22273    0.39983  -0.557    0.676

Residual standard error: 0.3998 on 1 degrees of freedom
Multiple R-Squared: 0.9641,     Adjusted R-squared: 0.6767
F-statistic: 3.355 on 8 and 1 DF,  p-value: 0.4

Si on est plus raisonnable, le coefficient de détermination et le coefficient de détermination ajusté sont très proches.

> x <- seq(0,1,length=n)
> y <- 1-2*x+.3*rnorm(n)
> summary(lm(y~poly(x,10)))

Call:
lm(formula = y ~ poly(x, 10))

Residuals:
      Min        1Q    Median        3Q       Max
-0.727537 -0.206951 -0.002332  0.177562  0.902353

Coefficients:
              Estimate Std. Error t value Pr(>|t|)
(Intercept)   -0.01312    0.02994  -0.438    0.662
poly(x, 10)1  -6.11784    0.29943 -20.431   <2e-16 ***
poly(x, 10)2  -0.11099    0.29943  -0.371    0.712
poly(x, 10)3  -0.04936    0.29943  -0.165    0.869
poly(x, 10)4  -0.28863    0.29943  -0.964    0.338
poly(x, 10)5  -0.15348    0.29943  -0.513    0.610
poly(x, 10)6   0.12146    0.29943   0.406    0.686
poly(x, 10)7   0.05066    0.29943   0.169    0.866
poly(x, 10)8   0.09707    0.29943   0.324    0.747
poly(x, 10)9   0.07554    0.29943   0.252    0.801
poly(x, 10)10  0.42494    0.29943   1.419    0.159
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.2994 on 89 degrees of freedom
Multiple R-Squared: 0.8256,     Adjusted R-squared: 0.8059
F-statistic: 42.12 on 10 and 89 DF,  p-value: < 2.2e-16


> summary(lm(y~poly(x,1)))
...
Multiple R-Squared: 0.8182,     Adjusted R-squared: 0.8164
...

Echantillon trop petit

Si l'échantillon est trop petit, on risque de ne pas pouvoir estimer grand-chose. On est alors contraint de se limiter à des modèles simples (linéaires), voire simplistes, car le risque de surmodélisation (overfitting) est trop grand.

Underfit (curvilinéarité)

Parfois, le modèle est trop simpliste.

x <- runif(100, -1, 1)
y <- 1-x+x^2+.3*rnorm(100)
plot(y~x)
abline(lm(y~x), col='red')

*

Sur le graphique précédent, ce n'est pas évident, mais on peut s'appercevoir du problème en regardant si un modèle polynomial ne serait pas préférable,

> summary(lm(y~poly(x,10)))
...
Coefficients:
              Estimate Std. Error t value Pr(>|t|)
(Intercept)    1.29896    0.02841  45.725  < 2e-16 ***
poly(x, 10)1  -4.98079    0.28408 -17.533  < 2e-16 ***
poly(x, 10)2   2.53642    0.28408   8.928 5.28e-14 ***
poly(x, 10)3  -0.06738    0.28408  -0.237    0.813
poly(x, 10)4  -0.15583    0.28408  -0.549    0.585
poly(x, 10)5   0.15112    0.28408   0.532    0.596
poly(x, 10)6   0.04512    0.28408   0.159    0.874
poly(x, 10)7  -0.29056    0.28408  -1.023    0.309
poly(x, 10)8  -0.39384    0.28408  -1.386    0.169
poly(x, 10)9  -0.25763    0.28408  -0.907    0.367
poly(x, 10)10 -0.09940    0.28408  -0.350    0.727

en utilisant des splines (ou une autre méthode de régularisation), et en regardant graphiquement le résultat,

plot(y~x)
lines(smooth.spline(x,y), col='red', lwd=2)
title(main="Relation non linéaire mise en évidence par des splines")

*

plot(y~x)
lines(lowess(x,y), col='red', lwd=2)
title(main='Relation non linéaire mise en évidence par "lowess"')

*

plot(y~x)
xx <- seq(min(x),max(x),length=100)
yy <- predict( loess(y~x), data.frame(x=xx) )
lines(xx,yy, col='red', lwd=3)
title(main='Relation non linéaire mise en évidence par "loess"')

*

en regardant les résidus, et en leur appliquant une quelconque méthode de régularisation (on peut représenter les résidus en fonction des valeurs prédites ou en fonction des variables indépendantes).

r <- lm(y~x)
plot(r$residuals ~ r$fitted.values,
      xlab='valeurs prédites', ylab='résidus',
      main='résidus en fonction des valeurs prédites')
lines(lowess(r$fitted.values, r$residuals), col='red', lwd=2)
abline(h=0, lty=3)

*

plot(r$residuals ~ x,
      xlab='x', ylab='résidus',
      main='résidus en fonction de la variable prédictive')
lines(lowess(x, r$residuals), col='red', lwd=2)
abline(h=0, lty=3)

*

Dans certains cas, on dispose de plusieurs observations pour une même valeur des variables prédictives : on peut alors procéder au test suivant.

x <- rep(runif(10, -1, 1), 10)
y <- 1-x+x^2+.3*rnorm(100)
r1 <- lm(y ~ x)
r2 <- lm(y ~ factor(x))
anova(r1,r2)

Les deux modèles devraient donner les mêmes prédictions : ici ce n'est pas le cas.

Analysis of Variance Table

Model 1: y ~ x
Model 2: y ~ factor(x)
  Res.Df     RSS Df Sum of Sq      F    Pr(>F)
1     98 19.5259
2     90  6.9845  8   12.5414 20.201 < 2.2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Essayons avec une relation linéaire.

x <- rep(runif(10, -1, 1), 10)
y <- 1-x+.3*rnorm(100)
r1 <- lm(y ~ x)
r2 <- lm(y ~ factor(x))
anova(r1,r2)

Les deux modèles ne sont pas sensiblement différents.

Analysis of Variance Table

Model 1: y ~ x
Model 2: y ~ factor(x)
  Res.Df    RSS Df Sum of Sq      F Pr(>F)
1     98 9.6533
2     90 8.9801  8    0.6733 0.8435 0.5671

Points influents

Certains points peuvent beaucoup influencer la régression. Parfois, ils proviennent d'erreurs (qu'il faudra identifier et corriger), parfois ce sont des points tout à fait normaux, mais qui ont des valeurs extrèmes. De tels points peuvent exercer un effet de levier et fausser la régression.

n <- 20
done.outer <- F
while (!done.outer) {
  done <- F
  while(!done) {
    x <- rnorm(n)
    done <- max(x)>4.5
  }
  y <- 1 - 2*x + x*rnorm(n)
  r <- lm(y~x)
  done.outer <- max(cooks.distance(r))>5
}
plot(y~x)
abline(1,-2,lty=2)
abline(lm(y~x),col='red',lwd=3)
lm(y~x)$coef

*

La première chose à faire consiste à observer les différentes variables (avant même de faire une régression).

boxplot(x, horizontal=T)

*

stripchart(x, method='jitter')

*

hist(x, col='light blue', probability=T)
lines(density(x), col='red', lwd=3)

*

Idem pour y.

boxplot(y, horizontal=T)

*

stripchart(y, method='jitter')

*

hist(y, col='light blue', probability=T)
lines(density(y), col='red', lwd=3)

*

On voit ici qu'il y a un point extrème. Comme il n'y en a qu'un seul, on aurait bien envie de l'enlever -- s'il y en avait plusieurs, ce serait plus problématique : on essaierait de transformer les données.

Il y a une mesure du caractère extrème d'un point (leverage, en anglais) : ce sont le éléments diagonaux de la matrice chapeau

H = X (X' X)^-1 X'

appelée ainsi car

\hat Y = H Y.

Ces valeurs nous renseignent donc sur la manière dont une erreur sur une variable prédictive propage à la prédiction.

Ces valeurs sont entre 1/n et 1. Tant qu'on reste en dessous de 0.2, c'est bon. On remarquera qu'on n'utilise que les variables prédictives (X), pas du tout la variable à prédire (Y).

plot(hat(x), type='h', lwd=5)

*

On peut ensuite mesurer l'effet de chacun des points sur chacun des coefficients de la régression : pour chacun des points, on refait la régression sans le point, et on compare les valeurs prédites :

plot(dffits(r),type='h',lwd=3)

*

ou les coefficients :

plot(dfbetas(r)[,1],type='h',lwd=3)

*

plot(dfbetas(r)[,2],type='h',lwd=3)

*

En dimensions supérieures, on peut représenter la variation d'un coefficient en fonction de la variation en les autres.

n <- 200
x1 <- rnorm(n)
x2 <- rnorm(n)
yy <- x1 - x2 + rnorm(n)
yy[1] <- 10
r <- lm(yy~x1+x2)
pairs(dfbetas(r))

*

La distance de cook mesure l'effet du point sur la régression en général. En particulier, on fera attention si D>4/n.

cd <- cooks.distance(r)
plot(cd,type='h',lwd=3)

*

On peut aussi regarder la boite à moustache, le diagramme de dispersion, l'histogramme et la densité des distances de Cook (nous mettons momentanément de côté notre exemple pathologique).

n <- 100
xx <- rnorm(n)
yy <- 1 - 2 * x + rnorm(n)
rr <- lm(yy~xx)
cd <- cooks.distance(rr)
plot(cd,type='h',lwd=3)

*

boxplot(cd, horizontal=T)

*

stripchart(cd, method='jitter')

*

hist(cd, probability=T, breaks=20, col='light blue')

*

plot(density(cd), type='l', col='red', lwd=3)

*

qqnorm(cd)
qqline(cd, col='red')

*

Certains suggèrent de comparer la distribution des distances de Cook avec une demi-gaussienne. On fait souvent ça pour les variables dont toutes les valeurs sont positives -- pourtant, ça ne ressemble pas vraiment à une demi-gaussienne.

half.qqnorm <- function (x) {
  n <- length(x)
  qqplot(qnorm((1+ppoints(n))/2), x)
}
half.qqnorm(cd)

*

On peut utiliser ces valeurs pour représenter les points les plus importants en plus gros, sur le graphique de dispersion, ou sur les graphiques des résidus.

m <- max(cooks.distance(r))
plot(y~x, cex=1+5*cooks.distance(r)/m)

*

cd <- cooks.distance(r)
# rescaled Cook's distance
rcd <- (99/4) * cd*(cd+1)^2
rcd[rcd>100] <- 100
plot(r$res~r$fitted.values, cex=1+.05*rcd)
abline(h=0,lty=3)

*

On peut aussi utiliser des couleurs.

m <- max(cd)
plot(r$res, 
     cex=1+5*cd/m,
     col=heat.colors(100)[ceiling(70*cd/m)],
     pch=16,
    )
points(r$res, cex=1+5*cd/m)
abline(h=0,lty=3)

*

plot(r$res, 
     cex=1+.05*rcd,
     col=heat.colors(100)[ceiling(rcd)],
     pch=16,
    )
points(r$res, cex=1+.05*rcd)
abline(h=0,lty=3)

*

L'exemple suivant devrait être plus coloré.

n <- 100
x <- rnorm(n)
y <- 1 - 2*x + rnorm(n)
r <- lm(y~x)
cd <- cooks.distance(r)
m <- max(cd)
plot(r$res ~ r$fitted.values,
     cex=1+5*cd/m,
     col=heat.colors(100)[ceiling(70*cd/m)],
     pch=16,
    )
points(r$res ~ r$fitted.values, cex=1+5*cd/m)
abline(h=0,lty=3)

*

Sur un fond noir, c'est peut-être plus beau...

op <- par(fg='white', bg='black', 
          col='white', col.axis='white', 
          col.lab='white', col.main='white', 
          col.sub='white')
plot(r$res ~ r$fitted.values,
     cex=1+5*cd/m,
     col=heat.colors(100)[ceiling(100*cd/m)],
     pch=16,
    )
abline(h=0,lty=3)
par(op)

*

Voir aussi les commandes lm.influence, influence.measures et ls.diag.

A FAIRE : effacer le graphique suivant ?

# Avec la distance de Cook
x <- rnorm(20)
y <- 1 + x + rnorm(20)
x <- c(x,10)
y <- c(y,1)
r <- lm(y~x)
d <- cooks.distance(r)
d <- (99/4)*d*(d+1)^2 + 1
d[d>100] <- 100
d[d<20] <- 20
d <- d/20
plot( y~x, cex=d )
abline(r)
abline(coef(line(x,y)), col='red')
abline(lm(y[1:20]~x[1:20]),col='blue')

*

Amas de valeurs extrèmes

Quand il n'y a pas une seule observation extrème mais plusieurs, c'est beaucoup plus diffile à repérer. On peut utiliser une régression résistante, comme la régression élaguée (lts).

Généralement, ces valeurs extrèmes multiples ne proviennent pas du hasard (comme une valeur extrème isolée), mais ont une réelle signification. On peut tenter de les repérer avec des méthodes de classification non supervisée.

?hclust
?kmeans

A FAIRE : donner un exemple

Un amas de valeurs extrèmes peut aussi signaler un modèle inadéquat.

A FAIRE : rédiger (et modéliser) l'exemple suivant (mixture)

n <- 200
s <- .2
x <- runif(n)
y1 <- 1 - 2 * x + s*rnorm(n)
y2 <- 2 * x - 1 + s*rnorm(n)
y <- ifelse( sample(c(T,F),n,replace=T,prob=c(.25,.75)), y1, y2 )
plot(y~x)
abline(1,-2,lty=3)
abline(-1,2,lty=3)

*

Résidus non normaux

Si les résidus ne sont pas normaux, les estimateurs obtenus par la méthode des moindre carrés ne sont plus optimaux (certains estimateurs robustes sont alors préférables, même s'ils sont biaisés) et, pire encore, tous les tests et calculs de variance ou d'intervalles de confiance ou de prédiction sont faux.

Néanmoins, si les résidus sont moins dispersés qu'avec une loi normale ou, dans une moindre mesure, si l'échantillon est gros, on peut ignorer ces problèmes.

On peut repérer la non normalité des résidus à l'aide d'histogrammes, de boites à moustaches ou de graphiques quantiles-quantiles.

x <- runif(100)
y <- 1 - 2*x + .3*exp(rnorm(100)-1)
r <- lm(y~x)
boxplot(r$residuals, horizontal=T)

*

hist(r$residuals, breaks=20, probability=T, col='light blue')
lines(density(r$residuals), col='red', lwd=3)
f <- function(x) {
  dnorm(x,
        mean=mean(r$residuals),
        sd=sd(r$residuals),
  )
}
curve(f, add=T, col="red", lwd=3, lty=2)

*

Ne pas oublier les graphiques quantiles/quantiles...

qqnorm(r$residuals)
qqline(r$residuals, col='red')

*

Regardons quels sont les effets réels de résidus non normaux. Nous allons prendre une situation extrème : une variable de Cauchy avec un trou au milieu, et un échantillon pas très grand.

rcauchy.with.hole <- function (n) { 
  x <- rcauchy(n)
  x[x>0] <- 10+x[x>0]
  x[x<0] <- -10+x[x<0]
  x
}
n <- 20
x <- rcauchy(n)
y <- 1 - 2*x + .5*rcauchy.with.hole(n)
plot(y~x)
abline(1,-2)
r <- lm(y~x)
abline(r, col='red')

*

op <- par(mfrow=c(2,2))
hist(r$residuals, breaks=20, probability=T, col='light blue')
lines(density(r$residuals), col='red', lwd=3)
f <- function(x) {
  dnorm(x,
        mean=mean(r$residuals),
        sd=sd(r$residuals),
  )
}
curve(f, add=T, col="red", lwd=3, lty=2)
qqnorm(r$residuals)
qqline(r$residuals, col='red')
plot(r$residuals ~ r$fitted.values)
plot(r$residuals ~ x)
par(op)

*

Faisons quelques prédictions.

n <- 10000
xp <- runif(n,-50,50)
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
sum( yr < yp[,3] & yr > yp[,2] )/n

On obtient 0.9546, i.e., on est bien dans l'intervalle de prédiction dans (plus de) 5% des cas...

Réessayons avec un échantillon plus petit.

n <- 5
x <- rcauchy(n)
y <- 1 - 2*x + .5*rcauchy.with.hole(n)
r <- lm(y~x)
n <- 10000
xp <- sort(runif(n,-50,50))
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
sum( yr < yp[,3] & yr > yp[,2] )/n

C'est encore pire, on obtient 0.9975.

Pour vérifier que je ne me plante pas complètement en faisant ces calculs, traçons certains de ces points.

done <- F
while(!done) {
  # On choisit une situation où l'intervalle de prédiction n'est pas trop grand,
  # de sorte qu'il soit sur le dessin
  n <- 5
  x <- rcauchy(n)
  y <- 1 - 2*x + .5*rcauchy.with.hole(n)
  r <- lm(y~x)
  n <- 100000
  xp <- sort(runif(n,-50,50))
  yp <- predict(r, data.frame(x=xp), interval="prediction")
  done <- ( yp[round(n/2),2] > -75 & yp[round(n/2),3] < 75 )
}
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
plot(yp[,1]~xp, type='l',
     xlim=c(-50,50), ylim=c(-100,100))
points(yr~xp, pch='.')
lines(xp, yp[,2], col='blue')  
lines(xp, yp[,3], col='blue')
abline(r, col='red')
points(y~x, col='orange', pch=16, cex=1.5)
points(y~x, cex=1.5)

*

done <- F
while(!done) {
  # On choisit une situation encore pire, dans laquelle le signe du
  # coefficient directeur de la droite de régression n'est pas le bon
  n <- 5
  x <- rcauchy(n)
  y <- 1 - 2*x + .5*rcauchy.with.hole(n)
  r <- lm(y~x)
  n <- 100000
  xp <- sort(runif(n,-50,50))
  yp <- predict(r, data.frame(x=xp), interval="prediction")
  print(r$coef[2])
  done <- ( yp[round(n/2),2] > -75 & yp[round(n/2),3] < 75 & r$coef[2]>0)
}
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
plot(yp[,1]~xp, type='l',
     xlim=c(-50,50), ylim=c(-100,100))
points(yr~xp, pch='.')
lines(xp, yp[,2], col='blue')  
lines(xp, yp[,3], col='blue')
abline(r, col='red')
points(y~x, col='orange', pch=16, cex=1.5)
points(y~x, cex=1.5)

*

On constate que l'intervalle de prédiction est très grand si on prend des valeurs extrèmes. Essayons donc avec des valeurs plus modestes.

n <- 10000
xp <- sort(runif(n,-.1,.1))
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
sum( yr < yp[,3] & yr > yp[,2] )/n

On obtient 0.9932...

done <- F
while (!done) {
  n <- 5
  x <- rcauchy(n)
  y <- 1 - 2*x + .5*rcauchy.with.hole(n)
  r <- lm(y~x)
  done <- T
}
n <- 10000
xp <- sort(runif(n,-2,2))
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
plot(c(xp,x), c(yp[,1],y), pch='.',
     xlim=c(-2,2), ylim=c(-50,50) )
lines(yp[,1]~xp)
abline(r, col='red')
lines(xp, yp[,2], col='blue')  
lines(xp, yp[,3], col='blue')
points(yr~xp, pch='.')
points(y~x, col='orange', pch=16)
points(y~x)

*

On constate quand-même que parfois (plus d'une fois sur 100), les données ne sont pas dans l'intervalle de prédiction à 95%.

done <- F
essais <- 0
while (!done) {
  n <- 5
  x <- rcauchy(n)
  y <- 1 - 2*x + .5*rcauchy.with.hole(n)
  r <- lm(y~x)
  yp <- predict(r, data.frame(x=2), interval='prediction')
  done <- yp[3]<0
  essais <- essais+1
}
print(essais) # Autour de 20 ou 30
n <- 10000
xp <- sort(runif(n,-2,2))
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .5*rcauchy.with.hole(n)
plot(c(xp,x), c(yp[,1],y), pch='.',
     xlim=c(-2,2), ylim=c(-50,50) )
lines(yp[,1]~xp)
points(yr~xp, pch='.')
abline(r, col='red')
lines(xp, yp[,2], col='blue')  
lines(xp, yp[,3], col='blue')
points(y~x, col='orange', pch=16)
points(y~x)

*

done <- F
e <- NULL
for (i in 1:100) {
  essais <- 0
  done <- F
  while (!done) {
    n <- 5
    x <- rcauchy(n)
    y <- 1 - 2*x + .5*rcauchy.with.hole(n)
    r <- lm(y~x)
    yp <- predict(r, data.frame(x=2), interval='prediction')
    done <- yp[3]<0
    essais <- essais+1
  }
  e <- append(e,essais)
}
hist(e, probability=T, col='light blue')
lines(density(e), col='red', lwd=3)
abline(v=median(e), lty=2, col='red', lwd=3)

*

> mean(e)
[1] 25.8
> median(e)
[1] 19

En résumé, pour avoir l'intervalle de prédiction le plus incorrect possible, il faut prendre des valeurs de x pas trop petites (car au voisinage de zéro, les prédictions sont à peu près correctes) mais pas trop grandes non plus (car loin de zéro, les intervalles de prédiction sont démesurément grands).

Je voulais mettre en évidence, sur un exemple, que des résidus non normaux donnaient des intervalles de confiance trop petits, et donc des résultats faux : il n'en est rien. Si les résidus ne sont pas normaux, les intervalles de confiance calculés sont bien trop grands. Les prédictions sont donc annoncées comme peu précises, mais pas fausses.

Exercice : essayer avec d'autres distributions (uniforme, Cauchy, Cauchy trouée), que ce soit pour le bruit ou pour les variables.

Hétéroscédasticité

Pour que les estimateurs soient optimaux et que les tests donnent des résultats corrects, on a du supposer (entre autres) que les résidus étaient de variance constante. S'ils ne sont pas de variance constante, on parle d'hétéroscédasticité.

x <- runif(100)
y <- 1 - 2*x + .3*x*rnorm(100)
plot(y~x)
r <- lm(y~x)
abline(r, col='red')
title(main="Hétéroscédasticité")

*

On peut voir ce problème sur les résidus

plot(r$residuals ~ r$fitted.values)

*

ou plus précisément sur leur valeur absolue, sur laquelle on peut faire une régression (non linéaire).

plot(abs(r$residuals) ~ r$fitted.values)
lines(lowess(r$fitted.values, abs(r$residuals)), col='red')

*

plot(abs(r$residuals) ~ x)
lines(lowess(x, abs(r$residuals)), col='red')

*

Voici un exemple concret.

data(crabs)
plot(FL~RW, data=crabs)

*

r <- lm(FL~RW, data=crabs)
plot(r, which=1)

*

plot(r, which=3, panel = panel.smooth)

*

La commande spread.level.plot (dans la bibliothèque car) fait à peu près la même chose : elle représente la valeur absolue des résidus en fonction des valeurs prédites, sur des échelles logarithmiques, et suggère une transformation pour se débarasser de l'hétéroscédasticité.

library(car)
spread.level.plot(r)

*

On peut aussi le vérifier par le calcul, en découpant l'échantillon en deux et en faisant un test pour voir si les deux morceaux ont la même variance.

n <- length(crabs$RW)
m <- ceiling(n/2)
o <- order(crabs$RW)
r <- lm(FL~RW, data=crabs)
x <- r$residuals[o[1:m]]
y <- r$residuals[o[(m+1):n]]
var.test(y,x) # p-value = 1e-4

Nous allons tenter de montrer l'effet de l'hétéroscédasticité sur un exemple.

x <- runif(100)
y <- 1 - 2*x + .3*x*rnorm(100)
r <- lm(y~x)
xp <- runif(10000,0,.1)
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .3*xp*rnorm(100)
sum( yr < yp[,3] & yr > yp[,2] )/n

On obtient 1 : pour les valeurs de x correspondant à une petite variance, les intervalles de confiance sont trop grands.

x <- runif(100)
y <- 1 - 2*x + .3*x*rnorm(100)
r <- lm(y~x)
xp <- runif(10000,.9,1)
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .3*xp*rnorm(100)
sum( yr < yp[,3] & yr > yp[,2] )/n

On obtient 0.67 : pour les valeurs de x correspondant à une grande variance, les intervalles de confiance sont trop petits.

On peut voir cela graphiquement.

x <- runif(100)
y <- 1 - 2*x + .3*x*rnorm(100)
r <- lm(y~x)
n <- 10000
xp <- sort(runif(n,))
yp <- predict(r, data.frame(x=xp), interval="prediction")
yr <- 1 - 2*xp + .3*xp*rnorm(n)

plot(c(xp,x), c(yp[,1],y), pch='.')
lines(yp[,1]~xp)
abline(r, col='red')
lines(xp, yp[,2], col='blue')  
lines(xp, yp[,3], col='blue')
points(yr~xp, pch='.')
points(y~x, col='orange', pch=16)
points(y~x)
title(main="Effet de l'hétéroscédasticité sur les intervalles de prédiction")

*

La manière la plus simple de se débarasser de l'hétéroscédasticité, c'est (quand ça marche) de transformer les données. Si c'est possible, on s'efforce en même temps de normaliser les données et de se débarasser de l'hétéroscédasticité.

Les moindres carrés généralisés permettent d'effectuer une régression même en présence d'hétéroscédasticité, mais il faut préalablement estimer la variance et la manière dont elle évolue.

A FAIRE : mettre cet exemple plus loin ?

n <- 100
x <- runif(n)
y <- 1 - 2*x + x*rnorm(n)
plot(y~x)
r <- lm(y~x)
abline(r, col='red')
title(main="Régression linéaire classique")

*

plot(abs(r$res) ~ x)
r2 <- lm( abs(r$res) ~ x )
abline(r2, col="red")
title(main="Hétéroscédasticité des résidus")

*

Les moindres carrés pondérés consistent à donner un poids (i.e., une importance) moindre aux observations dont la variance est élevée.

# On fait l'hypothèse que l'écart-type des résidus
# est de la forme a*x
a <- lm( I(r$res^2) ~ I(x^2) - 1 )$coefficients
w <- (a*x)^-2
r3 <- lm( y ~ x, weights=w )
plot(y~x)
abline(1,-2, lty=3)
abline(lm(y~x), lty=3, lwd=3)
abline(lm(y~x, weights=w), col='red') 
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("modèle réel", "moindres carrés", "moindres carrés pondérés"),
        lwd=c(1,3,1),
        lty=c(3,3,1),
        col=c(par("fg"), par("fg"), 'red') )
title("Moindres carrés pondérés et hétéroscédasticité")

*

Par contre, les intervalles de prédiction ne sont pas vraiment convainquants...

A FAIRE : vérifier ce qui suit.

# Les intervalles de prédiction
N <- 10000
xx <- runif(N,min=0,max=2)
yy <- 1 - 2*xx + xx*rnorm(N)
plot(y~x, xlim=c(0,2), ylim=c(-3,2))
points(yy~xx, pch='.')
abline(1,-2, col='red')
xp <- seq(0,3,length=100)
yp1 <- predict(r, new=data.frame(x=xp), interval='prediction')
lines( xp, yp1[,2], col='red', lwd=3 )
lines( xp, yp1[,3], col='red', lwd=3 )
yp3 <- predict(r3, new=data.frame(x=xp), interval='prediction')
lines( xp, yp3[,2], col='blue', lwd=3 )
lines( xp, yp3[,3], col='blue', lwd=3 )
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("moindres carrés", "moindres carrés pondérés"),
        lwd=3, lty=1,
        col=c('red', 'blue') )
title(main="Bande de prédiction")

*

On peut aussi faire ça à l'aide de la commande gls :

A FAIRE

?gls
?varConstPower
?varPower

r4 <- gls(y~x, weights=varPower(1, form= ~x))
???

Corrélation entre les erreurs

Dans le cas d'une série temporelle, de données géographiques (ou de manière générale de données pour les observations desquelles on a une notion de proximité) les erreurs entre deux observations successives peuvent être liées.

Dans le cas des séries temporelles, on peut voir le problème avec un autocorrélogramme.

my.acf.plot <- function (x, n=10, ...) {
  y <- rep(NA,n)
  l <- length(x)
  for (i in 1:n) {
    y[i] <- cor( x[1:(l-i)], x[(i+1):l] )
  }
  plot(y, type='h', ylim=c(-1,1),...)
}
n <- 100
x <- runif(n)
b <- .1*rnorm(n+1)
y <- 1-2*x+b[1:n]
my.acf.plot(lm(y~x)$res, lwd=10)
abline(h=0, lty=2)

*

z <- 1-2*x+.5*(b[1:n]+b[1+1:n])
my.acf.plot(lm(z~x)$res, lwd=10)
abline(h=0, lty=2)

*

Voici un exemple hautement auto-corrélé.

n <- 500
x <- runif(n)
b <- rep(NA,n)
b[1] <- 0
for (i in 2:n) {
  b[i] <- b[i-1] + .1*rnorm(1)
}
y <- 1-2*x+b[1:n]
my.acf.plot(lm(y~x)$res, n=100)
abline(h=0, lty=2)
title(main='Exemple hautement autocorrélé')

*

On ne voit rien sur le graphe des résidus en fonction des valeurs prédites.

r <- lm(y~x)
plot(r$res ~ r$fitted.values)
title(main="Résidus de l'exemple hautement autocorrélé")

*

Par contre, si on représente les résidus en fonction du temps, c'est plus clair.

r <- lm(y~x)
plot(r$res)
title(main="Résidus de l'exemple hautement autocorrélé")

*

Une autre manière de repérer le problème, c'est de regarder si la corrélation entre x[i] et x[i-1] est significativement non nulle.

n <- 100
x <- runif(n)
b <- rep(NA,n)
b[1] <- 0
for (i in 2:n) {
  b[i] <- b[i-1] + .1*rnorm(1)
}
y <- 1-2*x+b[1:n]
r <- lm(y~x)$res
cor.test(r[1:(n-1)], r[2:n]) # p-valeur inférieure à 1e-15

n <- 100
x <- runif(n)
b <- .1*rnorm(n+1)
y <- 1-2*x+b[1:n]
r <- lm(y~x)$res
cor.test(r[1:(n-1)], r[2:n]) # p-valeur = 0.3

y <- 1-2*x+.5*(b[1:n]+b[1+1:n])
cor.test(r[1:(n-1)], r[2:n]) # p-valeur = 0.3 (encore)

Voir aussi le test de Durbin--Watson :

library(car)
?durbin.watson

library(lmtest)
?dwtest

ainsi que le chapitre sur les séries temporelles.

Une troisième manière de voir le problème, c'est de tracer les résidus successifs, en 2 ou 3 dimensions.

n <- 500
x <- runif(n)
b <- rep(NA,n)
b[1] <- 0
for (i in 2:n) {
  b[i] <- b[i-1] + .1*rnorm(1)
}
y <- 1-2*x+b[1:n]
r <- lm(y~x)$res
plot( r[1:(n-1)], r[2:n],
      xlab='i-ième résidu', 
      ylab='(i+1)-ième résidu' )

*

Dans l'exemple suivant, on ne voit rien en regardant deux termes successifs (enfin si, ça ressemble à un test de Rorschach, donc c'est suspect) : il en faut trois.

n <- 500
x <- runif(n)
b <- rep(NA,n)
b[1] <- 0
b[2] <- 0
for (i in 3:n) {
  b[i] <- b[i-2] + .1*rnorm(1)
}
y <- 1-2*x+b[1:n]
r <- lm(y~x)$res
plot(data.frame(x=r[3:n-2], y=r[3:n-1], z=r[3:n]))

*

plot(r)

*

C'est d'ailleurs ainsi qu'on s'apperçoit des problèmes de certains vieux générateurs de nombres aléatoires. En 3D, de face, on ne voit rien,

data(randu)
plot(randu)
# On ne voit rien...

*

il faut tourner la figure...

library(xgobi)
xgobi(randu)

*

On peut aussi la faire tourner directement sous R, en prenant une matrice de rotation au hasard (Exercice : écrire une telle fonction (il y en a une quelque part dans ce document))

m <- matrix( c(0.0491788982891203, -0.998585856299176, 0.0201921658647648,  
               0.983046639705112,  0.0448184901961194, -0.177793720645666, 
               -0.176637312387723,  -0.028593540105802, -0.983860594462783),
             nr=3, nc=3)
plot( t( m %*% t(randu) )[,1:2] )

*

Voici un exemple réel.

data(EuStockMarkets)
plot(EuStockMarkets)

*

x <- EuStockMarkets[,1]
y <- EuStockMarkets[,2]
r <- lm(y~x)
plot(y~x)
abline(r, col='red', lwd=3)

*

plot(r, which=1)

*

plot(r, which=3)

*

plot(r, which=4)

*

r <- r$res
hist(r, probability=T, col='light blue')
lines(density(r), col='red', lwd=3)

*

plot(r)

*

acf(r)

*

pacf(r)

*

r <- as.vector(r)
x <- r[1:(length(r)-1)]
y <- r[2:length(r)]
plot(x,y, xlab='x[i]', ylab='x[i+1]')

*

Dans ce genre de situation, on peut utiliser les moindres carrés généralisés. Le modèle AR1 suppose que les erreurs successives sont corrélées :

e_{i+1} = r * e_i + f_i

où r est le coefficient de corrélation et les f_i sont indépendantes.

n <- 100
x <- rnorm(n)
e <- vector()
e <- append(e, rnorm(1))
for (i in 2:n) {
  e <- append(e, .6 * e[i-1] + rnorm(1) )
}
y <- 1 - 2*x + e
i <- 1:n
plot(y~x)

*

r <- lm(y~x)$residuals
plot(r)

*

Les moindres carrés généralisés se trouvent dans la bibliothèque nlme.

library(nlme)
g <- gls(y~x, correlation = corAR1(form= ~i))

Voici le résultat.

> summary(g)
Generalized least squares fit by REML
  Model: y ~ x
  Data: NULL
       AIC      BIC    logLik
  298.4369 308.7767 -145.2184

Correlation Structure: AR(1)
 Formula: ~i
 Parameter estimate(s):
      Phi
0.3459834

Coefficients:
                Value  Std.Error    t-value p-value
(Intercept)  1.234593 0.15510022   7.959971  <.0001
x           -1.892171 0.09440561 -20.042992  <.0001

 Correlation:
  (Intr)
x 0.04

Standardized residuals:
        Min          Q1         Med          Q3         Max
-2.14818684 -0.75053384  0.02200128  0.57222518  2.45362824

Residual standard error: 1.085987
Degrees of freedom: 100 total; 98 residual

On peut regarder l'intervalle de confiance sur le coefficient d'autocorrélation.

> intervals(g)
Approximate 95% confidence intervals

 Coefficients:
                lower      est.     upper
(Intercept)  0.926802  1.234593  1.542385
x           -2.079516 -1.892171 -1.704826

 Correlation structure:
        lower      est.     upper
Phi 0.1477999 0.3459834 0.5174543

 Residual standard error:
   lower     est.    upper
0.926446 1.085987 1.273003

Comparons avec la régression naïve.

library(nlme)
plot(y~x)
abline(lm(y~x))
abline(gls(y~x, correlation = corAR1(form= ~i)), col='red')

*

Dans cet exemple, il n'y a aucun effet notable. Dans le suivant, la situation est plus dramatique.

n <- 1000
x <- rnorm(n)
e <- vector()
e <- append(e, rnorm(1))
for (i in 2:n) {
  e <- append(e, 1 * e[i-1] + rnorm(1) )
}
y <- 1 - 2*x + e
i <- 1:n
plot(lm(y~x)$residuals)

*

plot(y~x)
abline(lm(y~x))
abline(gls(y~x, correlation = corAR1(form= ~i)), col='red')
abline(1,-2, lty=2)

*

Nous reverrons cela quand nous parlerons de séries temporelles.

Dans le cas de données géographiques, c'est beaucoup plus compliqué.

A FAIRE : une référence ?

Modèles

On constate que si on donne les mêmes données à différentes personnes (par exemple, des étudiants, lors d'un examen), chacun aura un modèle différent et des prévisions différentes. Ces prévisions et leurs intervalles de confiance sont généralement incompatibles -- et les intervalles de prédiction sont toujours trop petits...

Multicolinéarité (unidentifiability)

Le coefficient de corrélation entre deux variables permet de voir si elles sont colinéaires. Mais on peut aussi avoir des relations entre plus de deux variables, du genre X3 = X1 + X2. Pour les détecter, on peut faire une régression de Xk par rapport aux autres Xi et regarder R^2 (le coefficient de corrélation ou pourcentage de la variance expliquée) : s'il est élevé (>1e-1), Xk se déduit des autres Xi.

n <- 100
x <- rnorm(n)
x1 <- x+rnorm(n)
x2 <- x+rnorm(n)
x3 <- rnorm(n)
y <- x+x3
summary(lm(x1~x2+x3))$r.squared  # 1e-1
summary(lm(x2~x1+x3))$r.squared  # 1e-1
summary(lm(x3~x1+x2))$r.squared  # 1e-3

Autre exemple, avec 3 variables liées.

n <- 100
x1 <- rnorm(n)
x2 <- rnorm(n)
x3 <- x1+x2+rnorm(n)
x4 <- rnorm(n)
y <- x1+x2+x3+x4+rnorm(n)
summary(lm(x1~x2+x3+x4))$r.squared  # 0.5
summary(lm(x2~x1+x3+x4))$r.squared  # 0.4
summary(lm(x3~x1+x2+x4))$r.squared  # 0.7
summary(lm(x4~x1+x2+x3))$r.squared  # 3e-3

Exemple réel (ici, je prends le coefficient de détermination ajusté) :

check.multicolinearity <- function (M) {
  a <- NULL
  n <- dim(M)[2]
  for (i in 1:n) {
    m <- as.matrix(M[, 1:n!=i])
    y <- M[,i]
    a <- append(a, summary(lm(y~m))$adj.r.squared)
  }
  names(a) <- names(M)
  print(round(a,digits=2))
  invisible(a)
}
data(freeny)
names(freeny) <- paste(
  names(freeny), 
  " (",
  round(check.multicolinearity(freeny), digits=2),
  ")",
  sep='')
pairs(freeny,
  upper.panel=panel.smooth,
  lower.panel=panel.smooth)

*

Dans une telle situation, le fait qu'un certain coefficient soit statistiquement non nul dépend de la présence des autres variables. Avec toutes les variables, la première variable explicative ne joue pas un rôle significatif, mais la seconde, si.

> summary(lm(freeny.y ~ freeny.x))
...
Coefficients:
                              Estimate Std. Error t value Pr(>|t|)
(Intercept)                   -10.4726     6.0217  -1.739   0.0911 .
freeny.xlag quarterly revenue   0.1239     0.1424   0.870   0.3904
freeny.xprice index            -0.7542     0.1607  -4.693 4.28e-05 ***
freeny.xincome level            0.7675     0.1339   5.730 1.93e-06 ***
freeny.xmarket potential        1.3306     0.5093   2.613   0.0133 *
...
Multiple R-Squared: 0.9981,     Adjusted R-squared: 0.9978

Par contre, si on ne retient que les deux premières variables, c'est le contraire.

> summary(lm(freeny.y ~ freeny.x[,1:2]))
...
                                     Estimate Std. Error t value Pr(>|t|)
(Intercept)                           2.18577    1.47236   1.485    0.146
freeny.x[, 1:2]lag quarterly revenue  0.89122    0.07412  12.024 3.63e-14 ***
freeny.x[, 1:2]price index           -0.25592    0.17534  -1.460    0.153
...
Multiple R-Squared: 0.9958,     Adjusted R-squared: 0.9956

De plus, l'estimation des coefficients et leur erreur standard sont assez inquiétants... A ce niveau, un autre problème peut apparaitre : en cas de multicolinéarité, le signe d'un coefficient n'est pas toujours bien déterminé.

n <- 100
v <- .1
x <- rnorm(n)
x1 <- x + v*rnorm(n)
x2 <- x + v*rnorm(n)
x3 <- x + v*rnorm(n)
y <- x1+x2-x3 + rnorm(n)

On vérifie que les variables sont bien liées.

> summary(lm(x1~x2+x3))$r.squared
[1] 0.986512
> summary(lm(x2~x1+x3))$r.squared
[1] 0.98811
> summary(lm(x3~x1+x2))$r.squared
[1] 0.9862133

On regarde quelles sont les variables les plus pertinentes.

> summary(lm(y~x1+x2+x3))

Call:
lm(formula = y ~ x1 + x2 + x3)

Residuals:
    Min      1Q  Median      3Q     Max
-3.0902 -0.7658  0.0793  0.6995  2.6456

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  0.06361    0.11368   0.560   0.5771
x1           1.47317    0.94653   1.556   0.1229
x2           1.18874    0.98481   1.207   0.2304
x3          -1.70372    0.94366  -1.805   0.0741 .
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 1.135 on 96 degrees of freedom
Multiple R-Squared: 0.4757,     Adjusted R-squared: 0.4593
F-statistic: 29.03 on 3 and 96 DF,  p-value: 1.912e-13

C'est la troisième.

> lm(y~x3)$coef
(Intercept)          x3
 0.06970513  0.98313878

Son coefficient était négatif, mais si on enlève les autres variables, il devient positif.

Au lieu de regarder les R^2, on peut regarder les Facteurs d'Inflation de la Variance (VIF),

          1
V_j = -----------
       1 - R_j^2

Au lieu de regarder les R^2, on peut regarder la matrice des corrélations entre les coefficients estimés.

n <- 100
v <- .1
x <- rnorm(n)
x1 <- x + v*rnorm(n)
x2 <- rnorm(n)
x3 <- x + v*rnorm(n)
y <- x1+x2-x3 + rnorm(n)
summary(lm(y~x1+x2+x3), correlation=T)$correlation

On obtient

             (Intercept)          x1            x2          x3
(Intercept)  1.0000000000 -0.02036269  0.0001812560  0.02264558
x1          -0.0203626936  1.00000000 -0.1582002900 -0.98992751
x2           0.0001812560 -0.15820029  1.0000000000  0.14729488
x3           0.0226455841 -0.98992751  0.1472948846  1.00000000

On constate que les variables X1 et X3 sont relativement liées. On peut aussi le voir graphiquement.

n <- 100
v <- .1
x <- rnorm(n)
x1 <- x + v*rnorm(n)
x2 <- rnorm(n)
x3 <- x + v*rnorm(n)
y <- x1+x2-x3 + rnorm(n)
m <- summary(lm(y~x1+x2+x3), correlation=T)$correlation
plot(col(m), row(m), cex=10*abs(m),
     xlim=c(0, dim(m)[2]+1), 
     ylim=c(0, dim(m)[1]+1),
     main="Matrice de corrélation des coefficients d'une régression")

*

Une autre manière de voir le problème consiste à faire le rapport entre la plus grande valeur propre de la matrice et la plus petite : si c'est en dessous de 100, c'est bon, au dessus de 1000, c'est grave. C'est l'indice de conditionnement.

m <- model.matrix(y~x1+x2+x3)
d <- eigen( t(m) %*% m, symmetric=T )$values
d[1]/d[4] # 230

Pour résoudre ce problème, on peut enlever les variables superflues (mais se posent alors des problèmes d'interprétation). On peut aussi demander plus de données (les problèmes de multicolinéarité sont fréquents quand on a beaucoup de variables et peu d'individus).

Nous avons en fait déjà rencontré cette problématique avec la régression polynômiale : pour se débarasser de la multicolinéarité, nous avions orthonormalisé la famille de vecteurs.

> y <- cars$dist
> x <- cars$speed
> m <- cbind(x, x^2, x^3, x^4, x^5)
> cor(m)
          x
x 1.0000000 0.9794765 0.9389237 0.8943823 0.8515996
  0.9794765 1.0000000 0.9884061 0.9635754 0.9341101
  0.9389237 0.9884061 1.0000000 0.9927622 0.9764132
  0.8943823 0.9635754 0.9927622 1.0000000 0.9951765
  0.8515996 0.9341101 0.9764132 0.9951765 1.0000000

> m <- poly(x,5)
> cor(m)
              1             2             3             4             5
1  1.000000e+00  6.409668e-17 -1.242089e-17 -3.333244e-17  7.935005e-18
2  6.409668e-17  1.000000e+00 -4.468268e-17 -2.024748e-17  2.172470e-17
3 -1.242089e-17 -4.468268e-17  1.000000e+00 -6.583818e-17 -1.897354e-18
4 -3.333244e-17 -2.024748e-17 -6.583818e-17  1.000000e+00 -4.903304e-17
5  7.935005e-18  2.172470e-17 -1.897354e-18 -4.903304e-17  1.000000e+00

La "ridge regression" (voir un peu plus haut dans ce document) est peu sensible à la multicolinéarité.

Variables manquantes

Il peut manquer des variables importantes, dont l'absence peut complètement changer les résultats de la régression et leur interprétation.

Dans l'exemple suivant, on a trois variables.

n <- 100
x <- runif(n)
z <- ifelse(x>.5,1,0)
y <- 2*z -x + .1*rnorm(n)
plot( y~x, col=c('red','blue')[1+z] )

*

Si on tient compte des trois variables, il y a une corrélation négative entre y et x.

> summary(lm( y~x+z ))

Call:
lm(formula = y ~ x + z)

Residuals:
      Min        1Q    Median        3Q       Max
-0.271243 -0.065745  0.002929  0.068085  0.215251

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  0.01876    0.02404    0.78    0.437
x           -1.05823    0.07126  -14.85   <2e-16 ***
z            2.05321    0.03853   53.28   <2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.1008 on 97 degrees of freedom
Multiple R-Squared: 0.9847,     Adjusted R-squared: 0.9844
F-statistic:  3125 on 2 and 97 DF,  p-value: < 2.2e-16

Par contre, si on n'a pas la variable z, la corrélation est positive...

> summary(lm( y~x ))

Call:
lm(formula = y ~ x)

Residuals:
     Min       1Q   Median       3Q      Max
-1.05952 -0.38289 -0.01774  0.50598  1.05198

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  -0.5689     0.1169  -4.865 4.37e-06 ***
x             2.1774     0.2041  10.669  < 2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.5517 on 98 degrees of freedom
Multiple R-Squared: 0.5374,     Adjusted R-squared: 0.5327
F-statistic: 113.8 on 1 and 98 DF,  p-value: < 2.2e-16

Pour contourner le problème, il faut inclure toutes les variables suceptibles d'être importantes (on sélectionne ces variables à l'aide de la connaissance préalable de domaine d'étude, i.e., par des moyens non statistiques). Pour confirmer un certain effet, on peut aussi essayer d'autre modèles (s'ils donnent tous un effet dans le même sens, c'est bon signe) ou faire d'autres études, dans des conditions différentes.

Extrapolation

On veut parfois extrapoler à partir des données, i.e., voir ce qui se passe sur une échelle plus grande. C'est problématique pour plusieurs raisons. Tout d'abord, les intervalles de confiances augmentent quand on s'éloigne des valeurs de l'échantillon.

n <- 20
x <- rnorm(n)
y <- 1 - 2*x - .1*x^2 + rnorm(n)
#summary(lm(y~poly(x,10)))
plot(y~x, xlim=c(-20,20), ylim=c(-30,30))
r <- lm(y~x)
abline(r, col='red')
xx <- seq(-20,20,length=100)
p <- predict(r, data.frame(x=xx), interval='prediction')
lines(xx,p[,2],col='blue')
lines(xx,p[,3],col='blue')
title(main="Augmentation de l'intervalle de prédiction")

*

D'autre part, si la relation entre les données a l'air linéaire à petite échelle, il n'en va peut-être pas de même sur une échelle plus grande.

plot(y~x, xlim=c(-20,20), ylim=c(-30,30))
r <- lm(y~x)
abline(r, col='red')
xx <- seq(-20,20,length=100)
yy <- 1 - 2*xx - .1*xx^2 + rnorm(n)
p <- predict(r, data.frame(x=xx), interval='prediction')
points(yy~xx)
lines(xx,p[,2],col='blue')
lines(xx,p[,3],col='blue')
title(main="Problème d'extropolation : ça n'était pas linéaire...")

*

data(cars)
y <- cars$dist
x <- cars$speed
o <- x<quantile(x,.25)
x1 <- x[o]
y1 <- y[o]
r <- lm(y1~x1)
xx <- seq(min(x),max(x),length=100)
p <- predict(r, data.frame(x1=xx), interval='prediction')
plot(y~x)
abline(r, col='red')
lines(xx,p[,2],col='blue')
lines(xx,p[,3],col='blue')

*

Erreurs de mesure

Il peut y avoir des erreurs de mesure sur la variable indépendante : cela conduit à une estimation biaisée (vers 0) des paramètres.

n <- 100
e <- .5
x0 <- rnorm(n)
x <- x0 + e*rnorm(n)
y <- 1 - 2*x0 + e*rnorm(n)
plot(y~x)
points(y~x0, col='red')
abline(lm(y~x))
abline(lm(y~x0),col='red')

*

Par contre, ça n'est pas un problème sur les prédictions, car les erreurs de mesure sur les variables indépendantes seront toujours présentes.

A FAIRE

library(help=lmtest)
library(help=strucchange)

# Hétéroscédasticité
library(lmtest)
?dwtest
?bgtest
?bptest
?gqtest
?hmctest

# Non-linearity
library(lmtest)
?harvtest
?raintest
?reset

La bibliothèque "strucchange" permet de repérer les changements structurels (très souvent avec des séries temporelles, par exemple en économétrie). On parle de changement structurel quand la régression linéaire est un modèle correct, mais que les coefficients de la régression changent brutalement. Si on sait où le changement a lieu, on peut simplement couper les données en deux, faire deux régressions et procéder à un test pour voir si les coefficients des deux régressions sont les mêmes. Si on ne sait pas, on peut déplacer une fenêtre le long des données et faire une régression à chaque fois, en regardant si les coefficients changent (la fenêtre peut avoir une largeur constante ou contenir un nombre d'observations constant).

A FAIRE : un exemple

# Structural change
library(strucchange)
efp(..., type="Rec-CUSUM")
efp(..., type="OLS-MOSUM")
plot(efp(...))
sctest(efp(...))

A FAIRE : on peut aussi regarder si une variable est constante (x~1).

On peut aussi déplacer le point en lequel on pense qu'il peut y avoir un changement et faire un test à chaque fois.

A FAIRE : un exemple

Fstat(...)
plot(Fstat(...))
sctest(Fstat(...))

A FAIRE

Expliquer ce qu'on peut faire si on a plus de variables que
d'observations.
Une approche naïve ne marche pas :
n <- 100
k <- 500
x <- matrix(rnorm(n*k), nr=n, nc=k)
y <- apply(x, 1, sum)
lm(y~x)

Vincent Zoonekynd
<zoonek@math.jussieu.fr>
latest modification on Wed Oct 13 22:32:57 BST 2004