La régression géographiquement pondérée : GWR

Comment prendre en compte l’effet local du spatial en statistique

Frédéric Audard (UMR LETG, Université Bretagne Occidentale)

Grégoire Le Campion (UMR Passages, CNRS)

Julie Pierson (UMR LETG, CNRS)

featured

Cet article présente la réalisation d’une analyse de données à l’aide de la régression géographique pondérée ou GWR (Geographical Weighted Regression). La modélisation statistique dite “classique” présente des risques élevés lorsqu’on souhaite traiter des données spatiales. Des phénomènes de dépendance spatiale, des problèmes d’échelles d’application, des effets de contexte, ainsi qu’une forme d’hétérogénéité spatiale peuvent apparaître et compromettre les analyses effectuées, et engendrer des interprétations tronquées voire inexactes. Souvent ignorées, ces dimensions spatiales ne peuvent pas être considérées comme un simple aléa.

L’objectif de cet article est de vous présenter des méthodes et leurs applications qui vous permettront d’étudier concrètement les effets des dimensions spatiales des données. Il s’agira d’une part de caractériser la structure spatiale de données attributaires liées à des entités spatiales ; et d’autre part de mesurer l’effet statistique de l’information spatiale dans un modèle de régression visant à déterminer les facteurs explicatifs d’un phénomène.

L’analyse présentée ici a pour objectif d’être reproductible : les données spatiales utilisées proviennent de la base ADMIN-EXPRESS de l’Institut national de l’information géographique et forestière (IGN) ; les données statistiques, quant à elles, ont été construites à partir de la base des notaires de France, complétées par des données de l’Institut national de la statistique et des études économiques (INSEE) pour les variables explicatives.

Toutes les figures et cartes ont été produites dans le cadre de la rédaction de cet article, sauf mention contraire.


Prérequis : connaissances de base en analyse de données statistiques, avec au moins une compréhension de ce qu’est une corrélation et une régression.

Pourquoi la GWR ?

Lorsque l’on souhaite dépasser la simple caractérisation d’attributs liés à des individus statistiques, on fait appel à des méthodes de modélisation explicitant des relations statistiques. La méthode la plus employée, principalement en sciences sociales, pour mesurer et analyser la nature des relations et des effets entre deux ou plusieurs variables, est le modèle de régression linéaire.

La régression linéaire consiste à modéliser la variable que nous souhaitons étudier (aussi appelée variable dépendante, VD) comme une fonction linéaire des variables que nous aurons définies comme explicatives de la VD (aussi appelées variables indépendantes, VI). Lorsque l’on s’intéresse à un phénomène social observé sur une maille territoriale, la régression linéaire pose plusieurs problèmes :

Le premier est empirique. La régression linéaire nous permet d’obtenir des coefficients (appelés betas β) et des résidus (notés epsilon ε). Ces β représentent l’effet des VI sur la VD. Ils sont considérés comme globaux, sans variation. Autrement dit, les modèles de régression linéaire considèrent que les VI interviennent de la même manière et avec la même importance sur l’ensemble de notre jeu de données. Si cette hypothèse peut être validée sur des populations statistiques définies aléatoirement et sans effet de structure a priori des VI ou de la VD, elle n’est que rarement vérifiée sur des données spatiales (Feuillet et al. (2019), chapitre 8). En effet, les caractéristiques propres de chaque territoire (l’unicité de chaque lieu) impliquent que l’effet de structure constaté en un lieu n’est pas forcément valable en un autre lieu de l’espace.

Les données sont ainsi structurées :

  • Par des relations de proximité : il s’agit d’un effet de situation induit par la position relative des lieux les uns par rapport aux autres
  • Par des hétérogénéités propres aux lieux qui proviennent de caractéristiques géographiques diverses. Ces caractéristiques relèvent d’un effet de site.

Les individus statistiques, représentés par des unités spatiales, ne peuvent pas être envisagés comme indépendants et identiquement distribués.

Concernant les prix des valeurs foncières que nous détaillerons par la suite dans cet article, on peut comprendre que la proximité au littoral, très prégnante en certains points de l’espace, ne joue absolument aucun rôle dans d’autres lieux. De même, certaines caractérisations du monde rural n’interviennent plus lorsqu’on se situe dans des milieux fortement urbanisés. Ainsi, les données spatialisées sont soumises à l’hétérogénéité spatiale : l’effet des VI va varier en fonction de l’espace. Un coefficient qui serait global et uniforme pour mesurer un effet paraît plus simple et donc tentant, mais non pertinent en géographie ; sur ce point nous pouvons nous référer à l’article de Brunsdon, Fotheringham et Charlton (Brunsdon et al. 1996). Ce concept d’hétérogénéité dans l’espace se traduit en statistique par celui de non-stationnarité.

Le deuxième problème est statistique : chaque méthode statistique doit répondre à un certain nombre de conditions de validité. La régression linéaire ne fait pas exception. Quatre conditions doivent être validées pour qu’une régression linéaire puisse être effectuée sans que l’interprétation des résultats ne conduise à des raisonnements fallacieux dus à des estimateurs qui seraient biaisés :

  • Les individus statistiques doivent être indépendants
  • Les résidus doivent suivre une distribution normale
  • Il ne peut pas y avoir plus de VI que d’individus statistiques
  • Les estimateurs doivent présenter une multicolinéarité limitée

Si les deux dernières conditions ne trouvent pas de matérialisation spécifique sur des données spatiales, la première quant à elle concrétise un problème récurrent sur les données en géographie. Par leur nature même, les données spatiales ne peuvent pas remplir cette condition fondamentale pour une régression classique. La première loi de la géographie de Tobler : “everything is related to everything else, but near things are more related than distant things” en est une traduction tout à fait parlante.

Le troisième problème est lié aux effets de contexte : on ne peut pas étudier des données spatiales sans considérer que les individus statistiques (les objets spatiaux) appartiennent eux-mêmes à des agrégats qui ont une influence sur la variable à expliquer. Ainsi, certains attributs de l’agrégat vont avoir une influence sur l’entité spatiale de cet agrégat.

Le quatrième problème est lié à la problématique du MAUP (Modifiable Area Unit Problem). Il concerne un problème d’échelle d’application de la régression et peut conduire à des observations erronées. Une corrélation constatée à une échelle peut être uniquement liée à l’agrégation réalisée à cette échelle, mais aussi s’avérer erronée à une échelle plus fine, invalidant de fait la relation entre les phénomènes étudiés (Mathian, Piron 2001). Certaines agrégations peuvent également varier et cacher des relations entre individus (Bailey, Gatrell 1995).

La GWR ne répond pas à l’ensemble de ces problèmes mais va nous permettre de résoudre les deux premiers en intégrant la dimension spatiale des données tout en tenant compte de l’hétérogénéité (ou non-stationnarité) de leur effet.

Il existe d’autres méthodes en statistiques spatiales comme les régressions spatiales. Elles permettent de traiter entre autres les problèmes d’autocorrélation spatiale. Le schéma 1 permet de voir quelle utilisation nous pouvons faire de ces différentes méthodes et leur combinaison éventuelle. Nous ne présentons dans cet article qu’un seul chemin dans ce continuum méthodologique : le but est de présenter ici uniquement la GWR. D’autres méthodes peuvent cependant y être associées.

Les packages

Voici les packages que nous utiliserons :

# Chargement, visualisation et manipulation de la données
library(here)
library(DT)
library(dplyr)

# Analyse et représentation statistique
library(car)
library(correlation)
library(corrplot)
library(ggplot2)
library(gtsummary)
library(GGally)
library(plotly)

# Manipulation et représentation de la donnée spatiale (cartographie)
library(sf)
library(mapsf)
library(rgeoda) # permet en plus de calculer les indices d'autocorrélation spatiale
library(RColorBrewer)

# Calcul du voisinage et réalisation de la GWR
library(spdep)
library(GWmodel)

Vous pouvez vérifier l’installation des différents packages à l’aide des lignes de codes suivantes :

#  Packages nécessaires
my_packages <- c("here", 
                 "DT", 
                 "dplyr", 
                 "car", 
                 "correlation", 
                 "corrplot", 
                 "ggplot2", 
                 "gtsummary", 
                 "GGally", 
                 "plotly", 
                 "sf", 
                 "mapsf", 
                 "rgeoda", 
                 "RColorBrewer", 
                 "spdep", 
                 "GWmodel")

# Vérifier si ces packages sont installés
missing_packages <- my_packages[!(my_packages %in% installed.packages()[,"Package"])]

# Installation des packages manquants depuis le CRAN
if(length(missing_packages)) install.packages(missing_packages, 
                                              repos = "http://cran.us.r-project.org")

# Chargement des packages nécessaires
lapply(my_packages, library, character.only = TRUE)

1 Présentation et préparation des données

Nous avons cherché à traiter ici une variable présentant des caractéristiques spatiales fortes et qui rencontrent les deux problèmes exposés précédemment d’indépendance statistique et de non-stationnarité. Les prix de l’immobilier en France sont effectivement soumis à ces problèmes et peuvent être expliqués, au moins partiellement, par des variables quantitatives issues de données de l’INSEE. Nous avons traité l’information liée au prix de l’immobilier à l’échelle des établissements publics de coopération intercommunale (EPCI) pour nous assurer un nombre d’observations suffisant dans chaque entité spatiale.

  • Les données du prix de l’immobilier par EPCI (prix médian au m²) sont issues des ventes observées sur l’année 2018, extraites depuis la base de données des notaires de France par Frédéric Audard et Alice Ferrari. Ce fichier a été simplifié pour ne conserver que les variables d’intérêts parmi une cinquantaine
  • Les données statistiques proviennent de l’INSEE (année 2019) : 9 variables ont été choisies pour leur capacité à expliquer les variations des prix de l’immobilier, concernant la population, le logement et les revenus et niveaux de vie. Elles sont détaillées un peu plus bas.
  • Comme indiqué en introduction les données spatiales proviennent de la base ADMIN-EXPRESS de l’IGN en accès libre. La couche utilisée est celle des EPCI de la base ADMIN-EXPRESS-COG édition 2021 par territoire pour la France métropolitaine.

Les données statistiques et du prix de l’immobilier ont été regroupées dans un même fichier CSV donnees_standr.csv.

Ces données de prix de l’immobilier sont fréquemment transformées (passées au logarithme) pour permettre un meilleur ajustement du modèle. Il s’agit d’un positionnement théorique de recherche. Ici, le choix a été fait de conserver la structure statistique initiale de nos données, sans transformation, pour deux raisons :

  • Le passage au logarithme amoindrit le poids des valeurs extrêmes fortes. Cette transformation aurait donc pour conséquence de réduire le poids de Paris dans la compréhension de la spatialisation des prix de l’immobilier. Or, Paris joue un rôle prépondérant dans ce phénomène. Cette unité territoriale influence directement toute la couronne parisienne, mais également les métropoles reliées directement en transport ferré à grande vitesse, les communes alentour de ces métropoles et, par voie de conséquence, quasiment l’ensemble du territoire national
  • L’interprétation des coefficients ne peut plus se faire directement et complexifie la compréhension de nos résultats. Nous avons standardisé nos variables pour permettre de comparer nos coefficients et interpréter les grandeurs qu’ils représentent. Le passage au logarithme empêche cette interprétation directe.

Les données spatiales (géométrie des EPCI) sont au format shapefile dans le fichier EPCI.shp.

1.1 Chargement des données sur le prix de l’immobilier par EPCI



Télécharger les données

Pour rejouer l’analyse, décompressez les données contenues dans l’archive data.zip puis ouvrez le jeu de données donnees_standr.csv et la couche géographique EPCI.shp. La couche REGION.shp est utilisée à des fins d’habillage cartographique.

