Balade en statistique spatiale

Initiation tranquille à l’hétérogénéité spatiale

Grégoire Le Campion - UMR Passages CNRS

DECA 4 - 1er semestre 2026 05/01/2025

Introduction

Objectifs du cours

  • Comprendre les limites de la régression classique
  • Découvrir les enjeux de la dimension spatiale en analyse de données
  • Découvrir les concepts fondamentaux de la statistique spatiale
  • Appliquer ces méthodes avec R
  • Découvrir d’autres outils de représentation : Geoda et Magrit

Plan

  1. Rappels : Régression linéaire classique
  2. Introduction à la statistique spatiale
  3. L’autocorrélation spatiale
  4. Indicateurs locaux (LISA)
  5. L’hétérogénéité spatiale et la GWR
  6. (les autres méthodes de l’hétérogénéité spatiale)
  7. (Les modèles de régressions spatiale)

Environnement de travail

R : le moteur !

Rstudio : l’interface de travail

Geoda : fait pour l’autocorrelation et les LISA

Magrit : le logiciel de carto !

Github : accéder à tout le cours

Premier pas en codage !

Etape 1

Double-cliquer sur le fichier script_liste_packages.r

# Pour installer un package

install.packages("mapsf")

# Pour l'appeler dans r

library(mapsf)

Félicitaions

Bravo vous voilà développeurs !

Découverte des données

Charger les données tabulaires

On utilise le package here pour gérer les chemins d’accès, ici on spécifie où on se situe :

library(here)

csv_path <- here("data", "donnees_standr.csv")
immo_df <- read.csv2(csv_path)

#Pour visualiser les données
library(DT)

# datatable(head(immo_df, 10))

Charger des données spatiales

library(sf)


shp_path <- here("data", "EPCI.shp")
epci_sf <- st_read(shp_path)
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
# et la table attributaire correspondante, avec le package DT
 # datatable(head(epci_sf, 10))

Pour représenter vos données spatiale

library(mapsf)

# pour voir les données géographiques
mf_map(x = epci_sf)

Rappels : La Régression Linéaire

Qu’est-ce que la régression linéaire ?

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\]

  • \(\beta_0\) : ordonnée à l’origine
  • \(\beta_i\) : coefficients de régression
  • \(\epsilon\) : terme d’erreur

Ce que permet la régression linéaire

Objectifs

  • Quantifier les relations entre variables
  • Prédire des valeurs
  • Tester des hypothèses
  • Identifier les variables significatives

Exemple simple

Prix d’un bien immobilier :

  • Surface
  • Nombre de pièces
  • Localisation (code postal)

Visualisation du modèle

Visualisation du modèle 2

Interpréter les coefficients

Exemple : Y = 2 000 + 500 × X

  • a = 2 000 : salaire de base (avec 0 année d’expérience)
  • b = 500 : chaque année d’expérience augmente le salaire de 2 000 €

Prédiction : Une personne avec 5 ans d’expérience :

Y = 2 000 + 500 × 5 = 4 500 €

Visualisation du modèle de l’exemple

Qualité du modèle : R²

Coefficient de détermination

\[R^2 = 1 - \frac{\text{Variance résiduelle}}{\text{Variance totale}}\]

Interprétation :

  • Varie de 0 à 1
  • R² = 0,75 → le modèle explique 75% de la variabilité de Y

Interpréter 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.

Mise en pratique avec R

Représentater le modèle avec R

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()

Représentater le modèle avec R 2

Une autre manière de faire qui peut s’avérer pratique…

library(car)

car::scatterplot(prix_med ~ med_niveau_vis, data = immo_df, id = list(n=10)) 

 [1]   36   63   75  180  266  507  717  927 1133 1139

Créer le modèle statistique

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

Premiers résultats

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 :

library(gtsummary)

# En tableau
gtsummary::tbl_regression(lm1)
Characteristic Beta 95% CI p-value
med_niveau_vis 486 448, 524 <0.001
Abbreviation: CI = Confidence Interval

Condition de réalisation d’une régression

Les hypothèses de la régression classique

  1. Linéarité : relation linéaire entre Y et X
  2. Indépendance : les observations sont indépendantes
  3. Homoscédasticité : variance constante des résidus
  4. Normalité : distribution normale des résidus
  5. Pas d’autocorrélation : les résidus ne doivent pas être auto-corrélés
  6. Non-colinéarité : les variables explicatives ne sont pas trop corrélées

