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

D'autres régressions

Définition générale de la régression
Quelques droites de régression
Asymétrie de la régression et ACP (Analyse en Composantes Principales)
Variantes des moindres carrés
Polynômes : régression curvilinéaire
Régressions locales
Régressions pénalisées
Régression non linéaire
A CLASSER
A FAIRE

A FAIRE : revoir l'ordre de ce chapitre

Définition générale de la régression
Quelques droites de régression (droites des médianes, etc.)
Régression et ACP
Variantes des moindres carrés (L1, Huber, etc.)
Moindres carrés généralisés (pondérés, IRLS, etc.)
Régressions polynomiale, splines
Régressions locales (on commence avec les lignes brisées)
PCR, PLS, ridge, lasso, PMLE, etc.
Régression non linéaire

Les moindres carrés ne sont pas la seule méthode pour approcher des données par une fonction : en voici quelques autres.

Définition générale de la régression

On s'intéresse à la prédiction d'une variable statistique Y à l'aide d'une autre variable X, toutes deux quantitatives.

La courbe de régression de Y sur X est la fonction f qui minimise

E( Y - f(X) )^2.

On peut l'obtenir ainsi :

f(x) = E[ Y | X=x ].

Le problème, c'est que pour calculer cette régression, on aurait besoin de connaître la distribution de Y|X, or on ne dispose que d'un échantillon. On peut tenter d'approcher la régression par des moyennes mobiles, ou des moyennes mobiles pondérées par un noyau.

Néanmoins, on se limite généralement à une fonction qui a une forme relativement simple, comme une fonction affine ou un polynôme (plus le modèle est simple, plus les estimateurs sont stables). On cherche alors à minimiser

E( Y - (aX+b) )^2.

C'est la régression linéaire.

Quelques droites de régression

Droite de Brown--Mood

On divise les données en deux groupes ; on prend le centre (le point médiant) de chaque, et on les relie.

data(cars)
x <- cars$speed
y <- cars$dist
plot(y~x)
o <- order(x)
n <- length(x)
m <- floor(n/2)
p1 <- c( median(x[o[1:m]]), median(y[o[1:m]]) )
m <- ceiling(n/2)
p2 <- c( median(x[o[m:n]]), median(y[o[m:n]]) )
p <- rbind(p1,p2)
points(p, pch='+', lwd=3, cex=5, col='red' )
lines(p, col='red', lwd=3)
title(main="Droite de Brown-Mood")

*

Autre droite

On sépare les données en trois groupes, on prend leurs centre, et on considère la droite passant par le centre de gravité du triangle ainsi obtenu et parallèle à sa base.

three.group.resistant.line <- function (y, x) { 
  o <- order(x)
  n <- length(x)
  o1 <- o[1:floor(n/3)]
  o2 <- o[ceiling(n/3):floor(2*n/3)]
  o3 <- o[ceiling(2*n/3):n]
  p1 <- c( median(x[o1]), median(y[o1]) )
  p2 <- c( median(x[o2]), median(y[o2]) )
  p3 <- c( median(x[o3]), median(y[o3]) )
  p <- rbind(p1,p2,p3)
  g <- apply(p,2,mean)
  plot(y~x)
  points(p, pch='+', lwd=3, cex=3, col='red')
  polygon(p, border='red')
  a <- (p3[2] - p1[2])/(p3[1] - p1[1])
  b <- g[2]-a*g[1]
  abline(b,a,col='red')
}
three.group.resistant.line(cars$dist, cars$speed)

*

Si le triangle n'est pas complètement applati, c'est signe que la relation n'est probablement pas linéaire...

n <- 100
x <- runif(n,min=0,max=2)
y <- x*(1-x) + rnorm(n)
three.group.resistant.line(y,x)

*

La droite des médianes

On note b_ij la pente de la droite passant par les points i et j et on prend

b = median b_ij
  
a = median (yi - b xi)
      i

droite.des.medianes <- function (y,x) {
  n <- length(x)
  b <- matrix(NA, nr=n, nc=n)
  # Exercice : écrire ça sans aucune boucle
  for (i in 1:n) {
    for (j in 1:n) {
      if(i!=j)
        b[i,j] <- ( y[i] - y[j] )/( x[i] - x[j] )
    }
  }
  b <- median(b, na.rm=T)
  a <- median(y-b*x)
  plot(y~x)
  abline(a,b, col='red')
  title(main="La droite des médianes")
}
droite.des.medianes(cars$dist, cars$speed)

*

Une autre droite des médianes

On procède à peu près comme précédemment, mais avec :

b = median median b_ij
      i     j!=i

a = median (yi - b xi)
      i

Cela donne :

autre.droite.des.medianes <- function (y,x) {
 n <- length(x)
  b <- matrix(NA, nr=n, nc=n)
  # Exercice : écrire ça sans aucune boucle
  for (i in 1:n) {
    for (j in 1:n) {
      if(i!=j)
        b[i,j] <- ( y[i] - y[j] )/( x[i] - x[j] )
    }
  }
  b <- median( apply(b, 1, median, na.rm=T), na.rm=T ) # Seule chose qui change
  a <- median(y-b*x)
  plot(y~x)
  abline(a,b, col='red')
  title(main="La droite des médianes")
}
autre.droite.des.medianes(cars$dist, cars$speed)

*

Asymétrie de la régression et ACP (Analyse en Composantes Principales)

La régression linéaire cherche à minimiser la somme des carrés des distances, mesurées verticalement, entre la droite et le nuage de points.

data(cars)
plot(cars)
abline(lm(cars$dist ~ cars$speed), col='red')
title(main="Régression dist ~ speed")

*

Ça n'est pas symétrique : si on intervertit les variables, on obtient quelque chose de différent (toutefois, les deux droites passent par le centre de gravité du nuage).

plot(cars)
r <- lm(cars$dist ~ cars$speed)
abline(r, col='red')
r <- lm(cars$speed ~ cars$dist)
a <- r$coefficients[1] # Intercept
b <- r$coefficients[2] # slope
abline(-a/b , 1/b, col="blue")
title(main="Régression dist ~ speed et speed ~ dist")

*

Dans un cas, on cherche à minimiser la somme des carrés des distances entre la droite et les points, mesurées verticalement, dans l'autre, on mesure ces distances horizontalement. Il s'agit de de manières « raisonnables » mais différentes de mesurer la distance entre un nuage de points et une droite. C'est pourqhoi on obtient des résultats différents.

plot(cars)
r <- lm(cars$dist ~ cars$speed)
abline(r, col='red')
segments(cars$speed, cars$dist, cars$speed, r$fitted.values,col="red")
title(main="Régression dist ~ speed : on mesure les distances verticalement")

*

plot(cars)
r <- lm(cars$speed ~ cars$dist)
a <- r$coefficients[1] # Intercept
b <- r$coefficients[2] # slope
abline(-a/b , 1/b, col="blue")
segments(cars$speed, cars$dist, r$fitted.values, cars$dist, col="blue")
title(main="Régression speed ~ dist : on mesure les distances horizontalement")

*

Il y a une autre manière raisonnable de mesurer la distance entre un nuage de points et une droite : la somme des carrés des distances entre les points et la droite, mesurées orthogonalement à la droite. C'est ce qu'on appelle l'ACP (Analyse en Composantes Principales), et cette fois-ci, c'est symétrique.

plot(cars)
r <- lm(cars$dist ~ cars$speed)
abline(r, col='red')
r <- lm(cars$speed ~ cars$dist)
a <- r$coefficients[1] # Intercept
b <- r$coefficients[2] # slope
abline(-a/b , 1/b, col="blue")
r <- princomp(cars)
b <- r$loadings[2,1] / r$loadings[1,1]
a <- r$center[2] - b * r$center[1]
abline(a,b)
title(main="Comparaison de trois régressions")

*

Dans cet exemple, les droites bleue et noire sont presque confondues. Voici une situation dans laquelle les trois droites devraient être distinctes.

x <- rnorm(100)
y <- x + rnorm(100)
plot(y~x)
r <- lm(y~x)
abline(r, col='red')
r <- lm(x ~ y)
a <- r$coefficients[1] # Intercept
b <- r$coefficients[2] # slope
abline(-a/b , 1/b, col="blue")
r <- princomp(cbind(x,y))
b <- r$loadings[2,1] / r$loadings[1,1]
a <- r$center[2] - b * r$center[1]
abline(a,b)
title(main="Comparaison de trois régressions")

*

plot(y~x, xlim=c(-4,4), ylim=c(-4,4) )
abline(a,b)
# Matrice de changement de base
u <- r$loadings
# Projection sur le premier axe
p <- matrix( c(1,0,0,0), nrow=2 )
X <- rbind(x,y)
X <- r$center + solve(u, p %*% u %*% (X - r$center))
segments( x, y, X[1,], X[2,] )
title(main="ACP : on mesure les distances perpendiculairement à la droite")

*

Variantes des moindres carrés

Régression L1 (LAR (Least Absolute Residuals) ou LAD (Least Absolute Deviation))

On peut minimiser

Somme abs( y_i - a - b x_i )
  i

au lieu de

Somme ( y_i - a - b x_i ) ^ 2
  i

x <- cars$speed
y <- cars$dist
my.lar <- function (y,x) {
  f <- function (arg) {
    a <- arg[1]
    b <- arg[2]
    sum(abs(y-a-b*x)) 
  }
  r <- optim( c(0,0), f )$par
  plot( y~x )
  abline(lm(y~x), col='red', lty=2)
  abline(r[1], r[2])
  legend( par("usr")[1], par("usr")[4], yjust=1,
          c("Moindres carrés", "Moindres valeurs absolues"),
          lwd=1, lty=c(2,1),
          col=c(par('fg'),'red'))
}
my.lar(y,x)

*

A FAIRE :

library(quantreg)
?rq

Régression de Huber

La régression au sens des moindres carrés consiste à minimiser

Somme( rho( (yi - \hat yi) / sigma ) )
  i

où rho(x) = x^2. Nous avons aussi vu la régression L1, ou régression au sens des moindres valeurs absolues, qui consiste à minimiser la même quantité avec rho(x) = abs(x). La régression de Huber est entre les deux : elle consiste toujours à minimiser cette somme, mais avec

rho(x) = x^2/2                si abs(x) <= c
         c * abs(x) - c^2/2   sinon

(où c est choisi arbitrairement, proportionnel à un estimateur robuste de la médiane du bruit).

On peut aussi l'interpréter comme une régression pondérée, dans laquelle les poinds sont donnés par

w(u) = 1          si abs(u) < c
       c/abs(u)   sinon

C'est une régression utile si l'erreur a une distribution très dispersée.

Sous R, c'est ce que fait la fonction rlm, dans la bibliothèque MASS.

Dans l'exemple suivant, on constate que la régression de Huber n'est pas résistante : en présence de valeurs atypiques, elles donne le même résultat que la régression classique.

library(MASS)
n <- 20
x <- rnorm(n)
y <- 1 - 2*x + rnorm(n)
y[ sample(1:n, floor(n/4)) ] <- 10
plot(y~x)
abline(1,-2,lty=3)
abline(lm(rlm(y~x)), col='red')
abline(lm(y~x), lty=3, lwd=3)

*

Par contre elle est sensée être robuste :

(A FAIRE : signaler que dans une situation comme la suivante, on
commencerait par transformer les données pour les rendre un peu plus
normales...)

n <- 100
x <- rnorm(n)
y <- 1 - 2*x + rcauchy(n,1)
plot(y~x)
abline(1,-2,lty=3)
abline(lm(rlm(y~x)), col='red')
abline(lm(y~x), lty=3, lwd=3)

*

A FAIRE : comprendre pourquoi le graphique précédent 
ne montre pas ce que je veux...
en particulier, comparer avec le graphique suivant...
  
#library(lqs)   # merged into MASS
x <- rnorm(20)
y <- 1 - x + rnorm(20)
x <- c(x,10)
y <- c(y,1)
plot(y~x)
abline(1,-1, lty=3)
abline(lm(y~x))
abline(rlm(y~x, psi = psi.bisquare, init = "lts"), col='orange',lwd=3)
abline(rlm(y~x), col='red')
abline(rlm(y~x, psi = psi.hampel, init = "lts"), col='green')
abline(rlm(y~x, psi = psi.bisquare), col='blue')
title(main='Régression de Huber (rlm)')

*

Moindre carrés élagués (LTS : Least Trimmed Squares)

Il ne s'agit plus de minimiser la somme des carrés de toutes les erreurs, mais uniquement la somme des carrés des erreurs "les plus petites". Si on désire faire les calculs de manière exacte, ça peut être très long (si on a n observations et si on désire en enlever k, il faut regarder tous les ensembles de n-k observations et retenir celui dont la somme des carrés est la plus petite) -- on préfèrera souvent une méthode approchée.