# On situe le dossier dans lequel se trouve nos données
csv_path <- here("data", "donnees_standr.csv")
# Lecture du CSV dans un dataframe
immo_df <- read.csv2(csv_path)
# Pour visualiser les 10 premières lignes
datatable(format(immo_df, digits = 1, scientific = FALSE))

Ce fichier est composé des 10 variables suivantes (les données datent de 2019 sauf prix médian 2018) :

  • SIREN : code SIREN de l’EPCI
  • prix_med : prix médian par EPCI à la vente (au m²)
  • perc_log_vac : % logements vacants
  • perc_maison : % maisons
  • perc_tiny_log : % petits logements
  • dens_pop : densité de population
  • med_niveau_vis : médiane du niveau de vie
  • part_log_suroccup : % logements suroccupés, cf. définition de l’INSEE
  • part_agri_nb_emploi : % agriculteurs
  • part_cadre_profintellec_nbemploi : % cadres et professions intellectuelles

La variable SIREN nous servira de “clé” pour joindre ces données statistiques aux données spatiales, la variable prix_med sera la variable que nous chercherons à expliquer (VD), et toutes les autres seront nos variables explicatives (VI).

1.2 Chargement des données géographiques : les EPCI de France métropolitaine

Ces données proviennent de la base ADMIN-EXPRESS-COG de l’IGN, édition 2021. Le format d’entrée est le shapefile que nous allons lire dans un objet sf :

# Lecture du shapefile en entrée dans un objet sf
shp_path <- here("data", "EPCI.shp")
epci_sf <- st_read(shp_path, quiet = TRUE)
# On vérifie dans les données qu'il existe un champ correspondant au code SIREN
# qui permettra de lier les données de immo_df à cet objet sf
datatable(epci_sf)
## Warning in instance$preRenderHook(instance): It seems your data is too big for
## client-side DataTables. You may consider server-side processing:
## https://rstudio.github.io/DT/server.html

Nous allons également charger une couche de régions qui nous servira d’habillage pour les cartes :

shp_path <- here("data", "REGION.shp")
region_sf <- st_read(shp_path, quiet = TRUE)

1.3 Jointure des données géographiques et tabulaires

On constate que les deux jeux de données n’ont pas exactement le même nombre de lignes. En effet, le jeu de données immo_df possède moins de lignes que l’objet sf epci_sf. Cela indique simplement que nous n’avons pas l’indication du prix médian de l’immobilier pour tous les EPCI de France métropolitaine.

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

Il peut être intéressant d’identifier et visualiser les EPCI qui n’ont pas de correspondance dans le tableau de données immo_df. Pour ce faire on réalise la jointure on conservant toutes les lignes de epci_sf :

# 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(data_immo)
## [1] 1242
# Filtre des données de la jointure pour ne voir que les EPCI sans correspondance dans immo_df
datatable(data_immo[is.na(data_immo$prix_med),])
plot(data_immo[is.na(data_immo$prix_med),]$geometry)

Cependant, la VD étant prix_med les lignes vides ne nous intéressent pas, nous ne les conserverons pas car elles pourraient poser problème lors de la réalisation de nos analyses. On refait la jointure en ne gardant que les EPCI ayant une correspondance dans le tableau de données :

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

On peut maintenant représenter les données du prix médian de l’immobilier par EPCI sous forme de carte :

mf_map(x = data_immo, 
       var = "prix_med", 
       type = "choro",
       breaks = "jenks",
       nbreaks = 9,
       pal = "Mint",
       lwd = 0.01,
       leg_title = "Discrétisation Jenks",
       leg_box_border = NA,
       leg_val_rnd = 0,
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#34282C",
       lwd = 1)
mf_title("Prix médian de l'immobilier au m² par EPCI")
mf_credits("Sources: Notaires de France 2018, IGN Admin Express")

Et avoir un aperçu rapide des variables explicatives issues de l’INSEE. Ces cartes suivent une discrétisation par quantiles.

# Correspondance entre le nom des variables et un intitulé plus lisible
noms_variables <- list('perc_log_vac' = '% logements vacants',
                       'perc_maison' = '% maisons',
                       'perc_tiny_log' = '% petits logements',
                       'dens_pop' = 'densité de population',
                       'med_niveau_vis' = 'médiane niveau de vie',
                       'part_log_suroccup' = '% logements suroccupés',
                       'part_agri_nb_emploi' = '% agriculteurs',
                       'part_cadre_profintellec_nbemploi' = '% cadres et professions intellectuelles')
# Création des cartes
par(mfrow = c(3,3))
for (var in colnames(data_immo)[6:13]) {
  mf_map(x = data_immo,
         var = var,
         type = "choro",
         breaks = "quantile",
         nbreaks = 7,
         pal = "Purples",
         lwd = 0.01,
         leg_pos = NA)
  mf_title(noms_variables[var])
  mf_credits('Sources: INSEE 2019, IGN Admin Express 2021')
}

Dans le cas où vous préféreriez manipuler vos données sous un format sp (package sp), ou dans le cas où ce format serait requis pour utiliser certains packages ou certaines formules, vous pouvez convertir votre objet sf en sp à l’aide de la ligne de commande suivante (nous en aurons besoin pour la suite) :

data_immo_sp <- as(data_immo, "Spatial")

2 Création du voisinage

Avant de procéder à nos différentes analyses, nous devons d’abord créer et définir le voisinage. Cette étape est absolument essentielle.

En effet, cette notion de voisinage est centrale en statistique spatiale, le principe de base étant que le voisinage a un effet sur les individus. Les choix qui seront faits dans la construction du voisinage impacteront de fait très fortement les résultats.

2.1 Voisinage

Nous ne développerons pas ici ce qu’implique la définition d’un voisinage. Pour cela, nous vous renvoyons vers les travaux de Sébastien Oliveau (Oliveau 2011).

Ici, il est important de savoir qu’un voisinage peut être de 3 types :

  • Basé sur la contiguïté
  • Basé sur la distance
  • Basé sur la proximité

Lorsque nous travaillons avec des polygones (comme c’est le cas ici), le plus souvent l’usage consiste à se baser sur une matrice de contiguïté. Il faut par ailleurs savoir qu’il existe plusieurs types de voisinages basé sur la contiguïté. Dans un cas classique nous utiliserons celui de type queen. Queen est une référence à la reine aux échecs, qui peut se déplacer dans toutes les directions. Nous considérons ici les voisins contigus à notre polygone de tous côtés. Il s’oppose au type rook qui fait référence à la tour, les voisins seront donc définis à partir des mouvements de cette pièce sur l’échiquier (dans toutes les directions sauf en diagonale).

R permet assez simplement de définir le voisinage.

# 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
neighbours_epci <- poly2nb(data_immo, queen = TRUE)
# Obtention des coordonnées des centroïdes
coord <- st_coordinates(st_centroid(data_immo))

Voici la représentation graphique du voisinage :

# Faire un graphe de voisinage
par(mfrow = c(1,1))
par(mai = c(0, 0, 0, 0)) # Pour réduire les marges de la figure
plot.nb(x = neighbours_epci, coords = coord, points = FALSE)

Pour comprendre ce que contient neighbours_epci :

# 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, border = "white")
# Pour voir les numéros
mf_label(x = neighbours1, var = "index", halo = TRUE)
# Pour ajouter les liens entre voisins, il faut convertir neighbours_epci en objet sf
neighbours_epci_sf <- spdep::nb2lines(neighbours_epci, coords=coord, proj4string="EPSG:2154", as_sf=T)
mf_map(x = neighbours_epci_sf[neighbours_epci_sf$i == 1,], col = "grey30", add = TRUE)

Nous précisons qu’un voisinage peut aussi tout à fait se calculer lorsque l’on n’a pas de polygones mais simplement des coordonnées (des points). Les matrices de distances sont alors souvent plus adaptées. Pour définir le voisinage il faut utiliser les fonctions knearneigh() et knn2nb().

2.2 Création de la matrice de voisinage

Une fois le voisinage défini nous pouvons créer une matrice de voisinage, qui permettra d’attribuer un poids à chaque voisin.

# La fonction nb2listw attribue un poids à chaque voisin
# par exemple 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 3e élément de neighbours_epci_w
# par exemple 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
# Cet élément a 7 voisins qui ont donc un poids de 1/7 soit ~0,14

3 Approche statistique “classique”

Nous pouvons donc commencer notre analyse.

Avant de se diriger vers une GWR, il est important de suivre la voie “classique”. Elle permet d’abord de mieux connaître et appréhender nos données mais surtout de vérifier que la méthode statistique classique ne parvient pas à rendre compte de la complexité du phénomène sans tenir compte de la dimension spatiale, et donc de justifier l’usage de la GWR.

Traditionnellement cela passe par trois étapes :

  1. Etude de la distribution des données.
  2. Analyse des corrélations
  3. Réalisation d’un modèle linéaire classique

N’étant pas le sujet de l’article nous passerons rapidement sur ces étapes et nous ne nous attarderons pas sur des considérations théoriques. Toutefois nous indiquerons des manières de les réaliser sur R et des éléments de lectures.

3.1 Exploration des variables

Cette première étape très importante permet d’étudier la distribution des données et d’identifier d’éventuels individus extrêmes qui pourrait venir perturber les résultats.

Ici par exemple nous serions en droit de nous poser la question de conserver dans notre jeu de données l’entité spatiale avec un prix médian à plus de 10 000 qui se détache très clairement de tous les autres EPCI.

Pour visualiser la distribution d’une variable quantitative l’histogramme est une bonne solution. Pour le réaliser nous utilisons dans ce cas le package plotly qui permet l’interactivité de la figure.

# Distribution de la variable dépendante :
add_histogram(plot_ly(data_immo, x = ~prix_med))

Il est important de réaliser également pour les VI cet histogramme que nous venons de faire pour la VD :

# Distribution des variables indépendantes :
a <- add_histogram(plot_ly(data_immo, x = ~(perc_log_vac), name = "perc_log_vac"))
b <- add_histogram(plot_ly(data_immo, x = ~(perc_maison), name = "perc_maison"))
c <- add_histogram(plot_ly(data_immo, x = ~(perc_tiny_log), name = "perc_tiny_log"))
d <- add_histogram(plot_ly(data_immo, x = ~(dens_pop), name = "dens_pop"))
e <- add_histogram(plot_ly(data_immo, x = ~(med_niveau_vis), name = "med_niveau_vis"))
f <- add_histogram(plot_ly(data_immo, x = ~(part_log_suroccup), name = "part_log_suroccup"))
g <- add_histogram(plot_ly(data_immo, x = ~(part_agri_nb_emploi), name = "part_agri_nb_emploi"))
h <- add_histogram(plot_ly(data_immo, x = ~(part_cadre_profintellec_nbemploi), name = "part_cadre_profintellec_nbemploi"))
fig = subplot(a, b, c, d, e, f, g, h, nrows = 2)
fig

3.2 Etude des corrélations

Très rapidement, la corrélation permet d’étudier le lien, la relation entre deux variables. La corrélation repose sur la covariance entre les variables.

Quand 2 variables covarient, pour un même individu, un écart à la moyenne pour une variable sera accompagné par un écart à la moyenne pour l’autre variable. Si les 2 écarts se font dans le même sens, on parle de corrélation positive, sinon il s’agit d’une corrélation négative.

La conception d’un modèle statistique doit absolument être le fruit d’une réflexion sur le choix des variables indépendantes (explicatives) et le choix de la méthode de régression. Pour définir un modèle de régression certaine règles doivent être respectées.

L’étude des corrélations peut apporter une aide précieuse dans cette réflexion. Elle pourra nous aider à choisir les variables à intégrer au modèle et dans le même temps à vérifier certaines des conditions de réalisation de notre régression.