Limites de la régression classique (1/2)

Hypothèse d’indépendance des observations

En géographie, cette hypothèse est souvent violée :

  • Les phénomènes spatiaux sont rarement indépendants : on parle de la dépendance spatiale : autocorrélation des résidus.
  • “Tout est lié à tout, mais les choses proches sont plus liées que les choses éloignées” (Tobler, 1970)

Limites de la régression classique (2/2)

Hypothèse de stationnarité

La régression classique suppose des coefficients constants dans l’espace :

  • L’effet d’une variable est le même partout
  • Pas de prise en compte des variations locales
  • Inadapté pour des phénomènes géographiques hétérogènes et plus largement en SHS

Diagnostiquer la régression avec R

On complexifie notre modèle

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
GGally::ggcoef_model(mod.lm)

Exploration des variables en jeu

# Distribution de la variable dépendante :
hist(immo_df$prix_med, freq=FALSE, breaks = 35, col = "darkblue", border = "white", 
     main = "Prix median au m² par EPCI")

La linéarité de la relation entre VD et VI

library(car)

scatterplotMatrix(~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, smoother=loessLine,
                  main="Matrice de graphiques cartésiens")

La multicolinéarité entre les VI

Etude des corrélations

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)
library(see)
immo_cor %>%
  summary(redundant = FALSE) %>%
  plot(type="tile", show_labels =TRUE, show_p = TRUE, digits = 1, size_text=3)

La multicolinéarité entre les VI

Le VIF

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.

library(car)

vif(mod.lm)
                    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

Analyse des résidus

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).

par(mfrow=c(1,3))
plot(rstudent(mod.lm))
hist(rstudent(mod.lm), breaks = 20, col="darkblue", border="white", main="Analyse visuelle des résidus")
qqPlot(mod.lm)

[1]  36 266

Analyse des résidus 2

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\)

# Pour étudier la normalité on peut utiliser le test de Shapiro-Wilk
shapiro.test(mod.lm$residuals)

    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

Outliers

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.

immo_df[c(36,266),]
        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

Outliers 2

Voyons les conséquences si on relance la régression sans l’individu 266

mod.lm2 <- update(mod.lm, subset=-266)
summary(mod.lm2)

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
par(mfrow=c(1,2))
plot(rstudent(mod.lm2)) 
qqPlot(mod.lm2)

[1]  36 180
car::compareCoefs(mod.lm, mod.lm2, pvals = TRUE)
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
                                                

Introduction à la Statistique Spatiale

Pourquoi la statistique spatiale ?

Les données spatiales ont des propriétés particulières :

  • Position : les observations ont une localisation
  • Dépendance spatiale : proximité = similitude
  • Hétérogénéité spatiale : les processus varient dans l’espace

Les deux problèmes fondamentaux

1. Autocorrélation spatiale

Les observations voisines se ressemblent

Conséquence : violation de l’hypothèse d’indépendance

2. Non-stationnarité spatiale

Les relations varient dans l’espace

Conséquence : coefficients non constants

Comment la statistique spatiale répond

Pour l’autocorrélation :

  • Mesurer et tester l’autocorrélation spatiale
  • Modèles spatiaux (SAR, SEM, etc.)

Pour la non-stationnarité :

  • Régressions géographiquement pondérées (GWR)
  • Modèles locaux

Préparation des données en vue de la stat spatiale !

La jointure

# l'option all.x = TRUE permet de garder toutes les lignes de epci_sf,
# même celles qui n'ont pas de correspondance dans immo_df
data_immo <- merge(x = epci_sf, y = immo_df, by.x = "CODE_SIREN", by.y = "SIREN", all.x = TRUE)


nrow(immo_df)
[1] 1223
nrow(epci_sf)
[1] 1242
nrow(data_immo)
[1] 1242

Représenter

mf_map(x = data_immo)
mf_map(x = data_immo[is.na(data_immo$prix_med),], col = 'red', add = TRUE)

La nouvelle jointure

data_immo <- merge(x = epci_sf, y = immo_df, by.x = "CODE_SIREN", by.y = "SIREN")
nrow(data_immo)
[1] 1223
mf_map(x = data_immo)