?ltsreg

n <- 100
x <- rnorm(n)
y <- 1 - 2*x + rnorm(n)
y[ sample(1:n, floor(n/4)) ] <- 7
plot(y~x)
r1 <- lm(y~x)
r2 <- lqs(y~x, method='lts')
abline(r1, col='red')
abline(r2, col='green')
abline(1,-2,lty=3)
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("Régression linéaire", "Régression élaguée"),
        lty=1, lwd=1,
        col=c("red", "green") )
title("Moindres carrés élagués")

*

Si on se contente de regarder les graphiques de diagnostic de la régression classique, on ne voit rien...

op <- par(mfrow=c(2,2))
plot(r1)
par(op)

*

A FAIRE : estimation de l'erreur par une méthode de bootstrap.

C'est une régression résistante.

La régression élaguée a quelques variantes :

lqs : c'est presque une régression normale, mais au lieu de
minimiser la somme des carrés des résidus, on met les résidus dans
l'ordre et on essaye de minimiser celui du milieu.

lqs(..., method='lms') : idem, mais c'est le résidu 
numéro << floor(n/2) + floor((p+1)/2) >> où n est le nombre d'observations
et p le nombre de variables prédictives.

lqs(..., method='S') : je n'ai pas compris
A FAIRE

Moindres carrés généralisés

La méthode des moindres carrés suppose que le terme d'erreur (le bruit) est formé de variables normales centrées, indépendantes et de même variance. Les moindres carrés généralisés traitent le cas de variables de variances différentes (mais il faut connaître ces variances) ou non indépendantes (par exemple, avec des séries temporelles : il faut alors choisir un modèle pour décrire cette dépendance).

Nous détaillons ces deux situations un peu plus loin dans ce document.

library(MASS)
?lm.gls
library(nlme)
?gls
A FAIRE : relire

Moindres carrés pondérés (WLS: Weighted Least Squares)

La méthode des moindres carrés pondérés consiste à calculer la régression et les résidus, à assigner à chaque individu un poids, d'autant plus faible que le résidu est grand, et à faire une nouvelle régression.

# Des données pas trop gentilles...
n <- 10
x <- rnorm(n)
y <- 1 - 2*x + rnorm(n)
x[1] <- 5
y[1] <- 0
my.wls <- function (y,x) {
  # On effectue une première régression
  r <- lm(y~x)$residuals
  # On calcule les poids
  w <- compute.weights(r)
  # on fait une nouvelle régression
  lm(y~x, weights=w)
}
compute.weights <- function (r) {
  # On calcule les poids comme on veut, du moment que la comme des 
  # poids est 1 et que les points de résidus élevés ont des poids faibles.
  # Ce choix que je fais n'est probablement ni standard ni judicieux
  w <- r*r
  w <- w/mean(w)
  w <- 1/(1+w)
  w <- w/mean(w)
}
plot(y~x)
abline(1,-2, lty=3)
abline(lm(y~x))
abline(my.wls(y,x), col='red')
title(main="Régression pondérée")

*

Moindres carrés itérativement repondérés (IRLS : Iteratively Reweighted Least Squares)

Avec la méthode des moindres carrés itérativement repondérés, on fait pareil, mais on itère jusqu'à ce que ça se stabilise.

my.irls.plot <- function (y,x, n=10) {
  plot(y~x)
  abline(lm(y~x))
  r <- lm(y~x)$residuals
  for (i in 1:n) {
    w <- compute.weights(r)
    print(w)
    r <- lm(y~x, weights=w)
    abline(r, col=topo.colors(n)[i], lwd=ifelse(i==n,3,1))
    r <- r$residuals
  }
  lm(y~x, weights=w)
}
my.irls.plot(y,x)
abline(1,-2, lty=3)
abline(my.wls(y,x), col='blue', lty=3, lwd=3)
title(main="Régression itérativement repondérée")

*

A FAIRE : IRLS
On trace le graphe des poids en fonction des résidus : c'est sur une
courbe en forme de cloche, sur laquelle on voit bien les points
influents (leur poids est faible). On essayer alors de faire une
nouvelle régression (à la fois classique et robuste) sans ces
points. Si le résultat diffère, c'est mauvais signe.

Polynômes : régression curvilinéaire

A FAIRE : mettre cette partie avant les régressions précédentes ?
(c'est plus élémentaire)

Reprenons l'exemple de la distance d'arret d'une voiture : on s'attend plutôt à ce que la distance dépende linéairement du carré de la vitesse (ou que ce soit un polynôme de degré 2).

y <- cars$dist
x <- cars$speed
o = order(x)
plot( y~x )
do.it <- function (model, col) {
  r <- lm( model )
  yp <- predict(r)
  lines( yp[o] ~ x[o], col=col, lwd=3 )
}
do.it(y~x, col="red")
do.it(y~x+I(x^2), col="blue")
do.it(y~-1+I(x^2), col="green")
legend(par("usr")[1], par("usr")[4], 
       c("fonction affine", "polynôme de degré 2", "monôme de degré 2"),
       lwd=3,
       col=c("red", "blue", "green"),
      )

*

Quand on définit un modèle à l'aide d'une telle formule, le caractère ^ a un sens précis (ainsi, (a+b+c)^2 signifie a+b+c+a:b+a:c+b:c). Si on rajoute I(...), il est bien interprété comme un carré. On a le même problème avec les caractères + et *.

Si on veut se débarasser du terme constant (c'est généralement une mauvaise idée : il permet d'absorber l'effet d'éventuelles variables manquantes), on peut ajouter -1 dans le modèle.

Nous avons donné un exemple avec des monômes, mais en fait, on peut prendre n'importe quoi comme fonctions.

n <- 100
x <- runif(n,min=-4,max=4) + sign(x)*.2
y <- 1/x + rnorm(n)
plot(y~x)
lm( 1/y ~ x )

n <- 100
x <- rlnorm(n)^3.14
y <- x^-.1 * rlnorm(n)
plot(y~x)
lm(log(y) ~ log(x))

Cela revient en fait à transformer les variables : nous reverrons cela un peu plus loin.

Polynômes orthogonaux

Pour faire une régression polynômiale, on peut procéder comme précédemment en ajoutant progressivement des termes.

y <- cars$dist
x <- cars$speed
summary( lm(y~x) )
summary( lm(y~x+I(x^2)) )
summary( lm(y~x+I(x^2)+I(x^3)) )
summary( lm(y~x+I(x^2)+I(x^3)+I(x^4)) )
summary( lm(y~x+I(x^2)+I(x^3)+I(x^4)+I(x^5)) )

Voici quelques morceaux du résultat.

lm(formula = y ~ x)
            Estimate Std. Error t value Pr(>|t|)
(Intercept) -17.5791     6.7584  -2.601   0.0123 *
x             3.9324     0.4155   9.464 1.49e-12 ***

lm(formula = y ~ x + I(x^2))
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  2.47014   14.81716   0.167    0.868
x            0.91329    2.03422   0.449    0.656
I(x^2)       0.09996    0.06597   1.515    0.136

lm(formula = y ~ x + I(x^2) + I(x^3))
             Estimate Std. Error t value Pr(>|t|)
(Intercept) -19.50505   28.40530  -0.687    0.496
x             6.80111    6.80113   1.000    0.323
I(x^2)       -0.34966    0.49988  -0.699    0.488
I(x^3)        0.01025    0.01130   0.907    0.369

lm(formula = y ~ x + I(x^2) + I(x^3) + I(x^4))
Coefficients:
              Estimate Std. Error t value Pr(>|t|)
(Intercept)  45.845412  60.849115   0.753    0.455
x           -18.962244  22.296088  -0.850    0.400
I(x^2)        2.892190   2.719103   1.064    0.293
I(x^3)       -0.151951   0.134225  -1.132    0.264
I(x^4)        0.002799   0.002308   1.213    0.232

lm(formula = y ~ x + I(x^2) + I(x^3) + I(x^4) + I(x^5))
Coefficients:
              Estimate Std. Error t value Pr(>|t|)
(Intercept) -2.650e+00  1.401e+02  -0.019    0.985
x            5.484e+00  6.736e+01   0.081    0.935
I(x^2)      -1.426e+00  1.155e+01  -0.124    0.902
I(x^3)       1.940e-01  9.087e-01   0.214    0.832
I(x^4)      -1.004e-02  3.342e-02  -0.300    0.765
I(x^5)       1.790e-04  4.648e-04   0.385    0.702

On peut utiliser ces résultats dans les deux sens : soit on part d'un modèle simple et on rajoute des termes jusqu' ce que l'effet de ces termes devienne non significatif, soit on part d'un modèle compliqué et on retire des termes jusqu'à ce que le terme de plus haut degré ait un effet significatif.

Dans les deux cas, on se retrouve avec un modèle affine.

Néanmoins, on constate que des coefficients qui sont significativement non nuls au début ne le sont plus à la fin. Pire encore, à la fin il n'y a plus aucun terme significatif et la p-valeur des termes qu'on a retenus est plus élevée que celle des autres ! Heureusement, on peut faire en sorte que les coefficients ne s'influencent pas les uns les autres en choisissant une base de polynômes autre que 1, x, x^2, x^3, etc.

y <- cars$dist
x <- cars$speed
summary( lm(y~poly(x,1)) )
summary( lm(y~poly(x,2)) )
summary( lm(y~poly(x,3)) )
summary( lm(y~poly(x,4)) )
summary( lm(y~poly(x,5)) )

Les résultats sont beaucoup plus clairs : les p-valeurs changent, mais très peu.

lm(formula = y ~ poly(x, 1))
Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)   42.980      2.175  19.761  < 2e-16 ***
poly(x, 1)   145.552     15.380   9.464 1.49e-12 ***
 
lm(formula = y ~ poly(x, 2))
Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)   42.980      2.146  20.026  < 2e-16 ***
poly(x, 2)1  145.552     15.176   9.591 1.21e-12 ***
poly(x, 2)2   22.996     15.176   1.515    0.136

lm(formula = y ~ poly(x, 3))
Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)    42.98       2.15  19.988  < 2e-16 ***
poly(x, 3)1   145.55      15.21   9.573  1.6e-12 ***
poly(x, 3)2    23.00      15.21   1.512    0.137
poly(x, 3)3    13.80      15.21   0.907    0.369

lm(formula = y ~ poly(x, 4))
Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)   42.980      2.139  20.090  < 2e-16 ***
poly(x, 4)1  145.552     15.127   9.622 1.72e-12 ***
poly(x, 4)2   22.996     15.127   1.520    0.135
poly(x, 4)3   13.797     15.127   0.912    0.367
poly(x, 4)4   18.345     15.127   1.213    0.232

lm(formula = y ~ poly(x, 5))
Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)   42.980      2.160  19.899  < 2e-16 ***
poly(x, 5)1  145.552     15.273   9.530 2.88e-12 ***
poly(x, 5)2   22.996     15.273   1.506    0.139
poly(x, 5)3   13.797     15.273   0.903    0.371
poly(x, 5)4   18.345     15.273   1.201    0.236
poly(x, 5)5    5.881     15.273   0.385    0.702

On peut visualiser l'évolution de ces p-valeurs sur un graphique.

n <- 5
p <- matrix( nrow=n, ncol=n+1 )
for (i in 1:n) {
  p[i,1:(i+1)] <- summary(lm( y ~ poly(x,i) ))$coefficients[,4]
}
matplot(p, type='l', lty=1, lwd=3)
legend( par("usr")[1], par("usr")[4],
        as.character(1:n),
        lwd=3, lty=1, col=1:n
      )
title(main="Evolution des p-valeurs (famille orthonormale)")

*

p <- matrix( nrow=n, ncol=n+1 )
p[1,1:2] <- summary(lm(y ~ x) )$coefficients[,4]
p[2,1:3] <- summary(lm(y ~ x+I(x^2)) )$coefficients[,4]
p[3,1:4] <- summary(lm(y ~ x+I(x^2)+I(x^3)) )$coefficients[,4]
p[4,1:5] <- summary(lm(y ~ x+I(x^2)+I(x^3)+I(x^4)) )$coefficients[,4]
p[5,1:6] <- summary(lm(y ~ x+I(x^2)+I(x^3)+I(x^4)+I(x^5)) )$coefficients[,4]
matplot(p, type='l', lty=1, lwd=3)
legend( par("usr")[1], par("usr")[4],
        as.character(1:n),
        lwd=3, lty=1, col=1:n
      )
title(main="Evolution des p-valeurs (famille non orthonormale)")

*

On peut construire ces polynômes orthogonaux soi-même, à la main, en partant des vecteurs 1, x, x^2, ..., qui forment une famille libre, et en l'orthonormalisant.

