Ajustez une régression linéaire simple
Jusqu’ici, nous avons étudié une variable à la fois. Mais la question la plus fréquente en data science porte sur un lien : comment le loyer dépend-il de la surface ? La distance de freinage de la vitesse ? Le nombre de lettres à trier du poids du courrier ?
La régression linéaire simple modélise ce lien par une droite, tout en tenant compte du bruit. Elle réutilise tout ce que vous avez vu : estimation, lois de Student et de Fisher, intervalles et tests. Notre fil rouge : le loyer d’appartements rennais en fonction de leur surface.
Le fil rouge et le modèle
Section intitulée « Le fil rouge et le modèle »Voici 10 annonces de T1 et T2 dans un même quartier :
| Surface (m²) | 18 | 25 | 30 | 34 | 40 | 45 | 52 | 60 | 68 | 75 |
|---|---|---|---|---|---|---|---|---|---|---|
| Loyer (€) | 430 | 480 | 590 | 560 | 700 | 660 | 810 | 820 | 990 | 980 |
Le nuage de points monte clairement, à peu près en ligne droite. On pose le modèle de régression linéaire simple :
- est la variable à expliquer (ou réponse), aléatoire : le loyer ;
- est la variable explicative, considérée comme fixée : la surface ;
- (ordonnée à l’origine) et (pente) sont les paramètres inconnus ;
- est l’erreur : tout ce que la surface n’explique pas (étage, état, luminosité…).
Les hypothèses portent sur les erreurs :
- : le modèle est correct en moyenne (la relation est bien linéaire) ;
- pour tout : la variance est constante, on parle d’homoscédasticité ;
- les sont non corrélées (indépendantes) ;
- pour les tests et intervalles, les sont gaussiennes : .
Estimer la droite par les moindres carrés
Section intitulée « Estimer la droite par les moindres carrés »Quelle droite choisir ? On cherche et qui minimisent la somme des carrés des écarts verticaux entre les points et la droite :
En annulant les deux dérivées partielles, on obtient les estimateurs des moindres carrés :
La droite passe donc toujours par le point moyen . Sous l’hypothèse gaussienne, ces estimateurs coïncident avec ceux du maximum de vraisemblance (chapitre 3) : maximiser la vraisemblance gaussienne revient à minimiser la somme des carrés.
Les estimateurs et sont sans biais, et la variance de la pente vaut
La pente est d’autant mieux estimée que les sont étalés : pour étudier l’effet de la surface, mieux vaut des studios et des grands T2 que dix appartements de 40 m².
Résidus et variance résiduelle
Section intitulée « Résidus et variance résiduelle »Les valeurs ajustées sont , et les résidus (leur somme est nulle). La variance des erreurs s’estime sans biais par
où est la somme des carrés résiduelle. On divise par car on a estimé deux paramètres : c’est le même phénomène que le de la variance corrigée. De plus, .
Ici, , donc et € : à surface égale, les loyers fluctuent d’une quarantaine d’euros autour de la droite.
Tester l’effet de la variable explicative
Section intitulée « Tester l’effet de la variable explicative »La surface a-t-elle vraiment un effet sur le loyer ? On teste (pas de lien linéaire) contre . En remplaçant par dans l’écart-type de , on obtient l’erreur-type , et
Ici, et , à comparer à . On rejette très largement (p-valeur de l’ordre de ). L’IC à 95 % de la pente vaut , soit environ €/m².
La décomposition de la variabilité et le R²
Section intitulée « La décomposition de la variabilité et le R² »La variabilité totale des loyers se décompose en une part expliquée par la droite et une part résiduelle :
est la somme des carrés totale, celle du modèle (expliquée), la résiduelle. Le coefficient de détermination mesure la part expliquée :
Il est compris entre 0 et 1 et, en régression simple, il vaut le carré du coefficient de corrélation linéaire entre et . Ici, : la surface explique 96 % de la variabilité des loyers de l’échantillon.
Le test de Fisher global compare la part expliquée à la part résiduelle :
Ici : en régression simple, ce test est identique au test de Student sur la pente (le carré d’une est une ).
logements <- data.frame( surface = c(18, 25, 30, 34, 40, 45, 52, 60, 68, 75), loyer = c(430, 480, 590, 560, 700, 660, 810, 820, 990, 980))reg <- lm(loyer ~ surface, data = logements)summary(reg) # coefficients, erreurs-types, t, p-valeurs, sigma, R², Fconfint(reg) # IC à 95 % des coefficientsimport pandas as pdimport statsmodels.formula.api as smf
logements = pd.DataFrame({ "surface": [18, 25, 30, 34, 40, 45, 52, 60, 68, 75], "loyer": [430, 480, 590, 560, 700, 660, 810, 820, 990, 980],})reg = smf.ols("loyer ~ surface", data=logements).fit()print(reg.summary()) # même tableau que summary() en RPrévoir : intervalle de confiance ou intervalle de prévision ?
Section intitulée « Prévoir : intervalle de confiance ou intervalle de prévision ? »Pour un appartement de m², la droite prévoit €. Mais cette prévision peut répondre à deux questions différentes.
Le loyer moyen de tous les 40 m² du quartier, : c’est un paramètre, et son IC tient compte de l’incertitude sur la droite seulement :
Le loyer d’un appartement précis de 40 m², : il faut ajouter la variabilité propre de cet appartement, d’où un « » sous la racine. C’est l’intervalle de prévision :
nouveau <- data.frame(surface = 40)predict(reg, nouveau, interval = "confidence") # loyer moyenpredict(reg, nouveau, interval = "prediction") # loyer d'un appartementValider le modèle : regarder les résidus
Section intitulée « Valider le modèle : regarder les résidus »Toutes ces conclusions reposent sur les hypothèses du modèle. On les vérifie graphiquement sur les résidus.
- Résidus en fonction des valeurs ajustées : le nuage doit être sans structure, centré sur 0, d’épaisseur constante. On trace souvent les droites : environ 95 % des résidus doivent être entre les deux.
- Une courbure (résidus positifs aux extrémités, négatifs au milieu) signale que la relation n’est pas linéaire.
- Un entonnoir (résidus de plus en plus dispersés) signale une variance non constante.
- Une droite de Henry (QQ-plot) des résidus vérifie la normalité.
Pour nos loyers, les résidus vont de à €, tous dans la bande €, sans forme particulière.
Quand un graphique révèle un problème, on transforme les variables : ou stabilisent une variance qui croît avec la moyenne ; ou capturent une courbure. Le modèle reste « linéaire » au sens où il est linéaire en les paramètres. En TP, nous avons ainsi étudié la distance de freinage d’un véhicule en fonction de sa vitesse : la physique (l’énergie cinétique) suggère une distance proportionnelle au carré de la vitesse, et c’est le graphe des résidus qui permet de juger si une simple droite suffit ou s’il faut régresser sur ce carré.
En résumé
Section intitulée « En résumé »- Modèle : , erreurs centrées, de variance constante, non corrélées, gaussiennes pour l’inférence.
- Moindres carrés : et ; .
- Test de la pente : ; en simple, il équivaut au test de Fisher global.
- et , égal à .
- Intervalle de prévision plus large que l’IC de la moyenne ; les deux s’élargissent loin de .
- Toujours tracer les résidus ; en cas de courbure ou d’entonnoir, transformer les variables.
Exercices
Section intitulée « Exercices »Exercice · Calculer une droite de régression
facileSur 5 jours, un glacier relève la température maximale (°C) et ses ventes (centaines de cornets) : (20 ; 3), (22 ; 4), (25 ; 6), (28 ; 7), (30 ; 10). Calculez et , puis la vente prévue à 26 °C.
Voir la solution
et . Les écarts valent et les écarts valent .
- ;
- .
Donc centaine de cornets par degré, et . À 26 °C : , soit environ 665 cornets.
En code
import pandas as pdimport statsmodels.formula.api as smf
glaces = pd.DataFrame({"temperature": [20, 22, 25, 28, 30], "ventes": [3, 4, 6, 7, 10]})reg = smf.ols("ventes ~ temperature", data=glaces).fit()print(reg.params) # Intercept -10.18, temperature 0.647print(reg.predict(pd.DataFrame({"temperature": [26]}))) # 6.65 centaines de cornetsglaces <- data.frame(temperature = c(20, 22, 25, 28, 30), ventes = c(3, 4, 6, 7, 10))reg <- lm(ventes ~ temperature, data = glaces)coef(reg) # (Intercept) -10.18, temperature 0.647predict(reg, newdata = data.frame(temperature = 26)) # 6.65 centaines de cornetsExercice · Lire une sortie de R
moyenUne régression du temps de trajet (minutes) sur la distance (km), sur trajets, donne : pente estimée 1,85 avec une erreur-type de 0,40, et .
- La distance a-t-elle un effet significatif au niveau 5 % () ?
- Que vaut la statistique du test de Fisher global ?
- Que penser du modèle ?
Voir la solution
- : on rejette , l’effet est significatif.
- En régression simple, , à comparer à une loi . On peut le retrouver par (l’écart vient des arrondis).
- L’effet de la distance est net (chaque kilomètre ajoute environ 1,85 minute), mais la distance n’explique que 46 % de la variabilité : d’autres facteurs (trafic, heure, mode de transport) comptent beaucoup. Significatif ne veut pas dire suffisant pour prévoir précisément un trajet.
En code
from scipy import stats
n, pente, erreur_type, R2 = 27, 1.85, 0.40, 0.46t0 = pente / erreur_typeprint(t0, stats.t.ppf(0.975, n - 2), 2 * stats.t.sf(t0, n - 2)) # 4.625 > 2.060, p = 0.0001
F_par_t = t0**2 # F = t^2 en régression simpleF_par_R2 = (R2 / 1) / ((1 - R2) / (n - 2)) # à partir du R²print(F_par_t, F_par_R2, stats.f.sf(F_par_t, 1, n - 2)) # 21.4 21.3 p = 0.0001n <- 27; pente <- 1.85; erreur_type <- 0.40; R2 <- 0.46t0 <- pente / erreur_typet0; qt(0.975, n - 2); 2 * pt(-t0, n - 2) # 4.625 > 2.060, p = 0.0001
F_par_t <- t0^2 # F = t^2 en régression simpleF_par_R2 <- (R2 / 1) / ((1 - R2) / (n - 2)) # à partir du R²F_par_t; F_par_R2; 1 - pf(F_par_t, 1, n - 2) # 21.4 21.3 p = 0.0001Exercice · Linéariser un modèle exponentiel
difficileLe nombre d’utilisateurs d’une application semble croître exponentiellement : . Comment estimer et par une régression linéaire simple ? Quelle hypothèse cela suppose-t-il sur les erreurs ?
Voir la solution
En passant au logarithme : . On régresse donc sur : le modèle donne et .
Cela suppose que les erreurs sont additives et de variance constante sur l’échelle logarithmique, donc multiplicatives sur l’échelle d’origine : . Autrement dit, l’écart typique est un pourcentage constant du nombre d’utilisateurs, ce qui est souvent réaliste pour une croissance. On le vérifie sur le graphe des résidus du modèle en logarithme.
Attention : estime la médiane de plutôt que sa moyenne, car l’exponentielle d’une moyenne n’est pas la moyenne de l’exponentielle.
En code
import numpy as npimport pandas as pdimport statsmodels.formula.api as smf
# Croissance simulée avec erreur multiplicative : U = a exp(b t) exp(eps)rng = np.random.default_rng(0)a, b = 500, 0.15semaines = np.arange(1, 31)U = a * np.exp(b * semaines) * np.exp(rng.normal(0, 0.2, size=30))donnees = pd.DataFrame({"t": semaines, "U": U})
reg = smf.ols("np.log(U) ~ t", data=donnees).fit() # régression sur le logprint(np.exp(reg.params["Intercept"]), reg.params["t"]) # a_hat = 493, b_hat = 0.149 (vrais : 500 et 0.15)
# exp(prévision) estime la médiane ; la moyenne vaut médiane * exp(sigma^2 / 2)print(np.exp(0.2**2 / 2)) # 1.020 : écart de 2 %# Croissance simulée avec erreur multiplicative : U = a exp(b t) exp(eps)set.seed(0)a <- 500; b <- 0.15semaines <- 1:30U <- a * exp(b * semaines) * exp(rnorm(30, 0, 0.2))
reg <- lm(log(U) ~ semaines) # régression sur le logexp(coef(reg)[1]); coef(reg)[2] # a_hat = 531, b_hat = 0.146 (vrais : 500 et 0.15)plot(fitted(reg), resid(reg)) # résidus : pas de structure attendue
# exp(prévision) estime la médiane ; la moyenne vaut médiane * exp(sigma^2 / 2)exp(0.2^2 / 2) # 1.020 : écart de 2 %