Aller au contenu

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.

Surface (m²)182530344045526068752855
Distance au centre (km)0,52,01,03,51,54,02,51,03,05,04,50,8
Étage314025130241
Loyer (€)470470600500700610790900930900450880

Le modèle de régression linéaire multiple à pp variables explicatives s’écrit

Yi=β0+β1xi1+β2xi2+⋯+βpxip+εi,εi i.i.d. N(0,σ2)Y_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \dots + \beta_p x_{ip} + \varepsilon_i, \qquad \varepsilon_i \text{ i.i.d. } \mathcal{N}(0, \sigma^2)

avec les mêmes hypothèses qu’en régression simple. Ici, p=3p = 3 : la surface x1x_1, la distance x2x_2 et l’étage x3x_3.

Avec plusieurs variables, les formules deviennent lisibles sous forme matricielle :

Y=Xβ+εY = X\beta + \varepsilon
  • YY est le vecteur des nn réponses (les loyers) ;
  • XX est la matrice du plan d’expérience, de taille n×(p+1)n \times (p+1) : une première colonne de 1 (pour la constante β0\beta_0), puis une colonne par variable explicative ;
  • β=(β0,…,βp)⊤\beta = (\beta_0, \dots, \beta_p)^\top est le vecteur des paramètres ;
  • ε∼Nn(0,σ2In)\varepsilon \sim \mathcal{N}_n(0, \sigma^2 I_n) est le vecteur des erreurs, InI_n désignant la matrice identité.

Minimiser ∥Y−Xβ∥2\lVert Y - X\beta \rVert^2 conduit aux équations normales X⊤Xβ^=X⊤YX^\top X \hat\beta = X^\top Y, d’où, si X⊤XX^\top X est inversible :

β^=(X⊤X)−1X⊤Y\hat\beta = (X^\top X)^{-1} X^\top Y

Cet estimateur est sans biais et suit la loi Np+1(β, σ2(X⊤X)−1)\mathcal{N}_{p+1}\left(\beta,\ \sigma^2 (X^\top X)^{-1}\right) : les éléments diagonaux de σ2(X⊤X)−1\sigma^2 (X^\top X)^{-1} donnent les variances des coefficients. La variance des erreurs s’estime par

σ^2=SCRn−p−1\hat\sigma^2 = \frac{SCR}{n - p - 1}

car on a estimé p+1p + 1 paramètres. En TP, nous avons d’ailleurs calculé (X⊤X)−1X⊤y(X^\top X)^{-1} X^\top y « à la main » avec solve() et t() en R, puis vérifié que lm() donnait les mêmes coefficients.

Y (les loyers)Ŷ = X β̂résidu Y − Ŷorthogonal au planensemble des Xβ(combinaisons des colonnes de X)
Les moindres carrés cherchent le point du plan des Xβ le plus proche de Y : c'est sa projection orthogonale. Les équations normales disent exactement que le résidu est orthogonal à chaque colonne de X.
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$loyer
solve(t(X) %*% X) %*% t(X) %*% y # beta chapeau, calculé à la main
complet <- lm(loyer ~ surface + distance + etage, data = logements)
summary(complet) # mêmes coefficients

La sortie de summary(complet) donne :

CoefficientEstimationErreur-typettp-valeur
Constante299,623,912,531,5×10−61{,}5 \times 10^{-6}
Surface11,010,4325,725,6×10−95{,}6 \times 10^{-9}
Distance−43,16-43{,}164,78−9,03-9{,}031,8×10−51{,}8 \times 10^{-5}
Étage1,344,490,300,77

avec σ^≈23,1\hat\sigma \approx 23{,}1 €, R2≈0,989R^2 \approx 0{,}989 et F≈244,5F \approx 244{,}5 sur (3,8)(3, 8) 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, −43,16-43{,}16, 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 −0,03-0{,}03, 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.

024distance au centre (km)4005006007008009001 000loyer (€)taille des points : surfacedistance seule : pente −4,3 (p = 0,91)024distance au centre (km)550600650700750800loyer corrigé de la surface (€)à surface égale : pente −42,95 €/km
Seule, la distance semble sans effet : les grands appartements (gros points) sont souvent loin du centre. Une fois l'effet de la surface retiré, la baisse de 43 € par kilomètre apparaît nettement.

Pour chaque jj, on teste H0:βj=0H_0 : \beta_j = 0 (« la variable xjx_j n’apporte rien, les autres étant dans le modèle ») par

Tj=β^jσ^β^j∼Tn−p−1 sous H0T_j = \frac{\hat\beta_j}{\hat\sigma_{\hat\beta_j}} \sim \mathcal{T}_{n-p-1} \text{ sous } H_0