# Construction de la matrice
n <- 5
m <- matrix( ncol=n+1, nrow=length(x) )
for (i in 2:(n+1)) {
  m[,i] <- x^(i-1)
}
m[,1] <- 1
# Orthonormalisation (Gram--Schmidt)
for (i in 1:(n+1)) {
  if(i==1) m[,1] <- m[,1] / sqrt( t(m[,1]) %*% m[,1] )
  else {
    for (j in 1:(i-1)) {
      m[,i] <- m[,i] - (t(m[,i]) %*% m[,j])*m[,j]
    }
    m[,i] <- m[,i] / sqrt( t(m[,i]) %*% m[,i] )
  }
}
p <- matrix( nrow=n, ncol=n+1 )
p[1,1:2] <- summary(lm(y~ -1 +m[,1:2] ))$coefficients[,4]
p[2,1:3] <- summary(lm(y~ -1 +m[,1:3] ))$coefficients[,4]
p[3,1:4] <- summary(lm(y~ -1 +m[,1:4] ))$coefficients[,4]
p[4,1:5] <- summary(lm(y~ -1 +m[,1:5] ))$coefficients[,4]
p[5,1:6] <- summary(lm(y~ -1 +m[,1:6] ))$coefficients[,4]
matplot(p, type='l', lty=1, lwd=3)
legend( par("usr")[1], par("usr")[4],
        as.character(1:n),
        lwd=3, lty=1, col=1:n
      )
title(main="Idem, orthonormalisation à la main")

*

C'est en fait le même problème que la multicolinéarité (voir plus loin) : en orthogonalisant les polynômes, on se débarasse des redondances.

Autre exemple.

library(ts)
data(beavers)
y <- beaver1$temp
x <- 1:length(y)
plot(y~x)
for (i in 1:10) {
  r <- lm( y ~ poly(x,i) )
  lines( predict(r), type="l", col=i )
}
summary(r)

*

On obtient :

>   summary(r)

             Estimate Std. Error  t value Pr(>|t|)
(Intercept)  36.86219    0.01175 3136.242  < 2e-16 ***
poly(x, i)1   0.86281    0.12549    6.875 4.89e-10 ***
poly(x, i)2  -0.73767    0.12549   -5.878 5.16e-08 ***
poly(x, i)3  -0.09652    0.12549   -0.769  0.44360
poly(x, i)4  -0.20405    0.12549   -1.626  0.10700
poly(x, i)5   1.00687    0.12549    8.023 1.73e-12 ***
poly(x, i)6   0.07253    0.12549    0.578  0.56457
poly(x, i)7   0.19180    0.12549    1.528  0.12950
poly(x, i)8   0.06011    0.12549    0.479  0.63294
poly(x, i)9  -0.22394    0.12549   -1.784  0.07730 .
poly(x, i)10 -0.39531    0.12549   -3.150  0.00214 **
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.1255 on 103 degrees of freedom
Multiple R-Squared: 0.6163,     Adjusted R-squared: 0.579
F-statistic: 16.54 on 10 and 103 DF,  p-value: < 2.2e-16

On notera toutefois que certaines relations ne sont pas polynômiales non plus (mais dans l'exemple suivant, il faudrait commencer par transformer la variable y pour que la distribution soit à peu près normale).

> n <- 100; x <- rnorm(n); y <- exp(x); summary(lm(y~poly(x,20)))

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

Residuals:
       Min         1Q     Median         3Q        Max
-4.803e-16 -9.921e-17 -3.675e-19  4.924e-17  1.395e-15

Coefficients:
               Estimate Std. Error   t value Pr(>|t|)
(Intercept)   1.562e+00  2.250e-17 6.943e+16  < 2e-16 ***
poly(x, 20)1  1.681e+01  2.250e-16 7.474e+16  < 2e-16 ***
poly(x, 20)2  1.174e+01  2.250e-16 5.219e+16  < 2e-16 ***
poly(x, 20)3  5.491e+00  2.250e-16 2.441e+16  < 2e-16 ***
poly(x, 20)4  1.692e+00  2.250e-16 7.522e+15  < 2e-16 ***
poly(x, 20)5  4.128e-01  2.250e-16 1.835e+15  < 2e-16 ***
poly(x, 20)6  8.163e-02  2.250e-16 3.628e+14  < 2e-16 ***
poly(x, 20)7  1.495e-02  2.250e-16 6.645e+13  < 2e-16 ***
poly(x, 20)8  2.499e-03  2.250e-16 1.111e+13  < 2e-16 ***
poly(x, 20)9  3.358e-04  2.250e-16 1.493e+12  < 2e-16 ***
poly(x, 20)10 3.825e-05  2.250e-16 1.700e+11  < 2e-16 ***
poly(x, 20)11 3.934e-06  2.250e-16 1.749e+10  < 2e-16 ***
poly(x, 20)12 3.454e-07  2.250e-16 1.536e+09  < 2e-16 ***
poly(x, 20)13 2.893e-08  2.250e-16 1.286e+08  < 2e-16 ***
poly(x, 20)14 2.235e-09  2.250e-16 9.933e+06  < 2e-16 ***
poly(x, 20)15 1.524e-10  2.250e-16 6.773e+05  < 2e-16 ***
poly(x, 20)16 1.098e-11  2.250e-16 4.883e+04  < 2e-16 ***
poly(x, 20)17 6.431e-13  2.250e-16 2.858e+03  < 2e-16 ***
poly(x, 20)18 3.628e-14  2.250e-16 1.613e+02  < 2e-16 ***
poly(x, 20)19 1.700e-15  2.250e-16 7.558e+00  6.3e-11 ***
poly(x, 20)20 5.790e-16  2.250e-16 2.574e+00   0.0119 *
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 2.25e-16 on 79 degrees of freedom
Multiple R-Squared:     1,      Adjusted R-squared:     1
F-statistic: 4.483e+32 on 20 and 79 DF,  p-value: < 2.2e-16

n <- 100
x <- rnorm(n)
y <- exp(x)
plot(y~x)
title(main="Relation non polynômiale")

*

Si on ajoute un peu de bruit :

> n <- 100; x <- rnorm(n); y <- exp(x) + .1*rnorm(n); summary(lm(y~poly(x,20)))

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

Residuals:
       Min         1Q     Median         3Q        Max
-2.059e-01 -5.577e-02 -4.859e-05  5.322e-02  3.271e-01

Coefficients:
               Estimate Std. Error t value Pr(>|t|)
(Intercept)    1.617035   0.010222 158.185   <2e-16 ***
poly(x, 20)1  15.802708   0.102224 154.588   <2e-16 ***
poly(x, 20)2  11.282604   0.102224 110.371   <2e-16 ***
poly(x, 20)3   4.688413   0.102224  45.864   <2e-16 ***
poly(x, 20)4   1.433877   0.102224  14.027   <2e-16 ***
poly(x, 20)5   0.241665   0.102224   2.364   0.0205 *
poly(x, 20)6  -0.009086   0.102224  -0.089   0.9294
poly(x, 20)7  -0.097021   0.102224  -0.949   0.3455
poly(x, 20)8   0.133978   0.102224   1.311   0.1938
poly(x, 20)9   0.077034   0.102224   0.754   0.4533
poly(x, 20)10  0.200996   0.102224   1.966   0.0528 .
poly(x, 20)11 -0.168632   0.102224  -1.650   0.1030
poly(x, 20)12  0.161890   0.102224   1.584   0.1173
poly(x, 20)13 -0.049974   0.102224  -0.489   0.6263
poly(x, 20)14  0.090020   0.102224   0.881   0.3812
poly(x, 20)15 -0.228817   0.102224  -2.238   0.0280 *
poly(x, 20)16 -0.005180   0.102224  -0.051   0.9597
poly(x, 20)17 -0.015934   0.102224  -0.156   0.8765
poly(x, 20)18  0.053635   0.102224   0.525   0.6013
poly(x, 20)19  0.059976   0.102224   0.587   0.5591
poly(x, 20)20  0.226613   0.102224   2.217   0.0295 *
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.1022 on 79 degrees of freedom
Multiple R-Squared: 0.9979,     Adjusted R-squared: 0.9974
F-statistic:  1920 on 20 and 79 DF,  p-value: < 2.2e-16

n <- 100
x <- rnorm(n)
y <- exp(x) + .1*rnorm(n)
plot(y~x)
title(main="Relation non polynômiale")

*

Splines

La régression par lignes brisées est locale (ce qui se passe pour une valeur de X donnée ne dépend pas de ce qui se passe pour des valeurs de X très différentes), mais pas lisse. Au contraire, la régression polynomiale est lisse mais pas locale. Les splines permettent d'effectuer une régression à la fois locale et lisse.

Les splines sont en particulier utilisées lors de la recherche d'un modèle : elles permettent de voir (graphiquement) si un modèle non-linéaire est souhaitable et sinon, d'avoir une idée de la forme du modèle non-linéaire à choisir.

library(modreg)
plot(cars$speed, cars$dist)
lines( smooth.spline(cars$speed, cars$dist), col='red')
abline(lm(dist~speed,data=cars), col='blue', lty=2)

*

plot(quakes$long, quakes$lat)
lines( smooth.spline(quakes$long, quakes$lat), col='red', lwd=3)

*

Il y a différents types de splines : les splines cubiques (de classe C^2, obtenue en recollant des polynômes de degré 3), que nous venons d'utiliser, ou les splines cubiques restreintes (idem, mais c'est affine aux extrémités), obtenues à l'aide de la fonction rcs dans la bibliothèque Design.

library(Design)
# Spline à 4 noeuds
r3 <- lm( quakes$lat ~ rcs(quakes$long) )
plot( quakes$lat ~ quakes$long )
o <- order(quakes$long)
lines( quakes$long[o], predict(r)[o], col='red', lwd=3 )
r <- lm( quakes$lat ~ rcs(quakes$long,10) )
lines( quakes$long[o], predict(r)[o], col='blue', lwd=6, lty=3 )
title(main="Régression avec rcs")
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("4 noeuds", "10 noeuds"),
        lwd=c(3,3), lty=c(1,3), col=c("red", "blue") )

*

En fait, il y a déjà une fonction bs (splines) et ns (splines restreintes) dans la bibliothèque splines.

library(splines)
data(quakes)
x <- quakes[,2]
y <- quakes[,1]
o <- order(x)
x <- x[o]
y <- y[o]
r1 <- lm( y ~ bs(x,df=10) )
r2 <- lm( y ~ ns(x,df=6) )
plot(y~x)
lines(predict(r1)~x, col='red', lwd=3)
lines(predict(r2)~x, col='green', lwd=3)

*

?SafePrediction
# Le manuel dit de faire attention aux prédictions
# avec d'autres valeurs de x : je ne vois pas de problème.
plot(y~x)
xp <- seq(min(x), max(x), length=200)
lines(predict(r2) ~ x, col='green', lwd=3)
lines(xp, predict(r2,data.frame(x=xp)), col='blue', lwd=7, lty=3)

*

Régression dans d'autres bases

A FAIRE : ondelettes

Régression dans d'autres bases

Les données peuvent suggérer une régression dans une certaine base. Ainsi, en dimensions supérieures, on peut essayer d'écrire Y comme une somme de gaussiennes : on utilise des heuristiques pour estimer le nombre, le centre et la variance des noyaux (ça n'est pas du tout linéaire) et on passe aux moindres carrés pour trouver les coefficients.

A FAIRE : exemple

Régressions locales

Ligne brisée

Une manière très simple de voir si une régression linéaire est une bonne idée (ou si un modèle plus complexe est souhaitable) consiste à découper les données en deux, selon les valeurs de l'une des variables prédictives, et de faire une régression pour chacun de ces deux sous-échantillons.

broken.line <- function (x, y) { 
  n <- length(x)
  n2 = floor(n/2)
  o <- order(x)
  m <- mean(c(x[o[n2+0:1]]))
  x1 <- x[o[1:n2]]
  y1 <- y[o[1:n2]]
  n2 <- n2+1
  x2 <- x[o[n2:n]]
  y2 <- y[o[n2:n]]
  r1 <- lm(y1~x1)
  r2 <- lm(y2~x2)
  plot(y~x)
  segments(x[o[1]], r1$coef[1] + x[o[1]]*r1$coef[2],
           m, r1$coef[1] + m *r1$coef[2],
           col='red')
  segments(m, r2$coef[1] + m*r2$coef[2],
           x[o[n]], r2$coef[1] + x[o[n]] *r2$coef[2],
           col='blue')
  abline(v=m, lty=3)
}
n <- 10
x <- runif(n)
y <- 1-x+.2*rnorm(n)
broken.line(x,y)