Ainsi, une analyse des corrélations pourra vérifier :

  • l’existence d’un lien entre les variables indépendantes et la variable à étudier. En effet dans une régression linéaire, il est nécessaire d’avoir une relation linéaire entre la VD et les différentes VI.
  • la multicolinéarité des variables indépendantes. Les corrélations ne doivent pas être trop fortes entre les VI. Un coefficient > 0,7 en valeur absolue doit entraîner la suppression des variables concernées. Cela peut aussi être vérifié très efficacement avec le VIF (Variance Inflation Factor) mais peut se faire seulement après avoir lancé le modèle.
  • que toutes les variables d’intérêt sont bien présentes dans le modèle (sauf dans le cas où cela induirait une trop grande multicolinéarité).

Pour calculer une matrice de corrélation :

# On commence par créer un dataframe identique à immo_df mais sans la colonne SIREN
data_cor <- immo_df %>% select(-SIREN)
# On peut ensuite créer la matrice de corrélation
immo_cor <- correlation(data_cor, redundant = TRUE)
# Et afficher cette matrice
summary(immo_cor)
## # Correlation Matrix (pearson-method)
## 
## Parameter                        | part_cadre_profintellec_nbemploi | part_agri_nb_emploi | part_log_suroccup | med_niveau_vis | dens_pop | perc_tiny_log | perc_maison | perc_log_vac | prix_med
## -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## prix_med                         |                          0.55*** |            -0.45*** |           0.60*** |        0.59*** |  0.52*** |       0.49*** |    -0.60*** |     -0.68*** |  1.00***
## perc_log_vac                     |                         -0.31*** |             0.39*** |          -0.28*** |       -0.46*** | -0.21*** |      -0.19*** |     0.35*** |      1.00*** |         
## perc_maison                      |                         -0.58*** |             0.54*** |          -0.79*** |       -0.24*** | -0.46*** |      -0.75*** |     1.00*** |              |         
## perc_tiny_log                    |                          0.75*** |            -0.51*** |           0.87*** |        0.22*** |  0.62*** |       1.00*** |             |              |         
## dens_pop                         |                          0.59*** |            -0.29*** |           0.62*** |        0.18*** |  1.00*** |               |             |              |         
## med_niveau_vis                   |                          0.43*** |            -0.33*** |           0.15*** |        1.00*** |          |               |             |              |         
## part_log_suroccup                |                          0.65*** |            -0.43*** |           1.00*** |                |          |               |             |              |         
## part_agri_nb_emploi              |                         -0.57*** |             1.00*** |                   |                |          |               |             |              |         
## part_cadre_profintellec_nbemploi |                          1.00*** |                     |                   |                |          |               |             |              |         
## 
## p-value adjustment method: Holm (1979)

On peut aussi visualiser les corrélations de manière plus lisible à l’aide d’un corrélogramme, en utilisant le package corrplot :

mat_cor_comp <- summary(immo_cor, redundant = TRUE)

# Nom des lignes = valeurs de la première colonne ("Parameter")
rownames(mat_cor_comp ) <- mat_cor_comp[,1]

# Transformation du dataframe en objet matrice (+ suppression de la première colonne)
mat_cor <- as.matrix(mat_cor_comp[,-1])

# Calcul du nombre total d'individus
nb <- nrow(data_cor)

# Calcul des matrices de p-values et des intervalles de confiance
p.value <- cor_to_p(mat_cor, n = nb, method = "auto")

# Extraction de la matrice des p-values uniquement
p_mat <- p.value$p

# Affichage du corrélogramme
corrplot(mat_cor, 
         p.mat = p_mat, 
         type = "upper", 
         order = "hclust", 
         addCoef.col = "white", 
         tl.col = "gray",
         number.cex = 0.5,
         tl.cex= 0.7,
         tl.srt = 45, 
         col=brewer.pal(n = 8, name = "PRGn"), 
         sig.level = 0.000001, 
         insig = "blank", 
         diag = FALSE, )

L’étude des corrélations nous permet ici de confirmer une relation entre la variable à expliquer et toutes les variables explicatives définies. Par exemple, elle est négativement corrélée avec le pourcentage de logements vacants.

Elle met aussi à jour de très fortes multicolinéarités, ce qui pose problème. Par exemple, la part de logements suroccupés est fortement corrélée au pourcentage de petits logements. Dans le cadre de la définition d’un modèle linéaire classique, il faudrait sortir du modèle les variables explicatives qui sont trop fortement corrélées. Dans le cadre de cet article nous faisons le choix de conserver toutes les VI, cela fera sens au moment de la GWR.

3.3 Régression linéaire ou méthode des moindres carrés ordinaire (MCO)

3.3.1 Principe et réalisation de la régression linéaire

La régression linéaire est un des modèles les plus utilisés en sciences humaines et sociales (SHS), elle peut être simple (une seule variable explicative) ou multiple (plusieurs VI). Le principe n’est en réalité pas si complexe. La régression linéaire consiste à modéliser la covariation entre une variable à expliquer et une ou des variables explicatives.

Pour ce faire le modèle de régression va chercher à estimer les termes de l’équation de la droite de régression entre la variable à expliquer et la variable explicative. Cette équation va prendre la forme \(f(x)= ax + b + e_i\). Équation qui ressemble à la fonction affine \(f(x)= ax + b\), qui est étudiée en général au collège.

Si l’on cherche à modéliser la covariation entre le prix médian et le pourcentage de logements vacants, l’équation de la droite de régression est la suivante  : \(prixmed_i = a*perclogvac_i + b + e_i\).

\(a\) est le coefficient associé à la variable du pourcentage de logements vacants, \(b\) est la constante qui apparaîtra sous le nom d’intercept dans les résultats et enfin \(e\) qui correspond aux résidus (ce qui incarne l’écart au modèle).

Traditionnellement on va faire usage d’une régression linéaire lorsque l’on veut prédire les valeurs de la VD dans les cas où elle n’aurait pas été mesurée, ou si l’on souhaite comprendre les relations statistiques entre les variables.

Pour lancer le modèle de régression linéaire avec toutes les VI voici les lignes de commandes :

# Dans le fonctionnement sur R il est important de stocker la régression dans un objet.
# Pour lancer la régression on va utiliser la fonction lm() 
# dont les 2 lettres sont l'acronyme de linear model
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 = data_immo)
# On affiche les principaux résultats avec la fonction summary
summary(mod.lm)
## 
## 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 = data_immo)
## 
## 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    
## ---
## 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

Pour visualiser les résultats de manière plus agréable on peut aussi utiliser la fonction tbl_regression du package gtsummary :

mod.lm %>%
  tbl_regression(intercept = TRUE)
Characteristic Beta 95% CI1 p-value
(Intercept) 1,593 1,571, 1,615 <0.001
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
1 CI = Confidence Interval

On peut également visualiser graphiquement les coefficients des variables explicatives avec le package GGally :

GGally::ggcoef_model(mod.lm)

3.3.2 Interprétation des résultats

Pour interpréter les résultats, plusieurs éléments fournis par la commande summary(mod.lm) sont importants.

D’abord l’information fournie par Adjusted R-squared, il s’agit du \(R^2\) qui est le coefficient de détermination. Il est ici de \(0,77\) ce qui veut dire que 77 % de la variation du prix médian est expliqué par le modèle. Juste en-dessous, on a le p-value associé au modèle. S’il est inférieur à \(0,05\) on peut considérer qu’il est statistiquement significatif.

Ensuite, on peut regarder ce qu’il se passe au niveau des variables explicatives. La première colonne appelée estimate est le coefficient de régression associé à la variable explicative. Le signe va être très important car il va donner la direction de la relation (exactement comme pour les corrélations). Ici pour le pourcentage de logements vacants il est de \(-287\), ce qui veut dire que lorsque ce pourcentage augmente d’une unité alors le prix médian baisse de \(287€\).

La colonne \(Pr(>|t|)\) correspond à la p-value associée à ce résultat. Une fois encore s’il est inférieur à \(0,05\) alors on peut considérer le résultat comme statistiquement significatif. Dans notre cas on peut dire que la part d’agriculteurs, de cadres et professions intellectuelles dans le nombre d’emplois n’a pas un effet significatif sur le prix médian du logement à l’échelle de l’EPCI.

Nous allons tout de même conserver ces variables qui pourtant devraient être considérées comme non significatives ; les modèles de GWR utilisés par la suite montreront l’utilité de telles variables dont l’intérêt aurait pu être rejeté a priori, puisque leur significativité peut varier localement.

3.4 Diagnostic du modèle linéaire

3.4.1 Multicolinéarité

Un des enjeux les plus importants dans le cadre de régression multiple est de vérifier la multicolinéarité entre les variables explicatives. Le risque d’une trop grande colinéarité est de biaiser le modèle et notamment les estimations des erreurs type des coefficients de régression (et donc les t-value et p-value).

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.

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 coefficients obtenus avec gtsummary :

mod.lm %>%
  tbl_regression(intercept = TRUE) %>% add_vif()