où σ^β^j\hat\sigma_{\hat\beta_j} est l’erreur-type, racine du jj-ème élément diagonal de σ^2(X⊤X)−1\hat\sigma^2 (X^\top X)^{-1}. Ici, avec n−p−1=8n - p - 1 = 8 degrés de liberté, la surface et la distance sont très significatives, l’étage non (p-valeur 0,77).

Le modèle sert-il à quelque chose ? On teste H0:β1=⋯=βp=0H_0 : \beta_1 = \dots = \beta_p = 0 (aucune variable n’a d’effet) contre H1H_1 : au moins un βj\beta_j est non nul :

F0=SCM/pSCR/(n−p−1)∼Fp,n−p−1 sous H0F_0 = \frac{SCM / p}{SCR / (n - p - 1)} \sim \mathcal{F}_{p, n-p-1} \text{ sous } H_0

Ici F0≈244,5F_0 \approx 244{,}5 pour un seuil f3,8;0,95≈4,07f_{3,8;0,95} \approx 4{,}07 : on rejette très largement. C’est la dernière ligne de summary().

Faut-il garder l’étage ? Le R2R^2 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 R2R^2 ajusté corrige le R2R^2 par les degrés de liberté :

Raj2=1−SCR/(n−p−1)SCT/(n−1)R^2_{aj} = 1 - \frac{SCR / (n - p - 1)}{SCT / (n - 1)}

Il peut baisser quand on ajoute une variable inutile.

Le critère d’information d’Akaike (AIC) vaut −2ln⁡L^+2k-2 \ln \hat L + 2k, où L^\hat L est la vraisemblance maximale et kk 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 nln⁡(SCR/n)+2kn \ln(SCR/n) + 2k (fonction extractAIC).

ModèleR2R^2Raj2R^2_{aj}AIC
surface0,8780,866103,65
surface + distance0,98910,986776,65
surface + distance + étage0,98920,985278,51

Le R2R^2 préfère le modèle complet, de très peu. Le R2R^2 ajusté et l’AIC préfèrent tous deux le modèle sans l’étage.

surfacesurface+ distancecomplet(+ étage)0,860,880,900,920,940,960,981,00part de variabilité expliquée0,98910,98670,98920,9852R² : monte toujoursR² ajusté : redescendsurfacesurface+ distancecomplet(+ étage)60708090100110AIC (plus petit = meilleur)103,6576,6578,51
Ajouter l'étage fait encore monter le R² (de 0,0001), mais le R² ajusté baisse et l'AIC remonte : la variable coûte plus qu'elle n'apporte.

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 SCRSCR. Pour l’étage, on trouve F=0,089F = 0{,}089, soit exactement le carré du t=0,30t = 0{,}30 de la table : retirer une seule variable, c’est la même chose que son test de Student.