*

z <- x*(1-x)
broken.line(x,z)

*

Mais ça n'est pas très continu : on peut exiger que les deux segments se rejoignent.

broken.line <- function (x,y) {
  n <- length(x)
  o <- order(x)
  n1 <- floor((n+1)/2)
  n2 <- ceiling((n+1)/2)
  m <- mean(c( x[o[n1]], x[o[n2]] ))
  plot(y~x)
  B1 <- function (xx) {
    x <- xx
    x[xx<m] <- m - x[xx<m]
    x[xx>=m] <- 0
    x
  }
  B2 <- function (xx) {
    x <- xx
    x[xx>m] <- x[x>m] -m
    x[xx<=m] <- 0
    x
  }
  x1 <- B1(x)
  x2 <- B2(x)
  r <- lm(y~x1+x2)
  xx <- seq(x[o[1]],x[o[n]],length=100)
  yy <- predict(r, data.frame(x1=B1(xx), x2=B2(xx)))
  lines( xx, yy, col='red' )
  abline(v=m, lty=3)        
}
broken.line(x,y)

*

broken.line(x,z)

*

Exercice laissé au lecteur : réécrire la fonction précédente afin qu'elle accepte un argument supplémentaire, le nombre de segments constituant la droite brisée.

Autre ligne brisée : lowess

La fonction lowess permet d'obtenir une courbe (ligne brisée) "proche" du nuage de points.

plot(y~x)
lines(lowess(x,y), col="red")

*

plot(z~x)
lines(lowess(x,z), col="red")

*

data(quakes)
plot(lat~long, data=quakes)
lines(lowess(quakes$long, quakes$lat), col='red', lwd=3)

*

La fonction scatter.smooth fait la même chose.

Moyenne (et quartiles) mobile(s)

Pour chaque point à prédire, on fait la moyenne des prédictions des points voisins.

En d'autres termes, on essaye localement d'approcher le nuage de points par une droite horizontale.

n <- 1000
x <- runif(n, min=-1,max=1)
y <- (x-1)*x*(x+1) + .5*rnorm(n)
# Ne pas utiliser ce code sur des données trop grosses
moyenne.mobile <- function (y,x, r=.1) {
  o <- order(x)
  n <- length(x)
  m <- floor((1-r)*n)
  p <- n-m
  x1 <- vector(mode='numeric',length=m)
  y1 <- vector(mode='numeric',length=m)
  y2 <- vector(mode='numeric',length=m)
  y3 <- vector(mode='numeric',length=m)
  for (i in 1:m) {
    xx <- x[ o[i:(i+p)] ]
    yy <- y[ o[i:(i+p)] ]
    x1[i] <- mean(xx)
    y1[i] <- quantile(yy,.25)
    y2[i] <- quantile(yy,.5)
    y3[i] <- quantile(yy,.75)
  }
  plot(y~x)
  lines(x1,y2,col='red', lwd=3)
  lines(x1,y1,col='blue', lwd=2)
  lines(x1,y3,col='blue', lwd=2)
}
moyenne.mobile(y,x)

*

Il y a aussi une telle fonction dans la bibliothèque gregmisc.

library(gregmisc)
bandplot(x,y)

*

A FAIRE : le reprendre l'exemple précédent avec la commande filter (sans boucle).

La fonction ksmooth fait à peu près la même chose (les observations sont pondérées par un noyau).

library(modreg)
plot(cars$speed, cars$dist)
lines(ksmooth(cars$speed, cars$dist, "normal", bandwidth=2), col='red')
lines(ksmooth(cars$speed, cars$dist, "normal", bandwidth=5), col='green')
lines(ksmooth(cars$speed, cars$dist, "normal", bandwidth=10), col='blue')

*

Moindre carrés locaux pondérés : loess

La méthode que nous venons de présenter, la moyenne mobile, présente d'innombrables variantes. On peut tout d'abord changer le noyau.

curve(dnorm(x), xlim=c(-3,3), ylim=c(0,1.1))
x <- seq(-3,3,length=200)
D.Epanechikov <- function (t) {
  ifelse(abs(t)<1, 3/4*(1-t^2), 0)
}
lines(D.Epanechikov(x) ~ x, col='red')
D.tricube <- function (t) { # aka "triweight kernel"
  ifelse(abs(t)<1, (1-abs(t)^3)^3, 0)
}
lines(D.tricube(x) ~ x, col='blue')
legend( par("usr")[1], par("usr")[4], yjust=1,
        c("noyau gaussien", "noyau d'Epanechikov", "noyau tricube"),
        lwd=1, lty=1,
        col=c(par('fg'),'red', 'blue'))
title(main="Différents noyaux")

*

De manière plus formelle, si D est l'une des fonctions définies ci-dessus, le lissage revient à minimiser

RSS(f,x0) = Somme K(x0,xi)(yi-f(xi))^2

K_lambda(x0,x) = D( abs(x-x0) / lambda )

pour un paramètre lambda choisi par l'utilisateur.

(On peut aussi imaginer des noyaux qui s'adaptent un peu mieux aux données, dont la fenêtre n'est pas de largeur constante mais dépend de la densité des observations -- fenêtre plus étroite s'il y en a beaucoup, plus large s'il y en a peu.)

Cela revient en fait à essayer d'approcher, localement, les données par une fonctions constante. On peut généraliser cette méthode, en prenant une fonction affine (on parle de régression linéaire locale) ou un polynôme de degré 2.

# Sur des données réelles...
library(KernSmooth)
data(quakes)
x <- quakes$long
y <- quakes$lat
plot(y~x)
bw <- dpill(x,y) # .2
lines( locpoly(x,y,degree=0, bandwidth=bw), col='red' )
lines( locpoly(x,y,degree=1, bandwidth=bw), col='green' )
lines( locpoly(x,y,degree=2, bandwidth=bw), col='blue' )
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("degré = 0", "degré = 1", "degré = 2"),
        lwd=1, lty=1,
        col=c('red', 'green', 'blue'))
title(main="Régression polynomiale locale")

*

plot(y~x)
bw <- .5
lines( locpoly(x,y,degree=0, bandwidth=bw), col='red' )
lines( locpoly(x,y,degree=1, bandwidth=bw), col='green' )
lines( locpoly(x,y,degree=2, bandwidth=bw), col='blue' )
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("degré = 0", "degré = 1", "degré = 2"),
        lwd=1, lty=1,
        col=c('red', 'green', 'blue'))
title(main="Régression polynomiale locale (fenêtre plus large)")

*

La moyenne mobile (régression de Nadaraya-Watson) est biaisée aux extrémités du domaine, la régression linéaire locale est biaisée dans les régions de courbure élevée.

On peut le vérifier sur les données artificielles : sur cet exemple, on constate qu'aux bornes de l'intervalle la régression polynomiale locale de degré 0 s'éloigne beaucoup des valeurs de l'échantillon.

n <- 50
x <- runif(n)
f <- function (x) { cos(3*x) + cos(5*x) }
y <- f(x) + .2*rnorm(n)
plot(y~x)
curve(f(x), add=T, lty=2)
bw <- dpill(x,y)
lines( locpoly(x,y,degree=0, bandwidth=bw), col='red' )
lines( locpoly(x,y,degree=1, bandwidth=bw), col='green' )
lines( locpoly(x,y,degree=2, bandwidth=bw), col='blue' )
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("degré = 0", "degré = 1", "degré = 2"),
        lwd=1, lty=1,
        col=c('red', 'green', 'blue'))

*

a <- locpoly(x,y,degree=0, bandwidth=bw)
b <- locpoly(x,y,degree=1, bandwidth=bw)
c <- locpoly(x,y,degree=2, bandwidth=bw)
matplot( cbind(a$x,b$x,c$x), abs(cbind(a$y-f(a$x), b$y-f(b$x), c$y-f(c$x)))^2, 
         xlab='', ylab='',
         type='l', lty=1, col=c('red', 'green', 'blue') )
legend( .8*par("usr")[1]+.2*par("usr")[2], par("usr")[4], yjust=1,
        c("degré = 0", "degré = 1", "degré = 2"),
        lwd=1, lty=1,
        col=c('red', 'green', 'blue'))
title(main="Erreur quadratique")

*

Si la courbe présente une courbure plus élevée, les polynômes de degré plus élevé l'approchent un peu mieux.

f <- function (x) { sqrt(abs(x-.5)) }
y <- f(x) + .1*rnorm(n)
plot(y~x)
curve(f(x), add=T, lty=2)
bw <- dpill(x,y)
lines( locpoly(x,y,degree=0, bandwidth=bw), col='red' )
lines( locpoly(x,y,degree=1, bandwidth=bw), col='green' )
lines( locpoly(x,y,degree=2, bandwidth=bw), col='blue' )
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("degré = 0", "degré = 1", "degré = 2"),
        lwd=1, lty=1,
        col=c('red', 'green', 'blue'))

*

a <- locpoly(x,y,degree=0, bandwidth=bw)
b <- locpoly(x,y,degree=1, bandwidth=bw)
c <- locpoly(x,y,degree=2, bandwidth=bw)
matplot( cbind(a$x,b$x,c$x), abs(cbind(a$y-f(a$x), b$y-f(b$x), c$y-f(c$x)))^2, 
         xlab='', ylab='',
         type='l', lty=1, col=c('red', 'green', 'blue') )
legend( .8*par("usr")[1]+.2*par("usr")[2], par("usr")[4], yjust=1,
        c("degré = 0", "degré = 1", "degré = 2"),
        lwd=1, lty=1,
        col=c('red', 'green', 'blue'))
title(main="Erreur quadratique")

*

Remarque: Au lieu de faire des régression locales avec les moindres carrés, on peut aussi utiliser le maximum de vraissemblance.

On dispose de plein d'autres fonctions du même genre :

loess (modreg) régression polynomiale locale, avec un noyau tricubique
               (si on ne sait pas quoi utiliser, c'est ce qu'on choisira)
lowess (base)
locpoly (KernSmooth) : régression polynômiale locale, dont on peut fixer le degré, 
                       avec un noyau normal.
smooth (eda) : avec des médianes
supsmu (modreg)
density (base)

En particulier, nous utiliserons ce genre de régression pour voir si un modèle linéaire suffit ou pas.

# C'est linéaire
library(modreg)
n <- 10
op <- par(mfrow=c(2,2))
for (i in 1:4) {
  x <- rnorm(n)
  y <- 1-2*x+.3*rnorm(n)
  plot(y~x)
  lo <- loess(y~x)
  xx <- seq(min(x),max(x),length=100)
  yy <- predict(lo, data.frame(x=xx))
  lines(xx,yy, col='red')
  lo <- loess(y~x, family='sym')
  xx <- seq(min(x),max(x),length=100)
  yy <- predict(lo, data.frame(x=xx))
  lines(xx,yy, col='red', lty=2)
  lines(lowess(x,y),col='blue',lty=2)
#  abline(lm(y~x), col='green')
#  abline(1,-2, col='green', lty=2)
}
par(op)

*

# Ça n'est pas linéaire
n <- 10
op <- par(mfrow=c(2,2))
for (i in 1:4) {
  x <- rnorm(n)
  y <- x*(1-x)+.3*rnorm(n)
  plot(y~x)
  lo <- loess(y~x)
  xx <- seq(min(x),max(x),length=100)
  yy <- predict(lo, data.frame(x=xx))
  lines(xx,yy, col='red')
  lo <- loess(y~x, family='sym')
  xx <- seq(min(x),max(x),length=100)
  yy <- predict(lo, data.frame(x=xx))
  lines(xx,yy, col='red', lty=2)
  lines(lowess(x,y),col='blue',lty=2)
#  curve(x*(1-x), add = TRUE, col = "green", lty=2)
}
par(op)

*

Régressions pénalisées

A FAIRE : introduction (en particulier, il faudra expliquer pourquoi on met PCR et PLS ici, en les comparant à la ridge regression).

Régression régularisée (PCR : Principal Component Regression)

C'est une régression sur les premières composantes principales.

On peut la calculer à la main

# La matrice de changement de base
e <- eigen(cor(x)))
# On normalise x et on effectue le changement de base
enx <- scale(x) %*% e$vec
# On fait la régression sur ces nouveaux vecteurs
lm(y ~ enx)

ou directement :

lm(y ~ princomp(x)$scores)

Si on veut juste les trois premières variables :

lm(y ~ princomp(x)$scores[,1:3])

L'interprétation des nouvelles coordonnées demande un peu plus de travail (on s'aidera de la commande biplot(princomp(x))).

Il y a toutefois un problème : lors du choix de la nouvelle base, on n'a tenu compte que de x et pas de y, or certaines variables prédictives peuvent être peu significatives si on les compare aux autres et bien plus significatives si on tient compte de y (ou le contraire).