Characteristic Beta 95% CI1 p-value VIF1
(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
1 CI = Confidence Interval, VIF = Variance Inflation Factor

On peut facilement représenter graphiquement les scores de VIF :

score_vif <- vif(mod.lm)
score_vif
##                     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
# Création du graphique
par(mar=c(3, 12, 3, 1), mgp=c(1, 0.4, 0)) # pour pouvoir lire les noms des variables
x = barplot(height = score_vif, main = "VIF Values", horiz = TRUE, col = "steelblue", las = 1)
# Ajout du seuil de 4
abline(v = 4, lwd = 3, lty = 2)
# Et de la limite de 3
abline(v = 3, lwd = 3, lty = 2)

Comme le laissait supposer l’étude des corrélations de nos variables, nous avons en effet une forte multicolinéarité entre certaines des variables explicatives. Selon le VIF nous devrions donc relancer notre modèle sans les variables fortement colinéaires, c’est à dire sans le pourcentage de logements suroccupés et sans celui de petits logements.

Pour commencer, on peut retirer du modèle la variable ayant le VIF le plus élevé à savoir le pourcentage de petits logements.

mod.lm2 <- lm(formula = prix_med ~ 
                perc_log_vac + 
                perc_maison + 
                dens_pop + 
                med_niveau_vis + 
                part_log_suroccup + 
                part_agri_nb_emploi + 
                part_cadre_profintellec_nbemploi, 
              data = data_immo)

summary(mod.lm2)
## 
## Call:
## lm(formula = prix_med ~ perc_log_vac + perc_maison + dens_pop + 
##     med_niveau_vis + part_log_suroccup + part_agri_nb_emploi + 
##     part_cadre_profintellec_nbemploi, data = data_immo)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1502.2  -226.3   -42.3   173.5  3535.9 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                      1592.549     11.597 137.329  < 2e-16 ***
## perc_log_vac                     -328.301     13.911 -23.601  < 2e-16 ***
## perc_maison                      -100.010     20.507  -4.877 1.22e-06 ***
## dens_pop                          161.783     15.750  10.272  < 2e-16 ***
## med_niveau_vis                    280.586     14.322  19.591  < 2e-16 ***
## part_log_suroccup                 237.069     22.792  10.401  < 2e-16 ***
## part_agri_nb_emploi                 2.696     15.370   0.175    0.861    
## part_cadre_profintellec_nbemploi  -77.744     19.098  -4.071 4.99e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 405.5 on 1215 degrees of freedom
## Multiple R-squared:  0.7623, Adjusted R-squared:  0.761 
## F-statistic: 556.8 on 7 and 1215 DF,  p-value: < 2.2e-16
vif(mod.lm2)
##                     perc_log_vac                      perc_maison 
##                         1.426783                         3.131469 
##                         dens_pop                   med_niveau_vis 
##                         1.850756                         1.527378 
##                part_log_suroccup              part_agri_nb_emploi 
##                         3.874580                         1.762159 
## part_cadre_profintellec_nbemploi 
##                         2.717634

mod.lm2 %>%
  tbl_regression(intercept = TRUE) %>% add_vif()
Characteristic Beta 95% CI1 p-value VIF1
(Intercept) 1,593 1,570, 1,615 <0.001
perc_log_vac -328 -356, -301 <0.001 1.4
perc_maison -100 -140, -60 <0.001 3.1
dens_pop 162 131, 193 <0.001 1.9
med_niveau_vis 281 252, 309 <0.001 1.5
part_log_suroccup 237 192, 282 <0.001 3.9
part_agri_nb_emploi 2.7 -27, 33 0.9 1.8
part_cadre_profintellec_nbemploi -78 -115, -40 <0.001 2.7
1 CI = Confidence Interval, VIF = Variance Inflation Factor

GGally::ggcoef_model(mod.lm2)

On note qu’au niveau global il y a peu de changements, le \(R^2\) a très légèrement baissé, on passe d’une variation expliquée à 77 % à un taux d’explication de 76 % et le modèle est toujours significatif. Les changements les plus importants se situent au niveau des effets partiels. L’effet de la part de cadres et de professions intellectuelles dans le nombre d’emplois sur le prix médian du logement est devenu significatif et on constate même que le VIF du pourcentage de logement suroccupés est passé sous le seuil critique.

3.4.2 Principe de parcimonie

Lors de la conception d’un modèle linéaire, nous sommes censés respecter un principe de parcimonie. Ce principe implique qu’un bon modèle a un nombre optimal de variables. Bref, qu’il ne s’embarrasse pas de variables non significatives.

Ce principe veut donc que nous retirions du modèle la variable part d’agriculteurs dans le nombre d’emplois.

La fonction step() permet de réaliser une régression pas à pas descendante (ou ascendante).

Dans le cas d’une régression descendante, le modèle initial comprend toutes les variables, comme pour la régression standard mais cette fois l’algorithme va retirer la variable ayant la plus faible contribution au modèle si la variation du \(R^2\) n’est pas significative en l’éliminant. La procédure va être répétée jusqu’à ce que toutes les variables conservées contribuent significativement à l’amélioration du \(R^2\). La régression descendante va donc retirer les variables non significatives une à une. Ainsi, le dernier modèle proposé doit contenir toutes les variables ayant une contribution significative au \(R^2\).

# L'argument "both" permet d'utiliser les deux méthodes : ascendante et ascendante
step(mod.lm2, direction = "both")
## Start:  AIC=14696.68
## prix_med ~ perc_log_vac + perc_maison + dens_pop + med_niveau_vis + 
##     part_log_suroccup + part_agri_nb_emploi + part_cadre_profintellec_nbemploi
## 
##                                    Df Sum of Sq       RSS   AIC
## - part_agri_nb_emploi               1      5060 199817567 14695
## <none>                                          199812507 14697
## - part_cadre_profintellec_nbemploi  1   2725360 202537867 14711
## - perc_maison                       1   3911245 203723752 14718
## - dens_pop                          1  17351111 217163618 14796
## - part_log_suroccup                 1  17792190 217604698 14799
## - med_niveau_vis                    1  63121115 262933622 15030
## - perc_log_vac                      1  91601521 291414028 15156
## 
## Step:  AIC=14694.71
## prix_med ~ perc_log_vac + perc_maison + dens_pop + med_niveau_vis + 
##     part_log_suroccup + part_cadre_profintellec_nbemploi
## 
##                                    Df Sum of Sq       RSS   AIC
## <none>                                          199817567 14695
## + part_agri_nb_emploi               1      5060 199812507 14697
## - part_cadre_profintellec_nbemploi  1   3211704 203029272 14712
## - perc_maison                       1   4155413 203972980 14718
## - dens_pop                          1  17567092 217384659 14796
## - part_log_suroccup                 1  18007058 217824626 14798
## - med_niveau_vis                    1  63117884 262935451 15028
## - perc_log_vac                      1  94962190 294779757 15168
## 
## Call:
## lm(formula = prix_med ~ perc_log_vac + perc_maison + dens_pop + 
##     med_niveau_vis + part_log_suroccup + part_cadre_profintellec_nbemploi, 
##     data = data_immo)
## 
## Coefficients:
##                      (Intercept)                      perc_log_vac  
##                          1592.55                           -327.82  
##                      perc_maison                          dens_pop  
##                           -99.01                            162.05  
##                   med_niveau_vis                 part_log_suroccup  
##                           280.55                            237.44  
## part_cadre_profintellec_nbemploi  
##                           -78.93

On observe ici que la variable part_agri_nb_emploi n’est pas conservée.

Notre nouveau modèle se définit donc ainsi :

mod.lm3 <- lm(formula = prix_med ~ 
                perc_log_vac + 
                perc_maison + 
                dens_pop + 
                med_niveau_vis + 
                part_log_suroccup + 
                part_cadre_profintellec_nbemploi, 
              data = data_immo)

3.4.3 Analyser les résidus

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 reposent grandement sur les résidus. Ils permettent en outre d’identifier les individus extrêmes (ou outliers).

Les 3 conditions qui concernent les résidus sont :

  • Ils doivent suivre une loi normale.
  • Ils ne doivent pas varier en fonction des variables explicatives. C’est l’hypothèse d’homoscédasticité, ils ont une variance homogène.
  • Ils ne doivent pas être spatialement autocorrélés.

Pour obtenir les résidus :

res_modlm <- mod.lm$residuals
datatable(cbind("Nom EPCI" = data_immo$NOM, data.frame("Résidu" = res_modlm))
          %>% format(digits = 2, scientific = FALSE))

On peut également les visualiser :

par(mfrow=c(1,3))
# Diagramme quantile-quantile qui permet de vérifier l'ajustement
# d'une distribution à un modèle théorique, ici la loi normale
qqPlot(mod.lm)
## [1]  36 266
# Histogramme pour donner une autre indication sur la normalité
hist(rstudent(mod.lm), breaks = 50, col="darkblue", border="white", main="Analyse visuelle des résidus")
# Un graphique pour visualiser l'homoscédasticité des résidus
plot(rstudent(mod.lm))

Si la voie graphique ne vous inspire pas il existe des tests statistiques qui permettent de vérifier la normalité des résidus ou 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

Dans les deux cas il nous faut rejeter H0, les résidus n’ont donc pas une distribution normale et il y a hétéroscédasticité de la variance des résidus.

Le modèle ne peut donc pas être analysé en l’état. Le problème de l’hétéroscédasticité des résidus indique un problème de spécification du modèle (par exemple une variable manquante).

3.4.4 L’autocorrélation spatiale des résidus

C’est la condition la plus difficile à vérifier et celle qui pose le plus problème.

Heureusement la géographie s’est dotée d’outils pour mesurer notamment l’autocorrélation spatiale. Nous espérons très fortement qu’il y ait une autocorrélation spatiale, ce qui rendrait notre modèle linéaire classique caduc mais nous permettrait de justifier l’utilisation de la régression spatiale.

Les deux outils connus au moins de nom par tous les géographes sont les tests de Moran et de Geary (ces 2 tests sont explicités un peu plus loin.

Dans la littérature le test de Moran semble être préféré à celui de Geary en raison d’une stabilité plus grande.

Test de Moran des résidus de la régression (Hypothèse nulle H0 : pas d’autocorrélation spatiale).

lm.morantest(model = mod.lm, 
             listw = neighbours_epci_w)
## 
##  Global Moran I for regression residuals
## 
## data:  
## model: 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 =
## data_immo)
## weights: neighbours_epci_w
## 
## Moran I statistic standard deviate = 21, p-value < 2.2e-16
## alternative hypothesis: greater
## sample estimates:
## Observed Moran I      Expectation         Variance 
##      0.359707408     -0.003517610      0.000299161

Test de Geary (Hypothèse nulle H0 : pas d’autocorrélation).

# Attention : pour avoir le  coefficient il faut faire 1-"Résultat test de Geary"
# (soit ici le coefficient est 0,67)
# Le coefficient de Geary s'étend de 0 à 2, 1 signifiant qu'il n'y a aucune corrélation
# Par ailleurs, un score < 1 implique une corrélation positive 
# et un score > 1 une corrélation négative.
geary(x = data_immo$prix_med, 
      listw = neighbours_epci_w,
      n = length(neighbours_epci), 
      n1 = length(neighbours_epci)-1, 
      S0 = Szero(neighbours_epci_w))
## $C
## [1] 0.3324317
## 
## $K
## [1] 18.24222

On voit dans les deux cas qu’il y aurait bien une autocorrélation spatiale. Cela implique deux choses très importantes :

  • La condition d’absence d’autocorrélation des résidus n’est pas vérifiée, le modèle classique n’est pas interprétable en l’état.
  • La dimension spatiale joue un rôle, nous pouvons justifier d’étudier de manière plus approfondie l’autocorrélation spatiale et l’usage de la GWR.

3.4.5 Cartographie des résidus de la régression

On intègre les résidus à la table attributaire de notre objet sf. A priori, comme on a utilisé nos données spatiales (sf) pour la régression les données sont classées dans le bon ordre.

data_immo$res_reg <- mod.lm$residuals

Pour cartographier les résidus, nous allons utiliser une discrétisation centrée sur 0, où l’intervalle de classe correspondra à l’écart-type/2. De plus, nous allons regrouper les classes extrêmes qui ne contiennent que peu d’individus.

Comme nous allons réutiliser cette méthode de discrétisation par la suite, centrée sur 0 ou sur la moyenne, nous en faisons une fonction discr qui renvoie en sortie les limites de classes, disponible ici.

Cette fonction permet, à partir d’une série de valeurs, d’une valeur centrale et d’un intervalle, d’obtenir les limites de classes correspondantes. Un paramètre permet également de choisir si la valeur centrale constitue une limite de classe ou bien le centre d’une classe. Enfin, il est possible de spécifier un nombre minimum d’individus par classe : les classes extrêmes seront fusionnées si besoin, jusqu’à obtenir une classe avec un nombre d’individus supérieur ou égal à ce seuil (mais si une classe “au milieu” des autres contient un nombre d’individus inférieur à ce seuil, elle sera conservée).

# Pour appeler la fonction discr contenue dans le fichier discr.R
source("discr.R")

Vous pouvez également utiliser le logiciel de cartographie en ligne Magrit pour calculer les limites de classe !

Nous pouvons maintenant appeler cette fonction sur les résidus, avec comme valeur centrale 0, et comme intervalle un demi écart-type. Les classes extrêmes seront regroupées afin de ne pas avoir de classes d’effectif < 5.

res_residus <- discr(values = data_immo$res_reg, 
                     center = 0, 
                     pos_center = "class_center", 
                     interval = sd(data_immo$res_reg)*0.5, 
                     min_nb = 10)
breaks_residus <- res_residus[[1]]
nb_cl_sup0_res <- res_residus[[2]]
nb_cl_inf0_res <- res_residus[[3]]

On peut maintenant faire la carte des résidus :

# On fonce légèrement la couleur de fond pour mieux voir la classe centrale
mf_theme("default", bg = "#dedede")
# Création d'une palette divergente avec une couleur neutre centrale
palette = mf_get_pal(n = c(nb_cl_inf0_res, nb_cl_sup0_res), 
                     pal = c("Teal", "Peach"), 
                     neutral = "#f5f5f5")
# Création de la carte
par(mfrow = c(1,1))
mf_map(x = data_immo, 
       var = "res_reg", 
       type = "choro", 
       border = NA,
       lwd = 0.1,
       breaks = breaks_residus,
       pal = palette,
       leg_title = "Valeur centrale =  0\nIntervalle = σ / 2",
       leg_box_border = NA,
       leg_val_rnd = 0,
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#B6B6B4",
       lwd = 1.5)
mf_title("Résidus de régression linéaire classique")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

# réinitialisation du thème
mf_theme("default")

La dimension spatiale semble jouer un rôle ; sans autocorrélation nous aurions eu une répartition aléatoire des résidus. Notre hypothèse repose sur l’existence d’une structure spatiale avec à la fois des effets de situation (Île-de-France par exemple) et des effets de sites (les littoraux). Certains écarts relèvent d’une réalité certainement plus complexe, comme les zones de montagne (Alpes et Pyrénées) qui correspondent à la fois à un effet de situation et de site.

4 Analyse de l’autocorrélation spatiale

La question de l’autocorrélation est centrale. Elle rend notre modèle linéaire inopérant.

À ce stade, nous pouvons nous demander ce que traduit - au-delà d’un problème statistique - l’autocorrélation spatiale. Elle traduit statistiquement la dépendance spatiale, identifiée précédemment comme condition de validité. Si une autocorrélation spatiale est constatée sur la VI, alors les conditions de validité minimales à l’application d’une régression linéaire ne sont pas remplies. L’autocorrélation spatiale mesure, dans les faits, l’intensité de la relation entre la proximité des lieux et leur degré de ressemblance.

Certains auteurs parlent d’effets spatiaux de premier ordre ou de deuxième ordre : la variation spatiale de premier ordre se produit lorsque les observations dans une région varient en fonction des propriétés locales de l’environnement. En revanche, la variation spatiale de second ordre résulte des interactions entre les observations (O’Sullivan, Unwin 2010).

Si l’autocorrélation spatiale est positive, mes données seront semblables à celles de mes voisins et dissemblables de celles des individus éloignés. À l’inverse si l’autocorrélation spatiale est négative, mes données seront différentes de celles de mes voisins et ressembleront davantage à celles des individus éloignés.

L’étude de l’autocorrélation spatiale est particulièrement intéressante car elle permet de mieux comprendre l’éventuelle structure spatiale du phénomène observé. C’est d’autant plus important que lorsqu’on constate une structure spatiale sous-jacente, la plupart des méthodes des statistiques classiques deviennent inadaptées.

L’analyse de l’autocorrélation spatiale se fait à deux niveaux :

  • Le niveau global
  • Le niveau local

4.1 Niveau global

Pour mesurer l’autocorrélation comme nous l’avons vu précédemment les deux outils les plus utilisés sont les test de Moran et de Geary.

L’indice de Moran va considérer les variances et covariances en tenant compte de la différence entre chaque et la moyenne de toutes les observations. L’indice de Geary de son côté prend en compte la différence entre les observations voisines.

Plus concrètement, l’indice de Moran est une retranscription directe du coefficient de corrélation qu’on peut avoir entre une VI et une VP ; on opère simplement un remplacement de la VP par la valeur moyenne des voisins de la VI. Il s’interprète donc comme un coefficient de corrélation.

Le nombre de voisins étant défini par une distance, il peut être variable en fonction des contextes. On appelle ça le biais de l’effet de bord (Anselin 1995). Le calcul présenté dans cet article prend en compte ce biais en standardisant la matrice de voisinage en ligne.

Concernant l’indice de Geary, il met en rapport d’une part l’écart entre la VI et la moyenne des voisins et d’autre part l’écart entre la VI et la moyenne de l’ensemble des observations. Un indice proche de 0 traduit ainsi une autocorrélation positive, un score proche de 2 une autocorrélation négative. Un score de 1 signifie qu’aucune autocorrélation n’est constatée.

Pourquoi utiliser un indice plutôt qu’un autre ? Ils traduisent sensiblement la même chose, mais diffèrent dans leur construction : Moran est une agrégation de logique locale quand Geary se rapporte à des écarts au global. Cela dit le coût de réalisation n’est pas très élevé et rien n’empêche de faire l’un et l’autre pour voir si les résultats sont cohérents.

Commençons par représenter sur une carte la variable du prix médian des logements par EPCI :

mf_map(x = data_immo, 
       var = "prix_med", 
       type = "choro",
       breaks = "quantile",
       nbreaks = 7,
       pal = "Mint",
       lwd = 0.01,
       leg_title = "Discrétisation par quantile",
       leg_box_border = NA,
       leg_val_rnd = 0,
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#34282C",
       lwd = 1.5)
mf_title("Prix médian de l'immobilier au m² par EPCI")
mf_credits("Sources: Notaires de France 2018, IGN Admin Express 2021")

Une structure spatiale apparaît de manière assez prégnante, ce qui est vérifié par les indicateurs statistiques :

# Pour l'occasion on va standardiser le 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)

Nous pouvons commencer par réaliser le test de Geary :

# Attention à la lecture particulière des résultats de l'indice de Geary
geary.test(data_immo$prix_med_z, 
           neighbours_epci_w, 
           zero.policy = TRUE, 
           randomisation = FALSE) 
## 
##  Geary C test under normality
## 
## data:  data_immo$prix_med_z 
## weights: neighbours_epci_w   
## 
## Geary C statistic standard deviate = 35.972, p-value < 2.2e-16
## alternative hypothesis: Expectation greater than statistic
## sample estimates:
## Geary C statistic       Expectation          Variance 
##      0.3324317242      1.0000000000      0.0003444045

On réalise ensuite le 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 est l’instruction utile pour préciser 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

Ce test d’autocorrélation se lit exactement comme un test de corrélation classique. On va donc s’intéresser au signe, à la grandeur du coefficient et à la p-value du test.

Ici on a donc une autocorrélation spatiale positive et importante.

Qu’implique l’existence de cette autocorrélation ?

Comme nous l’avons mentionné, l’autocorrélation spatiale, positive ou négative, décrit la relation d’une variable avec elle-même du fait de la localisation spatiale des observations.

Cette autocorrélation spatiale peut donc être :

  • Structurelle : les valeurs s’influencent dans l’espace et créent ainsi une structure spatiale sous-jacente. On contrevient ainsi à l’hypothèse d’indépendance statistique des individus. Cette autocorrélation peut être constatée autant sur une VI que sur des VP.
  • Contextuelle : le résultat d’un phénomène inobservé (un aléa) ou qu’on ne peut mesurer efficacement. Ce phénomène s’inscrit dans l’espace et de ce fait crée une structure spatiale. Les phénomènes de diffusion par exemple : il existe différents phénomènes sociaux de la sorte comme par exemple des phénomènes d’interaction ou de diffusion (comme les phénomènes de diffusion technologique). Ces phénomènes peuvent produire de l’autocorrélation spatiale.
  • Symptomatique : dans la définition du modèle statistique, la mesure de l’autocorrélation spatiale peut être envisagée comme un outil de diagnostic et de détection d’un “mauvais” (du point de vue statistique) modèle (variables non intégrées spatialement corrélées, erreurs sur le choix de l’échelle à laquelle le phénomène spatial est analysé, etc.)

Pour visualiser rapidement la structure spatiale on peut aussi réaliser un diagramme de Moran qui est complémentaire au test statistique.

moran.plot(data_immo$prix_med_z, neighbours_epci_w, 
           labels = TRUE, 
           xlab = "prix medians centrés réduits par epci" , 
           ylab = "moyenne du prix médian centré réduit par epci des voisins")

Comment lire ce diagramme ?

Il présente le nuage de points entre les valeurs d’une variable et les valeurs moyennes des voisins de chaque valeur de la variable initiale. Si les individus sont répartis aléatoirement dans l’espace alors c’est le signe d’une absence d’autocorrélation, la pente de la droite de régression sera donc de 0. Si au contraire la pente est non nulle, comme c’est le cas ici, c’est le signe de la présence d’une autocorrélation spatiale.

Un aspect important de ce diagramme est qu’il permet d’ores et déjà de caractériser les coefficients d’autocorrélation spatiale. Comme ce graphique présente des valeurs centrées-réduites, les valeurs sont centrées sur la moyenne qui est de 0. De ce fait, tous les points à droite de l’axe des ordonnées pour x = 0 auront une moyenne > 0 et ceux à gauche < 0. Cette réflexion s’applique également à l’axe des abscisses. Par convention on désigne les individus avec une moyenne > 0 par le terme high et ceux avec une moyenne < 0 par le terme low, au sens de supérieur ou inférieur à la moyenne.

Ainsi on peut découper ce diagramme en 4 quadrants. Les quadrants en haut à droite et en bas à gauche correspondent aux individus ayant une autocorrélation spatiale positive, c’est-à-dire une valeur proche de celle de leurs voisins.

  • Pour le quadrant en haut à droite on parle du quadrant High-High composé d’individus ayant une valeur de la variable plus élevée que la moyenne entourés d’autres individus qui leur ressemblent.
  • Pour le quadrant en bas à gauche on parle du quadrant Low-Low composé d’individus avec un score plus faible que la moyenne avec des voisins avec un score similaire.
  • Le quadrant en bas à droite est considéré comme le quadrant High-Low. On y retrouve des individus avec un score plus élevé que la moyenne sur la variable du prix médian mais avec un voisinage qui ne lui ressemble pas : autocorrélation spatiale négative mais score élevé à la variable.
  • Enfin, en haut à gauche on retrouve à l’inverse les individus avec une valeur du prix médian plus faible que la moyenne et un indice d’autocorrélation spatiale négatif. C’est le quadrant Low-High.
Diagramme de Moran avec découpage en 4 quadrants

Diagramme de Moran avec découpage en 4 quadrants

4.2 Niveau local

Les indices de Geary et Moran présentent une limite importante : ils partent du principe d’un processus spatial stationnaire, autrement dit global. Ainsi, l’effet de dépendance serait le même dans tout notre espace et la mesure fournie ne permet pas de différencier les contextes spatiaux locaux. De plus, l’effet de la dimension serait le même dans tout notre espace ; ce qui pose question et ce d’autant plus que l’emprise géographique augmente.

La compréhension de ces contextes locaux passe par la réalisation d’autocorrélation au niveau local, étape nécessaire pour continuer vers la GWR. Luc Anselin va développer le concept d’indicateur d’autocorrélation spatiale locale avec les LISA (Local Indicators of Spatial Association) (Anselin 1995).

D’après Luc Anselin, le LISA de chaque individu statistique indique l’intensité du regroupement spatial de valeurs similaires autour de cet individu. Autrement dit, un individu avec un LISA élevé va avoir une concentration autour de lui de voisins avec des valeurs similaires (pour nous par exemple un regroupement d’individus avec un prix particulièrement élevé ou à l’inverse particulièrement bas).

Le LISA le plus utilisé est le I de Moran local.

L’idée est de faire appel ici au LISA pour compléter la compréhension du niveau global par une approche locale. On cherche à la fois à détecter des clusters qui correspondent à un regroupement significatif de valeurs identiques dans une zone particulière, et à repérer des zones de non-stationnarité spatiale, c’est-à-dire qui ne suivent pas le processus global.

Nous allons calculer le I de Moran local sur nos données, grâce au package rgeoda développé également par Luc Anselin pour réaliser sur R les traitements de GeoDa.

# Calcul I de Moran local

# Pour utiliser la fonction local_Moran du 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 des bases de données
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)

Pour illustrer les Moran locaux on peut réaliser une carte :

# On récupère les moran locaux dans data_immo
data_immo$moranlocalvalue <- lisa_values(lisa)
# Discrétisation standardisée avec des classes de un demi-écart-type
res_moranloc <- discr(values = data_immo$moranlocalvalue, 
                      center = 0, 
                      pos_center = "class_break", 
                      interval = sd(data_immo$moranlocalvalue)/2, 
                      min_nb = 10)
breaks_moranloc <- res_moranloc[[1]]
nb_cl_sup0_moranloc <- res_moranloc[[2]]
nb_cl_inf0_moranloc <- res_moranloc[[3]]
# Création d'une palette associée
palette = mf_get_pal(n = c(nb_cl_inf0_moranloc, nb_cl_sup0_moranloc), 
                     pal = c("Teal", "Reds"))
# Création de la carte
mf_map(x = data_immo, 
       var = "moranlocalvalue", 
       type = "choro",
       breaks = breaks_moranloc,
       border = "gray",
       lwd = 0.2,
       pal = palette,
       leg_title = "Discrétisation standardisée\nvaleur centrale = 0\nintervalle = σ / 2", 
       leg_box_border = "gray",
       leg_val_rnd = 1,
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "gray",
       lwd = 1.5)
mf_title("Carte des LISA du prix médian du logement (Moran local)")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Si le score du Moran local est > 0 cela implique que l’on a un regroupement de valeurs similaires, plus faibles ou plus élevées que la moyenne. En revanche si le score est < 0 alors on a un regroupement de valeurs dissimilaires, par exemple des valeurs plus faibles entourées de valeurs plus fortes (centre de la Corse).

L’étude des p-values associées est également importante pour l’interprétation : une p-value < 0,05 des LISA apparaît comme significative et renvoie à des clusters de valeurs (similaires ou dissimilaires) qui sont plus marqués que ce l’on pourrait observer si la répartition spatiale du phénomène était considérée comme aléatoire.

# Carte des p-values des Moran locaux
data_immo$moranlocalpvalue<- lisa_pvalues(lisa)

# Pour plus de lisibilité dans la carte on va faire des classes des p-values
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.08, 
       pal = mypal, 
       leg_title = "p-value Moran local",
       leg_box_border = "grey3",
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "gray",
       lwd = 1.5)
