Formation au carroyage et lissage spatial sur R

Kévin Milin - Solène Colin

1. Introduction

1.1 Objectifs du TP

  • En 2018, création du package R btb (PSAR Analyse Urbaine, Arlindo Dos Santo et François Sémécurbe).
  • Sa principale fonction, btb_smooth, permet de réaliser très facilement un carroyage et un lissage sur des données géolocalisées.

À partir de données ponctuelles, nous allons apprendre, en utilisant le langage R :

  • À carroyer les informations.
  • À réaliser des lissages :
    • de densité,
    • de moyennes,
    • de taux,
    • quantiles.
  • À calculer un indicateur sur une zone à façon

Liens utiles

1.2 Avertissements

Secret statistique

Avant toute diffusion auprès des partenaires, il faut bien veiller à respecter :

  • le secret
    • primaire
    • secondaire
    • fiscal
  • les conventions établies avec les fournisseurs des données

Qualité des cartes

Pour simplifier : on prend des libertés avec la sémiologie cartographique

Auteur : Timothée Giraud, auteur de la librairie mapsf

Système de projection

Nom Description Code EPSG
Lambert93 Système de projection officiel pour la métropole 2154
LAEA Système de projection européen 3035
WGS84 GPS (utile pour utiliser Leaflet) 4326

2. Configurations

2.1 Chargement des librairies

Pour ce TP, 5 librairies principales sont nécessaires :

  • sf pour manipuler des fichiers spatiaux (importer des .shp, transformer des projections, et réaliser des géotraitements) ;
  • dplyr pour le traitement des données, en particulier l’agrégation géographique ;
  • mapsf pour réaliser des cartes ;
  • mapview (reposant sur leaflet) pour réaliser des cartes interactives ;
  • btb pour le carroyage et lissage.

Charger les librairies nécessaires

## Liste des librairies utilisées
packages <-  c("sf", "dplyr", "mapsf", "leaflet", "mapview", "btb")

## Vérifier si la librairie est installée, si non l'installer, puis la charger
package.check <- lapply(
  packages,
  FUN = function(x) {
    if (!require(x, character.only = TRUE)) {
      install.packages(x, dependencies = TRUE, quiet = TRUE)
      library(x, character.only = TRUE)
    }
  }
)

2.2 Chargement de la base

Base « Demandes de Valeurs Foncières »,

  • Produite par la Direction générale des finances publiques (actes notariés).

Elle recense :

  • les ventes de biens fonciers (bâtis ou terrains),
  • au cours des 5 dernières années,
  • hors Mayotte et Alsace-Moselle.

Constitution de la base à partir de cette source

  • Uniquement sur le périmètre de la petite couronne parisienne
  • Pour l’année 2021.

8 variables utilisées

  • id_mutation : identifiant unique de la vente
  • date_mutation : date de la vente
  • type_local : appartement ou maison
  • nombre_pieces_principales : nombre de pièces dans le logement
  • valeur_fonciere : prix de vente
  • surface_reelle_bati : surface du logement
  • x : longitude (en projection Lambert 93)
  • y : latitude (en projection Lambert 93)

Remarques

Chargement

Importation de la base ventesImmo_couronneParis.RDS, stockée sous Minio.

Téléchargement via URL :

# Charger la source de données
url_bucket <- "https://minio.lab.sspcloud.fr/projet-formation/r-lissage-spatial/"
object <- "ventesImmo_couronneParis.RDS"

url_file <- url(paste0(url_bucket, object))
dfBase <- readRDS(url_file)

Manipulation de notre base chargée en mémoire

dim(dfBase) ; head(dfBase, 3)
[1] 34489     8
  id_mutation date_mutation  type_local nombre_pieces_principales
1 2021-447023    2021-01-08 Appartement                         3
2 2021-447024    2021-01-05 Appartement                         2
3 2021-447025    2021-01-08 Appartement                         3
  valeur_fonciere surface_reelle_bati        x       y
1          480000                  64 647357.3 6868635
2          345000                  43 644483.5 6867695
3          384000                  41 648001.8 6866153

2.3 Chargement et préparation des données cartographiques

Chargement du fond de carte du territoire à étudier

  • Chargement de la couche vectorielle des départements de la petite couronne parisienne.
  • Type de fichiers : .shp ou .gpkg (format recommmandé, voir ici)
