Author

Dr. Elisabetta Pietrostefani

Published

March 22, 2026

7.1 Introduction

Dans ce chapitre, nous appliquons les concepts vus jusqu’ici à un cas concret : les ventes immobilières à Londres. Nous allons charger des données, les spatialiser, les joindre, les agréger, et produire des cartes publiables.

Les opérations que nous couvrons — jointure spatiale, zone tampon, intersection, calcul de distance — constituent le cœur du géotraitement (geoprocessing).

Code
library(sf)
library(ggplot2)
library(dplyr)
library(viridis)
library(ggspatial)
library(spdep)
library(scales)

7.2 Charger et spatialiser les données

7.2.1 Exercice 1 — Charger les données

Chargez le shapefile des districts londoniens et le fichier CSV des ventes immobilières.

Indice

Pour lire un shapefile dans R avec sf, la fonction est read_sf(). Pour un CSV, c’est read.csv().

Code
# À compléter
london <- ___(dsn = "data/London/Polygons/districts.shp")
ventes <- read.csv("data/London/Tables/housesales.csv")
Voir la réponse
london <- read_sf(dsn = "data/London/Polygons/districts.shp")
ventes <- read.csv("data/London/Tables/housesales.csv")

7.2.2 Exercice 2 — Créer des points spatiaux

Le fichier ventes est un data frame ordinaire avec des colonnes de coordonnées. Convertissez-le en objet sf avec st_as_sf(). Les coordonnées se trouvent dans les colonnes 17 et 18.

Indice

st_as_sf(data, coords = c(col_lon, col_lat)) — passez les numéros de colonnes ou les noms.

Code
ventes <- st_as_sf(ventes, coords = c(___, ___))
Voir la réponse
ventes <- st_as_sf(ventes, coords = c(17, 18))

7.2.3 Exercice 3 — Définir le bon SCR

Les coordonnées de Londres sont en British National Grid (EPSG:27700). Définissez ce SCR sur l’objet ventes.

Indice

Souvenez-vous de la distinction st_set_crs() vs st_transform() — ici nous déclarons le SCR, nous ne reprojetons pas.

Code
ventes <- st_set_crs(ventes, ___)
Voir la réponse
ventes <- st_set_crs(ventes, 27700)

7.3 Visualiser les données

Maintenant que les données sont chargées, explorons-les visuellement. Ce bloc est entièrement fourni — lisez le code attentivement, chaque ligne est commentée.

7.3.1 Construire une carte pas à pas avec ggplot2

Code
wards <- read_sf("data/London/Polygons/wards.shp")
wards$level4p  <- wards$LEVEL4_5 / wards$POP16_74
wards$level4pp <- wards$level4p * 100

# Carte minimale
ggplot(wards) +
  geom_sf(fill = "grey90", color = "white", linewidth = 0.2) +
  theme_void(base_size = 12) +
  labs(title = "Wards de Londres", caption = "Source : données Londres | Cours R-Carto") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 7.1: Wards londoniens — carte de base
Code
ggplot(wards) +
  geom_sf(aes(fill = level4p), color = "white", linewidth = 0.15) +