mf_title("Carte de significativité des LISA")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Les regroupements que l’on observe vont pouvoir se rapprocher des 4 types que nous avions sur le diagramme de Moran (High-High, Low-Low, High-Low et Low-High).

Cette carte nous apporte donc une information supplémentaire sur les LISA en différenciant, parmi les clusters, les valeurs plus faibles ou plus fortes que la moyenne. On voit, sur cette carte, se dégager la diagonale du vide qui présente un cluster important des prix médians de l’immobilier faibles, qui se différencie des la Région parisienne, de la Côte d’Azur, de la Corse ou des Alpes du nord qui présentent des clusters de prix médians élevés..

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("Non significatif","Low-Low","Low-High","High-Low","High-High"),
       leg_title = "Lisa cluster",
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#34282C",
       lwd = 1.5)
mf_title("Regroupement des LISA")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Si on est en présence d’une autocorrélation spatiale au niveau global, les LISA pourront aussi servir d’indicateur d’instabilité locale. Ils indiquent à la fois des clusters locaux qui vont avoir un impact fort sur le processus spatial global (un score d’autocorrélation locale plus marqué que l’autocorrélation globale) ou à l’inverse des zones qui se démarquent du processus global (plus faible autocorrélation).

En revanche, s’il n’y a pas d’autocorrélation spatiale au niveau global les LISA vont nous permettre de détecter des zones où des valeurs semblables se regroupent de façon significative. Ils font émerger des structures au niveau local au sein desquelles les liens entre voisins seront particulièrement importants. On peut imaginer certaines configurations où l’autocorrélation spatiale globale ne paraisse pas significative, mais où ce score masque localement des clusters marqués qui restreignent la qualité des modèles de régression classique.

