Passez à la régression linéaire multiple
La surface explique bien le loyer d’un appartement, mais pas tout : à surface égale, un logement près du centre se loue plus cher. Et l’étage ? Et la présence d’un balcon ? Pour tenir compte de plusieurs facteurs à la fois, on passe à la régression linéaire multiple.
Le principe est le même qu’en régression simple, mais deux questions nouvelles apparaissent : comment interpréter un coefficient quand les variables sont liées entre elles, et comment choisir les variables à garder. Notre fil rouge : 12 appartements rennais décrits par trois variables.
Le fil rouge et le modèle
Section intitulée « Le fil rouge et le modèle »| Surface (m²) | 18 | 25 | 30 | 34 | 40 | 45 | 52 | 60 | 68 | 75 | 28 | 55 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Distance au centre (km) | 0,5 | 2,0 | 1,0 | 3,5 | 1,5 | 4,0 | 2,5 | 1,0 | 3,0 | 5,0 | 4,5 | 0,8 |
| Étage | 3 | 1 | 4 | 0 | 2 | 5 | 1 | 3 | 0 | 2 | 4 | 1 |
| Loyer (€) | 470 | 470 | 600 | 500 | 700 | 610 | 790 | 900 | 930 | 900 | 450 | 880 |
Le modèle de régression linéaire multiple à variables explicatives s’écrit
avec les mêmes hypothèses qu’en régression simple. Ici, : la surface , la distance et l’étage .
L’écriture matricielle
Section intitulée « L’écriture matricielle »Avec plusieurs variables, les formules deviennent lisibles sous forme matricielle :
- est le vecteur des réponses (les loyers) ;
- est la matrice du plan d’expérience, de taille : une première colonne de 1 (pour la constante ), puis une colonne par variable explicative ;
- est le vecteur des paramètres ;
- est le vecteur des erreurs, désignant la matrice identité.
Minimiser conduit aux équations normales , d’où, si est inversible :
Cet estimateur est sans biais et suit la loi : les éléments diagonaux de donnent les variances des coefficients. La variance des erreurs s’estime par
car on a estimé paramètres. En TP, nous avons d’ailleurs calculé « à la main » avec solve() et t() en R, puis vérifié que lm() donnait les mêmes coefficients.
logements <- data.frame( surface = c(18, 25, 30, 34, 40, 45, 52, 60, 68, 75, 28, 55), distance = c(0.5, 2.0, 1.0, 3.5, 1.5, 4.0, 2.5, 1.0, 3.0, 5.0, 4.5, 0.8), etage = c(3, 1, 4, 0, 2, 5, 1, 3, 0, 2, 4, 1), loyer = c(470, 470, 600, 500, 700, 610, 790, 900, 930, 900, 450, 880))X <- cbind(1, as.matrix(logements[, c("surface", "distance", "etage")]))y <- logements$loyersolve(t(X) %*% X) %*% t(X) %*% y # beta chapeau, calculé à la main
complet <- lm(loyer ~ surface + distance + etage, data = logements)summary(complet) # mêmes coefficientsLire et tester les coefficients
Section intitulée « Lire et tester les coefficients »La sortie de summary(complet) donne :
| Coefficient | Estimation | Erreur-type | p-valeur | |
|---|---|---|---|---|
| Constante | 299,6 | 23,9 | 12,53 | |
| Surface | 11,01 | 0,43 | 25,72 | |
| Distance | 4,78 | |||
| Étage | 1,34 | 4,49 | 0,30 | 0,77 |
avec €, et sur degrés de liberté.
Interpréter « toutes choses égales par ailleurs »
Section intitulée « Interpréter « toutes choses égales par ailleurs » »Le coefficient de la distance, , se lit ainsi : à surface et étage égaux, chaque kilomètre supplémentaire depuis le centre fait baisser le loyer de 43 € en moyenne. C’est un effet partiel, qui neutralise les autres variables du modèle.
Cette nuance est essentielle. Regardez la distance seule : sa corrélation avec le loyer vaut , et la régression simple du loyer sur la distance donne une pente non significative (p-valeur 0,91). Pourtant, dans le modèle multiple, son effet est net. Pourquoi ? Parce que dans cet échantillon, les grands appartements sont plutôt loin du centre (corrélation de 0,30 entre surface et distance) : l’effet positif de la surface masque l’effet négatif de la distance tant qu’on ne les sépare pas.
Le test de Student sur chaque coefficient
Section intitulée « Le test de Student sur chaque coefficient »Pour chaque , on teste (« la variable n’apporte rien, les autres étant dans le modèle ») par
où est l’erreur-type, racine du -ème élément diagonal de . Ici, avec degrés de liberté, la surface et la distance sont très significatives, l’étage non (p-valeur 0,77).
Le test de Fisher global
Section intitulée « Le test de Fisher global »Le modèle sert-il à quelque chose ? On teste (aucune variable n’a d’effet) contre : au moins un est non nul :
Ici pour un seuil : on rejette très largement. C’est la dernière ligne de summary().
Comparer des modèles : R² ajusté et AIC
Section intitulée « Comparer des modèles : R² ajusté et AIC »Faut-il garder l’étage ? Le ne peut pas trancher : il augmente toujours quand on ajoute une variable, même inutile, car la somme des carrés résiduelle ne peut que diminuer. Il faut des critères qui pénalisent la complexité.
Le ajusté corrige le par les degrés de liberté :
Il peut baisser quand on ajoute une variable inutile.
Le critère d’information d’Akaike (AIC) vaut , où est la vraisemblance maximale et le nombre de paramètres. Il récompense l’ajustement (premier terme) et pénalise la complexité (second terme) : plus il est petit, mieux c’est. Pour un modèle linéaire gaussien, R l’évalue à une constante près par (fonction extractAIC).
| Modèle | AIC | ||
|---|---|---|---|
| surface | 0,878 | 0,866 | 103,65 |
| surface + distance | 0,9891 | 0,9867 | 76,65 |
| surface + distance + étage | 0,9892 | 0,9852 | 78,51 |
Le préfère le modèle complet, de très peu. Le ajusté et l’AIC préfèrent tous deux le modèle sans l’étage.
On peut aussi comparer deux modèles emboîtés par un test de Fisher partiel : anova(petit, grand) teste si les variables ajoutées réduisent significativement la . Pour l’étage, on trouve , soit exactement le carré du de la table : retirer une seule variable, c’est la même chose que son test de Student.
Sélectionner les variables avec step
Section intitulée « Sélectionner les variables avec step »Avec 3 variables, on peut comparer les sous-modèles (recherche exhaustive). Avec 30, il y en a plus d’un milliard. On utilise alors des procédures pas à pas :
- descendante (backward) : on part du modèle complet et on retire, à chaque étape, la variable dont le retrait diminue le plus l’AIC, tant que c’est possible ;
- ascendante (forward) : on part du modèle vide et on ajoute la variable qui diminue le plus l’AIC ;
- pas à pas (both) : à chaque étape, on autorise ajouts et retraits.
retenu <- step(complet, direction = "backward") # affiche chaque étape et l'AICsummary(retenu)extractAIC(complet); extractAIC(retenu)predict(retenu, data.frame(surface = 40, distance = 2), interval = "prediction")import pandas as pdimport statsmodels.formula.api as smf
logements = pd.DataFrame({ "surface": [18, 25, 30, 34, 40, 45, 52, 60, 68, 75, 28, 55], "distance": [0.5, 2.0, 1.0, 3.5, 1.5, 4.0, 2.5, 1.0, 3.0, 5.0, 4.5, 0.8], "etage": [3, 1, 4, 0, 2, 5, 1, 3, 0, 2, 4, 1], "loyer": [470, 470, 600, 500, 700, 610, 790, 900, 930, 900, 450, 880],})for formule in ["loyer ~ surface + distance + etage", "loyer ~ surface + distance"]: m = smf.ols(formule, data=logements).fit() print(formule, round(m.rsquared_adj, 4), round(m.aic, 2)) # AIC complet (constante différente de R)En TP, nous avons suivi cette démarche sur un jeu de données marketing : les ventes d’un produit en fonction des budgets publicitaires à la télévision, à la radio et dans les journaux. Le travail consistait à tester chaque média, à ne garder que ceux qui ont un effet, puis à conseiller le client sur le média où investir, avec un intervalle de prévision. Sur les données laitières, step en mode descendant servait à simplifier le modèle complet, et l’AIC à départager les candidats.
En résumé
Section intitulée « En résumé »- Modèle , de taille avec une colonne de 1 ; , .
- Un coefficient mesure un effet partiel ; une variable peut sembler inutile seule et être essentielle avec les autres, ou l’inverse.
- Test de Student par coefficient (), test de Fisher global (), test de Fisher partiel pour des modèles emboîtés.
- Le augmente toujours avec : on compare avec le ajusté ou l’AIC (plus petit = meilleur).
- Sélection exhaustive, descendante, ascendante ou pas à pas (
stepen R).
Exercices
Section intitulée « Exercices »Exercice · Construire la matrice X
facileOn modélise la consommation d’une voiture (L/100 km) par sa masse (tonnes) et sa puissance (ch) sur 4 voitures : (1,2 ; 90 ; 6,1), (1,5 ; 120 ; 7,0), (1,1 ; 75 ; 5,4), (1,8 ; 150 ; 8,3). Écrivez et , et donnez la taille de . Combien de degrés de liberté reste-t-il pour estimer ?
Voir la solution
est de taille , donc est une matrice carrée . Il reste degré de liberté pour estimer : c’est bien trop peu pour une inférence sérieuse. Il faudrait beaucoup plus de voitures.
En code
import numpy as np
masse = np.array([1.2, 1.5, 1.1, 1.8])puissance = np.array([90, 120, 75, 150])Y = np.array([6.1, 7.0, 5.4, 8.3])
X = np.column_stack([np.ones(4), masse, puissance]) # colonne de 1, puis x1 et x2print(X.shape, (X.T @ X).shape) # (4, 3) (3, 3)n, p = X.shape[0], X.shape[1] - 1print(n - p - 1) # 1 degré de liberté résiduelprint(np.linalg.solve(X.T @ X, X.T @ Y)) # 3.23 -1.67 0.053 : masse négative, estimation très fragilevoitures <- data.frame(masse = c(1.2, 1.5, 1.1, 1.8), puissance = c(90, 120, 75, 150), conso = c(6.1, 7.0, 5.4, 8.3))X <- model.matrix(conso ~ masse + puissance, data = voitures) # colonne de 1 incluseY <- voitures$consoX # matrice 4 x 3dim(X); dim(t(X) %*% X) # 4 3 puis 3 3reg <- lm(conso ~ masse + puissance, data = voitures)df.residual(reg) # 1 degré de liberté résiduelcoef(reg) # 3.23 -1.67 0.053 : masse négative, estimation très fragileExercice · Choisir entre deux modèles
moyenSur observations, avec : le modèle A (2 variables) a ; le modèle B (5 variables, dont celles de A) a . Calculez le et le ajusté des deux modèles. Lequel préférer ?
Voir la solution
- Modèle A : ; .
- Modèle B : ; .
B a un meilleur , comme toujours avec plus de variables, mais un moins bon ajusté : les trois variables supplémentaires réduisent trop peu la pour justifier leur coût. On préfère A. Un test de Fisher partiel le confirmerait : , bien en dessous du seuil de (environ 2,8).
En code
from scipy import stats
n, SCT = 50, 1000modeles = {"A": (2, 400), "B": (5, 380)} # (p, SCR)for nom, (p, SCR) in modeles.items(): R2 = 1 - SCR / SCT R2_aj = 1 - (SCR / (n - p - 1)) / (SCT / (n - 1)) print(nom, R2, round(R2_aj, 3)) # A 0.6 0.583 ; B 0.62 0.577
# Test de Fisher partiel : les 3 variables de B en plus de AF = ((400 - 380) / 3) / (380 / 44)print(F, stats.f.ppf(0.95, 3, 44), stats.f.sf(F, 3, 44)) # 0.77 2.82 p = 0.52n <- 50; SCT <- 1000p <- c(A = 2, B = 5)SCR <- c(A = 400, B = 380)R2 <- 1 - SCR / SCTR2_aj <- 1 - (SCR / (n - p - 1)) / (SCT / (n - 1))R2; round(R2_aj, 3) # A 0.60 0.583 ; B 0.62 0.577
# Test de Fisher partiel : les 3 variables de B en plus de AF <- ((400 - 380) / 3) / (380 / 44)F; qf(0.95, 3, 44); 1 - pf(F, 3, 44) # 0.77 2.82 p = 0.52Exercice · Un modèle non linéaire rendu linéaire
difficileUne loi physique relie la pression à la température par . Montrez qu’on peut estimer par une régression linéaire multiple, en précisant la réponse et les variables explicatives. Quelle précaution prendre si une température vaut 0 ?
Voir la solution
En passant au logarithme : . C’est un modèle linéaire en les paramètres :
avec la réponse , les deux variables explicatives et , et , , . On estime par lm(log(P) ~ log(T) + I(1/T)), puis .
Si une température vaut 0, ni ni ne sont définis : il faut soit retirer ce point, soit (si c’est justifié physiquement) travailler en kelvins, où la température est strictement positive. C’est exactement la situation d’un exercice de TP sur la tension de vapeur du mercure, où l’on contournait le problème en remplaçant la température nulle par une petite valeur positive.
En code
import numpy as npimport pandas as pdimport statsmodels.formula.api as smf
# Données simulées : P = 2 * T^0.8 * exp(-20 / T), bruit multiplicatifrng = np.random.default_rng(0)temp = np.linspace(5, 100, 25) # températures, toutes > 0P = 2 * temp**0.8 * np.exp(-20 / temp) * np.exp(rng.normal(0, 0.02, size=25))donnees = pd.DataFrame({"temp": temp, "P": P})
# Réponse log(P), variables explicatives log(T) et 1/Treg = smf.ols("np.log(P) ~ np.log(temp) + I(1 / temp)", data=donnees).fit()b0, b1, b2 = reg.paramsprint(np.exp(b0), b1, b2) # 1.96 0.804 -19.90 (vrais : 2, 0.8, -20)# Données simulées : P = 2 * T^0.8 * exp(-20 / T), bruit multiplicatifset.seed(0)temp <- seq(5, 100, length.out = 25) # températures, toutes > 0P <- 2 * temp^0.8 * exp(-20 / temp) * exp(rnorm(25, 0, 0.02))
# Réponse log(P), variables explicatives log(T) et 1/Treg <- lm(log(P) ~ log(temp) + I(1 / temp))b <- coef(reg)c(alpha1 = exp(b[[1]]), alpha2 = b[[2]], alpha3 = b[[3]])## alpha1 1.98, alpha2 0.801, alpha3 -19.84 (vrais : 2, 0.8, -20)