Initiation tranquille à l’hétérogénéité spatiale
DECA 4 - 1er semestre 2026 05/01/2025
Etape 1
Double-cliquer sur le fichier script_liste_packages.r
Félicitaions
Bravo vous voilà développeurs !
On utilise le package here pour gérer les chemins d’accès, ici on spécifie où on se situe :
Reading layer `EPCI' from data source
`C:\Users\gregoireLC\Documents\Projet_Passages\DECA4_2026\deca4_2026\data\EPCI.shp'
using driver `ESRI Shapefile'
Simple feature collection with 1242 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 124110 ymin: 6049642 xmax: 1242428 ymax: 7110453
Projected CRS: RGF93 Lambert 93
La régression linéaire cherche à expliquer ou prédire une variable Y par une ou plusieurs variables X.
Elle peut être simple :
\[Y = a + bX + \epsilon\]
Ou multiple :
\[Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + ... + \beta_n X_n + \epsilon\]
Objectifs
Exemple simple
Prix d’un bien immobilier :
Exemple : Y = 2 000 + 500 × X
Prédiction : Une personne avec 5 ans d’expérience :
Y = 2 000 + 500 × 5 = 4 500 €
Coefficient de détermination
\[R^2 = 1 - \frac{\text{Variance résiduelle}}{\text{Variance totale}}\]
Interprétation :
| R² | Interprétation |
|---|---|
| < 0,3 | Modèle faible |
| 0,3 - 0,6 | Modèle moyen |
| 0,6 - 0,8 | Bon modèle |
| > 0,8 | Excellent modèle |
Note
En SHS, un R² de 0,3-0,5 n’est pas rare.
Essayons tout d’abord de représenter un modèle !
Nous cherchons à expliquer le prix median de l’immobilier par le niveau de vie médian des menages par EPCI :
library(ggplot2)
plot1 <- ggplot(immo_df, aes(x = med_niveau_vis, y = prix_med)) +
geom_point() +
labs(title = "Relation prix median au m² vs niveau de vie median par EPCI") +
xlab("niveau de vie median par EPCI (€)") + ylab("Prix median au m² (€)")+
theme(plot.title = element_text(face = "bold"))
plot1 + geom_point(col="blue") + geom_smooth(method = "lm", color="red", se=TRUE) + theme_grey()Une autre manière de faire qui peut s’avérer pratique…
Produire un modèle de régression rien de plus simple !
# Estimation du modèle avec la fonction lm (pour linear model)
lm1 <- lm(data = immo_df, formula = prix_med ~ med_niveau_vis)
summary(lm1)
Call:
lm(formula = prix_med ~ med_niveau_vis, data = immo_df)
Residuals:
Min 1Q Median 3Q Max
-1717.4 -374.0 -147.5 172.0 8888.1
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1590.85 19.22 82.77 <2e-16 ***
med_niveau_vis 485.91 19.21 25.30 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 672.2 on 1221 degrees of freedom
Multiple R-squared: 0.3439, Adjusted R-squared: 0.3434
F-statistic: 640 on 1 and 1221 DF, p-value: < 2.2e-16
La fonction summary() présente les estimations de façon brute, mais d’autres fonctions permettent des rendus plus esthétiques, comme avec le package gtsummary :
En géographie, cette hypothèse est souvent violée :
La régression classique suppose des coefficients constants dans l’espace :
Attention
Avant de lancer ce modèle des conditions d’applications doivent être vérifiées!
mod.lm <- lm(formula = prix_med ~ perc_log_vac + perc_maison +
perc_tiny_log + dens_pop + med_niveau_vis +
part_log_suroccup + part_agri_nb_emploi +
part_cadre_profintellec_nbemploi, data = immo_df)
gtsummary::tbl_regression(mod.lm)| Characteristic | Beta | 95% CI | p-value |
|---|---|---|---|
| perc_log_vac | -287 | -315, -260 | <0.001 |
| perc_maison | -128 | -168, -89 | <0.001 |
| perc_tiny_log | -262 | -316, -207 | <0.001 |
| dens_pop | 174 | 144, 204 | <0.001 |
| med_niveau_vis | 288 | 261, 315 | <0.001 |
| part_log_suroccup | 389 | 335, 443 | <0.001 |
| part_agri_nb_emploi | -21 | -50, 8.8 | 0.2 |
| part_cadre_profintellec_nbemploi | -7.8 | -47, 31 | 0.7 |
| Abbreviation: CI = Confidence Interval | |||
library(correlation)
data_cor <- immo_df %>% select(-SIREN)
immo_cor <- correlation(data_cor, redundant = TRUE)
summary(immo_cor)# Correlation Matrix (pearson-method)
Parameter | part_cadre_profintellec_nbemploi
-------------------------------------------------------------------
prix_med | 0.55***
perc_log_vac | -0.31***
perc_maison | -0.58***
perc_tiny_log | 0.75***
dens_pop | 0.59***
med_niveau_vis | 0.43***
part_log_suroccup | 0.65***
part_agri_nb_emploi | -0.57***
part_cadre_profintellec_nbemploi | 1.00***
Parameter | part_agri_nb_emploi | part_log_suroccup
--------------------------------------------------------------------------
prix_med | -0.45*** | 0.60***
perc_log_vac | 0.39*** | -0.28***
perc_maison | 0.54*** | -0.79***
perc_tiny_log | -0.51*** | 0.87***
dens_pop | -0.29*** | 0.62***
med_niveau_vis | -0.33*** | 0.15***
part_log_suroccup | -0.43*** | 1.00***
part_agri_nb_emploi | 1.00*** |
part_cadre_profintellec_nbemploi | |
Parameter | med_niveau_vis | dens_pop | perc_tiny_log
----------------------------------------------------------------------------
prix_med | 0.59*** | 0.52*** | 0.49***
perc_log_vac | -0.46*** | -0.21*** | -0.19***
perc_maison | -0.24*** | -0.46*** | -0.75***
perc_tiny_log | 0.22*** | 0.62*** | 1.00***
dens_pop | 0.18*** | 1.00*** |
med_niveau_vis | 1.00*** | |
part_log_suroccup | | |
part_agri_nb_emploi | | |
part_cadre_profintellec_nbemploi | | |
Parameter | perc_maison | perc_log_vac | prix_med
------------------------------------------------------------------------
prix_med | -0.60*** | -0.68*** | 1.00***
perc_log_vac | 0.35*** | 1.00*** |
perc_maison | 1.00*** | |
perc_tiny_log | | |
dens_pop | | |
med_niveau_vis | | |
part_log_suroccup | | |
part_agri_nb_emploi | | |
part_cadre_profintellec_nbemploi | | |
p-value adjustment method: Holm (1979)
VIF
La VIF (Variance Inflation Factor) est une très bonne méthode pour vérifier les risques de multicolinéarité. Elle suppose simplement d’avoir estimé un premier modèle pour être utilisée.
perc_log_vac perc_maison
1.577984 3.204058
perc_tiny_log dens_pop
6.221631 1.864236
med_niveau_vis part_log_suroccup
1.532403 5.977951
part_agri_nb_emploi part_cadre_profintellec_nbemploi
1.812419 3.162576
# On peut aussi directement l'ajouter au résumé des coefficient obtenu avec gtsummary
library(gtsummary)
mod.lm %>%
tbl_regression(intercept = TRUE) %>% add_vif()| Characteristic | Beta | 95% CI | p-value | VIF |
|---|---|---|---|---|
| (Intercept) | 1,593 | 1,571, 1,615 | <0.001 | |
| perc_log_vac | -287 | -315, -260 | <0.001 | 1.6 |
| perc_maison | -128 | -168, -89 | <0.001 | 3.2 |
| perc_tiny_log | -262 | -316, -207 | <0.001 | 6.2 |
| dens_pop | 174 | 144, 204 | <0.001 | 1.9 |
| med_niveau_vis | 288 | 261, 315 | <0.001 | 1.5 |
| part_log_suroccup | 389 | 335, 443 | <0.001 | 6.0 |
| part_agri_nb_emploi | -21 | -50, 8.8 | 0.2 | 1.8 |
| part_cadre_profintellec_nbemploi | -7.8 | -47, 31 | 0.7 | 3.2 |
| Abbreviations: CI = Confidence Interval, VIF = Variance Inflation Factor | ||||
Notes
L’analyse des résidus est très importante car les conditions de validité d’un modèle linéaire au delà des résultats repose grandement sur les résidus. Ils permettent en outre d’identifier les individus extrêmes (ou outliers).
Si la voie graphique ne vous inspire pas il existe des tests statistiques qui permettent de vérifier la normalité des résidus ou bien leur homoscédasticité.
Ils ont cela de particulier qu’ici nous cherchons à accepter H0 et donc pour valider la normalité ou l’homoscédasticité il faut que \(p−value>0.05\)
Shapiro-Wilk normality test
data: mod.lm$residuals
W = 0.90792, p-value < 2.2e-16
# Pour évaluer l'homoscédasticité on peut utiliser le test de Breusch-Pagan. Le package car propose une fonction pour le réaliser
ncvTest(mod.lm)Non-constant Variance Score Test
Variance formula: ~ fitted.values
Chisquare = 1151.844, Df = 1, p = < 2.22e-16
Aparte
La question des outliers (ou individus extrême) est très importante ! Un seul individu de par son côté extrême pourra faire varier voire fausser complétement notre test.
SIREN prix_med perc_log_vac perc_maison perc_tiny_log dens_pop
36 200023372 6400 -2.3174835 -2.558919 -0.07390879 -0.2437935
266 200054781 10920 -0.7044846 -3.732690 6.43751449 22.7709258
med_niveau_vis part_log_suroccup part_agri_nb_emploi
36 0.7430146 1.873292 -1.027161
266 0.9077121 7.345980 -1.068415
part_cadre_profintellec_nbemploi
36 0.07587777
266 6.30681026
Voyons les conséquences si on relance la régression sans l’individu 266
Call:
lm(formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log +
dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi +
part_cadre_profintellec_nbemploi, data = immo_df, subset = -266)
Residuals:
Min 1Q Median 3Q Max
-1622.6 -215.0 -37.5 170.0 2767.7
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1587.246 10.526 150.797 < 2e-16 ***
perc_log_vac -302.182 13.317 -22.692 < 2e-16 ***
perc_maison -132.159 18.814 -7.024 3.57e-12 ***
perc_tiny_log -227.353 26.356 -8.626 < 2e-16 ***
dens_pop -4.725 19.978 -0.237 0.813
med_niveau_vis 284.947 13.012 21.899 < 2e-16 ***
part_log_suroccup 404.159 25.701 15.725 < 2e-16 ***
part_agri_nb_emploi -17.545 14.138 -1.241 0.215
part_cadre_profintellec_nbemploi 23.384 18.841 1.241 0.215
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 367.8 on 1213 degrees of freedom
Multiple R-squared: 0.7823, Adjusted R-squared: 0.7809
F-statistic: 544.9 on 8 and 1213 DF, p-value: < 2.2e-16
Calls:
1: lm(formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log +
dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi +
part_cadre_profintellec_nbemploi, data = immo_df)
2: lm(formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log +
dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi +
part_cadre_profintellec_nbemploi, data = immo_df, subset = -266)
Model 1 Model 2
(Intercept) 1592.9 1587.2
SE 11.2 10.5
Pr(>|z|) < 2e-16 < 2e-16
perc_log_vac -287.3 -302.2
SE 14.1 13.3
Pr(>|z|) < 2e-16 < 2e-16
perc_maison -128.3 -132.2
SE 20.0 18.8
Pr(>|z|) 1.6e-10 2.1e-12
perc_tiny_log -261.6 -227.4
SE 27.9 26.4
Pr(>|z|) < 2e-16 < 2e-16
dens_pop 173.94 -4.72
SE 15.27 19.98
Pr(>|z|) < 2e-16 0.81
med_niveau_vis 288.0 284.9
SE 13.9 13.0
Pr(>|z|) < 2e-16 < 2e-16
part_log_suroccup 389.0 404.2
SE 27.4 25.7
Pr(>|z|) < 2e-16 < 2e-16
part_agri_nb_emploi -20.8 -17.5
SE 15.1 14.1
Pr(>|z|) 0.17 0.21
part_cadre_profintellec_nbemploi -7.84 23.38
SE 19.90 18.84
Pr(>|z|) 0.69 0.21
Les données spatiales ont des propriétés particulières :
Les observations voisines se ressemblent
Conséquence : violation de l’hypothèse d’indépendance
Les relations varient dans l’espace
Conséquence : coefficients non constants
# La palette de couleurs :
cols_v1 <- c("#08519c", "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#eff3ff", "#ffffce", "#fee5d9", "#fcbba1", "#fc9272", "#fb6a4a", "#de2d26")
# Carte du prix médian
mf_map(x = data_immo,
var = "prix_med",
type = "choro",
border = "#ebebeb",
lwd = 0.1,
breaks=quantile(data_immo$prix_med,seq(0,1, by=1/11)),
pal=cols_v1,
leg_title = "Prix médian",
leg_val_rnd = 1)
mf_title("Prix Médian du logement par EPCI France Métropolitaine") #titredata_immo$res_reg <- mod.lm$residuals
# Définition d'une palette de couleur
cols_v1 <- c("#08519c", "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#eff3ff", "#ffffce", "#fee5d9", "#fcbba1", "#fc9272", "#fb6a4a", "#de2d26")
# Réalisation de la carte
mf_map(x = data_immo,
var = "res_reg",
type = "choro",
border = "#ebebeb",
lwd = 0.1,
breaks=quantile(data_immo$res_reg,seq(0,1, by=1/11)),
pal=cols_v1,
leg_title = "Résidus de régression\nlinéaire 'classique'",
leg_val_rnd = 1)
mf_title("Résidus modèle lm") #titreL’autocorrélation spatiale mesure la similitude entre observations voisines :
Pour mesurer l’autocorrélation, il faut définir le voisinage :
Matrice W : qui est voisin de qui ?
library(spdep)
# Création de la liste des voisins : avec l'option queen = TRUE,
# sont considérés comme voisins 2 polygones possédant au moins 1 sommet commun
#help(poly2nb)
neighbours_epci <- poly2nb(data_immo, queen = TRUE)
# cette opération se fait aussi avec un objet sp
#shape_nbq <- poly2nb(shape, queen = TRUE)
# Obtention des coordonnées des centroïdes
#coord<-coordinates(shape)
coord <- st_coordinates(st_centroid(data_immo))
# Faire un graphe de voisinage
plot(neighbours_epci, coord)# si on prend le 1er élément de neighbours_epci, on voit qu'il a pour voisins les polygones 62, 74 etc.
neighbours_epci[[1]][1] 62 74 338 1135 1136 1137 1140
# la fonction nb2listw attribue des poids à chaque voisin
# par ex. si un polygone a 4 voisins, ils auront chacun un poids de 1/4 = 0.25
#help("nb2listw")
neighbours_epci_w <- nb2listw(neighbours_epci)
# les poids sont stockés dans le 3ème élément de neighbours_epci_w
# par ex. si on veut connaître les poids des voisins du 1er élément :
neighbours_epci_w[[3]][1][[1]]
[1] 0.1428571 0.1428571 0.1428571 0.1428571 0.1428571 0.1428571 0.1428571
Tip
Comment faire si l’on a un polygone sans voisins ? Sur le plan technique, la fonction nb2listw prévoit ce cas de figure. Il faut utiliser l’argument zero.policy est indiquer TRUE. Au niveau théorique, c’est moins clair. De manière générale les indices d’autocorrélation spatiale et autres régressions spatiales ont été conçus en partant du principe que les entités spatiales avaient un voisinage. Ceci dit il n’y a pas à ma connaissance de règles qui obligent à les supprimer ou intégrer, ni de consensus scientifique sur l’approche à adopter.
\[I = \frac{n}{\sum_{i}\sum_{j}w_{ij}} \times \frac{\sum_{i}\sum_{j}w_{ij}(x_i - \bar{x})(x_j - \bar{x})}{\sum_{i}(x_i - \bar{x})^2}\]
Test d’hypothèse :
En pratique :
Nuage de points : valeur vs. moyenne des voisins
# La palette de couleurs :
cols_v1 <- c("#08519c", "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#eff3ff", "#ffffce", "#fee5d9", "#fcbba1", "#fc9272", "#fb6a4a", "#de2d26")
# Carte du prix médian
mf_map(x = data_immo,
var = "prix_med",
type = "choro",
border = "#ebebeb",
lwd = 0.1,
breaks=quantile(data_immo$prix_med,seq(0,1, by=1/11)),
pal=cols_v1,
leg_title = "Prix médian",
leg_val_rnd = 1)
mf_title("Prix Médian du logement par EPCI France Métropolitaine") #titrelibrary(spdep)
# Pour l'occasion on va standardiser notre prix médian.
# Cela permettra par la suite de le comparer à d'autres variables si d'autres analyses d'autocorrélation spatiale sont réalisées
data_immo$prix_med_z <- (data_immo$prix_med-mean(data_immo$prix_med))/sd(data_immo$prix_med)
# Test de Moran :
# On indique dans un premier temps la variable que l'on souhaite analyser
# Puis la matrice de voisinage
# L'argument zero.policy=TRUE permet de préciser que l'on souhaite intégrer à l'analyse les entités spatiales qui n'auraient pas de voisins
# L'argument randomisation=FALSE transmet à la fonction que nous supposons que la distribution est normale. Dans le cas contraire on devrait partir sur une solution de type Monte-Carlo qui repose sur la randomisation
moran.test(data_immo$prix_med_z, neighbours_epci_w, zero.policy = TRUE, randomisation = FALSE)
Moran I test under normality
data: data_immo$prix_med_z
weights: neighbours_epci_w
Moran I statistic standard deviate = 41.143, p-value < 2.2e-16
alternative hypothesis: greater
sample estimates:
Moran I statistic Expectation Variance
0.7154340217 -0.0008183306 0.0003030652
LISA : Local Indicators of Spatial Association
Pour chaque observation i :
\[I_i = \frac{(x_i - \bar{x})}{\sigma^2} \sum_{j} w_{ij}(x_j - \bar{x})\]
Mesure la contribution locale à l’autocorrélation globale
Valeur élevée entourée de valeurs élevées
Valeur faible entourée de valeurs faibles
Valeur élevée entourée de valeurs faibles
Valeur faible entourée de valeurs élevées
Utilité :
Attention : tenir compte des tests de significativité !
library(rgeoda)
# Pour utiliser la fonction local_moran proposée par le package rgeoda 2 pré-requis:
# 1- utiliser la fonction queen_weights du package rgeoda pour calculer une matrice de contiguïté de type queen
queen_w <- queen_weights(data_immo)
# 2- Sortir la variable à étudier dans un vecteur
prix_med_z = data_immo["prix_med_z"]
lisa <- local_moran(queen_w, prix_med_z)
# Pour visualiser les résultats des LISA il faut les stocker dans des objets ou dans les base de données pour les représenter
lisa_colors <- lisa_colors(lisa)
lisa_labels <- lisa_labels(lisa)
lisa_clusters <- lisa_clusters(lisa)
lisa_value <- lisa_values(lisa)
lisa_pvalue<- lisa_pvalues(lisa)
# Carte de Moran locaux
data_immo$moranlocalvalue <- lisa_values(lisa)
cols_v2 <- c("#08519c", "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#eff3ff", "#ffffce", "#fee5d9", "#fcbba1", "#fc9272", "#fb6a4a", "#de2d26")
mf_map(x = data_immo,
var = "moranlocalvalue",
type = "choro",
border = "#ebebeb",
lwd = 0.1,
pal=cols_v2,
leg_title = "Local Moran",
leg_val_rnd = 1)
mf_title("Carte des LISA du prix médian du logement")# Carte des p-value des moran locaux
data_immo$moranlocalpvalue<- lisa_pvalues(lisa)
library(dplyr)
# Pour plus de lisibilité dans la carte on va faire des classes des p-value
data_immo<- data_immo %>% mutate(lisapvalue_fac = case_when(moranlocalpvalue <= 0.002 ~ "[0.001;0.002[",
moranlocalpvalue <= 0.01 ~ "[0.002;0.01[",
moranlocalpvalue <= 0.05 ~ "[0.01;0.05[",
TRUE ~ "[0.05;0.5]")) %>%
mutate(lisapvalue_fac = factor(lisapvalue_fac,
levels = c("[0.001;0.002[", "[0.002;0.01[",
"[0.01;0.05[",
"[0.05;0.5]")))
mypal <- mf_get_pal(n = 4, palette = "Reds")
mf_map(x = data_immo,
var = "lisapvalue_fac",
type = "typo",
border = "grey3",
lwd = 0.1,
pal=mypal,
leg_title = "P-value Local Moran")
mf_title("Carte des significativité des LISA")# Carte des clusters lisa avec rgeoda
plot(st_geometry(data_immo),
col=sapply(lisa_clusters, function(x){return(lisa_colors[[x+1]])}),
border = "#333333", lwd=0.2)
title(main = "Cluster LISA")
legend('bottomleft', legend = lisa_labels, fill = lisa_colors, border = "#eeeeee")# En utilisant le package mapsf
data_immo$testmoran <- sapply(lisa_clusters, function(x){return(lisa_labels[[x+1]])})
colors <- c("white","blue",rgb(0,0,1,alpha=0.4),rgb(1,0,0,alpha=0.4),"red")
mf_map(x = data_immo,
var = "testmoran",
type = "typo",
border = "black",
lwd = 0.1,
pal= colors,
val_order = c("Not significant","Low-Low","Low-High","High-Low","High-High"),
leg_title = "Lisa cluster")
mf_title("LISA clusters")Dans une régression classique, les coefficients sont constants :
\[Y_i = \beta_0 + \beta_1 X_{1i} + \beta_2 X_{2i} + \epsilon_i\]
Mais en réalité, les effets peuvent varier dans l’espace !
Question : Quel est l’effet de la surface sur le prix immobilier ?
➜ Un seul coefficient global est inadapté
Variables d’interaction : limité et arbitraire
Modèles par sous-zones : perte de continuité spatiale
Geographically Weighted Regression (GWR) :
Geographically Weighted Regression :
Méthode de régression où les coefficients varient localement :
\[Y_i = \beta_0(u_i, v_i) + \sum_{k}\beta_k(u_i, v_i)X_{ki} + \epsilon_i\]
Pour chaque point i, on estime un modèle local :
Chaque point a son propre modèle local
Test : La GWR est-elle meilleure que l’OLS ?
Problèmes possibles :
Bonnes pratiques :
Fonction gaussienne (la plus courante) :
\[w_{ij} = \exp\left(-\frac{d_{ij}^2}{b^2}\right)\]
Le bandwidth (b) détermine la taille du voisinage :
Critère AICc (Akaike Information Criterion corrigé) :
Cross-validation :
Pour réaliser une GWR sur R plusieurs packages reconnus existent. On peut citer notamment le package spgwret le package GWmodel. Nous choisirons d’utiliser ici le package GWmodel.
Note
Attention, les fonctions de ce package ne sont pas utilisables sur des objets de type sf, il faut travailler sur des objets de type Spatial DataFrame (SpatialPointsDataFrame or SpatialPolygonsDataFrame) (contrairement à spgwr qui peut travailler avec un format sf).
La première étape est de calculer la distance entre toutes nos observations. Pour ce faire nous utiliserons la fonction gw.dist().
# Définition de la bande passante (bandwidth en anglais)
# Cette fonction n'est pas compatible avec le format sf il faut un Spatial DataFrame (contrairement à spgwr qui peut travailler avec un format sf)
bw_g <- bw.gwr(data = sp_data_immo,
approach = "AICc",
kernel = "gaussian",
adaptive = TRUE,
dMat = dm.calib,
formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log + dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi + part_cadre_profintellec_nbemploi)Adaptive bandwidth (number of nearest neighbours): 763 AICc value: 17959.04
Adaptive bandwidth (number of nearest neighbours): 480 AICc value: 17884.14
Adaptive bandwidth (number of nearest neighbours): 303 AICc value: 17798.07
Adaptive bandwidth (number of nearest neighbours): 196 AICc value: 17715.24
Adaptive bandwidth (number of nearest neighbours): 127 AICc value: 17622.76
Adaptive bandwidth (number of nearest neighbours): 87 AICc value: 17526.89
Adaptive bandwidth (number of nearest neighbours): 59 AICc value: 17422.28
Adaptive bandwidth (number of nearest neighbours): 45 AICc value: 17353.68
Adaptive bandwidth (number of nearest neighbours): 33 AICc value: 17283.09
Adaptive bandwidth (number of nearest neighbours): 29 AICc value: 17261.83
Adaptive bandwidth (number of nearest neighbours): 23 AICc value: 17250.23
Adaptive bandwidth (number of nearest neighbours): 22 AICc value: 17247.23
Adaptive bandwidth (number of nearest neighbours): 19 AICc value: 17201.79
Adaptive bandwidth (number of nearest neighbours): 19 AICc value: 17201.79
Une fois la bande passante définie, on peut lancer l’estimation de notre modèle de GWR :
mod.gwr_g <- gwr.robust(data = sp_data_immo,
dMat = dm.calib,
bw = bw_g,
kernel = "gaussian",
filtered = FALSE, # un des problèmes de la GWR est de gérer des individus "aberrants" au niveau local. 2 méthodes ont été définies pour gérer cela.
# Méthode 1 (argument TRUE) on filtre en fonction des individus standardisés. L'objectif est de détecter les individus dont les résidus sont très élevés et de les exclure.
# Methode 2 (argument FALSE) on diminue le poids des observations aux résidus élevés.
adaptive = TRUE,
formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log + dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi + part_cadre_profintellec_nbemploi)Si on souhaite comparer deux modèles car nous avons un doute sur les paramètres c’est tout à fait possible. Par exemple ici nous souhaitons comparer deux formes de noyaux.
bw_tri <- bw.gwr(data = sp_data_immo,
approach = "AICc",
kernel = "tricube",
adaptive = TRUE,
dMat = dm.calib,
formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log + dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi + part_cadre_profintellec_nbemploi)Adaptive bandwidth (number of nearest neighbours): 763 AICc value: 17701.17
Adaptive bandwidth (number of nearest neighbours): 480 AICc value: 17586.94
Adaptive bandwidth (number of nearest neighbours): 303 AICc value: 17422.51
Adaptive bandwidth (number of nearest neighbours): 196 AICc value: 17294.75
Adaptive bandwidth (number of nearest neighbours): 127 AICc value: 17254.1
Adaptive bandwidth (number of nearest neighbours): 87 AICc value: 17279.33
Adaptive bandwidth (number of nearest neighbours): 154 AICc value: 17259.05
Adaptive bandwidth (number of nearest neighbours): 112 AICc value: 17257.17
Adaptive bandwidth (number of nearest neighbours): 138 AICc value: 17254.8
Adaptive bandwidth (number of nearest neighbours): 122 AICc value: 17253.75
Adaptive bandwidth (number of nearest neighbours): 117 AICc value: 17255.04
Adaptive bandwidth (number of nearest neighbours): 123 AICc value: 17253.57
Adaptive bandwidth (number of nearest neighbours): 126 AICc value: 17254.25
Adaptive bandwidth (number of nearest neighbours): 123 AICc value: 17253.57
mod.gwr_tri <- gwr.robust(data = sp_data_immo,
dMat = dm.calib,
bw = bw_tri,
kernel = "gaussian",
filtered = FALSE,
adaptive = TRUE,
formula = prix_med ~ perc_log_vac + perc_maison + perc_tiny_log + dens_pop + med_niveau_vis + part_log_suroccup + part_agri_nb_emploi + part_cadre_profintellec_nbemploi)
Best_gwr <- cbind(
rbind(bw_g, bw_tri),
rbind(mod.gwr_g$GW.diagnostic$gw.R2,mod.gwr_tri$GW.diagnostic$gw.R2),
rbind(mod.gwr_g$GW.diagnostic$AIC,mod.gwr_tri$GW.diagnostic$AIC)) %>%
`colnames<-`(c("Nb Voisins","R2","AIC")) %>%
`rownames<-`(c("GAUSSIAN","TRICUBE"))
Best_gwr Nb Voisins R2 AIC
GAUSSIAN 19 0.9324328 16849.26
TRICUBE 123 0.8577370 17567.05
Pour chaque observation, on obtient :
Cartographie des coefficients :
Comme pour le modèle linéaire classique l’objet qui contient notre GWR est composé de plusieurs éléments. A la différence du modèle classique pour obtenir nos résultats il suffit d’appeler l’objet :
Length Class Mode
GW.arguments 11 -none- list
GW.diagnostic 8 -none- list
lm 14 lm list
SDF 1223 SpatialPolygonsDataFrame S4
timings 5 -none- list
this.call 15 -none- call
Ftests 0 -none- list
Pour accéder aux résultats :
***********************************************************************
* Package GWmodel *
***********************************************************************
Program starts at: 2026-02-27 19:41:28.175683
Call:
gwr.basic(formula = formula, data = data, bw = bw, kernel = kernel,
adaptive = adaptive, p = p, theta = theta, longlat = longlat,
dMat = dMat, F123.test = F123.test, cv = T, W.vect = W.vect,
parallel.method = parallel.method, parallel.arg = parallel.arg)
Dependent (y) variable: prix_med
Independent variables: perc_log_vac perc_maison perc_tiny_log dens_pop med_niveau_vis part_log_suroccup part_agri_nb_emploi part_cadre_profintellec_nbemploi
Number of data points: 1223
***********************************************************************
* Results of Global Regression *
***********************************************************************
Call:
lm(formula = formula, data = data)
Residuals:
Min 1Q Median 3Q Max
-1566.8 -220.2 -27.2 174.0 3277.3
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1592.873 11.204 142.171 < 2e-16 ***
perc_log_vac -287.337 14.134 -20.330 < 2e-16 ***
perc_maison -128.255 20.041 -6.400 2.22e-10 ***
perc_tiny_log -261.556 27.934 -9.363 < 2e-16 ***
dens_pop 173.942 15.272 11.389 < 2e-16 ***
med_niveau_vis 288.017 13.860 20.781 < 2e-16 ***
part_log_suroccup 388.982 27.352 14.221 < 2e-16 ***
part_agri_nb_emploi -20.785 15.059 -1.380 0.168
part_cadre_profintellec_nbemploi -7.841 19.904 -0.394 0.694
---Significance stars
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 391.8 on 1214 degrees of freedom
Multiple R-squared: 0.7784
Adjusted R-squared: 0.7769
F-statistic: 532.9 on 8 and 1214 DF, p-value: < 2.2e-16
***Extra Diagnostic information
Residual sum of squares: 186354789
Sigma(hat): 390.6721
AIC: 18086.13
AICc: 18086.31
BIC: 16985.31
***********************************************************************
* Results of Geographically Weighted Regression *
***********************************************************************
*********************Model calibration information*********************
Kernel function: gaussian
Adaptive bandwidth: 19 (number of nearest neighbours)
Regression points: the same locations as observations are used.
Distance metric: A distance matrix is specified for this model calibration.
****************Summary of GWR coefficient estimates:******************
Min. 1st Qu. Median
Intercept 1109.13394 1309.99018 1516.79508
perc_log_vac -919.82407 -224.60028 -163.40316
perc_maison -898.16365 -258.64481 -110.20592
perc_tiny_log -1210.96882 -206.68578 -92.91874
dens_pop -411.20172 72.82448 217.03593
med_niveau_vis 81.29015 287.78992 358.32914
part_log_suroccup -543.68600 42.42839 142.48859
part_agri_nb_emploi -498.74706 -65.81443 -32.88823
part_cadre_profintellec_nbemploi -347.37935 -48.57329 0.58811
3rd Qu. Max.
Intercept 1696.07367 2330.78
perc_log_vac -113.83881 388.64
perc_maison 76.01079 896.95
perc_tiny_log 32.81544 773.30
dens_pop 268.35651 659.84
med_niveau_vis 413.36560 853.98
part_log_suroccup 247.33650 700.50
part_agri_nb_emploi -1.15602 120.33
part_cadre_profintellec_nbemploi 49.93194 388.50
************************Diagnostic information*************************
Number of data points: 1223
Effective number of parameters (2trace(S) - trace(S'S)): 320.246
Effective degrees of freedom (n-2trace(S) + trace(S'S)): 902.754
AICc (GWR book, Fotheringham, et al. 2002, p. 61, eq 2.33): 17201.79
AIC (GWR book, Fotheringham, et al. 2002,GWR p. 96, eq. 4.22): 16849.26
BIC (GWR book, Fotheringham, et al. 2002,GWR p. 61, eq. 2.34): 17068.01
Residual sum of squares: 56808914
R-square value: 0.9324328
Adjusted R-square value: 0.9084372
***********************************************************************
Program stops at: 2026-02-27 19:41:39.380692
# Pour visualiser ce fichier dans R
#View(mod.gwr_g$SDF@data)
#Pour voir à quoi il ressemble de manière interactive
datatable(mod.gwr_g$SDF@data) [1] "Intercept" "perc_log_vac"
[3] "perc_maison" "perc_tiny_log"
[5] "dens_pop" "med_niveau_vis"
[7] "part_log_suroccup" "part_agri_nb_emploi"
[9] "part_cadre_profintellec_nbemploi" "y"
[11] "yhat" "residual"
[13] "CV_Score" "Stud_residual"
[15] "Intercept_SE" "perc_log_vac_SE"
[17] "perc_maison_SE" "perc_tiny_log_SE"
[19] "dens_pop_SE" "med_niveau_vis_SE"
[21] "part_log_suroccup_SE" "part_agri_nb_emploi_SE"
[23] "part_cadre_profintellec_nbemploi_SE" "Intercept_TV"
[25] "perc_log_vac_TV" "perc_maison_TV"
[27] "perc_tiny_log_TV" "dens_pop_TV"
[29] "med_niveau_vis_TV" "part_log_suroccup_TV"
[31] "part_agri_nb_emploi_TV" "part_cadre_profintellec_nbemploi_TV"
[33] "E_weigts" "Local_R2"
res_gwr <- mod.gwr_g$SDF$Stud_residual
data_immo$res_gwr <- res_gwr
# carto résidus v2
mf_map(x = data_immo,
var = "res_gwr",
type = "choro",
border = "#ebebeb",
lwd = 0.1,
breaks = quantile(data_immo$res_gwr, seq(0,1, by=1/11)),
pal = cols_v2,
leg_title = "Résidus GWR",
leg_val_rnd = 1)
mf_title("Résidus GWR")# On ajoute à data_immo les coefficients
data_immo$agri.coef=mod.gwr_g$SDF$part_agri_nb_emploi
data_immo$perc_maison.coef <- mod.gwr_g$SDF$perc_maison
data_immo$dens_pop.coef=mod.gwr_g$SDF$dens_pop
data_immo$med_vie.coef=mod.gwr_g$SDF$med_niveau_vis
data_immo$logvac.coef=mod.gwr_g$SDF$perc_log_vac
data_immo$tinylog.coef=mod.gwr_g$SDF$perc_tiny_log
data_immo$suroccup.coef=mod.gwr_g$SDF$part_log_suroccup
data_immo$cadre.coef=mod.gwr_g$SDF$part_cadre_profintellec_nbemploi
# Réaliser la collection des cartes
par(mfrow = c(2, 4))
mf_map(x = data_immo, var = "agri.coef", type = "choro", pal= "Earth")
mf_title("Coefficients Agriculteurs")
mf_map(x = data_immo, var = "perc_maison.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de Maison")
mf_map(x = data_immo, var = "dens_pop.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de dens pop")
mf_map(x = data_immo, var = "med_vie.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de Médianne niveau de vie")
mf_map(x = data_immo, var = "logvac.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de Logements vacants")
mf_map(x = data_immo, var = "tinylog.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de Petits logements")
mf_map(x = data_immo, var = "suroccup.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de logement suroocupés")
mf_map(x = data_immo, var = "cadre.coef", type = "choro", pal= "Earth")
mf_title("Coefficients de Cadre")data_immo$agri.t <- mod.gwr_g$SDF$part_agri_nb_emploi_TV
data_immo$maison.t <- mod.gwr_g$SDF$perc_maison_TV
data_immo$dens.t <- mod.gwr_g$SDF$dens_pop_TV
data_immo$medvie.t <- mod.gwr_g$SDF$med_niveau_vis_TV
data_immo$logvac.t <- mod.gwr_g$SDF$perc_log_vac_TV
data_immo$tinylog.t <- mod.gwr_g$SDF$perc_tiny_log_TV
data_immo$suroccup.t <- mod.gwr_g$SDF$part_log_suroccup_TV
data_immo$cadre.t <- mod.gwr_g$SDF$part_cadre_profintellec_nbemploi_TV
#Définir contrib max
df<- as.data.frame(data_immo)
# On passe les t-values en valeurs absolues pour voir la plus grande contribution dans un sens sens ou dans l'autre
data_immo$contribmax<- colnames(df[, c(30:37)])[max.col(abs(df[, c(30:37)]),ties.method="first")]
par(mfrow = c(1, 1))
# Carte
mf_map(x = data_immo, var = "contribmax", type = "typo", pal= "Zissou 1")
mf_title("Carte des variables contribuant le plus par epci")# Pour rappel si on a plus de 200 individus et le t-value > |1.96| on pourra considérer le coefficient comme significatif au seuil de 0.05 (95% chances que ce ne soit pas dû au hasard)
data_immo$nbsignif_t<- rowSums(abs(df[, c(30:37)]) > 1.96)
mf_map(x = data_immo, var = "nbsignif_t", type = "typo", pal= "Reds")
mf_title("Nombre des Betas significatif par EPCI (t-value)")# Les p-value ne sont pas fournit dans le modèle de la GWR, on pourrait les calculer à partir de t-value et de l'erreur standard mais le package GWmodel propose une fonction pour les obttenir
pvalue<-gwr.t.adjust(mod.gwr_g)
# On ajoute les p-value à notre fichier
data_immo$agri.p <- pvalue$SDF$part_agri_nb_emploi_p
data_immo$maison.p <- pvalue$SDF$perc_maison_p
data_immo$dens.p <- pvalue$SDF$dens_pop_p
data_immo$medvie.p <- pvalue$SDF$med_niveau_vis_p
data_immo$logvac.p <- pvalue$SDF$perc_log_vac_p
data_immo$tinylog.p <- pvalue$SDF$perc_tiny_log_p
data_immo$suroccup.p <- pvalue$SDF$part_log_suroccup_p
data_immo$cadre.p <- pvalue$SDF$part_cadre_profintellec_nbemploi_p
df<- as.data.frame(data_immo)
data_immo$nbsignif_p<- rowSums(df[, c(41:48)] < 0.05)
mf_map(x = data_immo, var = "nbsignif_p", type = "typo", pal= "Reds")# Ici nous représenterons les p-value avec un decoupage par classe de significativité et seulement les p-value de 2 VI
par(mfrow = c(1, 2))
# Par exemple les p-value des coefficients de la variable part de l'emploi agriculteur
data_immo<- data_immo %>% mutate(agri.p_fac = case_when(agri.p <= 0.002 ~ "[0;0.002[",
agri.p <= 0.01 ~ "[0.002;0.01[",
agri.p <= 0.05 ~ "[0.01;0.05[",
agri.p <= 0.1 ~ "[0.05;0.1[",
TRUE ~ "[0.1;1]")) %>%
mutate(agri.p_fac = factor(agri.p_fac,
levels = c("[0;0.002[", "[0.002;0.01[",
"[0.01;0.05[",
"[0.05;0.1[", "[0.1;1]")))
mypal2 <- mf_get_pal(n = 5, palette = "SunsetDark")
mf_map(x = data_immo,
var = "agri.p_fac",
type = "typo",
border = "grey3",
lwd = 0.1,
pal=mypal2,
leg_title = "Classe P-value")
mf_title("P-value du coefficient de la part d'emploi agriculteurs")
# Pour la densité de population
data_immo<- data_immo %>% mutate(dens.p_fac = case_when(dens.p<= 0.002 ~ "[0;0.002[",
dens.p <= 0.01 ~ "[0.002;0.01[",
dens.p <= 0.05 ~ "[0.01;0.05[",
dens.p <= 0.1 ~ "[0.05;0.1[",
TRUE ~ "[0.1;1]")) %>%
mutate(dens.p_fac = factor(dens.p_fac,
levels = c("[0;0.002[", "[0.002;0.01[",
"[0.01;0.05[",
"[0.05;0.1[", "[0.1;1]")))
mf_map(x = data_immo,
var = "dens.p_fac",
type = "typo",
border = "grey3",
lwd = 0.1,
pal=mypal2,
leg_title = "Classe P-value")
mf_title("P-value du coefficient de la densité de population")Idée : différentes variables peuvent varier à différentes échelles
\[Y_i = \sum_{k}\beta_k(u_i, v_i, b_k)X_{ki} + \epsilon_i\]
Combine GWR et modèles d’autocorrélation spatiale :
mgwrsar
Comment la statistique spatiale répond
Pour l’autocorrélation :
Pour la non-stationnarité :