5 Régression géographiquement pondérée (GWR)

Bien que l’analyse d’autocorrélation spatiale soit riche en enseignements sur nos données, il reste l’étape de la modélisation pour prendre en compte les différents effets constatés. Comme nous l’avons vu, les données, lorsqu’elles sont spatialisées, sont souvent soumises aux phénomènes de dépendance et d’hétérogénéité spatiale.

Pour prendre en compte le phénomène de dépendance spatiale, on fait souvent appel aux régressions spatiales. Elles permettent à la fois de mieux comprendre la relation qui unit les variables explicatives à la variable étudiée d’une part ; et d’autre part de traiter la relation de la VD à son propre voisinage. Les régressions spatiales peuvent donc (en fonction du type de régression choisie) intégrer les caractéristiques du voisinage (de la VD ou des VI) pour expliquer la VD.

Ces méthodes ne sont pas l’objet du présent article et nous renvoyons les lecteurs et lectrices à d’autres références (Elhorst 2014).

Par ailleurs, l’hétérogénéité, qui renvoie à une instabilité, induit une variabilité spatiale de nos paramètres. L’idée est que les VI peuvent avoir un effet qui n’est pas le même partout dans l’espace. Dans ce cas nous optons pour la régression géographiquement pondérée (GWR).

Pour réaliser une GWR sur R plusieurs packages reconnus existent. On peut citer notamment le package spgwr et le package GWmodel. Nous choisirons d’utiliser ici le package GWmodel.

5.1 Calcul de la matrice des distances

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

# Le package GWmodel n'est pas compatible avec le format sf il a besoin d'un objet sp
# (contrairement à spgwr qui peut travailler avec un format sf)

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

5.2 Définition de la bande passante

La bande passante est une distance au-delà de laquelle le poids des observations est considéré comme nul. Le calcul de cette distance est très important car la valeur de la bande passante pourra fortement influencer notre modèle. La définition de la bande passante renvoie au type de pondération que nous souhaitons appliquer. Heureusement la fonction bw.gwr va choisir pour nous le résultat optimal…

Pour ce faire la fonction va se baser sur un critère statistique que l’utilisateur devra définir : le CV (Cross Validation ou validation croisée) ou le AIC (Akaike Information Criterion ou critère d’information d’Akaike). Elle reposera aussi sur un type de noyau qu’il faudra également définir : gaussien, exponentiel, bicarré, tricube ou encore Boxcar. Enfin, il sera également nécessaire de savoir si ce noyau pourra être adaptatif ou fixe.

Voici quelques informations pour guider nos choix :

  • Le critère CV a pour objectif de maximiser le pouvoir prédictif du modèle, le critère AIC va chercher un compromis entre le pouvoir prédictif du modèle et son degré de complexité. En général, le critère AIC est privilégié.
  • Avec un noyau fixe l’étendue du noyau est déterminée par la distance au point d’intérêt et il est identique en tout point de l’espace. Un noyau fixe est adapté si la répartition des données est homogène dans l’espace, l’unité de la bande passante sera donc une distance. Avec un noyau adaptatif l’étendue du noyau est déterminée par le nombre de voisins. Il est donc plus adapté à une répartition non homogène, l’unité sera alors le nombre de voisins.

Concernant la forme des noyaux :

  • Les noyaux gaussiens et exponentiels vont pondérer toutes les observations avec un poids qui tend vers zéro avec la distance au point estimé.
  • Les noyaux bisquare et tricube (dont les formes sont très proches) accordent également aux observations un poids décroissant avec la distance, mais par contre ce poids est nul au-delà de la distance définie par la bande passante.
  • Le noyau Boxcar traite un phénomène continu de façon discontinue.
Manuel de géographie quantitative [@feuillet_2019]

Manuel de géographie quantitative (Feuillet et al. 2019)

Sachant que, sur la forme du noyau, il est tout à fait possible de comparer deux pondérations et deux modèles de GWR.

Ici, nous allons tester avec un noyau gaussien, ce qui sera justifié un petit peu plus bas.

Pour éviter les répétitions la formule est stockée dans une variable au préalable.

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
# Définition de la bande passante (bandwidth en anglais) :
bw_g <- bw.gwr(data = data_immo_sp, 
              approach = "AICc", 
              kernel = "gaussian", 
              adaptive = TRUE, 
              dMat = dm.calib,
              formula = formula)
## 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
bw_g
## [1] 19

La bande passante est donc ici de 19 voisins, ce qui implique que les EPCI au-delà de cette “distance” auront un poids ramené à 0 et ne joueront donc plus de rôle dans la description de la relation statistique.

5.3 Estimation du modèle

Une fois la bande passante définie on peut lancer l’estimation du modèle de GWR.

# 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 (filtered = 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.
# Méthode 2 (filtered = FALSE) on diminue le poids des observations aux résidus élevés.
mod.gwr_g <- gwr.robust(data = data_immo_sp, 
                        dMat = dm.calib,
                        bw = bw_g,
                        kernel = "gaussian",
                        filtered = FALSE,
                        adaptive = TRUE,
                        formula = formula)

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 noyau :

bw_tri <- bw.gwr(data = data_immo_sp, 
              approach = "AICc", 
              kernel = "tricube", 
              adaptive = TRUE, 
              dMat = dm.calib,
              formula = formula)
## 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 = data_immo_sp, 
                   dMat = dm.calib,
                   bw = bw_tri,
                   kernel = "gaussian",
                   filtered = FALSE,
                   adaptive = TRUE,
                   formula = formula)

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

Le modèle utilisant un noyau gaussien a un meilleur pouvoir explicatif (\(R^2\)) et un meilleur score de qualité (\(AIC\)). Ce modèle est donc plus performant et dans notre situation c’est plutôt ce modèle qu’il faut privilégier.

5.4 Interprétation des premiers résultats

Comme pour le modèle linéaire classique, l’objet qui contient la GWR est composé de plusieurs éléments. Pour obtenir les 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: 2024-09-25 17:54:21.062216 
##    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: 2024-09-25 17:54:32.206493

Cette visualisation des résultats nous propose d’abord un rappel complet du modèle linéaire classique. Puis viennent ensuite les informations concernant la GWR. Le premier indicateur à analyser est le \(R^2 ajusté\) de la GWR, qui est nettement meilleur que celui de la régression linéaire multiple. On passe de \(77\ %\) de variance expliquée à \(91\ %\) avec la GWR. Ce \(R^2\), pour la GWR, est en fait une moyenne calculée des \(R^2\) de toutes les régressions locales réalisées par la GWR.

La seconde information qui nous intéresse particulièrement est les coefficients associés aux VI. Nous voyons qu’ils ne sont pas présentés de la même manière que ceux de la régression linéaire. En effet, chaque VI va avoir des coefficients en fonction du minimum, du maximum et des quartiles. Cela permet de rendre compte de la stationnarité de l’effet ou non. Dans notre cas on voit qu’il y a bien une variation et même dans certains cas une inversion des signes. Cela laisse supposer une non-stationnarité spatiale des effets : un effet local peut être présent qui ne suivrait pas l’effet global.

Par exemple, pour le pourcentage de logement vacant avec un coefficient global (modèle linéaire) de \(-287\), quand ce pourcentage augmente le prix médian baisse. En simplifiant, quand le pourcentage baisse d’une unité le prix médian augmente de \(287€\). Dans le cas de la densité de population on a un coefficient global positif donc une relation positive. La densité augmente donc le prix médian augmente. Ici, au global, quand la densité augmente d’une unité le prix médian augmente de \(173€\).

Les résultats de la GWR peuvent donc être lus à une échelle globale pour mesurer la pertinence du modèle ; mais également à des échelles locales : les résultats illustrent ainsi comment les coefficients varient en fonction des unités spatiales. Gardons l’exemple de la densité de population. Dans les lieux où le coefficient est à son minimum, c’est-à-dire \(-411\), on a une relation négative. Dans ces espaces, quand la densité augmente d’un écart-type le prix médian baisse de \(411€\).

Ensuite nous pouvons constater une inversion du signe du coefficient. Ainsi dans les EPCI du dernier quartile où le prix médian du logement est le plus élevé (par ex. Paris) le coefficient est positif. À son maximum une augmentation d’une unité entraîne une augmentation du prix de \(659€\). On a donc très clairement un effet de la densité qui ne sera pas du tout le même en fonction du lieu.

Nous pouvons également étudier l’intervalle interquartile. Ainsi, toujours pour la densité, ce résultat implique que pour 50 % des unités spatiales (EPCI entre quartile 1 et 3), une augmentation d’une unité de la densité va impliquer une augmentation du prix médian entre \(72€\) et \(268€\).

La cartographie va être la meilleure manière de représenter les betas (coefficients) et les différents indicateurs fournis avec la GWR, cela nous permet de décrire plus finement et plus précisément les phénomènes observés.

