library(tidyverse)
library(AmesHousing)
library(broom)
library(performance)
library(scales)
library(patchwork)
theme_set(theme_minimal(base_size = 14))Tutoriel du jour 1
Expliquer et prédire le prix des propriétés d’Ames
Objectif
Ce tutoriel construit un modèle linéaire multiple du prix de vente de propriétés résidentielles. Il suit la même progression que la présentation et les trois missions:
- comprendre l’unité statistique et la réponse;
- choisir un petit ensemble de prédicteurs;
- comprendre comment les coefficients sont estimés;
- interpréter les coefficients et leur incertitude;
- produire une prédiction honnête;
- vérifier les hypothèses, l’influence et la sensibilité;
- comparer le prix brut et le prix logarithmique.
Les données décrivent 2 930 ventes à Ames, en Iowa (D. De Cock, 2011; M. Kuhn, 2020).
Carte théorique de la matinée
Une régression linéaire multiple ne se résume pas à la commande lm(). Elle relie cinq décisions qui doivent rester cohérentes:
| Décision | Question à résoudre |
|---|---|
| objectif | veut-on décrire, expliquer une association ou prédire? |
| représentation | quelle est l’unité statistique et comment coder les variables? |
| structure | quelle forme de moyenne conditionnelle paraît plausible? |
| estimation | comment choisir les coefficients et quantifier leur incertitude? |
| évaluation | quelles hypothèses et quelles données permettent de faire confiance au résultat? |
Le parcours commence par une association conditionnelle, puis examine une prédiction individuelle et les diagnostics. Une analyse causale demanderait une stratégie supplémentaire concernant le plan d’étude, les variables de confusion et les hypothèses d’identification.
Préparer l’environnement
Chaque paquet possède ici un rôle précis. AmesHousing fournit les données, tidyverse prépare et visualise, broom transforme les sorties du modèle en tableaux, performance aide au diagnostic et patchwork combine les graphiques.
Charger et simplifier les données
make_ames() fournit le prix de vente et 80 caractéristiques. Nous conservons quelques variables faciles à expliquer, tout en gardant deux extensions possibles.
ames_complet <- make_ames()
niveaux_qualite_attendus <- c(
"Very_Poor", "Poor", "Fair", "Below_Average", "Average",
"Above_Average", "Good", "Very_Good", "Excellent",
"Very_Excellent"
)
stopifnot(identical(
levels(ames_complet$Overall_Qual),
niveaux_qualite_attendus
))
ames <- ames_complet |>
transmute(
sale_price = Sale_Price,
gr_liv_area = Gr_Liv_Area,
overall_qual = as.integer(Overall_Qual),
year_built = Year_Built,
garage_cars = Garage_Cars,
full_bath = Full_Bath,
neighborhood = Neighborhood
)
dim(ames_complet)[1] 2930 81
glimpse(ames)Rows: 2,930
Columns: 7
$ sale_price <int> 215000, 105000, 172000, 244000, 189900, 195500, 213500, 1…
$ gr_liv_area <int> 1656, 896, 1329, 2110, 1629, 1604, 1338, 1280, 1616, 1804…
$ overall_qual <int> 6, 5, 6, 7, 5, 6, 8, 8, 8, 7, 6, 6, 6, 7, 8, 8, 8, 9, 4, …
$ year_built <int> 1960, 1961, 1958, 1968, 1997, 1998, 2001, 1992, 1995, 199…
$ garage_cars <dbl> 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, …
$ full_bath <int> 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 2, 1, 1, …
$ neighborhood <fct> North_Ames, North_Ames, North_Ames, North_Ames, Gilbert, …
Une ligne est une vente résidentielle. sale_price est en dollars américains et gr_liv_area est la surface habitable au-dessus du sol, en pieds carrés. overall_qual est un score ordinal de 1 à 10.
Le passage de Overall_Qual à un entier impose une hypothèse: une hausse d’un point possède le même sens moyen partout sur l’échelle. Cette simplification rend le coefficient lisible, mais elle doit être nommée.
Comprendre la réponse
ames |>
summarise(
ventes = n(),
prix_median = median(sale_price),
prix_moyen = mean(sale_price),
prix_min = min(sale_price),
prix_max = max(sale_price)
)# A tibble: 1 × 5
ventes prix_median prix_moyen prix_min prix_max
<int> <dbl> <dbl> <int> <int>
1 2930 160000 180796. 12789 755000
ames |>
ggplot(aes(sale_price)) +
geom_histogram(bins = 40, fill = "#0f7c80", color = "white") +
scale_x_continuous(labels = label_dollar()) +
labs(x = "Prix de vente", y = "Nombre de ventes")La distribution est asymétrique vers la droite. Cela n’interdit pas d’ajuster un modèle en dollars. La forme de la réponse n’est pas, à elle seule, une hypothèse de la régression. Elle invite cependant à examiner attentivement les résidus et à comparer une transformation logarithmique.
Explorer avant de modéliser
p_surface <- ames |>
ggplot(aes(gr_liv_area, sale_price)) +
geom_point(alpha = 0.25, color = "#277da1") +
geom_smooth(method = "lm", se = TRUE, color = "#b5222e") +
scale_y_continuous(labels = label_dollar()) +
labs(x = "Surface habitable, pieds carrés", y = "Prix de vente")
p_qualite <- ames |>
ggplot(aes(overall_qual, sale_price)) +
geom_jitter(width = 0.15, alpha = 0.2, color = "#0f7c80") +
geom_smooth(method = "lm", se = TRUE, color = "#b5222e") +
scale_y_continuous(labels = label_dollar()) +
labs(x = "Qualité globale, de 1 à 10", y = "Prix de vente")
p_surface + p_qualiteLes graphiques suggèrent des associations positives, mais montrent aussi quelques propriétés très grandes ou très coûteuses. Un graphique bivarié ne permet pas d’isoler l’association propre à chaque caractéristique. C’est précisément le rôle de la comparaison conditionnelle dans le modèle multiple.
Du modèle de population au modèle ajusté
Le modèle théorique décrit la moyenne du prix pour des propriétés possédant certaines caractéristiques:
\[ E(Y_i \mid X_i) = \beta_0 + \beta_1 X_{i1} + \cdots + \beta_p X_{ip}. \]
Les paramètres \(\beta_j\) appartiennent à une population conceptuelle et sont inconnus. Le modèle ajusté les remplace par des estimations \(\widehat{\beta}_j\) calculées à partir des ventes observées.
Pour chaque vente, le modèle produit une valeur ajustée \(\widehat{y}_i\) et un résidu:
\[ e_i = y_i - \widehat{y}_i. \]
Le résidu n’est pas nécessairement une erreur de saisie. Il représente la partie du prix que la moyenne conditionnelle du modèle n’explique pas pour cette vente.
Le principe des moindres carrés
La fonction lm() choisit les coefficients qui minimisent la somme des résidus au carré:
\[ \sum_{i=1}^{n}(y_i - \widehat{y}_i)^2. \]
Élever les résidus au carré empêche les erreurs positives et négatives de s’annuler et accorde davantage de poids aux grandes erreurs. Cette propriété rend aussi l’ajustement sensible aux observations extrêmes, ce qui motive l’analyse d’influence plus loin.
« Linéaire » signifie que la moyenne est linéaire dans les coefficients. Un modèle linéaire peut contenir un logarithme, un terme quadratique ou une interaction. Ces choix changent toutefois la forme et l’interprétation du modèle.
Ajuster le modèle commun
modele_prix <- lm(
sale_price ~ gr_liv_area + overall_qual + year_built + garage_cars,
data = ames
)
tidy(modele_prix, conf.int = TRUE)# A tibble: 5 × 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) -820149. 60058. -13.7 3.32e- 41 -937908. -702389.
2 gr_liv_area 57.3 1.82 31.5 4.59e-188 53.7 60.8
3 overall_qual 23960. 777. 30.8 3.85e-181 22437. 25483.
4 year_built 377. 31.6 11.9 4.17e- 32 315. 439.
5 garage_cars 14723. 1275. 11.5 3.43e- 30 12223. 17223.
glance(modele_prix)# A tibble: 1 × 12
r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0.765 0.765 38761. 2379. 0 4 -35111. 70234. 70270.
# ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
Le modèle s’écrit:
\[ Y_i = \beta_0 + \beta_1 S_i + \beta_2 Q_i + \beta_3 A_i + \beta_4 G_i + \varepsilon_i, \]
où \(Y_i\) est le prix, \(S_i\) la surface, \(Q_i\) la qualité globale, \(A_i\) l’année de construction et \(G_i\) la capacité du garage.
tidy() décrit les coefficients individuels. glance() résume l’ajustement entier. Le \(R^2\) mesure la proportion de variation observée expliquée dans ces données. Le \(R^2\) ajusté ajoute une pénalité liée au nombre de termes. Aucun des deux ne mesure directement l’erreur sur de nouvelles ventes.
Interpréter les coefficients
Les valeurs numériques sont calculées directement à partir du modèle.
coef_surface <- coef(modele_prix)[["gr_liv_area"]]
coef_qualite <- coef(modele_prix)[["overall_qual"]]
coef_annee <- coef(modele_prix)[["year_built"]]
coef_garage <- coef(modele_prix)[["garage_cars"]]À qualité, année de construction et capacité du garage identiques, 100 pieds carrés supplémentaires sont associés en moyenne à environ $5,727 de plus sur le prix de vente.
À surface, année de construction et capacité du garage identiques, un point supplémentaire de qualité globale est associé en moyenne à environ $23,960 de plus.
Ces phrases décrivent des associations conditionnelles. Elles ne démontrent pas qu’une intervention sur une caractéristique causerait exactement la variation estimée.
L’ordonnée à l’origine correspondrait notamment à une surface nulle et à une année de construction égale à zéro. Elle est nécessaire au calcul, mais n’a pas ici d’interprétation immobilière utile. Un centrage des variables pourrait lui donner une référence plus plausible.
Coefficient, intervalle et valeur p
Ces trois éléments répondent à des questions différentes:
- le coefficient donne la direction et la taille estimée dans les unités du modèle;
- l’intervalle de confiance décrit une plage de valeurs compatibles avec les données et les hypothèses;
- la valeur p évalue la compatibilité des données avec une hypothèse nulle, généralement \(\beta_j = 0\).
Une petite valeur p ne mesure ni la taille de l’association, ni son importance pratique, ni la qualité des prédictions. Avec 2 930 ventes, de petites associations peuvent être estimées avec précision. Il faut donc lire ensemble l’estimation, l’intervalle, les unités, le contexte et les diagnostics.
Le test d’un coefficient est conditionnel aux autres termes du modèle. Modifier la formule peut modifier l’estimation, son erreur-type et sa valeur p, même lorsque les données ne changent pas.
Vérifier la colinéarité
check_collinearity(modele_prix)# Check for Multicollinearity
Low Correlation
Term VIF VIF 95% CI adj. VIF Tolerance Tolerance 95% CI
gr_liv_area 1.64 [1.57, 1.73] 1.28 0.61 [0.58, 0.64]
overall_qual 2.34 [2.22, 2.48] 1.53 0.43 [0.40, 0.45]
year_built 1.78 [1.69, 1.88] 1.33 0.56 [0.53, 0.59]
garage_cars 1.84 [1.75, 1.94] 1.36 0.54 [0.52, 0.57]
Les facteurs d’inflation de la variance sont tous inférieurs à 2.3 dans ce modèle. Ils ne signalent pas une colinéarité sévère. Cette vérification ne garantit toutefois ni la bonne forme fonctionnelle ni l’absence de variables omises.
La colinéarité concerne surtout la capacité à isoler des coefficients. Deux prédicteurs très redondants peuvent produire une bonne prédiction globale tout en donnant des coefficients individuels instables et des intervalles larges.
Hypothèses et portée des conclusions
Les hypothèses ne sont pas une liste à cocher mécaniquement. Chacune protège certaines conclusions:
| Hypothèse ou condition | Pourquoi elle compte | Comment l’examiner |
|---|---|---|
| moyenne correctement structurée | évite des résidus systématiques dans certaines zones | résidus contre valeurs ajustées et connaissance du domaine |
| variance conditionnelle à peu près constante | soutient les erreurs-types et intervalles usuels | étalement vertical des résidus |
| erreurs indépendantes | évite de compter plusieurs fois la même information | plan de collecte, ordre temporel, proximité spatiale ou groupes |
| erreurs compatibles avec l’inférence utilisée | soutient les queues des intervalles | diagramme quantile-quantile et résidus extrêmes |
| absence de colinéarité parfaite | permet d’isoler les coefficients | structure des variables, matrice du modèle et VIF |
| absence de domination individuelle | soutient la stabilité de la conclusion | levier, distance de Cook et sensibilité |
La normalité ne concerne pas la distribution brute de sale_price. Elle concerne les erreurs conditionnelles autour de la moyenne du modèle. Les estimations par moindres carrés peuvent être calculées sans normalité, mais l’inférence usuelle est plus fragile lorsque les queues s’éloignent fortement du modèle supposé.
L’indépendance ne se lit pas uniquement dans un graphique. Des ventes provenant d’un même quartier ou d’une même période peuvent partager des caractéristiques non représentées. Le plan de collecte et la population visée font donc partie du diagnostic.
Confiance et prédiction
Considérons une propriété de 1 500 pieds carrés, de qualité 6 sur 10, construite en 2000 et dotée de deux places de garage.
propriete_type <- tibble(
gr_liv_area = 1500,
overall_qual = 6L,
year_built = 2000L,
garage_cars = 2
)
intervalle_moyenne <- predict(
modele_prix,
propriete_type,
interval = "confidence"
)
intervalle_individuel <- predict(
modele_prix,
propriete_type,
interval = "prediction"
)
intervalle_moyenne fit lwr upr
1 192778.7 190525.4 195031.9
intervalle_individuel fit lwr upr
1 192778.7 116744 268813.3
Les deux intervalles sont centrés sur environ $192,779. L’intervalle de confiance vise le prix moyen des propriétés possédant ces caractéristiques. L’intervalle de prédiction vise une nouvelle vente individuelle et est beaucoup plus large, car il inclut aussi la variabilité entre propriétés.
Ce que ces intervalles ne valident pas
Les deux intervalles supposent que la formule, la structure de variance et la population cible sont suffisamment appropriées. Ils ne remplacent pas une évaluation prédictive sur de nouvelles données.
Le modèle est ici ajusté et résumé avec les mêmes 2 930 ventes. Son erreur dans ces données est donc optimiste si l’on veut prédire de futures ventes. Pour évaluer la performance prédictive, il faudrait réserver un jeu test ou utiliser un rééchantillonnage qui répète la séparation entre ajustement et évaluation. Cette étape sera approfondie au jour 3.
Diagnostiquer les résidus
diagnostic_prix <- augment(modele_prix, data = ames)p_residus <- diagnostic_prix |>
ggplot(aes(.fitted, .resid)) +
geom_point(alpha = 0.25, color = "#277da1") +
geom_hline(yintercept = 0, linetype = 2) +
geom_smooth(se = FALSE, color = "#b5222e") +
scale_x_continuous(labels = label_dollar()) +
scale_y_continuous(labels = label_dollar()) +
labs(x = "Prix ajusté", y = "Résidu")
p_qq <- diagnostic_prix |>
ggplot(aes(sample = .std.resid)) +
stat_qq(alpha = 0.3, color = "#0f7c80") +
stat_qq_line(color = "#b5222e") +
labs(x = "Quantiles théoriques", y = "Résidus standardisés")
p_residus + p_qqLe graphique des résidus doit idéalement ressembler à une bande horizontale centrée sur zéro, sans courbure claire et d’épaisseur à peu près constante. Ici, l’étalement augmente pour les prix ajustés élevés et plusieurs observations sont extrêmes.
Le diagramme quantile-quantile compare les résidus standardisés à une distribution de référence. L’écart dans les queues ne signifie pas que toute association disparaît. Il signale surtout que les erreurs-types, les tests et les intervalles usuels méritent davantage de prudence.
Examiner l’influence
ventes_influentes <- diagnostic_prix |>
mutate(numero = row_number()) |>
arrange(desc(.cooksd)) |>
select(
numero, sale_price, gr_liv_area, overall_qual,
year_built, garage_cars, .resid, .cooksd
)
ventes_influentes |>
slice_head(n = 8)# A tibble: 8 × 8
numero sale_price gr_liv_area overall_qual year_built garage_cars .resid
<int> <int> <int> <int> <int> <dbl> <dbl>
1 1499 160000 5642 10 2008 2 -368847.
2 2181 183850 5095 10 2008 3 -328393.
3 2182 184750 4676 10 2007 3 -303120.
4 1768 755000 4316 10 1994 3 292647.
5 1761 745000 4476 10 1996 3 272730.
6 2446 625000 3627 10 1995 3 201730.
7 2451 584500 3500 9 1993 3 193217.
8 1064 615000 2470 10 2003 3 254977.
# ℹ 1 more variable: .cooksd <dbl>
Une observation peut être atypique par sa réponse, posséder un fort levier par ses prédicteurs, ou être influente parce qu’elle modifie sensiblement l’ajustement. La distance de Cook combine ces deux dernières idées.
La vente la plus influente est la ligne 1499. Elle combine une surface de 5,642 pieds carrés, une qualité de 10 sur 10 et un prix de $160,000. Elle est inhabituelle, mais son caractère extrême ne prouve pas une erreur de saisie.
Avant toute exclusion, il faut vérifier la source, comprendre le contexte, comparer les résultats avec et sans l’observation et documenter la décision.
Faire une analyse de sensibilité
numero_max <- which.max(cooks.distance(modele_prix))
modele_sans_max <- lm(
sale_price ~ gr_liv_area + overall_qual + year_built + garage_cars,
data = ames[-numero_max, ]
)
comparaison_coefficients <- bind_rows(
tidy(modele_prix) |>
mutate(analyse = "toutes les ventes"),
tidy(modele_sans_max) |>
mutate(analyse = "sans la vente la plus influente")
) |>
filter(term != "(Intercept)") |>
select(analyse, term, estimate)
comparaison_coefficients# A tibble: 8 × 3
analyse term estimate
<chr> <chr> <dbl>
1 toutes les ventes gr_liv_area 57.3
2 toutes les ventes overall_qual 23960.
3 toutes les ventes year_built 377.
4 toutes les ventes garage_cars 14723.
5 sans la vente la plus influente gr_liv_area 60.2
6 sans la vente la plus influente overall_qual 23775.
7 sans la vente la plus influente year_built 388.
8 sans la vente la plus influente garage_cars 13798.
Les coefficients changent, surtout celui de la surface, mais leur signe et la conclusion générale restent stables. Une variation numérique n’est pas nécessairement une différence substantielle. L’analyse de sensibilité informe le verdict; elle ne justifie pas à elle seule une suppression.
Comparer une échelle logarithmique
modele_log <- lm(
log(sale_price) ~ log(gr_liv_area) + overall_qual +
year_built + garage_cars,
data = ames
)
tidy(modele_log, conf.int = TRUE)# A tibble: 5 × 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 2.77 0.301 9.21 5.83e- 20 2.18 3.36
2 log(gr_liv_area) 0.440 0.0131 33.5 1.43e-208 0.414 0.466
3 overall_qual 0.120 0.00356 33.6 4.22e-210 0.113 0.127
4 year_built 0.00263 0.000143 18.4 1.60e- 71 0.00235 0.00291
5 garage_cars 0.0766 0.00582 13.2 1.82e- 38 0.0652 0.0880
glance(modele_log)# A tibble: 1 × 12
r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0.813 0.813 0.176 3175. 0 4 927. -1843. -1807.
# ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
Dans ce modèle, le coefficient de log(gr_liv_area) est une élasticité conditionnelle. Une hausse de 1 % de la surface est associée en moyenne à une variation d’environ 0.44% du prix, à caractéristiques identiques.
diagnostic_log <- augment(modele_log, data = ames)
p_prix <- diagnostic_prix |>
ggplot(aes(.fitted, .resid)) +
geom_point(alpha = 0.22, color = "#277da1") +
geom_hline(yintercept = 0, linetype = 2) +
geom_smooth(se = FALSE, color = "#b5222e") +
labs(title = "Prix en dollars", x = "Valeurs ajustées", y = "Résidus")
p_log <- diagnostic_log |>
ggplot(aes(.fitted, .resid)) +
geom_point(alpha = 0.22, color = "#0f7c80") +
geom_hline(yintercept = 0, linetype = 2) +
geom_smooth(se = FALSE, color = "#b5222e") +
labs(title = "Prix logarithmique", x = "Valeurs ajustées", y = "Résidus")
p_prix + p_logLa version logarithmique produit un nuage généralement plus régulier et explique davantage de variation sur son échelle. Le choix ne doit cependant pas reposer uniquement sur le \(R^2\): l’échelle modifie la question, les unités et la manière de revenir aux dollars.
Catégories, interactions et non-linéarité
Le modèle commun reste volontairement simple. Trois extensions sont importantes à connaître:
- une variable nominale comme
neighborhoodest représentée par des indicatrices comparées à une catégorie de référence; - une interaction permet à l’association entre la surface et le prix de varier selon une autre caractéristique;
- un terme quadratique ou une transformation permet de représenter une courbure.
Ces extensions doivent être choisies pour répondre à une question ou corriger une structure visible, pas seulement pour augmenter le \(R^2\). Elles compliquent l’interprétation et doivent faire partie de toute validation prédictive.
Ce que le modèle permet de dire
Une conclusion proportionnée est:
Parmi les 2 930 ventes observées à Ames, la surface, la qualité globale, l’année de construction et la capacité du garage sont associées au prix. Le modèle en dollars est facile à interpréter, mais ses résidus deviennent plus variables pour les propriétés coûteuses et certaines ventes sont influentes. Une transformation logarithmique améliore la régularité des diagnostics, au prix d’une interprétation différente. Le modèle peut soutenir une première estimation et une comparaison conditionnelle, mais il ne démontre pas d’effet causal et ne remplace pas une validation sur de nouvelles données.
Prolongements
- ajouter
neighborhoodcomme variable catégorielle; - tester une interaction entre surface et qualité;
- séparer apprentissage et test pour mesurer l’erreur hors échantillon;
- comparer le modèle linéaire à un modèle plus flexible au jour 3.
Lectures complémentaires
Pour consolider la théorie
- James et ses collègues, An Introduction to Statistical Learning, chapitre 3. Une présentation accessible de la régression simple et multiple, de l’inférence, des variables qualitatives et des extensions du modèle linéaire (G. James et al., 2021).
- OpenIntro, chapitre 7 sur la régression linéaire multiple. Une lecture plus détaillée des paramètres, des moindres carrés, des tests, des intervalles et des prédicteurs catégoriels.
- Documentation officielle de
lm(). La référence technique pour les formules, les contrastes, les valeurs manquantes et les composantes retournées par R.
Pour prolonger le travail avec Ames et la modélisation
- Kuhn et Silge, présentation des données Ames. Une exploration structurée du jeu et de sa préparation.
- Kuhn et Silge, fondamentaux des modèles dans R. Une explication des formules, objets ajustés, prédictions et conventions de base R.
- Kuhn et Silge, flux de modélisation. Une introduction à la séparation entre préparation, ajustement et évaluation, ainsi qu’à la validation sur des données réservées (M. Kuhn et J. Silge, 2022).
- De Cock, article original sur les données Ames. La source à consulter pour comprendre la construction du jeu et son objectif pédagogique (D. De Cock, 2011).