3  Données Spatiales et Simple Features

Author

Dr. Elisabetta Pietrostefani

Published

March 22, 2026

3.1 Contexte : pourquoi R pour la cartographie ?

R est un environnement open source conçu pour l’analyse de données et les graphiques, qui s’exécute sur toutes les plateformes. Choisir R pour la cartographie, c’est réunir dans un seul outil :

  • la manipulation des données (nettoyage, jointures, agrégation)
  • l’analyse statistique (modèles, tests)
  • la visualisation (cartes statiques, interactives, graphiques)
  • la reproductibilité (scripts, Quarto, Git)

Contrairement aux logiciels SIG à interface graphique (QGIS, ArcGIS), R encourage une approche en ligne de commande qui rend chaque étape de l’analyse traçable et reproductible.

Citation de référence

“Les données ne sont pas que des chiffres, ce sont des chiffres avec un contexte” — Cobb & Moore


3.2 Installation des packages

Code
# Packages principaux pour ce chapitre
library(sf)        # Simple Features — données vectorielles
library(spData)    # jeux de données géographiques
library(ggplot2)   # visualisation
library(dplyr)     # manipulation de données
library(leaflet)   # cartes interactives
library(maps)      # données cartographiques de base
library(stars)     # données raster (optionnel)
Code
install.packages(c("sf", "spData", "ggplot2", "dplyr",
                   "leaflet", "maps", "stars", "terra"))

3.3 Toutes les données sont spatiales

Toutes les données sont spatiales — les données proviennent de l’observation, et l’observation doit avoir lieu quelque part et à un certain moment.

Pour beaucoup de données, la localisation est secondaire : l’emplacement d’un scanner cérébral n’est pas pertinent pour le diagnostic. Mais pour d’autres — cartographie épidémique, transport, urbanisme, environnement — la position dans l’espace est la variable principale.

3.3.1 Un premier exemple interactif

Code
popup <- c("Clémence", "Baptiste", "Nathalie", "Daphné",
           "Fannie", "Gabrielle", "Yann", "Cédric", "Soilihi")

leaflet() |>
  addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") |>
  addMarkers(
    lng = c(-1.5536, -0.5792, 5.4474, -4.4861, -2.0257,
             3.0870,  2.3522,  4.4861,  2.0257),
    lat = c(47.2184, 44.8378, 43.5297, 48.3904, 48.6493,
            45.7772, 48.8566, 48.3904, 48.6493),
    popup = popup
  )
Figure 3.1: Carte interactive : villes d’origine des participants (données fictives)
Exercice 1

Modifiez la carte ci-dessus pour ajouter votre propre ville. Trouvez les coordonnées sur latlong.net.


3.4 Représenter des données spatiales dans R

Les trois types d’objets spatiaux fondamentaux sont les points, les polygones et les rasters. Voyons chacun avec un exemple concret, en utilisant ggplot2 — la même syntaxe que vous utiliserez tout au long du cours.

3.4.1 1. Points — des lieux précis

Un point est défini par une paire de coordonnées (longitude, latitude). C’est la représentation la plus simple : une ville, une station de mesure, une adresse.

Code
cities <- data.frame(
  nom = c("Paris", "Londres", "Berlin", "Madrid", "Rome"),
  lon = c(2.35,  -0.12,  13.40,  -3.70,  12.50),
  lat = c(48.85,  51.51,  52.52,  40.42,  41.90)
) |>
  st_as_sf(coords = c("lon", "lat"), crs = 4326)