Avec 3 variables, on peut comparer les 23=82^3 = 8 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.
modèle completAIC = 78,51retirer l’étageAIC = 76,65retirer la distanceAIC = 105,50retirer la surfaceAIC = 129,64surface + distanceAIC = 76,65retirer la distanceAIC = 103,65retirer la surfaceAIC = 128,85arrêt :aucun retraitn’amélioreétape 1 : 3 retraits testésétape 2 : 2 retraits testés
À chaque étape, step essaie tous les retraits possibles et garde celui qui fait le plus baisser l'AIC (en vert) ; il s'arrête quand tous les retraits le font monter (en rose).
retenu <- step(complet, direction = "backward") # affiche chaque étape et l'AIC
summary(retenu)
extractAIC(complet); extractAIC(retenu)
predict(retenu, data.frame(surface = 40, distance = 2), interval = "prediction")
import pandas as pd
import 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.

  • Modèle Y=Xβ+εY = X\beta + \varepsilon, XX de taille n×(p+1)n \times (p+1) avec une colonne de 1 ; β^=(X⊤X)−1X⊤Y\hat\beta = (X^\top X)^{-1}X^\top Y, σ^2=SCR/(n−p−1)\hat\sigma^2 = SCR/(n - p - 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 (Tn−p−1\mathcal{T}_{n-p-1}), test de Fisher global (Fp,n−p−1\mathcal{F}_{p, n-p-1}), test de Fisher partiel pour des modèles emboîtés.
  • Le R2R^2 augmente toujours avec pp : on compare avec le R2R^2 ajusté ou l’AIC (plus petit = meilleur).
  • Sélection exhaustive, descendante, ascendante ou pas à pas (step en R).

Exercice · Construire la matrice X

facile

On modélise la consommation yy d’une voiture (L/100 km) par sa masse x1x_1 (tonnes) et sa puissance x2x_2 (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 XX et YY, et donnez la taille de X⊤XX^\top X. Combien de degrés de liberté reste-t-il pour estimer σ2\sigma^2 ?

Voir la solution
X=(11,29011,512011,17511,8150),Y=(6,17,05,48,3)X = \begin{pmatrix} 1 & 1{,}2 & 90 \\ 1 & 1{,}5 & 120 \\ 1 & 1{,}1 & 75 \\ 1 & 1{,}8 & 150 \end{pmatrix}, \qquad Y = \begin{pmatrix} 6{,}1 \\ 7{,}0 \\ 5{,}4 \\ 8{,}3 \end{pmatrix}

XX est de taille 4×34 \times 3, donc X⊤XX^\top X est une matrice carrée 3×33 \times 3. Il reste n−p−1=4−2−1=1n - p - 1 = 4 - 2 - 1 = 1 degré de liberté pour estimer σ2\sigma^2 : 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 x2
print(X.shape, (X.T @ X).shape) # (4, 3) (3, 3)
n, p = X.shape[0], X.shape[1] - 1
print(n - p - 1) # 1 degré de liberté résiduel
print(np.linalg.solve(X.T @ X, X.T @ Y)) # 3.23 -1.67 0.053 : masse négative, estimation très fragile

Exercice · Choisir entre deux modèles

moyen

Sur n=50n = 50 observations, avec SCT=1 000SCT = 1\,000 : le modèle A (2 variables) a SCR=400SCR = 400 ; le modèle B (5 variables, dont celles de A) a SCR=380SCR = 380. Calculez le R2R^2 et le R2R^2 ajusté des deux modèles. Lequel préférer ?

Voir la solution
  • Modèle A : R2=1−400/1 000=0,60R^2 = 1 - 400/1\,000 = 0{,}60 ; Raj2=1−400/471 000/49=1−8,51120,408≈0,583R^2_{aj} = 1 - \frac{400/47}{1\,000/49} = 1 - \frac{8{,}511}{20{,}408} \approx 0{,}583.
  • Modèle B : R2=0,62R^2 = 0{,}62 ; Raj2=1−380/4420,408=1−8,63620,408≈0,577R^2_{aj} = 1 - \frac{380/44}{20{,}408} = 1 - \frac{8{,}636}{20{,}408} \approx 0{,}577.

B a un meilleur R2R^2, comme toujours avec plus de variables, mais un moins bon R2R^2 ajusté : les trois variables supplémentaires réduisent trop peu la SCRSCR pour justifier leur coût. On préfère A. Un test de Fisher partiel le confirmerait : F=(400−380)/3380/44≈0,77F = \frac{(400 - 380)/3}{380/44} \approx 0{,}77, bien en dessous du seuil de F3,44\mathcal{F}_{3,44} (environ 2,8).

En code

from scipy import stats
n, SCT = 50, 1000
modeles = {"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 A
F = ((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.52

Exercice · Un modèle non linéaire rendu linéaire

difficile

Une loi physique relie la pression PP à la température TT par P=α1Tα2eα3/TP = \alpha_1 T^{\alpha_2} e^{\alpha_3/T}. Montrez qu’on peut estimer α1,α2,α3\alpha_1, \alpha_2, \alpha_3 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 : ln⁡P=ln⁡α1+α2ln⁡T+α3⋅1T\ln P = \ln\alpha_1 + \alpha_2 \ln T + \alpha_3 \cdot \frac{1}{T}. C’est un modèle linéaire en les paramètres :

ln⁡Pi=β0+β1ln⁡Ti+β21Ti+εi\ln P_i = \beta_0 + \beta_1 \ln T_i + \beta_2 \frac{1}{T_i} + \varepsilon_i

avec la réponse ln⁡P\ln P, les deux variables explicatives ln⁡T\ln T et 1/T1/T, et β0=ln⁡α1\beta_0 = \ln\alpha_1, β1=α2\beta_1 = \alpha_2, β2=α3\beta_2 = \alpha_3. On estime par lm(log(P) ~ log(T) + I(1/T)), puis α^1=eβ^0\hat\alpha_1 = e^{\hat\beta_0}.

Si une température vaut 0, ni ln⁡T\ln T ni 1/T1/T 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 np
import pandas as pd
import statsmodels.formula.api as smf
# Données simulées : P = 2 * T^0.8 * exp(-20 / T), bruit multiplicatif
rng = np.random.default_rng(0)
temp = np.linspace(5, 100, 25) # températures, toutes > 0
P = 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/T
reg = smf.ols("np.log(P) ~ np.log(temp) + I(1 / temp)", data=donnees).fit()
b0, b1, b2 = reg.params
print(np.exp(b0), b1, b2) # 1.96 0.804 -19.90 (vrais : 2, 0.8, -20)