L’ensemble des données est stocké dans le sous-objet SDF du modèle. Il contient l’ensemble des informations du modèle associé à chaque donnée spatiale.

On peut le convertir en un dataframe pour le visualiser plus facilement. À l’origine il est au format “SpatialPointsDataFrame”.

# L'ensemble des données est stocké dans le sous objet SDF de notre modèle
# Pour voir à quoi il ressemble avec les noms des EPCI
datatable(cbind("Nom EPCI" = data_immo$NOM, mod.gwr_g$SDF@data)
          %>% format(digits = 2, scientific = FALSE))
## Warning in instance$preRenderHook(instance): It seems your data is too big for
## client-side DataTables. You may consider server-side processing:
## https://rstudio.github.io/DT/server.html
# 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"
# Intercept : c'est la constante c'est-à-dire le prix médian de référence
# nom de la variable : estimation du coefficient, du beta associé à la VI en chaque point.
# y : valeurs de la VD
# yhat : valeur de y prédite.
# residual, Stud_residual : résidu et résidu standardisé
# CV_score : score de validation croisée
# _SE : erreur standard de l'estimation du coefficient
# _TV : t-value de l'estimation du coefficient
# E_weight : poids des observations dans la régression robuste
# Local_R2 : R ²au niveau de chaque unité spatiale

5.4.1 Étude des résidus

Commençons par une étude des résidus afin de vérifier que cette fois ils n’ont pas de structure spatiale apparente.

# On récupère les résidus dans data_immo
res_gwr <- mod.gwr_g$SDF$Stud_residual
data_immo$res_gwr <- res_gwr
# calcul des limites de classes avec la fonction discr, centrées sur 0
res_resgwr <- discr(values = data_immo$res_gwr, 
                    center = 0, 
                    pos_center = "class_center", 
                    interval = sd(data_immo$res_gwr)*0.5, 
                    min_nb = 10)
breaks_gwr <- res_resgwr[[1]]
nb_cl_sup0_gwr <- res_resgwr[[2]]
nb_cl_inf0_gwr <- res_resgwr[[3]]
# Création de la palette correspondante
palette = mf_get_pal(n = c(nb_cl_inf0_gwr, nb_cl_sup0_gwr), 
                     pal = c("Teal", "Peach"), 
                     neutral = "#f5f5f5")
# Création de la carte des résidus
mf_map(x = data_immo, 
       var = "res_gwr", 
       type = "choro", 
       border = "gray", 
       lwd = 0.2, 
       breaks = breaks_gwr,
       pal = palette, 
       leg_title = "Discrétisation standardisée :\nvaleur centrale = 0\nintervalle = σ / 2",
       leg_box_border = "gray",
       leg_val_rnd = 1,
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "gray",
       lwd = 1.5)
mf_title("Résidus de la GWR")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Cette carte ne présente pas de structure spatiale marquée et nous amène à penser que nous avons expliqué l’ensemble des phénomènes spatiaux liés aux questions de prix de l’immobilier.

moran.test(data_immo$res_gwr,
           neighbours_epci_w,
           zero.policy = TRUE,
           randomisation = FALSE)
## 
##  Moran I test under normality
## 
## data:  data_immo$res_gwr  
## weights: neighbours_epci_w    
## 
## Moran I statistic standard deviate = 1.7079, p-value = 0.04383
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##      0.0289140229     -0.0008183306      0.0003030652

Le I de Moran à 0,02 tend à confirmer cette explication. Les individus conservant des résidus marqués (forts ou faibles), sont des outliers pour lesquels le modèle ne peut pas trouver d’explication mais ne correspondent plus à un effet de structure. On s’aperçoit cependant que le modèle s’avère performant pour expliquer les valeurs les plus faibles constatées (diagonale du vide) mais se révèle inadapté pour les valeurs extrêmes fortes (Paris, stations de ski alpines, stations balnéaires). Les écarts au modèle apparaissent de manière beaucoup plus prononcée pour les valeurs extrêmes fortes que pour les valeurs extrêmes faibles.

5.4.2 Étude des coefficients

Pour visualiser la non-stationnarité spatiale des effets des VI la solution la plus efficace est la carte.

# 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

# correspondance entre le nom des variables et un intitulé plus lisible
noms_coefs <- list('agri.coef' = '% agriculteurs',
                   'perc_maison.coef' = '% maisons',
                   'dens_pop.coef' = 'densité de population',
                   'med_vie.coef' = 'médiane niveau de vie',
                   'logvac.coef' = '% logements vacants',
                   'tinylog.coef' = '% petits logements',
                   'suroccup.coef' = '% logements suroccupés',
                   'cadre.coef' = '% cadres et professions intellectuelles')

Pour réaliser les cartes, avec une discrétisation standardisée centrée sur 0 :

par(mfrow = c(4, 2))
for (var in colnames(data_immo)[22:29]) {
  # calcul des limites de classe
  res <- discr(values = data.frame(data_immo)[, var], 
               center = 0, 
               pos_center = "class_center", 
               interval = sd(data.frame(data_immo)[, var])*0.5, 
               min_nb = 10)
  breaks <- res[[1]]
  # palette de couleurs
  nb_cl_sup0 <- res[[2]]
  nb_cl_inf0 <- res[[3]]
  if (nb_cl_inf0 > 0) {
    palette = mf_get_pal(n = c(nb_cl_inf0, nb_cl_sup0), 
                         pal = c("Teal", "Peach"), 
                         neutral = "#f5f5f5")
  } else { # cas de la médiane du niveau de vie où la valeur min est supérieure à 0
    palette = mf_get_pal(n = c(nb_cl_sup0), pal = c("Peach"), rev = TRUE)
  }
  # la carte
  mf_map(x = data_immo,
         var = var,
         type = "choro",
         border = "gray",
         lwd = 0.1,
         breaks = breaks,
         pal = palette,
         leg_pos = "left",
         leg_title = NA,
         leg_box_border = "gray",
         leg_val_rnd = 0)
  mf_title(noms_coefs[var])
}
Coefficients de la GWR (Sources : Notaires de France 2018, INSEE 2019, IGN Admin Express 2021)

Coefficients de la GWR (Sources : Notaires de France 2018, INSEE 2019, IGN Admin Express 2021)

Les cartes des betas vont illustrer la variation des effets en fonctions des entités spatiales et de leur voisinage. Dans notre cas on verra quels sont les EPCI où l’effet du coefficient est négatif et ceux où il est positif, c’est-à-dire dans quel EPCI la VI va entraîner une augmentation du prix médian et dans quel autre au contraire une diminution, toutes choses égales par ailleurs. Sachant que dans notre cas toutes les VI sont significatives, elles ont donc toutes un effet qui varie localement.

Au-delà de cette visualisation VI par VI, il peut être intéressant de savoir par EPCI quelle variable sera la plus explicative dans la relation à la VD et laquelle a l’impact le plus important. Nous avons donc réalisé une carte des contributions maximales par EPCI. Pour la réaliser nous nous sommes basés sur le t-value.

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 contribrution maxixmale
df <- as.data.frame(data_immo)
# On passe les t-values en valeur absolue pour voir la plus grande
# contribution dans un 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))
# Création de la carte
mf_map(x = data_immo, 
       var = "contribmax", 
       type = "typo", 
       pal = brewer.pal(6,'Set2'),
       border = "white",
       lwd = 0.2,
       leg_pos = NA)
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#736F6E",
       lwd = 1.5)
mf_legend(type = "typo",
          val = c("Densité de population", 
                  "% logements vacants", 
                  "% maisons", 
                  "médiane du niveau de vie",
                  "% logements suroccupés",
                  "% petits logements"),
          title = "",
          pal = brewer.pal(6,'Set2'),
          box_border = "white")
mf_title("Variables contribuant le plus à l’explication de la variabilité du prix médian au niveau de l'EPCI")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Au-delà de la non-stationnarité des VI, cette carte met en évidence un autre phénomène : dans un modèle linéaire classique, la sélection des VI se fait de manière itérative (ascendante, descendante ou mixte). L’inclusion d’une VI a pour conséquence d’en exclure d’autres qui présenteraient trop de multicolinéarité. Le phénomène mis en évidence ici repose sur le fait qu’en fonction du lieu, de la hiérarchie des VI et donc de leur pertinence au sein du modèle, n’est pas constante. Cette visualisation des données ouvre une perspective intéressante pour la suite. Il serait tout à fait pertinent de développer une méthode permettant d’effectuer un stepwise préalable à toute GWR valable pour chaque lieu individuellement. Un tel modèle traiterait dans sa totalité le problème de non-stationnarité.

Nous pouvons également cartographier les \(R^2\) locaux, ce qui fournit une indication sur les zones où la variabilité est la mieux expliquée.

data_immo$r2local <- mod.gwr_g$SDF$Local_R2

mf_map(x = data_immo, 
       var = "r2local", 
       type = "choro",
       breaks = "quantile",
       nbreaks = 11,
       pal= "Reds",
       border = "gray",
       lwd = 0.2,
       leg_title = "Discrétisation par quantile",
       leg_box_border = "gray",
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "gray",
       lwd = 1.5)
mf_title("R² locaux")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Nous avons choisi d’utiliser une palette de valeurs large pour représenter au mieux les différences de \(R^2\) locaux. Toutefois, cette carte peut paraître trompeuse ; tous les EPCI obtiennent une explication satisfaisante avec un R² local minimum de 0.68.

À partir des t-values on peut aussi étudier la significativité des effets sur le territoire. On peut ainsi calculer et cartographier un indicateur qui représenterait le nombre de VI dont l’effet est significatif sur chaque unité spatiale. Cela donne une bonne idée de la complexité du phénomène sur un espace donné (en effet sur un EPCI on peut avoir toutes les variables significatives, elle jouent donc sur cet espace toutes un rôle) et souligne l’importance d’avoir une carte par coefficient. Cette carte montre également qu’un modèle parfaitement adaptatif gagnerait en sobriété et donnerait un AIC plus satisfaisant en ne sélectionnant localement que les variables réellement significatives dans la relation.

# 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",
       border = "gray",
       lwd = 0.2,
       leg_box_border = "gray",
       leg_title = "Nombre de variables",
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "gray",
       lwd = 1.5)
mf_title("Nombre de variables expliquant significativement la variabilité des prix médians par EPCI (t-value)")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Il se peut que cela soit plus intéressant d’utiliser les p-values, notamment si vous avez moins de 200 individus. Dans le cas présent, les 2 cartes sont identiques puisque nous avons 1 223 individus.

# Les p-values ne sont pas fournis 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 obtenir
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",
       border = "gray",
       lwd = 0.2,
       leg_title = "Nombre de betas",
       leg_box_border = "gray",
       leg_adj = c(0, 1.5))
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "gray",
       lwd = 1.5)
mf_title("Nombre des betas significatifs par EPCI (p-value)")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Dans ce cadre, il est possible de réaliser une collection de cartes des p-values (ou t-values) comme ce qui a été fait pour les coefficients. L’intérêt est de voir où l’effet de la VI est significatif et où il ne l’est pas.

# Ici nous représenterons les p-values avec un découpage par classe de significativité
# et seulement les p-values de 2 VI
par(mfrow = c(1, 2))
# Par exemple les p-values 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 = "OrRd")
mf_map(x = data_immo, 
       var = "agri.p_fac", 
       type = "typo", 
       border = "grey3", 
       lwd = 0.1, 
       leg_box_border = "grey3",
       pal = mypal2, 
       leg_title = "p-value",
       leg_size = 0.8)
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#736F6E",
       lwd = 1.5)
mf_title("P-value du coefficient du % agriculteurs")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

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

mypal2 <- mf_get_pal(n = 5, palette = "OrRd")
mf_map(x = data_immo, 
       var = "dens.p_fac", 
       type = "typo", 
       border = "grey3", 
       lwd = 0.1, 
       leg_box_border = "grey3",
       pal=mypal2, 
       leg_title = "p-value",
       leg_size = 0.8)