scale_fill_viridis_c(
  name   = "Niveau 4+",
  option = "mako",
  labels = label_percent()
) +
  labs(
    title   = "Part de la population avec qualification niveau 4+",
    caption = "Source : Recensement 2001 | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 7.2: Part de la population avec qualification niveau 4+ par ward — palette divergente
Code
ggplot(wards) +
  geom_sf(aes(fill = level4p), color = "white", linewidth = 0.15) +
scale_fill_viridis_c(
  name   = "Niveau 4+",
  option = "mako",
  labels = label_percent()
) +
  annotation_scale(location = "bl") +
  annotation_north_arrow(location = "bl", pad_y = unit(1, "cm"),
                          style = north_arrow_fancy_orienteering()) +
  labs(
    title   = "Qualification niveau 4+ — Wards de Londres",
    caption = "Source : Recensement 2001 | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 7.3: Carte complète avec échelle et flèche nord

7.4 Ajouter les points de vente

7.4.1 Exercice 4 — Carte de base de Londres

Créez une carte simple des districts londoniens avec ggplot2.

Code
map1 <- ggplot() +
  ___(data = london, ___) +
  theme_void()
map1
Voir la réponse
map1 <- ggplot() +
  geom_sf(data = london, fill = "grey95", color = "white", linewidth = 0.4) +
  geom_sf_text(data = london, aes(label = DIST_NAME),
               fun.geometry = sf::st_centroid, size = 2, color = "grey30") +
  theme_void(base_size = 12) +
  labs(title   = "Districts de Londres",
       caption = "Source : données Londres | Cours R-Carto") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
map1

Districts londoniens avec étiquettes

7.4.2 Exercice 5 — Superposer les points de vente

Ajoutez les transactions immobilières (ventes) à la carte map1.

Indice

Dans ggplot2, on ajoute une couche avec + et un nouveau geom_sf().

Code
map1 + ___(data = ventes, ___)
Voir la réponse
map1 +
  geom_sf(data = ventes, color = "#d7191c", size = 0.3, alpha = 0.4)

Transactions immobilières superposées aux districts

7.5 Jointure spatiale

Une jointure spatiale (st_join()) associe à chaque point le polygone dans lequel il tombe — ici, chaque vente immobilière hérite des attributs du district qui la contient.

Code
# Rappel des fonctions spatiales disponibles :
# st_join(polygons, points)  → jointure spatiale
# st_intersects(x, y)        → renvoie les indices des objets qui se croisent
# st_intersection(x, y)      → renvoie les géométries découpées à l'intersection

7.5.1 Exercice 6 — Jointure spatiale districts × ventes

Créez un objet ventes_districts qui joint les polygones des districts avec les points de vente.

Indice

st_join(polygons, points) — l’ordre compte : le premier argument donne la structure géométrique du résultat.

Code
ventes_districts <- st_join(___, ___)
head(ventes_districts)
Voir la réponse
ventes_districts <- st_join(london, ventes)
head(ventes_districts)
Simple feature collection with 6 features and 33 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 530966.7 ymin: 180404.3 xmax: 551943.8 ymax: 191139
Projected CRS: OSGB36 / British National Grid
# A tibble: 6 × 34
  DIST_CODE DIST_NAME                     geometry propid  price lnprice advance
  <chr>     <chr>                    <POLYGON [m]>  <int>  <int>   <int>   <int>
1 00AA      City of Lon… ((531028.5 181611.2, 531…     NA     NA      NA      NA
2 00AB      Barking and… ((550817 184196, 550814 …   2145  92500      11   77500
3 00AB      Barking and… ((550817 184196, 550814 …   2155 105000      12   90000
4 00AB      Barking and… ((550817 184196, 550814 …   2164  64995      11   61740
5 00AB      Barking and… ((550817 184196, 550814 …   2165  49995      11   37496
6 00AB      Barking and… ((550817 184196, 550814 …   2167 136000      12  122400
# ℹ 27 more variables: age <int>, bathroom <int>, bedroom <int>, buyage <chr>,
#   centheat <int>, chelec <int>, chepart <int>, chgas <int>, chgpart <int>,
#   chnone <int>, floorm2 <int>, ftbuyer <int>, laarea <int>, labad <int>,
#   lagood <int>, lea <int>, pdetach <int>, pflat <int>, psemi <int>,
#   pterrace <int>, popdens <int>, prkdouble <int>, prknone <int>,
#   prksingle <int>, prkspace <int>, tenfree <int>, tenlease <int>

7.6 Agréger les données

7.6.1 Exercice 7 — Prix moyen par district

À partir de ventes_districts, calculez pour chaque district : le nombre de ventes et le prix moyen.

Indice

Utilisez group_by() + summarise() du tidyverse. Le prix est dans la colonne price.

Code
ventes_districts_agg <- ventes_districts |>
  group_by(___, ___) |>
  summarise(
    count_sales = n(),
    mean_price  = mean(___, na.rm = TRUE)
  )
head(ventes_districts_agg)
Voir la réponse
ventes_districts_agg <- ventes_districts |>
  group_by(DIST_CODE, DIST_NAME) |>
  summarise(
    count_sales = n(),
    mean_price  = mean(price, na.rm = TRUE)
  )
head(ventes_districts_agg)
Simple feature collection with 6 features and 4 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 515484.9 ymin: 156480.8 xmax: 554503.8 ymax: 198355.2
Projected CRS: OSGB36 / British National Grid
# A tibble: 6 × 5
# Groups:   DIST_CODE [6]
  DIST_CODE DIST_NAME           count_sales mean_price                  geometry
  <chr>     <chr>                     <int>      <dbl>             <POLYGON [m]>
1 00AA      City of London                1       NaN  ((531028.5 181611.2, 531…
2 00AB      Barking and Dagenh…          38     91802. ((550817 184196, 550814 …
3 00AC      Barnet                       83    169662. ((526830.3 187535.5, 526…
4 00AD      Bexley                       82    119276. ((552373.5 174606.9, 552…
5 00AE      Brent                        49    174498. ((524661.7 184631, 52466…
6 00AF      Bromley                     124    142468. ((533852.2 170129, 53385…

7.6.2 Exercice 8 — Améliorer la carte des prix

La carte ci-dessous est fonctionnelle mais peu lisible. Améliorez-la en ajoutant : une palette viridis option "magma", des labels en livres sterling (£), et un meilleur thème.

Code
map2 <- ggplot() +
  geom_sf(data = ventes_districts_agg, aes(fill = mean_price),
          color = "white", linewidth = 0.3) +
  labs(x = "", y = "")
map2
Figure 7.4: Prix moyen par district — version initiale (à améliorer)
Code
map3 <- ggplot() +
  geom_sf(data = ___, aes(fill = ___),
          color = "white", linewidth = 0.3) +
  scale_fill_viridis(
    name      = "Prix moyen",
    direction = ___,
    labels    = ___,
    option    = "___"
  ) +
  labs(
    title   = "Prix moyen des transactions immobilières — Londres",
    caption = "Source : données Londres | Cours R-Carto"
  ) +
  ___()   # choisissez un thème : ?ggtheme
map3
Voir la réponse
map3 <- ggplot() +
  geom_sf(data = ventes_districts_agg, aes(fill = mean_price),
          color = "white", linewidth = 0.3) +
  scale_fill_viridis(
    name      = "Prix moyen",
    direction = -1,
    labels    = dollar_format(prefix = "£"),
    option    = "magma"
  ) +
  labs(
    title   = "Prix moyen des transactions immobilières — Londres",
    caption = "Source : données Londres | Cours R-Carto"
  ) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
map3

Prix moyen par district — version améliorée
Exercice 9 — Paris

Maintenant que vous maîtrisez le géotraitement sur Londres, créez une carte similaire pour Paris avec les données du cours :

  1. Chargez le shapefile des arrondissements parisiens
  2. Chargez donnees_arrondissement_enrichi.csv
  3. Faites la jointure avec left_join() sur le code arrondissement
  4. Cartographiez une variable de votre choix (prix au m², taux de chômage, part de logements sociaux…)
  5. Appliquez une palette et un thème adaptés

7.7 Zones tampons (buffers)

Une zone tampon est une zone géographique à une distance donnée d’un objet. Ici, nous calculons la population vivant à moins de 500 m d’un échantillon de bâtiments classés londoniens.

7.7.1 Charger les bâtiments classés

Code
batiments_classes <- read_sf("data/London/Points/listed_buildings_london.shp")

# Sélectionner 20 bâtiments au hasard pour l'exemple
set.seed(42)
sample_ids        <- sample(batiments_classes$ListEntry, 20)
batiments_classes <- batiments_classes[batiments_classes$ListEntry %in% sample_ids, ]

cat("Nombre de bâtiments sélectionnés :", nrow(batiments_classes))
Nombre de bâtiments sélectionnés : 20

7.7.2 Télécharger les zones de recensement

Code
# Télécharger les zones de sortie du recensement 2011
dir.create("data/London", recursive = TRUE, showWarnings = FALSE)

download.file(
  url     = "https://data.london.gov.uk/download/statistical-gis-boundary-files-london/9ba8c833-6370-4b11-abdc-314aa020d5e0/statistical-gis-boundaries-london.zip",
  destfile = "data/London/statistical-gis-boundaries-london.zip"
)
unzip("data/London/statistical-gis-boundaries-london.zip", exdir = "data/London")
Code
oas_census <- read_sf(
  "data/London/statistical-gis-boundaries-london/ESRI/OA_2011_London_gen_MHW.shp"
)
oas_census$area_census <- st_area(oas_census)
Code
oas_Southwark <- oas_census[oas_census$LAD11NM == "Southwark", ]

ggplot(oas_Southwark) +
  geom_sf(fill = "#1a4f7a", color = "white", linewidth = 0.2) +
  labs(title   = "Zones de recensement — Southwark",
       caption = "Source : recensement 2011 | Cours R-Carto") +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 7.5: Zones de recensement — arrondissement de Southwark

7.7.3 Créer les zones tampons

Code
# Harmoniser les SCR
batiments_classes <- st_transform(batiments_classes, st_crs(oas_census))

# Créer les zones tampons de 500 m
buffers500m          <- st_buffer(batiments_classes, dist = 500)
buffers500m$area_buffer <- st_area(buffers500m)

ggplot() +
  geom_sf(data = buffers500m, fill = "#cce5f0", color = "#1a6ea8",
          linewidth = 1.0, alpha = 0.4) +
  geom_sf(data = batiments_classes, color = "#d7191c", size = 2.5,
          shape = 17) +
  labs(
    title    = "Bâtiments classés et zones tampons (500 m)",
    subtitle = "20 bâtiments sélectionnés aléatoirement",
    caption  = "Source : données Londres | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title    = element_text(face = "bold", hjust = 0.5),
        plot.subtitle = element_text(color = "grey40", hjust = 0.5))
Figure 7.6: Zones tampons de 500 m autour des bâtiments classés sélectionnés

7.7.4 Intersection et population pondérée

Code
# Corriger les géométries invalides si nécessaire
oas_census <- st_make_valid(oas_census)

# Intersection : quelles zones de recensement tombent dans les tampons ?
buffers_inter          <- st_intersection(buffers500m, oas_census)
buffers_inter$area_inter <- st_area(buffers_inter)
Code
# Agréger au niveau du bâtiment — population pondérée par la part de surface couverte
buffers_agg <- buffers_inter |>
  mutate(
    weight     = as.numeric(area_inter / area_buffer),
    population = as.numeric(USUALRES)
  ) |>
  group_by(ListEntry) |>
  summarise_at("population", list(sum = ~sum(. * weight)))

pop_moyenne <- round(mean(buffers_agg$sum))
cat("Population moyenne dans un rayon de 500 m :", pop_moyenne, "habitants\n")
Population moyenne dans un rayon de 500 m : 316 habitants

La population moyenne vivant à moins de 500 m d’un bâtiment classé est 316 habitants.

À noter — st_intersection vs st_join vs st_intersects
Fonction Ce qu’elle renvoie
st_join(x, y) Table jointe — chaque entité de x avec les attributs de y qui la touchent
st_intersects(x, y) Indices des paires qui se croisent (liste logique)
st_intersection(x, y) Géométries découpées à l’intersection — parties non sécantes supprimées

Référence : R as GIS for Economists


7.8 Calculs de distance

st_distance() calcule la distance entre deux objets sf. Le résultat est une matrice avec des unités (mètres si la projection est en mètres).

Code
Hackney  <- london[london$DIST_NAME == "Hackney",  ]
Southwark <- london[london$DIST_NAME == "Southwark", ]

Hackney_centroid  <- st_centroid(Hackney)
Southwark_centroid <- st_centroid(Southwark)

dist <- st_distance(Hackney_centroid, Southwark_centroid)
cat("Distance Hackney → Southwark :", round(as.numeric(dist)), "mètres\n")
Distance Hackney → Southwark : 8714 mètres
Code
ggplot() +
  geom_sf(data = london, fill = "grey90", color = "grey70", linewidth = 0.3) +
  geom_sf(data = Hackney,   fill = "#2c7bb6", color = "white", linewidth = 0.5) +
  geom_sf(data = Southwark, fill = "#d7191c", color = "white", linewidth = 0.5) +
  geom_sf(data = Hackney_centroid,   color = "#1a4f7a", size = 4, shape = 19) +
  geom_sf(data = Southwark_centroid, color = "#a01010", size = 4, shape = 19) +
  geom_sf(data = st_union(Hackney_centroid, Southwark_centroid) |>
            st_cast("LINESTRING"),
          color = "#333333", linewidth = 1.0, linetype = "dashed") +
  annotate("label",
           x     = -0.03, y = 51.525,
           label = paste0(round(as.numeric(dist) / 1000, 1), " km"),
           size  = 4, fontface = "bold",
           fill  = "white", label.size = 0.3) +
  coord_sf(
    xlim = c(st_bbox(london)["xmin"], st_bbox(london)["xmax"]),
    ylim = c(st_bbox(london)["ymin"], st_bbox(london)["ymax"]),
    expand = TRUE
  ) +
  labs(
    title   = "Distance entre Hackney et Southwark",
    caption = "Source : données Londres | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 7.7: Centroïdes de Hackney et Southwark avec distance calculée

7.8.1 Matrice de distances multiples

Code
# Distance de Hackney vers Southwark et Croydon
co <- london |> filter(grepl("Southwark|Croydon", DIST_NAME))
st_distance(Hackney_centroid, co)
Units: [m]
         [,1]    [,2]
[1,] 14558.19 5064.61

7.9 Voisinage par distance

Les poids spatiaux par distance identifient pour chaque point l’ensemble de ses voisins dans un rayon donné. Ici, toutes les propriétés à moins de 2 000 m.

Code
housesales    <- read.csv("data/London/Tables/housesales.csv")
housesales_sf <- st_as_sf(housesales, coords = c(17, 18)) |>
  st_set_crs(27700)

# Voisins dans un rayon de 0 à 2000 m
W_dist <- dnearneigh(housesales_sf, d1 = 0, d2 = 2000,
                     row.names = housesales_sf$propid)

# Convertir en liste de poids spatiaux
W_dist_mat <- nb2listw(W_dist, zero.policy = TRUE)
summary(W_dist_mat, zero.policy = TRUE)
Characteristics of weights list object:
Neighbour list object:
Number of regions: 2444 
Number of nonzero links: 71400 
Percentage nonzero weights: 1.195352 
Average number of links: 29.2144 
1 region with no links:
2
4 disjoint connected subgraphs
Link number distribution:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 
 1  3  2 16  9 12 19 24 38 43 35 44 55 55 61 52 61 80 60 71 68 84 69 84 77 57 
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 
51 56 59 57 50 47 51 54 44 57 48 27 27 47 38 32 33 36 23 33 36 32 29 17 22 19 
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 
13 11 12  6 15 15 22  6 21 26 16 10 11  3  7 11  9  8  4  4  2  5  2 
3 least connected regions:
815 1584 1900 with 1 link
2 most connected regions:
886 984 with 74 links

Weights style: W 
Weights constants summary:
     n      nn   S0       S1       S2
W 2443 5968249 2443 235.6446 9848.804
Code
# Visualiser le réseau
plot(W_dist_mat,
     coords   = housesales[, 17:18],
     pch      = 19,
     cex      = 0.1,
     col      = "#2c7bb6",
     main     = "Voisinage par distance (2 000 m)")

Réseau de voisinage par distance (2 000 m) — transactions immobilières

7.10 Récapitulatif des opérations de géotraitement

Ce qu’il faut retenir
Opération Fonction Usage
Spatialiser un CSV st_as_sf(df, coords = c(...)) Créer des points depuis lon/lat
Définir un SCR st_set_crs(x, epsg) Déclarer le SCR manquant
Reprojeter st_transform(x, epsg) Changer de projection
Jointure spatiale st_join(polygons, points) Associer points aux polygones
Zone tampon st_buffer(x, dist = ...) Zone à distance fixe
Intersection st_intersection(x, y) Découper les géométries
Calculer une superficie st_area(x) Superficie en m²
Calculer une distance st_distance(x, y) Distance entre entités
Trouver des voisins dnearneigh(x, d1, d2) Voisinage par distance