# Récupération du fond de carte grâce à st_read
chemin_file <- paste0(url_bucket, "depCouronne.gpkg")
depCouronne_sf <- st_read(chemin_file)
Reading layer `depCouronne' from data source 
  `https://minio.lab.sspcloud.fr/projet-formation/r-lissage-spatial/depCouronne.gpkg' 
  using driver `GPKG'
Simple feature collection with 4 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 637307.1 ymin: 6843303 xmax: 671686 ymax: 6879253
Projected CRS: RGF93 v1 / Lambert-93

Visualisation du fond de carte

depCouronne_sf
Simple feature collection with 4 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 637307.1 ymin: 6843303 xmax: 671686 ymax: 6879253
Projected CRS: RGF93 v1 / Lambert-93
  code           libelle reg surf                           geom
1   75             Paris  11  105 MULTIPOLYGON (((660897 6860...
2   92    Hauts-de-Seine  11  176 MULTIPOLYGON (((648796 6847...
3   93 Seine-Saint-Denis  11  237 MULTIPOLYGON (((659428 6861...
4   94      Val-de-Marne  11  245 MULTIPOLYGON (((656908 6846...
plot(depCouronne_sf$geom)
#On renomme la variable  `geom`  en `geometry`
depCouronne_sf <- depCouronne_sf %>% rename(geometry = geom)

Contours de Paris

# Sélection de Paris
paris_sf <- depCouronne_sf[depCouronne_sf$code == "75", ]
paris_sf
Simple feature collection with 1 feature and 4 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 643076 ymin: 6857499 xmax: 660897 ymax: 6867034
Projected CRS: RGF93 v1 / Lambert-93
  code libelle reg surf                       geometry
1   75   Paris  11  105 MULTIPOLYGON (((660897 6860...
plot(paris_sf$geometry)

Projections

# Connaître le système de correction d'une couche cartographique
st_crs(depCouronne_sf)$epsg ; st_crs(paris_sf)$epsg
[1] 2154
[1] 2154
  • Si besoin, on reprojette les deux bases dans le même système de projection.
  • Ici, en Lambert 93 (epsg 2154)
depCouronne_sf <- st_transform(depCouronne_sf, crs = 2154)
paris_sf <- st_transform(paris_sf, crs = 2154)

Territoires englobants

☠️ Eviter les effets de bord

➡️ Toujours électionner des données à lisser au-delà de la zone d’intérêt (ici Paris intramuros)

Méthode 1 : sélection géométrique [1/4]

  • Utiliser nos données individuelles comme un ensemble de points géolocalisés
  • Procéder à des intersections géographiques.

Avantages / Inconvénients

  • ✔️ court et logique à coder
  • potentiellement lourd d’un point de vue calculatoire.

Méthode 1 : sélection géométrique [2/4]

  1. On transforme nos observations en points vectoriels ;
sfBase <- dfBase %>% mutate(lon = x, lat = y) %>% 
  st_as_sf(coords = c("lon", "lat"), crs = 2154)
  1. On crée une zone tampon (buffer) autour du territoire d’intérêt, avec une marge (ici 2 000m), sous la forme d’un objet sf vectoriel ;
buffer_sf <- st_buffer(paris_sf, dist = 2000)

Remarque: Pour la zone tampon, prendre une marge légèrement plus grande que le rayon de lissage envisagé.

Méthode 1 : sélection géométrique [3/4]

  1. On repère les observations comprises dans cette zone tampon par intersection géographique.
sfBase_filtre <- st_join(sfBase, buffer_sf, left = FALSE)
head(sfBase_filtre, 3)
Simple feature collection with 3 features and 12 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 645132.7 ymin: 6864646 xmax: 648001.8 ymax: 6866153
Projected CRS: RGF93 v1 / Lambert-93
  id_mutation date_mutation  type_local nombre_pieces_principales
3 2021-447025    2021-01-08 Appartement                         3
5 2021-447029    2021-01-05 Appartement                         2
6 2021-447030    2021-01-15 Appartement                         3
  valeur_fonciere surface_reelle_bati        x       y code libelle reg surf
3          384000                  41 648001.8 6866153   75   Paris  11  105
5          407200                  24 646929.9 6864730   75   Paris  11  105
6         1040000                  90 645132.7 6864646   75   Paris  11  105
                  geometry
3 POINT (648001.8 6866153)
5 POINT (646929.9 6864730)
6 POINT (645132.7 6864646)

Méthode 1 : sélection géométrique [4/4]

Zone, buffer et 2000 points tirés aléatoirement.

code du schema explicatif
# Échantillon (en dehors et dans le buffer)
sfBase_sample <- sfBase[sample(1:nrow(sfBase), 2000), ] # échantillon 2000 obs
sfBase_filtre_sample <- st_join(sfBase_sample, buffer_sf, left = F) 

# Cartographie pédagogique
mf_map(buffer_sf, col = "yellow")
mf_base(paris_sf, col = "blue", add = T)
mf_base(sfBase_sample, col = "red", add = T)
mf_base(sfBase_filtre_sample, col = "green", add = T)

Méthode 2 : sélection non-géométrique [1/4]

  • filtrer les x et y compris dans un grand rectangle englobant Paris intramuros

Avantages / Inconvénients

  • ✔️ très efficace computationnellement (peut-être utilisée en première étape avant de repasser à la méthode géométrique)

  • mais requiert de construire au préalable un rectangle adapté…

Méthode 2 : sélection non-géométrique [2/4]

  • Création d’une bbox autour du territoire…
bbox <- st_bbox(paris_sf) ; bbox
   xmin    ymin    xmax    ymax 
 643076 6857499  660897 6867034 
  • … puis buffer autour de celle-ci (marge : 2 000m)
marge <- 2000
bufferBbox <- bbox
bufferBbox[["xmin"]] <- bufferBbox[["xmin"]] - marge
bufferBbox[["xmax"]] <- bufferBbox[["xmax"]] + marge
bufferBbox[["ymin"]] <- bufferBbox[["ymin"]] - marge
bufferBbox[["ymax"]] <- bufferBbox[["ymax"]] + marge
bufferBbox
   xmin    ymin    xmax    ymax 
 641076 6855499  662897 6869034 

Méthode 1 : sélection non-géométrique [3/4]

On peut alors filtrer (via les x, y) les logements dans la grande bbox :

# Ne garder que les données dans le rectangle englobant, 
# sans traitement vectoriel !
dfBase_filtre <- dfBase[dfBase$x >= bufferBbox["xmin"] &
                          dfBase$x <= bufferBbox["xmax"] &
                          dfBase$y >= bufferBbox["ymin"] &
                          dfBase$y <= bufferBbox["ymax"], ]

# nb de lignes avant/après le filtre
nrow(dfBase_filtre) ; nrow(dfBase)
[1] 24419
[1] 34489

Méthode 1 : sélection non-géométrique [4/4]

code du schema explicatif
# Échantillon (en dehors et dans le buffer)
sfBase_sample <- sfBase[sample(1:nrow(sfBase), 2000), ] # échantillon 2000 obs
sfBase_sample_filtre <- sfBase_sample[sfBase_sample$x >= bufferBbox["xmin"] &
                                        sfBase_sample$x <= bufferBbox["xmax"] &
                                        sfBase_sample$y >= bufferBbox["ymin"] &
                                        sfBase_sample$y <= bufferBbox["ymax"], ] 

# Petit rectangle vectoriel
bbox_sf = st_sf(geometry = st_as_sfc(bbox), crs = 2154)

# Grand rectangle vectoriel
bufferBbox_sf = st_sf(geometry = st_as_sfc(bufferBbox), crs = 2154)

# Cartographie pédagogique
mf_map(bufferBbox_sf, col = "yellow")
mf_map(bbox_sf, col = "grey", add = T)
mf_map(paris_sf, col = "blue", add = T)
mf_map(sfBase_sample, col = "red", add = T)
mf_map(sfBase_sample_filtre, col = "green", add = T)

3. Carroyage de données

Objectif du carroyage

Avant de lisser les données ponctuelles, on peut représenter ces données sous forme carroyée afin de se les approprier.

➡️ Le carroyage nécessite plusieurs étapes

Les étapes du carroyage [1/5]

  1. Associer chaque point (= vente géolocalisée) au centroïde du carreau auquel il appartient (via btb_add_centroids).

➡️ Le territoire est découpé en carreaux de 200 mètres à partir de l’origine du référentiel.

iCellSize = 200 # carreaux de 200m
points_carroyage <- btb_add_centroids(pts = dfBase_filtre, iCellSize = iCellSize) 
head(points_carroyage, 3)
  id_mutation date_mutation  type_local nombre_pieces_principales
1 2021-447023    2021-01-08 Appartement                         3
2 2021-447024    2021-01-05 Appartement                         2
3 2021-447025    2021-01-08 Appartement                         3
  valeur_fonciere surface_reelle_bati        x       y x_centro y_centro
1          480000                  64 647357.3 6868635   647300  6868700
2          345000                  43 644483.5 6867695   644500  6867700
3          384000                  41 648001.8 6866153   648100  6866100

Les étapes du carroyage [2/5]

  1. Agréger les données sur chaque centroïde de la grille. En d’autres termes, compter le nombre de ventes par carreau
points_centroides <- points_carroyage %>%
  group_by(x_centro, y_centro) %>% count(name = "nbVentes")
head(points_centroides, 3)
# A tibble: 3 × 3
# Groups:   x_centro, y_centro [3]
  x_centro y_centro nbVentes
     <dbl>    <dbl>    <int>
1   641100  6857700        1
2   641100  6857900        5
3   641100  6858300        1

Les étapes du carroyage [3/5]

  1. Passer d’une table de centroïdes à une table de carreaux vectoriels via btb_ptsToGrid.

Paramètres obligatoires :

  • df : un tableau avec les colonnes x_centro et y_centro représentant les coordonnées des centroïdes de la grille ;
  • sEPSG : une chaîne de caractères indiquant le code epsg du système de projection utilisé ;
  • iCellSize : la taille des carreaux (longueur du côté, en mètres).
carreaux <- btb_ptsToGrid(pts = points_centroides,
                          sEPSG = "2154", iCellSize = iCellSize)

Les étapes du carroyage [4/5]

  1. Se restreindre au champ des carreaux intersectant Paris
carreaux <- carreaux %>% st_join(paris_sf, left = FALSE)

Les étapes du carroyage [5/5]

On trace le carroyage des ventes dans Paris intramuros :

code de la carte
mf_map(x = paris_sf, col = NA, lwd = 4)
mf_map(x = carreaux,
       type = "choro",
       var = "nbVentes",
       breaks = "quantile",
       nbreaks = 5,
       leg_val_rnd = 1,
       add = TRUE)
mf_layout(title = "Carroyage du nombre de ventes",
          credits = "Insee-DSAU, DGFiP, Etalab, IGN, mapsf")

Remaques sur le carroyage

Le carroyage permet d’avoir un premier aperçu des données le plus fidèle à la réalité. Il est également utilisé pour simplifier les données avant lissage afin de rendre le calcul de lissage moins long en cas de très grand nombre d’observations.

4. Lissage

4.1 Calcul de densité

Lissage le plus simple à réaliser

  • Densité = quantité par unité de surface (un carreau)
  • Ici : densité de ventes de logements dans la ville de Paris au cours de l’année 2021

☠️ Effets de bord

Toujours pour éviter les effets de bord :

  • Lissage sur une zone plus large que la zone d’intérêt

➡️ Utilisation de dfBase_filtre (construite précédemment).

Création d’une variable nbObsLisse

  • On crée la variable nbObsLisse = 1 pour chaque observation
# Variable à lisser = nombre d'observations = nombre de ventes
dfBase_filtre$nbObsLisse <- 1
head(dfBase_filtre, 3)
  id_mutation date_mutation  type_local nombre_pieces_principales
1 2021-447023    2021-01-08 Appartement                         3
2 2021-447024    2021-01-05 Appartement                         2
3 2021-447025    2021-01-08 Appartement                         3
  valeur_fonciere surface_reelle_bati        x       y nbObsLisse
1          480000                  64 647357.3 6868635          1
2          345000                  43 644483.5 6867695          1
3          384000                  41 648001.8 6866153          1

➡️ On lisse ensuite la variable nbObsLisse avec la fonction btb_smooth

Lissage et enregistrement dans sfCarrLiss

Paramètres de btb_smooth

  • pts : tableau contenant les coordonnées (colonne x et y ou bien geometry), et 1 à n colonnes numériques (variables à lisser) ;
  • sEPSG : chaîne de caractères indiquant le code epsg du système de projection utilisé ;
  • iCellSize : un entier indiquant la taille des carreaux ;
  • iBandwidth : un entier indiquant le rayon de lissage.
sfCarrLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "x", "y")], 
                         sEPSG = "2154",
                         iCellSize = 200, 
                         iBandwidth = 400)

‼️ la fonction retourne une erreur en cas de :

  • présence d’une variable non-numérique ;
  • valeur(s) absente(s) dans les colonnes x ou y.

Résultat du lissage

# nombre de lignes lissées
nrow(sfCarrLiss)
[1] 4107
# aperçu de la table lissée
head(sfCarrLiss, 3)
Simple feature collection with 3 features and 3 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 646000 ymin: 6855400 xmax: 647200 ymax: 6855600
Projected CRS: RGF93 v1 / Lambert-93
       x       y nbObsLisse                       geometry
1 646100 6855500   1.712851 POLYGON ((646000 6855600, 6...
2 646500 6855500   1.261316 POLYGON ((646400 6855600, 6...
3 647100 6855500   2.301557 POLYGON ((647000 6855600, 6...

Visualisation de la grille où s’effectue le lissage

code
mf_map(x = paris_sf, lwd = 8, col = NA, border = "wheat")
mf_map(x = sfCarrLiss, col = NA, lwd = 1, add = T)
mf_layout(title = "btb_smooth génère une grille carroyée")
  • La grille lissée épouse le périmètre des points en entrée.
  • Elle va même un peu au-delà (voir infra).

Le lissage par densité est conservatif

# nb de ventes dans la petite couronne 
# vs nb lissé de ventes dans les carreaux
sum(dfBase_filtre$nbObsLisse) ; sum(sfCarrLiss$nbObsLisse) 
[1] 24419
[1] 24419

Remarque sur le secret statistique

‼️ Toujours vérifier le respect du secret statistique avant publication de cartes !

Par exemple, avec données fiscales, pas de carreaux comportant moins de 11 observations, même lissées…

sfCarrLiss <- sfCarrLiss[sfCarrLiss$nbObsLisse >= 11, ]

Pour avoir des cartes complétes lors de ce tutoriel, on garde tous les carreaux, même ceux avec moins de 11 observations.

Représentation cartographique de la densité lissée

  • Carte choroplèthe ;
  • Discrétisation quantile de la densité lissée ;
  • Avec 5 classes de carreaux (5 couleurs) .
code
# Filtrage des carreaux lissés intersectant la ville de Paris
sfCarrLiss_paris <- sfCarrLiss %>% 
  st_join(paris_sf, left = FALSE)

# Carte lissée
mf_map(x = sfCarrLiss_paris, 
       type = "choro",
       var = "nbObsLisse",
       breaks = "quantile",
       nbreaks = 5)
mf_map(x = paris_sf, lwd = 4, col = NA, 
       border = "black", add = TRUE)
mf_layout(title = "Lissage avec rayon de 400m",
          credits = "Insee-DSAU, DGFiP, Etalab, IGN")

Récapitulatif : lissage de densité

On souhaite obtenir le nombre de ventes lissé, en 2021 dans Paris intramuros :

  • On sélectionne les ventes au-delà de Paris intramuros (effets de bord)
  • On lisse grâce à btb_smooth
  • On obtient une grille carroyée bien plus vaste que Paris intramuros
  • On filtre uniquement les carreaux dans Paris
  • On cartographie la variable lissée (nbObsLissee)

Faire varier le rayon de lissage

Taille optimale du rayon de lissage ?

  • Rayon grand (1 km) : aspects structurels des données
  • Rayon petit (600 m) : spécificités locales des données.

➡️ Compromis à faire par le statisticien géographe entre précision et généralisation (connaissance des données et objectifs recherchés)

Faire varier le rayon de lissage (600m)

code
# reproduction pour 600m
sfCarrLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "x", "y")], 
                              sEPSG = 2154,
                              iCellSize = 200, 
                              iBandwidth = 600)

# Filtrage des carreaux lissés dans Paris
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)

# carte lissée
mf_map(x = sfCarrLiss_paris, 
       type = "choro",
       var = "nbObsLisse",
       breaks = "quantile",
       nbreaks = 5)
mf_map(x = paris_sf, 
       lwd = 4,
       col = NA, border = "black", add = TRUE)
mf_layout(title = "Lissage avec rayon de 600m",
          credits = "Insee-DSAU, DGFiP, Etalab, IGN, mapsf")

Faire varier le rayon de lissage (1000m)

code
# reproduction pour 1000m
sfCarrLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "x", "y")], 
                         sEPSG = 2154,
                         iCellSize = 200, 
                         iBandwidth = 1000)

# Filtrage des carreaux lissés dans Paris
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)

# Carte lissée
mf_map(x = sfCarrLiss_paris, 
       type = "choro",
       var = "nbObsLisse",
       breaks = "quantile",
       nbreaks = 5)
mf_map(x = paris_sf, 
       lwd = 4,
       col = NA, border = "black", add = TRUE)
mf_layout(title = "Lissage avec rayon de 1000m",
          credits = "Insee-DSAU, DGFiP, Etalab, IGN, mapsf")

Faire varier le rayon de lissage (1000m)

Sans grille apparente !

code
# pour automatiser les cartes, on fait une fonction
creation_carte <- function(data, var, titre){
  mf_map(x = data, 
         type = "choro",
         var = var,
         breaks = "quantile",
         nbreaks = 5, 
         leg_val_rnd = 1, 
         border = NA) # enlever la grille
  mf_map(x = paris_sf, 
         lwd = 4,
         col = NA, border = "black", add = TRUE)
  mf_layout(title = titre,
            credits = "Insee-DSAU, DGFiP, Etalab, IGN, mapsf")
}

creation_carte(sfCarrLiss_paris, "nbObsLisse", titre = "Lissage avec rayon de 1000m, sans grille")

Faire varier la taille des carreaux (50m)

  • Trop gros carreaux = effet granuleux : arêtes des carreaux visibles
  • Trop petits carreaux = temps de calcul important
code
sfCarrLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "x", "y")], 
                         sEPSG = 2154,
                         iCellSize = 50, # passage à 50m
                         iBandwidth = 1000)

# Filtrage des carreaux lissés dans Paris
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)

# carte lissée
creation_carte(sfCarrLiss_paris, "nbObsLisse", 
               titre = "Lissage avec pas de 50m")

Illustration des effets de bord

Sans effet de bord

code
sfCarLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "x", "y")], 
                        sEPSG = 2154,
                        iCellSize = 200, 
                        iBandwidth = 2000)
sfCarrLiss_paris <- sfCarLiss %>% st_join(paris_sf, left = FALSE)

creation_carte(sfCarrLiss_paris, var = "nbObsLisse", 
               titre = "Sans effets de bord, avec R = 2000m")

Avec effets de bord

code lissage
# Lissage sans gestion des effets de bord (intramuros uniquement)
sfBase_intramuros <- sfBase %>% st_join(paris_sf, left = FALSE) %>% 
  mutate(nbObsLisse = 1L)

sfCarrLiss_intramuros <- btb_smooth(pts = sfBase_intramuros[, c("nbObsLisse", "geometry")], 
                                   iCellSize = 200, 
                                   iBandwidth = 2000)

sfCarrLiss_intramuros_paris <- sfCarrLiss_intramuros %>%
  st_join(paris_sf, left = FALSE)

creation_carte(sfCarrLiss_intramuros_paris, var = "nbObsLisse", 
               titre = "Avec effets de bord, avec R = 2000m")

➡️ Périphérie de Paris : densités artificiellement plus faibles

Les grilles automatiques de btb [1/2]

La grille par défaut produite par btb_smooth dépasse les limites de la zone d’étude choisie (ici Paris intramuros) et contient :

  • l’ensemble des carreaux contenant au moins une vente…
  • + 4 carreaux limitrophes (règle automatique)
code carte
sfCarrLiss_extramuros <- btb_smooth(pts = sfBase_intramuros[, c("nbObsLisse", "geometry")], 
                                   iCellSize = 200, 
                                   iBandwidth = 2000)

mf_map(paris_sf, lwd = 2, col = NA)
mf_map(sfCarrLiss_extramuros, col = NA, border = "red", add = T)

Les grilles automatiques de btb [2/2]

Modification possible de cette règle avec le paramètre iNeighbor :

  • iNeighbor = 2 ➡️ 2 carreaux limitrophes aux carreaux contenant au moins une vente.
  • iNeighbor = 0 ➡️ grille uniquement constituée des carreaux contenant au moins une vente (attention à l’effet « gruyère »)
code
sfCarrLiss_intramuros <- btb_smooth(pts = sfBase_intramuros[, c("nbObsLisse", "geometry")], 
                                   iCellSize = 200, 
                                   iBandwidth = 2000,
                                   iNeighbor = 0)

mf_map(paris_sf, lwd = 2, col = NA)
mf_map(sfCarrLiss_intramuros, col = NA, border = "red", add = T)

Spécificités locales : ville cotière…

On peut souhaiter utiliser une grille de lissage particulière et adaptée à un territoire donné.

Par exemple, sur une ville cotière, on ne veut :

  • ni de carreau dans la mer
  • ni l’effet « gruyère » du paramétrage iNeighbor = 0.

Dans ce cas :

  • il convient de la confectionner soi-même
  • et de la renseigner dans le paramètre dfCentroids de la fonction btb_smooth.

Partir de données ponctuelles ou déjà carroyées [1/2]

données ponctuelles :

  • le plus précis possible
  • peut poser des problèmes en terme de volume/accès.
code carte
# par(mfrow = c(1, 2))

mf_map(x = paris_sf, col = NA, lwd = 4)
mf_map(x = sfBase_filtre, cex = 0.3, col = "red", add = TRUE)
mf_layout(title = "Données ponctuelles")

code carte
sfBase_filtre$nbObsLisse <- 1
sfCarrLiss <- btb_smooth(pts = sfBase_filtre[, c("nbObsLisse")], 
                         sEPSG = 2154,
                         iCellSize = 200, 
                         iBandwidth = 400)
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)

creation_carte(data = sfCarrLiss_paris, var = "nbObsLisse", 
               titre = "Lissage des données ponctuelles")

Partir de données ponctuelles ou déjà carroyées [2/2]

données déjà carroyées :

  • moins précis, on s’éloigne de la vérité (perte d’information)
  • plus simple en temps de calcul et en accès (moins de secret)
code carte
iCellSize = 200 # carreaux de 200m
points_carroyage <- btb_add_centroids(pts = dfBase_filtre, iCellSize = iCellSize) %>% 
  group_by(x_centro, y_centro) %>% count(name = "nbVentes") %>% 
  st_as_sf(coords = c("x_centro", "y_centro"), crs = 2154) %>% 
  st_join(paris_sf %>% st_buffer(2000), left = FALSE)

# par(mfrow = c(1, 2))

mf_map(x = paris_sf, col = NA, lwd = 4) 
mf_map(x = points_carroyage, cex = 0.3, col = "red", add = TRUE)
mf_layout(title = "Données carroyées")

code carte
sfCarrLiss <- btb_smooth(pts = points_carroyage[, c("nbVentes")], 
                         sEPSG = 2154,
                         iCellSize = 200, 
                         iBandwidth = 400)
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)

creation_carte(data = sfCarrLiss_paris, var = "nbVentes", 
               titre = "Lissage des données carroyées")

Comparaison de perte d’information

# comparaison de points 
nrow(points_carroyage) ; nrow(sfBase_filtre)
[1] 3235
[1] 22221

3.2 Calcul de moyenne

Calcul de moyenne [1/2]

Nous allons nous intéresser désormais au prix moyen des logements vendus à Paris en 2021.

Il faut :

  1. Lisser les prix de ventes des biens vendus
  2. Lisser le nombre de biens vendus (exactement comme précédemment)
  3. Faire le ratio des deux précédents lissages pour chaque carreau de la grille produite.

‼️ Une moyenne n’est pas le lissage du rapport liss(variable / nbObsLisse) mais le rapport des lissages liss(variable) / liss(nbObs).

Calcul de moyenne [2/2]

Les prix moyens sont particulièrement élevés dans le centre et l’Ouest de la capitale.

Remarque

Le lissage en moyenne est potentiellement sensible aux valeurs atypiques (contrairement au lissage quantile).

code
# Lissage
sfCarLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "valeur_fonciere", 
                                                "x", "y")] , 
                        sEPSG = 2154,
                        iCellSize = 100, 
                        iBandwidth = 800)

# Calculer le taux
sfCarLiss$prixMoyen <- sfCarLiss$valeur_fonciere / sfCarLiss$nbObsLisse
sfCarrLiss_paris <- sfCarLiss %>% st_join(paris_sf, left = FALSE)

creation_carte(data = sfCarrLiss_paris, var = "prixMoyen", 
               titre = "Lissage des prix de vente avec un rayon de 800m")

3.3 Calcul de taux

Taux de grands logements

Proportion de ventes comportant plus de 4 pièces principales ?

  • indicatrice quatrePieces
  • lissage numérateur et dénominateur
  • calcul taux
  • intersection géographique avec Paris
code
# Créer une indicatrice renseignant si le logement comporte plus de 4 pièces
dfBase_filtre <- dfBase_filtre %>% 
  mutate(quatrePieces = ifelse(nombre_pieces_principales >= 4, 1, 0))

# Lissage des 2 variables
sfCarLiss <- btb_smooth(pts = dfBase_filtre[, c("nbObsLisse", "quatrePieces",
                                                "x", "y")], 
                        sEPSG = 2154,
                        iCellSize = 100, 
                        iBandwidth = 800)

# Calcul du taux
sfCarLiss$txQuatrePieces <- sfCarLiss$quatrePieces / sfCarLiss$nbObsLisse

# Intersection géographique avec Paris
sfCarrLiss_paris <- sfCarLiss %>% st_join(paris_sf, left = FALSE)

# cartographie
creation_carte(data = sfCarrLiss_paris, var = "txQuatrePieces",
               titre = "Logements avec 4 pièces ou plus (rayon de 800m)")

➡️ Carte similaire à la carte des prix moyens

Prix au m²

Il faut lisser séparément les prix de vente (numérateur) et le nombre de mètres-carrés (dénominateur).

Si on lisse le prix au m² de chaque logement vendu, on sur-pondère artificiellement les prix au m² des petits logements (car l’unité statistique devient alors le logement, et non le m² vendu).

code
# lissage des 2 variables
sfCarrLiss <- btb_smooth(pts = dfBase_filtre[, c("surface_reelle_bati", 
                                                "valeur_fonciere", "x", "y")],
                        sEPSG = 2154,
                        iCellSize = 100,
                        iBandwidth = 800)

# Calcul du prix au m²
sfCarrLiss$prixM2 <- sfCarrLiss$valeur_fonciere / sfCarrLiss$surface_reelle_bati

# Intersection géographique avec Paris
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)

# Cartographie
creation_carte(data = sfCarrLiss_paris, var = "prixM2",
               titre = "Prix au m² (rayon de 800m)")

3.4 Calcul de quantiles géographiquement pondérés

Lissage quantile : pourquoi ?

  • Moins sensible aux valeurs extrêmes ;
  • Obtenir des indicateurs de dispersion lissés ;
    • ex : écart interquantile

Lissage quantile : comment ?

  • Toujours avec la fonction btb_smooth
  • En ajoutant un paramètre, vQuantiles, qui contient la liste des quantiles souhaités

Lissage quantile : rappel

Contrairement au lissage classique :

  • il n’est pas conservatif
  • la fonction crée :
    • une colonne nbObs indiquant le nombre réel (non lissé) d’observations ayant contribué au calcul du carreau.
    • pour chaque variable, autant de colonnes qu’il y a de quantiles souhaités

Lissage quantile : exemple

Nous allons calculer le 1er décile, la médiane et le 9ème décile du prix de vente des logements à Paris.

# calcul des déciles "locaux"
sfCarrLiss <- btb_smooth(pts = dfBase_filtre[, c("valeur_fonciere", "x", "y")],
                        sEPSG = 2154,
                        iCellSize = 100,
                        iBandwidth = 1500,
                        vQuantiles = c(0.1, 0.5, 0.9))

# Intersection géographique avec Paris
sfCarrLiss_paris <- sfCarrLiss %>% st_join(paris_sf, left = FALSE)
head(sfCarrLiss, 3)
Simple feature collection with 3 features and 6 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 651500 ymin: 6854700 xmax: 651800 ymax: 6854800
Projected CRS: RGF93 v1 / Lambert-93
  nbObs valeur_fonciere_01 valeur_fonciere_05 valeur_fonciere_09      x       y
1    57             182000             310000             610000 651550 6854750
2    53             182000             310000             620000 651650 6854750
3    52             182000             310000             620000 651750 6854750
                        geometry
1 POLYGON ((651500 6854800, 6...
2 POLYGON ((651600 6854800, 6...
3 POLYGON ((651700 6854800, 6...

Lissage quantile : exemple

Cartographie du 1er décile de prix des ventes

creation_carte(data = sfCarrLiss_paris, var = "valeur_fonciere_01", 
               titre = "Premier décile des prix de vente (rayon de 1500m)")

Lissage quantile : exemple

Cartographie de la médiane des prix des ventes

creation_carte(data = sfCarrLiss_paris, var = "valeur_fonciere_05", 
               titre = "Médiane des prix de vente (rayon de 1500m)")

Lissage quantile : exemple

Cartographie du rapport interdécile des prix des ventes

sfCarrLiss_paris <- sfCarrLiss_paris %>% 
  mutate(rapp_interdecile = valeur_fonciere_09 / valeur_fonciere_01)

creation_carte(data = sfCarrLiss_paris, var = "rapp_interdecile", 
               titre = "Rapport interdéciles des prix de vente (rayon de 1500m)")

Lissage quantile : remarque importante

Enfin, pour que le quantile lissé ait du sens, il faut qu’un nombre conséquent d’observations ait participé à sa construction. Ainsi, il est conseillé d’utiliser un rayon de lissage suffisamment élevé.

summary(sfCarrLiss_paris$nbObs)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     13     725    1040    1106    1440    2917 
# Centroïdes dont le quantile lissé a été calculé avec moins de 100 observations
sfCarrLiss_paris %>% st_drop_geometry() %>% group_by(nbObs < 100) %>% count()
# A tibble: 2 × 2
# Groups:   nbObs < 100 [2]
  `nbObs < 100`     n
  <lgl>         <int>
1 FALSE         10200
2 TRUE            229

Conclusion

Vous êtes désormais capables de carroyer et lisser toutes les données que vous aurez à votre disposition !

Autres logiciels

  • Python : library BTBpy
  • Qgis grâce à un plug-in développé par Lionel Cacheux (DR Insee Grand Est).

Références