# Pour exporter 

# st_write(data_immo, "data_immo.gpkg", driver = "GPKG")

La carte du prix de l’immobilier

# 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") #titre

La carte des résidus… ou montrer la structure spatiale

data_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") #titre

L’Autocorrélation Spatiale

Définition

L’autocorrélation spatiale mesure la similitude entre observations voisines :

  • Positive : les valeurs similaires sont proches
  • Négative : les valeurs dissimilaires sont proches
  • Nulle : distribution aléatoire dans l’espace

Représentation

La matrice de poids spatiaux (1/2)

Pour mesurer l’autocorrélation, il faut définir le voisinage :

Matrice W : qui est voisin de qui ?

  • \(w_{ij} = 1\) si i et j sont voisins
  • \(w_{ij} = 0\) sinon

La matrice de poids spatiaux (2/2)

Différentes définitions du voisinage

Voisinage avec R

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
# ce qu'on peut vérifier sur la carte :
neighbours1 <- data_immo[c(1,62,74,338,1135,1136,1137,1140),]
neighbours1$index <- rownames(neighbours1)
mf_map(x = neighbours1)
mf_label(x = neighbours1, var = "index")

Création de la matrice de voisinage

# 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.

L’indice de Moran (I de Moran)

\[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}\]

  • I > 0 : autocorrélation positive
  • I ≈ 0 : pas d’autocorrélation
  • I < 0 : autocorrélation négative

Interprétation de l’I de Moran

Test d’hypothèse :

  • H₀ : distribution spatiale aléatoire (I = 0)
  • H₁ : autocorrélation spatiale

En pratique :

  • Calcul de l’indice I
  • Test de significativité (permutations)
  • p-value pour rejeter H₀

Visualisation : Diagramme de Moran

Nuage de points : valeur vs. moyenne des voisins

Le prix de l’immobilier…

# 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") #titre

Moran avec R

library(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 
moran.plot(data_immo$prix_med_z,neighbours_epci_w, 
           labels = TRUE, 
           xlab="prix medians par epci", 
           ylab="moyenne du prix médians par epci des voisins")

Les Indicateurs LISA

Qu’est-ce que les LISA ?

LISA : Local Indicators of Spatial Association

  • Décomposition locale de l’I de Moran global
  • Identifie les clusters spatiaux locaux
  • Détecte les valeurs atypiques spatiales

L’indice de Moran local

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

Types de clusters (1/2)

Hot spots (HH)

Valeur élevée entourée de valeurs élevées

Cold spots (LL)

Valeur faible entourée de valeurs faibles

Outliers (HL)

Valeur élevée entourée de valeurs faibles

Outliers (LH)

Valeur faible entourée de valeurs élevées

Types de clusters (2/2)

Interprétation des LISA

Utilité :

  • Identifier des zones homogènes
  • Détecter des anomalies spatiales
  • Guider les politiques locales
  • Compléter l’analyse globale

Attention : tenir compte des tests de significativité !

LISA avec R

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")

L’Hétérogénéité Spatiale

Le problème de la non-stationnarité

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 !

Exemple conceptuel

Question : Quel est l’effet de la surface sur le prix immobilier ?

  • En centre-ville : effet fort
  • En périphérie : effet plus faible
  • En zone rurale : effet encore différent

➜ Un seul coefficient global est inadapté

Solutions possibles

  1. Variables d’interaction : limité et arbitraire

  2. Modèles par sous-zones : perte de continuité spatiale

  3. Geographically Weighted Regression (GWR) :

    • Coefficients variables dans l’espace
    • Continuité spatiale préservée
    • Exploration des variations locales

La GWR - Principes

Définition de la 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\]

  • \((u_i, v_i)\) : coordonnées spatiales de i
  • \(\beta_k(u_i, v_i)\) : coefficient variable dans l’espace

Principe de la GWR (1/2)

Pour chaque point i, on estime un modèle local :

  1. Définir un voisinage autour de i
  2. Pondérer les observations selon leur distance à i
  3. Estimer les coefficients pour i
  4. Répéter pour chaque observation

Principe de la GWR (2/2)

Chaque point a son propre modèle local

Comparaison GWR vs. OLS