ggplot() +
  geom_sf(data = world[world$continent == "Europe", ],
          fill = "grey90", color = "white", linewidth = 0.3) +
  geom_sf(data = cities, color = "#d7191c", size = 3) +
  geom_sf_label(data = cities, aes(label = nom),
                size = 3, nudge_y = 1.2, label.size = 0) +
  coord_sf(xlim = c(-10, 25), ylim = c(36, 58)) +
  labs(
    title   = "Points : capitales européennes",
    caption = "Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.2: Points : cinq capitales européennes
Exercice 2

Ajoutez une ville de votre choix au data frame cities. Trouvez ses coordonnées sur latlong.net.

3.4.2 2. Polygones — des surfaces

Un polygone est une séquence de points formant une surface fermée : commune, département, région, pays. Les polygones portent une table attributaire — c’est ce qui permet de les colorer selon une variable.

Code
europe <- world[world$continent == "Europe", ]

ggplot(europe) +
  geom_sf(aes(fill = gdpPercap), color = "white", linewidth = 0.3) +
  scale_fill_viridis_c(
    option   = "plasma",
    name     = "PIB/hab (USD)",
    na.value = "grey80",
    labels   = scales::label_comma()
  ) +
  coord_sf(xlim = c(-10, 40), ylim = c(34, 72)) +
  labs(
    title   = "Polygones : PIB par habitant en Europe",
    caption = "Source : spData | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(
    plot.title        = element_text(face = "bold", hjust = 0.5),
    legend.key.height = unit(1.2, "cm")
  )
Figure 3.3: Polygones : PIB par habitant par pays en Europe

3.4.3 3. Raster — des grilles de valeurs

Un raster divise l’espace en une grille régulière de pixels, chacun portant une valeur numérique. C’est le format naturel pour les phénomènes continus comme l’altitude, la température ou l’occupation du sol.

Le jeu de données volcano intégré à R représente le Maungawhau — un volcan éteint situé à Auckland, Nouvelle-Zélande — sous forme d’une matrice d’altitudes de 87 × 61 cellules.

Code
volcano_df <- expand.grid(
  x = 1:ncol(volcano),
  y = 1:nrow(volcano)
)
volcano_df$altitude <- as.vector(t(volcano))

ggplot(volcano_df, aes(x = x, y = y, fill = altitude)) +
  geom_raster() +
  scale_fill_gradientn(
    colors = hcl.colors(50, "Terrain"),
    name   = "Altitude (m)"
  ) +
  coord_equal() +
  labs(
    title    = "Raster : Maungawhau — Auckland, Nouvelle-Zélande",
    subtitle = "Chaque pixel = une cellule de la matrice d'altitudes",
    caption  = "Source : dataset volcano (base R) | 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 3.4: Raster : Modèle Numérique d’Élévation du volcan Maungawhau, Auckland (NZ)

3.5 Simple Features (sf)

sf (Simple Features) est le standard pour les données vectorielles dans R. Il est développé et approuvé par l’Open Geospatial Consortium (OGC) et stocke les géométries dans une colonne spéciale d’un data frame ordinaire.

3.5.1 Les 7 types de géométrie essentiels

Code
old_par <- par(mfrow = c(1, 3), pty = "s", mar = c(1, 2, 2, 1))

plot(st_as_sfc("POINT(5 2)"),
     axes = TRUE, main = "POINT", col = "#2c7bb6", pch = 19, cex = 2)

plot(st_as_sfc("LINESTRING(1 5, 4 4, 4 1, 2 2, 3 2)"),
     axes = TRUE, main = "LINESTRING", col = "#d7191c", lwd = 2)

plot(st_as_sfc("POLYGON((1 5, 2 2, 4 1, 4 4, 1 5))"),
     col = "#ffffbf", border = "#2c3e50", axes = TRUE, main = "POLYGON")

par(old_par)
Figure 3.5: Les trois types de géométrie fondamentaux dans sf

Syntaxe WKT (Well-Known Text) :

Type Exemple WKT
Point POINT (5 2)
Ligne LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2)
Polygone POLYGON ((1 5, 2 2, 4 1, 4 4, 1 5))
Polygone avec trou POLYGON ((1 5, 2 2, 4 1, 4 4, 1 5), (2 4, 3 4, 3 3, 2 3, 2 4))
Multi-polygone MULTIPOLYGON (((0 0,1 0,1 1,0 0)), ((3 3,4 3,4 4,3 3)))

3.5.2 Explorer un objet sf : le monde

Code
# Le dataset 'world' est un objet sf fourni par spData
class(world)
[1] "sf"         "tbl_df"     "tbl"        "data.frame"
Code
names(world)
 [1] "iso_a2"    "name_long" "continent" "region_un" "subregion" "type"     
 [7] "area_km2"  "pop"       "lifeExp"   "gdpPercap" "geom"     
Code
ggplot() +
  geom_sf(data = world, aes(fill = gdpPercap)) +
  scale_fill_viridis_c(
    option = "plasma",
    name   = "PIB/hab (USD)",
    na.value = "grey80"
  ) +
  labs(
    title   = "PIB par habitant dans le monde",
    caption = "Source : spData | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.6: PIB par habitant dans le monde avec ggplot2

3.5.3 Filtrer et superposer

Code
world_africa <- world[world$continent == "Africa", ]
africa       <- st_union(world_africa)

ggplot() +
  geom_sf(data = world, fill = "grey90", color = "white", linewidth = 0.2) +
  geom_sf(data = africa, fill = "#d7191c", color = "white", linewidth = 0.3) +
  labs(
    title   = "Le continent africain",
    caption = "Source : spData | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.7: Afrique mise en évidence sur la carte du monde
Code
kenya <- world[world$name_long == "Kenya", ]

ggplot() +
  geom_sf(data = world_africa, fill = "grey85", color = "white", linewidth = 0.3) +
  geom_sf(data = kenya, fill = "#2c7bb6", color = "white", linewidth = 0.5) +
  labs(
    title   = "Le Kenya en Afrique",
    caption = "Source : spData | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.8: Le Kenya en contexte africain
Exercice 3

Adaptez le code ci-dessus pour mettre en évidence un autre pays de votre choix sur son continent.


3.6 Application : Cartes de Londres

Nous travaillons maintenant avec de vraies données : les districts londoniens et des données de qualification tirées du recensement.

3.6.1 Charger les données

Code
# Charger le shapefile des districts londoniens
districts <- read_sf("data/London/Polygons/districts.shp")

# Aperçu de la structure
head(districts)
Simple feature collection with 6 features and 2 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 × 3
  DIST_CODE DIST_NAME                                                   geometry
  <chr>     <chr>                                                  <POLYGON [m]>
1 00AA      City of London       ((531028.5 181611.2, 531036.1 181611.5, 531074…
2 00AB      Barking and Dagenham ((550817 184196, 550814 184189.1, 550799 18416…
3 00AC      Barnet               ((526830.3 187535.5, 526830.3 187535.4, 526829…
4 00AD      Bexley               ((552373.5 174606.9, 552372.9 174603.9, 552371…
5 00AE      Brent                ((524661.7 184631, 524665.3 184626.4, 524667.9…
6 00AF      Bromley              ((533852.2 170129, 533850.4 170128.5, 533844.9…

3.6.2 Carte de base

Code
ggplot(districts) +
  geom_sf(fill = "lightgrey", color = "white", linewidth = 0.4) +
  labs(
    title   = "Districts de Londres",
    caption = "Source : données Londres | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.9: Districts londoniens — carte de base

3.6.3 Indexation et sélection

Code
# Indexation par position
districts[1, ]      # première ligne
districts[, 1]      # première colonne
districts[1, 1]     # cellule [1,1]

# Accès à une colonne par son nom
districts$DIST_NAME

# Filtrage conditionnel
districts[districts$DIST_NAME == "Hackney", ]
Code
hackney <- districts |> filter(DIST_NAME == "Hackney")

ggplot() +
  geom_sf(data = districts, fill = "lightgrey", color = "white", linewidth = 0.3) +
  geom_sf(data = hackney,   fill = "#2c7bb6",   color = "white", linewidth = 0.6) +
  labs(
    title   = "Hackney parmi les districts de Londres",
    caption = "Source : données Londres | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.10: Hackney mis en évidence parmi les districts londoniens
Exercice 4

Modifiez le code ci-dessus pour mettre en évidence un autre district de votre choix (ex. "Islington", "Tower Hamlets", "Southwark").

3.6.4 Joindre des données attributaires

Code
# Importer les données de qualification du recensement 2001
qualifications2001_df <- read.csv("data/London/Tables/qualifications2001_2.csv")
head(qualifications2001_df)
  Zone_Code            Zone_Name Population1674 Noquals Level1 Level2 Level3
1      00AA       City of London           6067     607    359    634    665
2      00AB Barking and Dagenham         113579   44873  21654  20564   6626
3      00AC               Barnet         228123   44806  25558  41118  24695
4      00AD               Bexley         156172   44887  32110  35312  10759
5      00AE                Brent         198712   48915  23913  33280  21121
6      00AF              Bromley         212368   47093  34879  48012  19550
  Level4
1   3647
2  11615
3  80907
4  20704
5  60432
6  49598
Code
# Jointure : shapefile districts + données CSV
# La clé commune : DIST_CODE (districts) = Zone_Code (qualifications)
districts <- left_join(
  districts,
  qualifications2001_df,
  by = c("DIST_CODE" = "Zone_Code")
)

# Créer la variable : part de personnes avec qualification niveau 4+
districts <- districts |>
  mutate(Level4p = Level4 / Population1674)

head(districts |> st_drop_geometry() |> select(DIST_NAME, Level4, Population1674, Level4p))
# A tibble: 6 × 4
  DIST_NAME            Level4 Population1674 Level4p
  <chr>                 <int>          <int>   <dbl>
1 City of London         3647           6067   0.601
2 Barking and Dagenham  11615         113579   0.102
3 Barnet                80907         228123   0.355
4 Bexley                20704         156172   0.133
5 Brent                 60432         198712   0.304
6 Bromley               49598         212368   0.234
Les types de jointure en dplyr
Fonction Résultat
left_join(x, y) Toutes les lignes de x, colonnes de x et y
inner_join(x, y) Seulement les lignes avec correspondance dans x et y
right_join(x, y) Toutes les lignes de y, colonnes de x et y
full_join(x, y) Toutes les lignes de x et y

3.6.5 Carte choroplèthe : niveau d’éducation

Code
ggplot(districts) +
  geom_sf(aes(fill = Level4p), color = "white", linewidth = 0.3) +
  scale_fill_viridis_c(
    option  = "viridis",
    name    = "Part niveau 4+",
    labels  = scales::label_percent()
  ) +
  labs(
    title    = "Niveau d'éducation (qualification niveau 4+)",
    subtitle = "Districts londoniens — Recensement 2001",
    caption  = "Source : données Londres | Cours R-Carto"
  ) +
  theme_void(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold", size = 14, hjust = 0.5),
    plot.subtitle = element_text(color = "grey40", hjust = 0.5),
    legend.key.height = unit(1.2, "cm")
  )
Figure 3.11: Part de la population avec qualification de niveau 4+ par district londonien

3.6.6 Nuage de points : population vs éducation

Code
ggplot(districts,
       aes(x = Population1674, y = Level4p)) +
  geom_point(aes(color = Level4p, size = Population1674), alpha = 0.8) +
  geom_text(
    aes(label = DIST_NAME, color = Level4p),
    size = 2.5, check_overlap = TRUE,
    nudge_x = 15000, nudge_y = 0.015
  ) +
  scale_color_viridis_c(option = "plasma", guide = "none") +
  scale_size_continuous(
    name   = "Population",
    range  = c(2, 8),
    labels = scales::label_comma()
  ) +
  scale_x_continuous(
    name   = "Population (16–74 ans)",
    labels = scales::label_comma()
  ) +
  scale_y_continuous(
    name   = "Part qualification niveau 4+",
    labels = scales::label_percent()
  ) +
  labs(
    title   = "Population et niveau d'éducation — Districts londoniens",
    caption = "Source : Recensement 2001 | Cours R-Carto"
  ) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
Figure 3.12: Relation entre population et niveau d’éducation par district
Exercice 5

À vous de jouer ! Créez une carte similaire pour Paris avec des données de votre choix. Étapes :

  1. Charger un shapefile des arrondissements ou communes de Paris

  2. Charger un fichier CSV avec une variable socio-économique (INSEE)

  3. Faire la jointure avec left_join()

  4. Créer la carte avec ggplot2 + geom_sf()