Moindres carrés partiels (PLS : Partial Least Squares)

Les poindres carrés partiels pallient à ce problème : c'est comme une régression sur les composantes principales, mais on tient compte de la variable à prédire lors du choix de la nouvelle base.

library(help=pls)
library(help=pls.pcr)
library(pls.pcr)
?simpls

A FAIRE : Comprendre les moindres carrés partiels...

Sur internet, on trouve l'algorithme suivant pour calculer la
décomposition en composantes principales à l'aide des moindres
carrés partiels itérés non linéairement (NIPALS) (t=scores,
p=loadings)

begin loop
  select a proxy t-vector (any large column of X)
  small = 10^(-6) (e.g.)
  begin loop
    p = X'*t
    p is normalised to length one: abs(p)=1
    t = X*p
  end loop if convergence: (t_new - t_old) << small
  remove the modelled information from X: X_new = X_old - t*p'
end loop (if all PCs are calculated)
http://www.bitjungle.com/~mvartools/muvanl/

PLS = entre la régression multiple et la régression sur les
composantes principales. On peut d'ailleurs imaginer d'autres
méthodes entr deux (on fait varier un paramétre entre 0 (MLR) et 1
(PCR), 0.5 correspondant aux PLS).

PCR: Factors from X'X
PLS: Factors from Y'XX'Y
The difference between PCR and PLS is the way factor scores are extracted.
  
Remarque : multi-way data analysis
On ne fait plus de l'algèbre linéaire sur des matrices, vues comme
des tableaux à deux dimensions, mais sur des tableaux à trois
dimensions (ou plus).