Régression OLS

  • 1 coefficient par variable
  • R² global
  • Hypothèse de stationnarité

GWR

  • n coefficients par variable
  • n R² locaux
  • Variation spatiale explicite

Test : La GWR est-elle meilleure que l’OLS ?

Avantages de la GWR

  • Capture l’hétérogénéité spatiale
  • Permet une analyse locale fine
  • Exploration des variations spatiales
  • Meilleure capacité prédictive (souvent)

Limites et précautions (1/2)

Problèmes possibles :

  • Multicolinéarité locale : instabilité des coefficients
  • Sur-ajustement : bandwidth trop petit
  • Autocorrélation spatiale : résidus non indépendants
  • Interprétation : complexité accrue

Limites et précautions (2/2)

Bonnes pratiques :

  1. Toujours comparer avec un modèle OLS
  2. Vérifier la multicolinéarité locale
  3. Analyser les résidus (autocorrélation)
  4. Interpréter avec prudence
  5. Croiser avec expertise du terrain

La fonction de pondération

Fonction gaussienne (la plus courante) :

\[w_{ij} = \exp\left(-\frac{d_{ij}^2}{b^2}\right)\]

  • \(d_{ij}\) : distance entre i et j
  • \(b\) : bandwidth (largeur de bande)
  • Plus on s’éloigne de i, moins le poids est important

Le paramètre clé : bandwidth

Le bandwidth (b) détermine la taille du voisinage :

  • b petit : forte variation locale, risque de sur-ajustement
  • b grand : lissage, se rapproche du modèle global
  • b optimal : minimise un critère (AICc, CV)

Sélection automatique du bandwidth

Critère AICc (Akaike Information Criterion corrigé) :

  • Compromis ajustement / complexité
  • Correction pour petits échantillons
  • Sélection automatique possible

Cross-validation :

  • Validation croisée
  • Erreur de prédiction

Produire une GWR avec R

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).

class(data_immo)
[1] "sf"         "data.frame"
sp_data_immo <- as(data_immo, "Spatial")
class(sp_data_immo)
[1] "SpatialPolygonsDataFrame"
attr(,"package")
[1] "sp"

Calcul de la matrice des distances

La première étape est de calculer la distance entre toutes nos observations. Pour ce faire nous utiliserons la fonction gw.dist().

library(GWmodel)

# Pour construire la matrice de distances entre centroïdes des EPCI :
dm.calib <- gw.dist(dp.locat = coordinates(sp_data_immo))

Définition de la bande passante avec R

# 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 

Estimation du modèle

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)

Comparer les modèless

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

La GWR - Interprétation

Résultats de la GWR

Pour chaque observation, on obtient :

  1. Coefficients locaux pour chaque variable
  2. R² local : qualité de l’ajustement local
  3. Résidus : erreurs de prédiction
  4. Tests de significativité

Visualisation des coefficients

Cartographie des coefficients :

  • Une carte par variable explicative
  • Identifier les zones de forte/faible influence
  • Comprendre les variations spatiales des relations

Interprétation des premiers résultats

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 :

# Pour voir les différents éléments qui composent notre modèle de GWR
summary(mod.gwr_g)
              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 :

mod.gwr_g
   ***********************************************************************
   *                       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 

Visualiser les résultats

# 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)
# Pour voir les variables qui le constituent
names(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"                           

Représenter nos résidus de la GWR

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")

Etude des coefficients

# 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")

Contribution max

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")

Cartographier les R2

data_immo$r2local=mod.gwr_g$SDF$Local_R2

mf_map(x = data_immo, var = "r2local", type = "choro", pal= "Reds")
mf_title("R2 Locaux")

Les t-value et p-value

# 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")

P-value par variable

# 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")

VIII. Extensions de la GWR

MGWR : Multiscale GWR

Idée : différentes variables peuvent varier à différentes échelles

  • Bandwidth spécifique par variable
  • Certaines variables à effet global
  • D’autres à effet local

\[Y_i = \sum_{k}\beta_k(u_i, v_i, b_k)X_{ki} + \epsilon_i\]

GWRSAR : GWR avec autocorrélation

Combine GWR et modèles d’autocorrélation spatiale :

  • Gère simultanément hétérogénéité ET autocorrélation
  • Modèle plus complexe
  • Package R : mgwrsar