mf_map(x = region_sf, 
       add = TRUE,
       type = "base",
       col = NA,
       border = "#736F6E",
       lwd = 1.5)
mf_title("P-value du coefficient de la densité de population")
mf_credits("Sources: Notaires de France 2018, INSEE 2019, IGN Admin Express 2021")

Ces cartes des p-values sont particulièrement importantes car elles nous donnent les endroits où l’effet est significatif. En effet, on sait que la VI a un effet global significatif, qu’elle a en plus une variabilité locale or localement elle n’est pas partout significative. Pour la part d’agriculteurs dans l’emploi, l’effet est significatif quasiment uniquement dans le Sud-Est.

Conclusion

Nous avons donc réalisé une analyse du prix médian de l’immobilier en France métropolitaine. La GWR nous semble être une méthode particulièrement intéressante, à la croisée de la statistique et de la géographie qui peut s’avérer très utile globalement en SHS pour essayer d’appréhender la complexité des phénomènes étudiés. Une autre ressource présentant ces méthodes est également disponible et permettra d’éclairer les lecteurs et lectrices sur un cas d’application plus local (Feuillet 2021).

En effet, nous avons vu la limite des statistique dites “classiques” pour appréhender des phénomènes avec une structure spatiale. Dans ce cadre, la GWR nous permet de dépasser cette limite de l’indépendance tout en s’intéressant à la variabilité des effets des variables en fonction de l’espace. C’est là son principal intérêt : elle permet à la fois de visualiser l’hétérogénéité spatiale tout en prenant en compte dans le modèle des effets de structure. Elle ne doit, par contre être utilisée qu’après une régression classique et une analyse d’autocorrélation spatiale permettant d’identifier une dépendance statistique.

Nous invitons les lecteurs et lectrices à consulter des cas d’application pour approfondir ces méthodes et l’interprétation de résultats (Helbich 2013 ; Bulteau J 2018).

Cette méthode présente également certaines faiblesses, qui constituent souvent des pistes d’amélioration :

  • La non-stationnarité n’est prise en compte que partiellement pour l’instant par les méthodes existantes ou les packages déjà élaborés. Une méthode redéfinissant pour chaque point de l’espace un groupe de variables explicatives différent garantirait certainement à la fois une meilleure explication (un \(R^2\) plus élevé) et une meilleure sobriété du modèle (un AIC plus faible).
  • La structure de la dépendance spatiale peut également être sujette à discussion : on peut imaginer que chaque point de l’espace présente une structure de la dépendance différente et qu’il soit pertinent de redéfinir, pour chaque point de l’espace, une forme de noyau et une bande passante différente (Comber et al. 2022).
  • L’autocorrélation spatiale n’est pour le moment pas prise en compte de manière concomitante avec la GWR. Il conviendrait de pouvoir coupler ces méthodes pour régler dans le même temps les différents problèmes liées aux structures spatiales des données (Schéma 1). Une des solutions pour répondre à ce problème serait de réaliser une GWR / MGWR de type SAR, SLX ou SDM en chaque point de l’espace. Cela permettrait d’associer les effets de dépendance spatiale à l’étude des effets de non-stationnarité. Ces méthodes ne sont pas encore implémentées et mériteraient de nouvelles investigations.

Les solutions qui permettraient de limiter les faiblesses de cette méthode nécessitent certainement de grosses capacités de calcul qui alourdiraient fortement la mise en œuvre des traitements statistiques.

La GWR n’est bien sûr pas la seule approche pour s’intéresser à l’aspect spatial de phénomènes et variables sociales. Il existe également d’autres méthodes telles que la GWR Lasso (Wheeler 2009) ou la GWR Multiscalaire (MGWR) (Fotheringam et al. 2017).

Dans un autre registre l’analyse territoriale multiscalaire (MTA) ou la MGWR (Lu 2014) peuvent également s’avérer extrêmement intéressantes et riches.

Bibliographie

ANSELIN, Luc, 1995. Local Indicators of Spatial Association—LISA. In : Geographical Analysis [en ligne]. 1995. Vol. 27, n° 2, pp. 93‑115. Disponible à l'adresse : https://doi.org/10.1111/j.1538-4632.1995.tb00338.x.
ANSELIN, Luc, IBNU, Syabri et YOUNGIHN, Kho, 2006. GeoDa: An Introduction to Sp3 condatial Data Analysis. [en ligne]. 2006. S.l. : s.n. Disponible à l'adresse : https://geodacenter.github.io/.
BAILEY, Trevor C. et GATRELL, Anthony C., 1995. Interactive spatial data analysis. S.l. : s.n.
BRUNSDON, Chris, FOTHERINGHAM, A. Stewart et CHARLTON, Martin E., 1996. Geographically Weighted Regression: A Method for Exploring Spatial Nonstationarity. In : Journal of the Royal Statistical Society: Series D (The Statistician) [en ligne]. 1996. Vol. 28, n° 4. DOI 10.1111/j.1538-4632.1996.tb00936.x. Disponible à l'adresse : https://doi.org/10.1111/j.1538-4632.1996.tb00936.x.
BULTEAU J, Le Boennec R., Feuillet T, 2018. Spatial Heterogeneity of Sustainable Transportation Offer Values: A Comparative Analysis of Nantes Urban and Periurban/Rural Areas (France). In : Urban Science [en ligne]. 2018. Vol. 2, n° 1, pp. 1‑14. Disponible à l'adresse : https://doi.org/10.3390/urbansci2010014.
COMBER, Alexis, BRUNSDON, Christopher, CHARLTON, Martin, DONG, Guanpeng, HARRIS, Richard, LU, Binbin, LÜ, Yihe, MURAKAMI, Daisuke, NAKAYA, Tomoki, WANG, Yunqiang et HARRIS, Paul, 2022. A Route Map for Successful Applications of Geographically Weighted Regression. In : Geographical Analysis [en ligne]. 2022. Vol. 55, n° 1, pp. 155‑178. Disponible à l'adresse : https://doi.org/10.1111/gean.12316.
DE BELLEFON, Marie-Pierre, LOONIS, Vincent et LE GLEUT, Renan, 2018. Codifier la structure de voisinage. In : EUROSTAT, Insee - (éd.), Manuel d’analyse spatiale [en ligne]. S.l. : s.n. pp. 33‑50. Disponible à l'adresse : https://www.insee.fr/fr/information/3635442.
ELHORST, J. Paul, 2014. Spatial Panel Data Models. In : SPRINGER (éd.), Spatial Econometrics. S.l. : s.n. pp. 37‑93.
FEUILLET, Thierry, 2021. SIGR 2021 - Atelier analyse spatiale (GWR) [en ligne]. 2021. S.l. : s.n. Disponible à l'adresse : https://sigr2021.github.io/gwr/.
FEUILLET, Thierry, COSSART, Étienne et COMMENGES, Hadrien, 2019. Manuel de géographie quantitative - Concepts, outils, méthodes. S.l. : s.n.
FOTHERINGAM, A. Stewart, YANG, Wenbai et KANG, Wei, 2017. Multiscale Geographically Weighted Regression (MGWR). In : Annals of the American Association of Geographers [en ligne]. 2017. Vol. 107, n° 6, pp. 1247‑1265. Disponible à l'adresse : https://doi.org/10.1080/24694452.2017.1352480.
HELBICH, Brunauer, M., 2013. Data-Driven Regionalization of Housing Markets. In : Annals of the Association of American Geographers [en ligne]. 2013. Vol. 103, n° 4, pp. 871‑889. Disponible à l'adresse : https://doi.org/10.1080/00045608.2012.707587.
LE CAMPION, Grégoire, 2021. Analyse des corrélations avec easystats. In : RZine [en ligne]. 2021. DOI 10.48645/QHAV-CB52. Disponible à l'adresse : https://rzine.fr/publication_rzine/20201127_glecampion_initiation_aux_correlations/.
LU, Harris, B., 2014. The GWmodel R package: further topics for exploring spatial heterogeneity using geographically weighted models. In : Geo-Spatial Information Science [en ligne]. 2014. Vol. 17, n° 2, pp. 85‑101. Disponible à l'adresse : https://doi.org/10.1080/10095020.2014.917453.
MATHIAN, Hélène et PIRON, Marie, 2001. Échelles géographiques et méthodes statistiques multidimensionnelles. In : SANDERS, Lena (éd.), Modèles en analyse spatiale. S.l. : Hermès - Lavoisier. pp. 61‑103.
O’SULLIVAN, David et UNWIN, David .J., 2010. Geographic Information Analysis. S.l. : s.n.
OLIVEAU, Sébastien, 2011. L’espace compte ! Mesurer les structures spatiales du changement social. Habilitation à diriger des recherches. S.l. : Université Aix-Marseille 1, UFR des sciences géographiques et de l’aménagement.
WHEELER, David C., 2009. Simultaneous Coefficient Penalization and Model Selection in Geographically Weighted Regression: The Geographically Weighted Lasso. In : Environment and Planning A [en ligne]. 2009. Vol. 41, n° 3, pp. 722‑742. Disponible à l'adresse : https://doi.org/10.1068/a40256.

Annexes

Info session

setting value
version R version 4.4.1 (2024-06-14 ucrt)
os Windows 10 x64 (build 19045)
system x86_64, mingw32
ui RTerm
language (EN)
collate French_France.utf8
ctype French_France.utf8
tz Europe/Paris
date 2024-09-25
pandoc 3.1.11 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
package ondiskversion source
car 3.1.2 CRAN (R 4.4.1)
carData 3.0.5 CRAN (R 4.4.1)
correlation 0.8.5 CRAN (R 4.4.1)
corrplot 0.94 CRAN (R 4.4.1)
digest 0.6.35 CRAN (R 4.4.0)
dplyr 1.1.4 CRAN (R 4.4.0)
DT 0.33 CRAN (R 4.4.0)
GGally 2.2.1 CRAN (R 4.4.1)
ggplot2 3.5.1 CRAN (R 4.4.0)
gtsummary 2.0.1 CRAN (R 4.4.1)
GWmodel 2.3.3 CRAN (R 4.4.1)
here 1.0.1 CRAN (R 4.4.0)
mapsf 0.9.0 CRAN (R 4.4.0)
plotly 4.10.4 CRAN (R 4.4.0)
RColorBrewer 1.1.3 CRAN (R 4.4.0)
Rcpp 1.0.13 CRAN (R 4.4.1)
rgeoda 0.0.10.4 CRAN (R 4.4.1)
robustbase 0.99.4 CRAN (R 4.4.1)
sf 1.0.17 CRAN (R 4.4.1)
sp 2.1.4 CRAN (R 4.4.0)
spData 2.3.1 CRAN (R 4.4.1)
spdep 1.3.5 CRAN (R 4.4.1)

Citation

Audard F, Le Campion G, Pierson J (2024). “La régression, géographiquement pondérée : GWR.” Rzine. doi:10.48645/wk1m-hg05, https://doi.org/10.48645/wk1m-hg05,, https://rzine.fr/articles_rzine/20240930_gwr/.

BibTex :

## @Article{,
##   title = {La régression géographiquement pondérée : GWR},
##   subtitle = {Comment prendre en compte l'effet local du spatial en statistique},
##   author = {Frédéric Audard and Grégoire {Le Campion} and Julie Pierson},
##   journal = {Rzine},
##   doi = {10.48645/wk1m-hg05},
##   url = {https://rzine.fr/articles_rzine/20240930_gwr/},
##   keywords = {FOS: Social sciences},
##   language = {fr},
##   publisher = {FR Collège international des sciences territoriales},
##   year = {2024},
##   copyright = {Creative Commons Attribution Share Alike 4.0 International},
## }

licensebuttons cc