A FAIRE : EFFACER CE QUI SUIT (c'est faux...)

C'est effacé.

Il existe une bibliothèque "pls" qui fait ce genre de chose :

http://cran.r-project.org/src/contrib/Devel/
http://www.gsm.uci.edu/~joelwest/SEM/PLS.html

Moindres carrés pénalisés

La régression au sens des moindres carrés consiste à minimiser

RSS(f) = Somme (y_i - f(x_i))^2
           i

Mais cela peut donner des fonctions assez "méchantes". Pour se limiter à des fonctions gentilles, on peut ajouter un terme

PRSS(f) = Somme (y_i - f(x_i))^2  + lambda J(f)
            i

où J est d'autant plus grand que la fonction est "méchante" et lambda un réel. Par exemple, si on veut des fonctions dont la courbure n'est pas trop élevée, on pourra prendre

J(f) = \int f''^2

(en fait, on peut définir les splines comme ça).

Ridge regression

La régression normale consiste à faire le calcul

b = (X'X)^-1 X' y.

Le problème, c'est que parfois, en cas de multicolinéarité (i.e., si on a des variables prédictives très liées, par exemple, si X1 = X2 + X3), la matrice X'X est presque singulière : l'estimateur obtenu a alors une variance très élevée. On peut contourner ce problème en décalant toutes les valeurs propres de la matrice, et en calculant

b = (X'X + kI)^-1 X' y.

Cela permet de réduire la variance -- mais en revanche, l'estimateur obtenu est biaisé.

On peut aussi présenter la ridge regression comme une régression pénalisée : au lieu de cherche b qui minimise

Somme( y_i - b0 - Somme( xij bj ) )^2 
  i                 j

On cherche b qui minimise

Somme( y_i - b0 - Somme( xij bj ) )^2 + k Somme bj^2
  i                 j                      j>0

Cela permet d'éviter que les coefficients soient trop grands (c'est ce qui arrive en cas de multicolinéarité : on peut avoir un gros coefficient positid et un gros coefficient négatif qui se compensent).

Il revient au même de chercher b qui minimise

Somme( y_i - b0 - Somme( xij bj ) )^2 
  i                 j

soumis à la condition

Somme bj^2 <= s
 j>0

Le problème est alors de choisir la valeur de k (ou s) pour que la variance soit faible et le biais pas trop élevé : à l'aide d'heuristiques, de graphiques, ou par validation croisée.

Dans la pratique, on commence par centrer et renormaliser les colonnes de X -- il n'y aura donc pas de terme constant. On pourrait le programmer soi-même, à la main :

my.lm.ridge <- function (y, x, k=.1) {
  my <- mean(y)
  y <- y - my
  mx <- apply(x,2,mean)
  x <- x - matrix(mx, nr=dim(x)[1], nc=dim(x)[2], byrow=T)
  sx <- apply(x,2,sd)
  x <- x/matrix(sx, nr=dim(x)[1], nc=dim(x)[2], byrow=T)
  b <- solve( t(x) %*% x + diag(k, dim(x)[2]), t(x) %*% y)
  c(my - sum(b*mx/sx) , b/sx)
}

ou directement en utilisant la définition en termes de moindres carrés pénalisés :

my.ridge <- function (y,x,k=0) {
  xm <- apply(x,2,mean)
  ym <- mean(y)
  y <- y - ym
  x <- t( t(x) - xm )
  ss <- function (b) {
    t( y - x %*% b ) %*% ( y - x %*% b ) + k * t(b) %*% b
  }
  b <- nlm(ss, rep(0,dim(x)[2]))$estimate
  c(ym-t(b)%*%xm, b)
}
my.ridge.test <- function (n=20, s=.1) {
  x <- rnorm(n)
  x1 <- x + s*rnorm(n)
  x2 <- x + s*rnorm(n)
  x <- cbind(x1,x2)
  y <- x1 + x2 + 1 + rnorm(n)
  lambda <- c(0, .001, .01, .1, .2, .5, 1, 2, 5, 10)
  b <- matrix(nr=length(lambda), nc=1+dim(x)[2])
  for (i in 1:length(lambda)) {
    b[i,] <- my.ridge(y,x,lambda[i])
  }
  plot(b[,2], b[,3], type="b", xlim=range(c(b[,2],1)), ylim=range(c(b[,3],1)))
  text(b[,2], b[,3], lambda, adj=c(-.2,-.2), col="blue")
  points(1,1,pch="+", cex=3, lwd=3)
  points(b[8,2],b[8,3],pch=15)
}
my.ridge.test()

*

A FAIRE : donner un titre au graphique
(ridge regression et forte multicolinéarité)
op <- par(mfrow=c(3,3))
for (i in 1:9) {
  my.ridge.test()
}
par(op)

*

A FAIRE : donner un titre au graphique
(ridge regression et multicolinéarité plus légère)
op <- par(mfrow=c(3,3))
for (i in 1:9) {
  my.ridge.test(20,10)
}
par(op)

*

mais il y a déjà une commande lm.ridge dans la bibliothèque MASS -- nous l'utiliserons.

Commençons par remarquer sur un exemple que dans certains cas (ici, de la multicolinéarité), la variance des estimateurs de la régression usuelle est très élevée. Par contre, la variance des estimateurs de la ridge regression est bien moindre -- mais ils sont biaisés.

my.sample <- function (n=20) {
  x <- rnorm(n)
  x1 <- x + .1*rnorm(n)
  x2 <- x + .1*rnorm(n)
  y <- 0 + x1 - x2 + rnorm(n)
  cbind(y, x1, x2)
}

n <- 500
r <- matrix(NA, nr=n, nc=3)
s <- matrix(NA, nr=n, nc=3)
for (i in 1:n) {
  m <- my.sample()
  r[i,] <- lm(m[,1]~m[,-1])$coef
  s[i,2:3] <- lm.ridge(m[,1]~m[,-1], lambda=.1)$coef
  s[i,1] <- mean(m[,1])
}
plot( density(r[,1]), xlim=c(-3,3),
      main="Variance élevée en cas de multicolinéarité")
abline(v=0, lty=3)
lines( density(r[,2]), col='red' )
lines( density(s[,2]), col='red', lty=2 )
abline(v=1, col='red', lty=3)
lines( density(r[,3]), col='blue' )
lines( density(s[,3]), col='blue', lty=2 )
abline(v=-1, col='blue', lty=3)
# On indique la moyenne, pour bien montrer que c'est biaisé
evaluate.density <- function (d,x, eps=1e-6) {
  density(d, from=x-eps, to=x+2*eps, n=4)$y[2]
}
x<-mean(r[,2]); points( x, evaluate.density(r[,2],x) )
x<-mean(s[,2]); points( x, evaluate.density(s[,2],x) )
x<-mean(r[,3]); points( x, evaluate.density(r[,3],x) )
x<-mean(s[,3]); points( x, evaluate.density(s[,3],x) )
legend( par("usr")[1], par("usr")[4], yjust=1,
        c("terme constant", "x1", "x2"),
        lwd=1, lty=1, 
        col=c(par('fg'), 'red', 'blue') )
legend( par("usr")[2], par("usr")[4], yjust=1, xjust=1,
        c("régression classique", "ridge regression"),
        lwd=1, lty=c(1,2), 
        col=par('fg') )

*

On peut calculer la moyenne et la variance de nos estimateurs :

# LS regression
> apply(r,2,mean)
[1] -0.02883416  1.04348779 -1.05150408
> apply(r,2,var)
[1] 0.0597358 2.9969272 2.9746692

# Ridge regression
> apply(s,2,mean)
[1] -0.02836816  0.62807824 -0.63910220
> apply(s,2,var)
[1] 0.05269393 0.99976908 0.99629095

Mais ça ne permet pas de bien les comparer ; pour ce faire, on préfère utiliser la MSE (Mean Square Error) : c'est la moyenne des carrés des différences avec la valeur réelle du paramètre recherché (alors que pour la variance, on fait la différence avec la moyenne des estimations).

> v <- matrix(c(0,1,-1), nr=n, nc=3, byrow=T)
> apply( (r-v)^2, 2, mean )
[1] 0.06044774 2.99282451 2.97137250
> apply( (s-v)^2, 2, mean )
[1] 0.05339329 1.13609534 1.12454560

On peut représenter l'évolution de cette MSE avec k.

n <- 500
v <- matrix(c(0,1,-1), nr=n, nc=3, byrow=T)
mse <- NULL
kl <- c(1e-4, 2e-4, 5e-4, 1e-3, 2e-3, 5e-3, 1e-2, 2e-2, 5e-2, 
        .1, .2, .3, .4, .5, .6, .7, .8, .9, 1, 1.2, 1.4, 1.6, 1.8, 2)
for (k in kl) {
  r <- matrix(NA, nr=n, nc=3)
  for (i in 1:n) {
    m <- my.sample()
    r[i,2:3] <- lm.ridge(m[,1]~m[,-1], lambda=k)$coef
    r[i,1] <- mean(m[,1])
  }
  mse <- append(mse, apply( (r-v)^2, 2, mean )[2])
}
plot( mse ~ kl, type='l' )  
title(main="Évolution de la MSE")

*

Avec une échelle logarithmique :

plot( mse-min(mse)+.01 ~ kl, type='l', log='y' )
title(main="Évolution de la MSE")

*

Avec les rangs :

plot( rank(mse) ~ kl, type='l' )
title(main="Évolution de la MSE")

*

Sur cet exemple, on constate que k=0.5 est une valeur convenable (mais d'après les livres, c'est vraiment une valeur très élevée : ils affirment qu'on ne dépasse pas 0.1...)

Le problème, c'est qu'en général on ne peut pas calculer la MSE : il faut connaître les valeurs exactes des paramètres, or c'est justement ce que l'on cherche.

On peut aussi choisir k en s'aidant de graphiques, en représentant la valeur des paramètres (ou autre chose que les paramètres, par exemple le coefficient de détermination, R^2) en fonction de k.

m <- my.sample()
b <- matrix(NA, nr=length(kl), nc=2)
for (i in 1:length(kl)) {
  b[i,] <- lm.ridge(m[,1]~m[,-1], lambda=kl[i])$coef
}
matplot(kl, b, type="l")
abline(h=c(0,-1,1), lty=3)
# Estimation empirique de la valeur de k
k <- min( lm.ridge(m[,1]~m[,-1], lambda=kl)$GCV )
abline(v=k, lty=3)
title(main="Le biais vers 0 dans la ridge regression")

*

On voit peut-être mieux avec une échelle logarithmique :

m <- my.sample()
b <- matrix(NA, nr=length(kl), nc=2)
for (i in 1:length(kl)) {
  b[i,] <- lm.ridge(m[,1]~m[,-1], lambda=kl[i])$coef
}
matplot(kl, b, type="l", log='x')
abline(h=c(0,-1,1), lty=3)
k <- min( lm.ridge(m[,1]~m[,-1], lambda=kl)$GCV )  
abline(v=k, lty=3)
title(main="Le biais vers 0 dans la ridge regression")

*

On s'apperçoit que les estimateurs sont d'autant plus biaisés vers 0 que k est grand : sur cet exemple, une valeur entre 0.01 et 0.1 convient.

my.lm.ridge.diag <- function (y, x, k=.1) {
  my <- mean(y)
  y <- y - my
  mx <- apply(x,2,mean)
  x <- x - matrix(mx, nr=dim(x)[1], nc=dim(x)[2], byrow=T)
  sx <- apply(x,2,sd)
  x <- x/matrix(sx, nr=dim(x)[1], nc=dim(x)[2], byrow=T)
  b <- solve( t(x) %*% x + diag(k, dim(x)[2]), t(x) %*% y)
  v <- solve( t(x) %*% x + diag(k, dim(x)[2]), 
       t(x) %*% x %*% solve( t(x) %*% x + diag(k, dim(x)[2]), 
       diag( var(y), dim(x)[2] ) ))
  ss <- t(b) %*% t(x) %*% y
  list( b = b, varb = v, ss = ss )
}

m <- my.sample()
b <- matrix(NA, nr=length(kl), nc=2)
v <- matrix(NA, nr=length(kl), nc=1)
ss <- matrix(NA, nr=length(kl), nc=1)
for (i in 1:length(kl)) {
  r <- my.lm.ridge.diag(m[,1], m[,-1], k=kl[i])
  b[i,] <- r$b
  v[i,] <- sum(diag(r$v))
  ss[i,] <- r$ss
}
matplot(kl, b, type="l", lty=1, col=par('fg'), axes=F, ylab='')
axis(1)
abline(h=c(0,-1,1), lty=3)
par(new=T)
matplot(kl, v, type="l", col='red', axes=F, ylab='')
par(new=T)
matplot(kl, ss, type="l", col='blue', axes=F, ylab='')
legend( par("usr")[2], par("usr")[4], yjust=1, xjust=1,
        c("paramètres", "variance", "sommes de carrés"),
        lwd=1, lty=1, col=c(par('fg'), "red", "blue") )

*

matplot(log(kl), b, type="l", lty=1, col=par('fg'), axes=F, ylab='')
axis(1)
abline(h=c(0,-1,1), lty=3)
par(new=T)
matplot(log(kl), v, type="l", col='red', axes=F, ylab='')
par(new=T)
matplot(log(kl), ss, type="l", col='blue', axes=F, ylab='')
# Je n'arrive pas à placer la légende si l'échelle est logarithmique...
legend( par("usr")[1], 
        .9*par("usr")[3] + .1*par("usr")[4], 
        yjust=0, xjust=0,
        c("paramètres", "variance", "sommes de carrés"),
        lwd=1, lty=1, col=c(par('fg'), "red", "blue") )

*

Exercice : ajouter R^2 au graphique précédent.

En fait, si on regarde plusieurs simulations de ce genre, on s'apperçoit que le graphique n'aide pas du tout à choisir la valeur de k : les courbes ont toujours la même forme, mais les valeurs exactes des paramètres peuvent être n'importe où...

op <- par(mfrow=c(3,3))
for (j in 1:9) {
  m <- my.sample()
  b <- matrix(NA, nr=length(kl), nc=2)
  for (i in 1:length(kl)) {
    b[i,] <- lm.ridge(m[,1]~m[,-1], lambda=kl[i])$coef
  }
  matplot(kl, b, type="l", log='x')
  abline(h=c(0,-1,1), lty=3)
  k <- min( lm.ridge(m[,1]~m[,-1], lambda=kl)$GCV )  
  abline(v=k, lty=3)
}
par(op)

*

Pour choisir la valeur de k, on peut aussi recourrir à la validation croisée.

m <- my.sample()
N <- 20
err <- matrix(nr=length(kl), nc=N)
for (j in 1:N) {
  s <- sample(dim(m)[1], floor(3*dim(m)[1]/4))
  mm <- m[s,]
  mv <- m[-s,]
  for (i in 1:length(kl)) {
    r <- lm.ridge(mm[,1]~mm[,-1], lambda=kl[i])
    # BUG...
    b <- r$coef / r$scales
    a <- r$ym - t(b) %*% r$xm
    p <- rep(a, dim(mv)[1]) + mv[,-1] %*% b
    e <- p-mv[,1]
    err[i,j] <- sum(e*e)
  }
}
err <- apply(err, 1, mean)
plot(err ~ kl, type='l', log='x')

*

A FAIRE : il y a une erreur dans le code ci-dessus 
(on cherche à minimiser l'erreur, qui doit être d'autant plus grande
que k est grand...)

En définitive, pour choisir les valeurs de k, on se contentera d'utiliser les différentes heuristiques proposées par la commande lm.ridge.

m <- my.sample()
x <- m[,1]
y <- m[,-1]

r <- lm.ridge(y~x, lambda=kl); 
r$kHKB; r$kLW; min(r$GCV)

Pour cet exemple, il vient :

[1] 0
[1] 0
[1] 0.04624034

On nous suggère donc d'utiliser la régression usuelle -- alors que nous avons vu plus haut que, pour cet exemple, k=0.5 était une valeur convenable...

Autres exemple :

data(longley)
y <- longley[,1]
x <- as.matrix(longley[,-1])
r <- lm.ridge(y~x, lambda=kl); 
r$kHKB; r$kLW; min(r$GCV)

Il vient :

[1] 0.006836982
[1] 0.05267247
[1] 0.1196090

Voir aussi :

library(help=lpridge)  # Local polynomial (ridge) regression.

On pourra aussi lire :

J.O. Rawlings, Applied Regression Analysis : A Research Tool (1988), chapter 12.

Lasso

C'est une variante de la ridge regression, avec une contrainte L1 au lieu de L2 : on minimise

Somme( y_i - b0 - Somme( xij bj ) )^2 + k Somme abs(bj).
  i                 j                      j>0

On pourrait l'implémenter à la main :

my.lasso <- function (y,x,k=0) {
  xm <- apply(x,2,mean)
  ym <- mean(y)
  y <- y - ym
  x <- t( t(x) - xm )
  ss <- function (b) {
    t( y - x %*% b ) %*% ( y - x %*% b ) + k * sum(abs(b))
  }
  b <- nlm(ss, rep(0,dim(x)[2]))$estimate
  c(ym-t(b)%*%xm, b)
}

my.lasso.test <- function (n=20) {
  s <- .1
  x <- rnorm(n)
  x1 <- x + s*rnorm(n)
  x2 <- x + s*rnorm(n)
  x <- cbind(x1,x2)
  y <- x1 + x2 + 1 + rnorm(n)
  lambda <- c(0, .001, .01, .1, .2, .5, 1, 2, 5, 10)
  b <- matrix(nr=length(lambda), nc=1+dim(x)[2])
  for (i in 1:length(lambda)) {
    b[i,] <- my.lasso(y,x,lambda[i])
  }
  plot(b[,2], b[,3], type="b", xlim=range(c(b[,2],1)), ylim=range(c(b[,3],1)))
  text(b[,2], b[,3], lambda, adj=c(-.2,-.2), col="blue")
  points(1,1,pch="+", cex=3, lwd=3)
  points(b[8,2],b[8,3],pch=15)
}
my.lasso.test()

*

op <- par(mfrow=c(3,3))
for (i in 1:9) {
  my.lasso.test()
}
par(op)

*

op <- par(mfrow=c(3,3))
for (i in 1:9) {
  my.lasso.test(1000)
}
par(op)

*

Mais il existe déjà une fonction qui fait ça :

library(help=lasso2)
library(lasso2)
?l1ce
?gl1ce

Reprenons le même exemple.

# Je ne mets pas de dessin à cause d'un bug dans la fonction l1ce :
# d'une part, elle exige qu'on lui donne un data.frame ; d'autre part, 
# elle exige que ce data.frame soit une variable globale...

library(lasso2)
set.seed(73823)
#other.lasso.test <- function (n=20) {
  s <- .1
  x <- rnorm(n)
  x1 <- x + s*rnorm(n)
  x2 <- x + s*rnorm(n)
  y <- x1 + x2 + 1 + rnorm(n)
  d <- data.frame(y,x1,x2)
  lambda <- c(0, .001, .01, .1, .2, .5, 1, 2, 5, 10)
  b <- matrix(nr=length(lambda), nc=dim(d)[2])
  for (i in 1:length(lambda)) {
    try( b[i,] <- l1ce(y~x1+x2, data=d, bound=lambda[i], absolute.t=T)$coefficients )
    # J'ai eu le message d'erreur très informatif : 
    #   Oops, something went wrong in .C("lasso",..): -1
  }
  plot(b[,2], b[,3], type="b", xlim=range(c(b[,2],1)), ylim=range(c(b[,3],1)))
  text(b[,2], b[,3], lambda, adj=c(-.2,-.2), col="blue")
  points(1,1,pch="+", cex=3, lwd=3)
  points(b[8,2],b[8,3],pch=15)
#}
#other.lasso.test()

*

op <- par(mfrow=c(3,3))
for (i in 1:9) {
  other.lasso.test()
}
par(op)

Comparaison de quelques unes de ces régressions

A FAIRE : 
y ~ x1 + x2
LS, ridge, lasso, PLS, PCR, best subset (leap): plot(b2~b1)
(some of these methods are discrete, others are continuous)
In particular, remark that ridge regression is very similar to PLS ou PCR.

Commençons par comparer la ridge regression (en rouge) avec la méthode du lasso (en noir) : la première semble préférable...

get.sample <- function (n=20,s=.1) {
  x <- rnorm(n)
  x1 <- x + s*rnorm(n)
  x2 <- x + s*rnorm(n)
  y <- x1 + x2 + 1 + rnorm(n)
  data.frame(y,x1,x2)
}
lambda <- c(0, .001, .01, .1, .2, .5, 1, 2, 5, 10)

do.it <- function (n=20,s=.1) {
  d <- get.sample(n,s)
  y <- d$y
  x <- cbind(d$x1,d$x2)
  ridge <- matrix(nr=length(lambda), nc=1+dim(x)[2])
  for (i in 1:length(lambda)) {
    ridge[i,] <- my.ridge(y,x,lambda[i])
  }
  lasso <- matrix(nr=length(lambda), nc=1+dim(x)[2])
  for (i in 1:length(lambda)) {
    lasso[i,] <- my.lasso(y,x,lambda[i])
  }
  xlim <- range(c( 1, ridge[,2], lasso[,2] ))        
  ylim <- range(c( 1, ridge[,3], lasso[,3] ))        
  plot(ridge[,2], ridge[,3], type="b", col='red', xlim=xlim, ylim=ylim)
  points(ridge[8,2],ridge[8,3],pch=15,col='red')
  lines(lasso[,2], lasso[,3], type="b")
  points(lasso[8,2],lasso[8,3],pch=15)
  points(1,1,pch="+", cex=3, lwd=3)
}

op <- par(mfrow=c(3,3))
for (i in 1:9) {
  do.it()
}
par(op)

*

Avec des échantillons plus gros, le lasso ne s'en va pas dans la bonne direction...

op <- par(mfrow=c(3,3))
for (i in 1:9) {
  do.it(100)
}
par(op)

*

Par contre, quand la multicolinéarité est faible, les deux méthodes sont comparables (et mauvaises).

op <- par(mfrow=c(3,3))
for (i in 1:9) {
  do.it(20,10)
}
par(op)

*

En fait, la régression sur les composantes principales et les moindres carrés partiels sont des analogues discrets de la ridge regression. On préfèrera donc la ridge régression, car on peut faire varier le paramètre de manière continue.

my.pcr <- function (y,x,k) {
  n <- dim(x)[1]
  p <- dim(x)[2]
  ym <- mean(y)
  xm <- apply(x,2,mean)
  # Idéalement, il faudrait aussi normaliser x et y... (Exercice laissé au lecteur)
  y <- y - ym 
  x <- t( t(x) - xm )
  pc <- princomp(x)
  b <- lm(y~pc$scores[,1:k]-1)$coef
  b <- c(b, rep(0,p-k))
  b <- pc$loadings %*% b
  names(b) <- colnames(x)
  b <- c(ym-t(b)%*%xm, b)
  b
}
get.sample <- function (n=20, s=.1) {
  x <- rnorm(n)
  x1 <- x + s*rnorm(n)
  x2 <- x + s*rnorm(n)
  x3 <- x + s*rnorm(n)
  x4 <- x + s*rnorm(n)
  x <- cbind(x1,x2,x3,x4)
  y <- x1 + x2 - x3 - x4 + 1 + rnorm(n)
  list(x=x,y=y)
}
pcr.test <- function (n=20, s=.1) {
  s <- get.sample(n,s)
  x <- s$x
  y <- s$y
  pcr <- matrix(nr=4,nc=5)
  for (k in 1:4) {
    pcr[k,] <- my.pcr(y,x,k)
  }
  plot(pcr[,2], pcr[,3], type="b", xlim=range(c(pcr[,2],1)), ylim=range(c(pcr[,3],1)))
  points(pcr[4,2],pcr[4,3], lwd=2)
  points(1,1,pch="+", cex=3, lwd=3)
}
pcr.test()

*

A FAIRE : ajouter les PLS

A FAIRE : mettre un titre au graphique suivant 
(comparaison PCR/ridge regression)
pcr.test <- function (n=20, s=.1) {
  s <- get.sample(n,s)
  x <- s$x
  y <- s$y

  lambda <- c(0, .001, .01, .1, .2, .5, 1, 2, 5, 10)
  ridge <- matrix(nr=length(lambda), nc=1+dim(x)[2])
  for (i in 1:length(lambda)) {
    ridge[i,] <- my.ridge(y,x,lambda[i])
  }

  pcr <- matrix(nr=4,nc=5)
  for (k in 1:4) {
    pcr[k,] <- my.pcr(y,x,k)
  }

  xlim <- range(c( 1, ridge[,2], pcr[,2] ))        
  ylim <- range(c( 1, ridge[,3], pcr[,3] ))        
  plot(ridge[,2], ridge[,3], type="b", col='red', xlim=xlim, ylim=ylim)
  points(ridge[4,2],ridge[4,3],pch=15,col='red')

  lines(pcr[,2], pcr[,3], type="b")
  points(pcr[4,2],pcr[4,3], lwd=2)
  points(1,1,pch="+", cex=3, lwd=3)    
}
op <- par(mfrow=c(3,3))
for (i in 1:9) {
  pcr.test()    
}
par(op)

*

Penalized MLE

Nous avons vu que la ridge regression, la méthode du lasso ou les splines minimisaient des sommes de carrés pénalisées. On peut aussi modifier la méthode du maximum de vraissemblance en la penalisant.

A FAIRE : trouver des exemples (non, je n'en ai pas...)

Régression non linéaire

On peut aussi avoir des modèles vraiment non linéaires (pour les modèles polynômiaux précédents, on rajoutait des termes correspondant à d'autres monômes, mais à la fin, on faisait toujours une régression linéaire). On conseille d'avoir une raison valable pour les utiliser. En voici quelques exemples.

Croissance ou décroissance exponentielle, Y = a e^(bX) + bruit

n <- 100
x <- seq(-2,2,length=n)
y <- exp(x)
plot(y~x, type='l', lwd=3)
title(main='Croissance exponentielle')

*

x <- seq(-2,2,length=n)
y <- exp(-x)
plot(y~x, type='l', lwd=3)
title(main='Décroissance exponentielle')

*

Exponentielle négative, Y = a(1-e^(bX)) + bruit

x <- seq(-2,4,length=n)
y <- 1-exp(-x)
plot(y~x, type='l', lwd=3)
title(main='Exponentielle négative')

*

Exponentielle double, Y = u/(u-v) * (e^(-vX) - e^(-uX)) + bruit

x <- seq(0,5,length=n)
u <- 1
v <- 2
y <- u/(u-v) * (exp(-v*x) - exp(-u*x))
plot(y~x, type='l', lwd=3)
title(main='Exponentielle double')

*

Croissance sigmoïde, Y = a/( 1+ce^(-bX) ) + bruit

x <- seq(-5,5,length=n)
y <- 1/(1+exp(-x))
plot(y~x, type='l', lwd=3)
title(main='Croissance sigmoïde')

*

Sigmoïde moins symétrique, Y = a exp( -ce^(-bX) ) + bruit

x <- seq(-2,5,length=n)
y <- exp(-exp(-x))
plot(y~x, type='l', lwd=3)
title(main='Sigmoïde moins symétrique')

*

Pour effectuer ces régressions, on utilise toujours les moindres carrés, mais on se retrouve avec un système non linéaire, qui l'on résout de manière approchée (par exemple, par la méthode de Newton--Raphson).

library(help=nls)
library(nls)
?nls

On définit une fonction f(x,p), où x est la variable et p un vecteur contenant les paramètres. On l'utilisera ainsi : f(x,c(a,b,c)).

library(nls)
f <- function (x,p) {
  u <- p[1]
  v <- p[2]
  u/(u-v) * (exp(-v*x) - exp(-u*x))    
}
n <- 100
x <- runif(n,0,2)
y <- f(x, c(3.14,2.71)) + .1*rnorm(n)
r <- nls( y ~ f(x,c(a,b)), start=c(a=3, b=2.5) )
plot(y~x)
xx <- seq(0,2,length=200)
lines(xx, f(xx,r$m$getAllPars()), col='red', lwd=3)
lines(xx, f(xx,c(3.14,2.71)), lty=2)

*

Voici le résultat.

> summary(r)
Formula: y ~ f(x, c(a, b))

Parameters:
  Estimate Std. Error t value Pr(>|t|)
a   3.0477     0.3358   9.075 1.23e-14 ***
b   2.6177     0.1459  17.944  < 2e-16 ***
---
Signif. codes:  0 `***' 0.001 `**' 0.01 `*' 0.05 `.' 0.1 ` ' 1

Residual standard error: 0.1059 on 98 degrees of freedom

Correlation of Parameter Estimates:
         a
b -0.06946

On peut représenter des intervalles de confiance :

op <- par(mfrow=c(2,2))
p <- profile(r)
plot(p, conf = c(95, 90, 80, 50)/100)
plot(p, conf = c(95, 90, 80, 50)/100, absVal = FALSE)
par(op)

*

Il y a quelques modèles non-linéaires prédéfinis. Pour certains d'entre eux, R est capable de trouver des valeurs approchées initiales des paramètres.

rm(r)
while(!exists("r")) {
  x <- runif(n,0,2)
  y <- SSbiexp(x,1,1,-1,2) + .01*rnorm(n)
  try(  r <- nls(y ~ SSbiexp(x,a,u,b,v))  )
}
plot(y~x)
xx <- seq(0,2,length=200)
lines(xx, SSbiexp(xx,
                  r$m$getAllPars()[1],
                  r$m$getAllPars()[2],
                  r$m$getAllPars()[3],
                  r$m$getAllPars()[4],
                  ), col='red', lwd=3)
title(main='biexponential')

*

op <- par(mfrow=c(2,2))
try(plot(profile(r)))
par(op)

*

rm(r)
while(!exists("r")) {
  x <- runif(n,-5,5)
  y <- SSlogis(x,1,0,1) + .1*rnorm(n)
  try( r <- nls(y ~ SSlogis(x,a,m,s)) )
}
plot(y~x)
xx <- seq(-5,5,length=200)
lines(xx, SSlogis(xx,
                  r$m$getAllPars()[1],
                  r$m$getAllPars()[2],
                  r$m$getAllPars()[3],
                  ), col='red', lwd=3)
title(main='logistic')

*

rm(r)
while(!exists("r")) {
  x <- runif(n,-5,5)
  y <- SSfpl(x,-1,1,0,1) + .1*rnorm(n)
  try( r <- nls(y ~ SSfpl(x,a,b,m,s)) )
}
plot(y~x)
xx <- seq(-5,5,length=200)
lines(xx, SSfpl(xx,
                  r$m$getAllPars()[1],
                  r$m$getAllPars()[2],
                  r$m$getAllPars()[3],
                  r$m$getAllPars()[4],
                  ), col='red', lwd=3)
title(main='4-parameter logistic model')

*

rm(r)
while(!exists("r")) {
  x <- runif(n,-5,5)
  y <- SSmicmen(x,1,1) + .01*rnorm(n)
  try( r <- nls(y ~ SSmicmen(x,m,h)) )
}
plot(y~x, ylim=c(-5,5))
xx <- seq(-5,5,length=200)
lines(xx, SSmicmen(xx,
                  r$m$getAllPars()[1],
                  r$m$getAllPars()[2],
                  ), col='red', lwd=3)
title(main='Michaelis-Menten')

*

rm(r)
while(!exists("r")) {
  x <- runif(n,0,5)
  y <- SSmicmen(x,1,1) + .1*rnorm(n)
  try( r <- nls(y ~ SSmicmen(x,m,h)) )
}
plot(y~x)
xx <- seq(0,5,length=200)
lines(xx, SSmicmen(xx,
                  r$m$getAllPars()[1],
                  r$m$getAllPars()[2],
                  ), col='red', lwd=3)
title(main='Michaelis-Menten')

*

rm(r)
while(!exists("r")) {
  x <- runif(n,0,1)
  y <- SSfol(1,x,1,2,1) + .1*rnorm(n)
  try( r <- nls(y ~ SSfol(1,x,a,b,c)) )
}
plot(y~x)
xx <- seq(0,1,length=200)
lines(xx, SSfol(1,
                   xx,
                   r$m$getAllPars()[1],
                   r$m$getAllPars()[2],
                   r$m$getAllPars()[3],
                  ), col='red', lwd=3)
title(main='SSfol')

*

rm(r)
while(!exists("r")) {
  x <- runif(n,0,2)
  y <- SSasymp(x,1,.5,1) + .1*rnorm(n)
  try( r <- nls(y ~ SSasymp(x,a,b,c)) )
}
plot(y~x, xlim=c(-.5,2),ylim=c(0,1.3))
xx <- seq(-1,2,length=200)
lines(xx, SSasymp(xx,
                  r$m$getAllPars()[1],
                  r$m$getAllPars()[2],
                  r$m$getAllPars()[3],
                 ), col='red', lwd=3)
title(main='SSasymp')
# Voir aussi SSasympOff et SSasympOrig

*

Le manuel explique comment interpréter ces paramètres.

# Copié sur le manuel
xx <- seq(0, 5, len = 101)
yy <- 5 - 4 * exp(-xx/(2*log(2)))
par(mar = c(0, 0, 4.1, 0))
plot(xx, yy, type = "l", axes = FALSE, ylim = c(0,6), xlim = c(-1, 5),
     xlab = "", ylab = "", lwd = 2,
     main = "Parameters in the SSasymp model")
usr <- par("usr")
arrows(usr[1], 0, usr[2], 0, length = 0.1, angle = 25)
arrows(0, usr[3], 0, usr[4], length = 0.1, angle = 25)
text(usr[2] - 0.2, 0.1, "x", adj = c(1, 0))
text(-0.1, usr[4], "y", adj = c(1, 1))
abline(h = 5, lty = 2, lwd = 0)
arrows(-0.8, 2.1, -0.8, 0, length = 0.1, angle = 25)
arrows(-0.8, 2.9, -0.8, 5, length = 0.1, angle = 25)
text(-0.8, 2.5, expression(phi[1]), adj = c(0.5, 0.5))
segments(-0.4, 1, 0, 1, lty = 2, lwd = 0.75)
arrows(-0.3, 0.25, -0.3, 0, length = 0.07, angle = 25)
arrows(-0.3, 0.75, -0.3, 1, length = 0.07, angle = 25)
text(-0.3, 0.5, expression(phi[2]), adj = c(0.5, 0.5))
segments(1, 3.025, 1, 4, lty = 2, lwd = 0.75)
arrows(0.2, 3.5, 0, 3.5, length = 0.08, angle = 25)
arrows(0.8, 3.5, 1, 3.5, length = 0.08, angle = 25)
text(0.5, 3.5, expression(t[0.5]), adj = c(0.5, 0.5))

*

# Copié sur le manuel
xx <- seq(0.5, 5, len = 101)
yy <- 5 * (1 -  exp(-(xx - 0.5)/(2*log(2))))
par(mar = c(0, 0, 4.0, 0))
plot(xx, yy, type = "l", axes = FALSE, ylim = c(0,6), xlim = c(-1, 5),
     xlab = "", ylab = "", lwd = 2,
     main = "Parameters in the SSasympOff model")
usr <- par("usr")
arrows(usr[1], 0, usr[2], 0, length = 0.1, angle = 25)
arrows(0, usr[3], 0, usr[4], length = 0.1, angle = 25)
text(usr[2] - 0.2, 0.1, "x", adj = c(1, 0))
text(-0.1, usr[4], "y", adj = c(1, 1))
abline(h = 5, lty = 2, lwd = 0)
arrows(-0.8, 2.1, -0.8, 0, length = 0.1, angle = 25)
arrows(-0.8, 2.9, -0.8, 5, length = 0.1, angle = 25)
text(-0.8, 2.5, expression(phi[1]), adj = c(0.5, 0.5))
segments(0.5, 0, 0.5, 3, lty = 2, lwd = 0.75)
text(0.5, 3.1, expression(phi[3]), adj = c(0.5, 0))
segments(1.5, 2.525, 1.5, 3, lty = 2, lwd = 0.75)
arrows(0.7, 2.65, 0.5, 2.65, length = 0.08, angle = 25)
arrows(1.3, 2.65, 1.5, 2.65, length = 0.08, angle = 25)
text(1.0, 2.65, expression(t[0.5]), adj = c(0.5, 0.5))

*

# Copié sur le manuel
xx <- seq(0, 5, len = 101)
yy <- 5 * (1- exp(-xx/(2*log(2))))
par(mar = c(0, 0, 3.5, 0))
plot(xx, yy, type = "l", axes = FALSE, ylim = c(0,6), xlim = c(-1, 5),
     xlab = "", ylab = "", lwd = 2,
     main = "Parameters in the SSasympOrig model")
usr <- par("usr")
arrows(usr[1], 0, usr[2], 0, length = 0.1, angle = 25)
arrows(0, usr[3], 0, usr[4], length = 0.1, angle = 25)
text(usr[2] - 0.2, 0.1, "x", adj = c(1, 0))
text(-0.1, usr[4], "y", adj = c(1, 1))
abline(h = 5, lty = 2, lwd = 0)
arrows(-0.8, 2.1, -0.8, 0, length = 0.1, angle = 25)
arrows(-0.8, 2.9, -0.8, 5, length = 0.1, angle = 25)
text(-0.8, 2.5, expression(phi[1]), adj = c(0.5, 0.5))
segments(1, 2.525, 1, 3.5, lty = 2, lwd = 0.75)
arrows(0.2, 3.0, 0, 3.0, length = 0.08, angle = 25)
arrows(0.8, 3.0, 1, 3.0, length = 0.08, angle = 25)
text(0.5, 3.0, expression(t[0.5]), adj = c(0.5, 0.5))

*

# Copié sur le manuel
xx <- seq(-0.5, 5, len = 101)
yy <- 1 + 4 / ( 1 + exp((2-xx)))
par(mar = c(0, 0, 3.5, 0))
plot(xx, yy, type = "l", axes = FALSE, ylim = c(0,6), xlim = c(-1, 5),
     xlab = "", ylab = "", lwd = 2,
     main = "Parameters in the SSfpl model")
usr <- par("usr")
arrows(usr[1], 0, usr[2], 0, length = 0.1, angle = 25)
arrows(0, usr[3], 0, usr[4], length = 0.1, angle = 25)
text(usr[2] - 0.2, 0.1, "x", adj = c(1, 0))
text(-0.1, usr[4], "y", adj = c(1, 1))
abline(h = 5, lty = 2, lwd = 0)
arrows(-0.8, 2.1, -0.8, 0, length = 0.1, angle = 25)
arrows(-0.8, 2.9, -0.8, 5, length = 0.1, angle = 25)
text(-0.8, 2.5, expression(phi[1]), adj = c(0.5, 0.5))
abline(h = 1, lty = 2, lwd = 0)
arrows(-0.3, 0.25, -0.3, 0, length = 0.07, angle = 25)
arrows(-0.3, 0.75, -0.3, 1, length = 0.07, angle = 25)
text(-0.3, 0.5, expression(phi[2]), adj = c(0.5, 0.5))
segments(2, 0, 2, 3.3, lty = 2, lwd = 0.75)
text(2, 3.3, expression(phi[3]), adj = c(0.5, 0))
segments(3, 1+4/(1+exp(-1)) - 0.025, 3, 2.5, lty = 2, lwd = 0.75)
arrows(2.3, 2.7, 2.0, 2.7, length = 0.08, angle = 25)
arrows(2.7, 2.7, 3.0, 2.7, length = 0.08, angle = 25)
text(2.5, 2.7, expression(phi[4]), adj = c(0.5, 0.5))

*

# Copié sur le manuel
xx <- seq(-0.5, 5, len = 101)
yy <- 5 / ( 1 + exp((2-xx)))
par(mar = c(0, 0, 3.5, 0))
plot(xx, yy, type = "l", axes = FALSE, ylim = c(0,6), xlim = c(-1, 5),
     xlab = "", ylab = "", lwd = 2,
     main = "Parameters in the SSlogis model")
usr <- par("usr")
arrows(usr[1], 0, usr[2], 0, length = 0.1, angle = 25)
arrows(0, usr[3], 0, usr[4], length = 0.1, angle = 25)
text(usr[2] - 0.2, 0.1, "x", adj = c(1, 0))
text(-0.1, usr[4], "y", adj = c(1, 1))
abline(h = 5, lty = 2, lwd = 0)
arrows(-0.8, 2.1, -0.8, 0, length = 0.1, angle = 25)
arrows(-0.8, 2.9, -0.8, 5, length = 0.1, angle = 25)
text(-0.8, 2.5, expression(phi[1]), adj = c(0.5, 0.5))
segments(2, 0, 2, 4.0, lty = 2, lwd = 0.75)
text(2, 4.0, expression(phi[2]), adj = c(0.5, 0))
segments(3, 5/(1+exp(-1)) + 0.025, 3, 4.0, lty = 2, lwd = 0.75)
arrows(2.3, 3.8, 2.0, 3.8, length = 0.08, angle = 25)
arrows(2.7, 3.8, 3.0, 3.8, length = 0.08, angle = 25)
text(2.5, 3.8, expression(phi[3]), adj = c(0.5, 0.5))

*

# Copié sur le manuel
xx <- seq(0, 5, len = 101)
yy <- 5 * xx/(1+xx)
par(mar = c(0, 0, 3.5, 0))
plot(xx, yy, type = "l", axes = FALSE, ylim = c(0,6), xlim = c(-1, 5),
     xlab = "", ylab = "", lwd = 2,
     main = "Parameters in the SSmicmen model")
usr <- par("usr")
arrows(usr[1], 0, usr[2], 0, length = 0.1, angle = 25)
arrows(0, usr[3], 0, usr[4], length = 0.1, angle = 25)
text(usr[2] - 0.2, 0.1, "x", adj = c(1, 0))
text(-0.1, usr[4], "y", adj = c(1, 1))
abline(h = 5, lty = 2, lwd = 0)
arrows(-0.8, 2.1, -0.8, 0, length = 0.1, angle = 25)
arrows(-0.8, 2.9, -0.8, 5, length = 0.1, angle = 25)
text(-0.8, 2.5, expression(phi[1]), adj = c(0.5, 0.5))
segments(1, 0, 1, 2.7, lty = 2, lwd = 0.75)
text(1, 2.7, expression(phi[2]), adj = c(0.5, 0))

*

Pour des modèles autres que ceux-ci, le gros problème est de trouver des valeurs initiales des paramètres, pas trop éloignées.

x <- runif(100)
w <- runif(100)
y <- x^2.4*w^3.2+rnorm(100,0,0.01)
# On utilise une régression linéaire pour avoir des valeurs approchées.
r1 <- lm(log(y) ~ log(x)+log(w)-1)
r2 <- nls(y~x^a*w^b,start=list(a=coef(r1)[1],b=coef(r1)[2]))

> coef(r1)
  log(x)   log(w)
1.500035 2.432708
> coef(r2)
       a        b
2.377525 3.185556

En faisant tous ces calculs, je constate que ces méthodes ne marchent pas toujours. Voici quelques exemples de messages d'erreur que j'ai obtenus. (Je ne pense pas que cela soit problématique avec des données réelles.) C'est pourquoi j'utilise une boucle avec la commande try dans les exemples précédents.

> r <- nls(y ~ SSfol(1,x,a,b,c))
Error in numericDeriv(form[[3]], names(ind), env) :
  Missing value or an Infinity produced when evaluating the model
In addition: Warning messages:
1: NaNs produced in: log(x)
2: NaNs produced in: log(x)

> r <- nls(y ~ SSfol(1,x,a,b,c))
Error in nls(resp ~ (Dose * (exp(-input * exp(lKe)) - exp(-input * exp(lKa))))/(exp(lKa) - :
  singular gradient

Error in nls(y ~ cbind(exp(-exp(lrc1) * x), exp(-exp(lrc2) * x)), data = xy,  :
  step factor 0.000488281 reduced below `minFactor' of 0.000976562

La comparaison des modèles non linéaires est un peu plus délicate. Il y a toutefois une fonction anova.nls.

library(nls)
?anova.nls

nlme

A FAIRE : Cette partie n'est pas encore écrite...

library(help=nlme)

(C'est par exemple là qu'il y a la fonction gls, pour les moindres
carrés généralisés.)

Je me contente de reprendre quelques exemples du manuel.

library(help=nlme)
library(nlme)

library(nlme)
data(Orthodont)
fm1 <- lme(distance ~ age, Orthodont, random = ~ age | Subject)
# standardized residuals versus fitted values by gender
plot(fm1, resid(., type = "p") ~ fitted(.) | Sex, abline = 0)

*

# box-plots of residuals by Subject
plot(fm1, Subject ~ resid(.))

*

# observed versus fitted values by Subject
plot(fm1, distance ~ fitted(.) | Subject, abline = c(0,1))

*

La bibliothèque nlme contient une fonction plot pour les modèles non
linéaires -- nous y reviendrons plus loin.

A FAIRE

A CLASSER

Comparons ces régressions

# library(lqs)   # now part of MASS
n <- 100
x <- rnorm(n)
y <- 1 - 2*x + rnorm(n)
y[ sample(1:n, floor(n/4)) ] <- 7
plot(y~x)
abline(1,-2,lty=3)
r1 <- lm(y~x)
r2 <- lqs(y~x, method='lts')
r3 <- lqs(y~x, method='lqs')
r4 <- lqs(y~x, method='lms')
r5 <- lqs(y~x, method='S')
abline(r1, col='red')
abline(r2, col='green')
abline(r3, col='blue')
abline(r4, col='orange')
abline(r5, col='purple')
legend( par("usr")[1], par("usr")[3], yjust=0,
        c("Régression linéaire", "lts (régression élaguée)",
        "lqs", "lms", "S"),
        lty=1, lwd=1,
        col=c("red", "green", "blue", "orange", "purple") )
title("Moindres carrés élagués et variantes")

*

Autres régressions linéaires robustes ou résistantes

On dit qu'une régression est résistante si elle est peu sensible à des observations atypiques ou aberrantes. On dit qu'elle est robuste si elle reste toujours valable quand ses hypothèses (données normales) ne sont plus vérifiées.

Nous en avons déjà vu quelques exemples, comme la régression élaguée (lqs) ou la régression de Huber (rlm). En voici quelques autres -- que je ne détaille pas.

A FAIRE : modifier les exemples suivants pour avoir une comparaison
des différentes régressions, dans différentes situations (une
observation atypique, plusieurs observations atypiques, prédicteur
non normal, erreurs non normales).

A FAIRE : idem avec les régressions non linéaires

# Exemple
x <- rnorm(20)
y <- 1 - x + rnorm(20)
x <- c(x,10)
y <- c(y,1)
plot(y~x)
abline(lm(y~x), col='blue')
title(main="Régression classique")

*

# How bad usual regression is
plot(lm(y~x), which=4, 
     main="Distances de Cook pour la régression classique")

*

plot(y~x)
for (i in 1:length(x))
  abline(lm(y~x, subset= (1:length(x))!=i ), col='red')
title(main="Régression classique après avoir enlevé un point")

*

# line (in the eda library)
library(eda)
plot(y~x)
abline(1,-1, lty=3)
abline(lm(y~x))
abline(coef(line(x,y)), col='red')
title(main='Regression avec "line", dans la bibliothèque "eda"')

*

# Régression élaguée
#library(lqs)
plot(y~x)
abline(1,-1, lty=3)
abline(lm(y~x))
abline(lqs(y~x), col='red')
title(main='Moindres carrés élagués (lqs)')

*

# glm (d'après le manuel, il s'agit bien des IWLS,
# mais on obtient exactement le même résultat...)
plot(y~x)
abline(1,-1, lty=3)
abline(lm(y~x))
abline(glm(y~x), col='red')
title(main='Régression avec "glm"')

*

plot(y~x)
abline(1,-1, lty=3)
abline(lm(y~x))
abline(rlm(y~x, psi = psi.bisquare, init = "lts"), col='orange',lwd=3)
abline(rlm(y~x), col='red')
abline(rlm(y~x, psi = psi.hampel, init = "lts"), col='green')
abline(rlm(y~x, psi = psi.bisquare), col='blue')
title(main='Régression de Huber (rlm)')
%--

Voici comment on utilise généralement la régression robuste. On commence par faire une régression classique et une régression robuste. Si les deux diffèrent, c'est mauvais signe...

A FAIRE

Quantile regression
library(quantreg)
?rq
rq(y~x, tau=.5) # Median regression
?table.rq
http://www.econ.uiuc.edu/~econ472/tutorial15.html

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