FAIRE DES CARTOGRAMS DANS R
Dans cette séquence, nous proposons d’explorer différentes méthodes disponibles dans R, pour réaliser des cartogrammes basés sur des données quantitatives absolues (stocks).
Préalables
Installation des packages
Voici une liste des packages à installer.
NB : Pour installer le package cartogramR, vous aurez besoin d’installer au préalable FFTW. Sous Windows, cette opération est loin d’être simple…
install.packages(knitr)
install.packages(sf)
install.packages(mapsf)
install.packages(packcircles)
install.packages(cartogram)
install.packages(recmap)
install.packages(dplyr)
install.packages(cartogramR)
# install.packages("https://cran.r-project.org/src/contrib/Archive/cartogramR/cartogramR_1.0-1.tar.gz", repos = NULL, type = "source")
Environnement de travail
- Créer un nouveau projet
- Créer un script R
- Créer un répertoire data pour stocker vos données
- Créer un répertoire maps pour ranger vos cartes
Import et préparation des données
Les données utilisées ici sont des données INSEE sur la population et quelques catégories socio-professionelles en 2018 au niveau communal. L’espace d’étude est le département de l’Isère.
Le package sf (pour simple features), développé principalement par Edzer Pebesma (et Roger Bivan), permet de gérer les objets spatiaux dans R (projections, geotraitements, etc.). C’est le package qui a succédé au package sp.
library(sf)
# Import des communes
<-
communes st_read(
"https://raw.githubusercontent.com/transcarto/rcartograms/main/data/isere.geojson",
quiet = TRUE
%>% st_transform(2154)
)
# Import des données attributaires
<-
data read.csv(
"https://raw.githubusercontent.com/transcarto/rcartograms/main/data/popisrere.csv",
dec = ","
)
# Jointure
= merge(x = communes[, c("id", "name", "geometry")],
communes y = data[, c("id",
"pop2018",
"agri",
"art",
"cadr",
"interm",
"emp",
"ouvr",
"retr")],
by = "id")
# Agrégation des communes
= st_union(communes) isere
::kable(communes[c(0:10),], row.names = F, digits = 1) knitr
id | name | pop2018 | agri | art | cadr | interm | emp | ouvr | retr | geometry |
---|---|---|---|---|---|---|---|---|---|---|
38001 | Les Abrets en Dauphiné | 6336 | 30.1 | 210.9 | 256.0 | 712.9 | 833.3 | 948.8 | 1397.2 | MULTIPOLYGON (((900716.2 64… |
38002 | Les Adrets | 1025 | 0.0 | 53.0 | 168.7 | 163.9 | 82.0 | 67.5 | 159.1 | MULTIPOLYGON (((931354.5 64… |
38003 | Agnin | 1127 | 0.0 | 28.9 | 72.2 | 168.6 | 139.7 | 134.9 | 192.6 | MULTIPOLYGON (((844459.1 64… |
38004 | L’Albenc | 1279 | 25.0 | 30.0 | 65.0 | 215.0 | 130.0 | 175.0 | 210.0 | MULTIPOLYGON (((893395.8 64… |
38005 | Allemond | 954 | 5.0 | 75.0 | 30.0 | 160.0 | 150.0 | 120.0 | 170.0 | MULTIPOLYGON (((934142.3 64… |
38006 | Allevard | 4062 | 0.0 | 176.7 | 323.2 | 535.2 | 469.6 | 469.6 | 984.2 | MULTIPOLYGON (((948125.2 64… |
38008 | Ambel | 28 | 10.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 10.0 | MULTIPOLYGON (((930843.9 64… |
38009 | Anjou | 1009 | 10.0 | 54.9 | 60.0 | 124.9 | 100.0 | 115.0 | 278.5 | MULTIPOLYGON (((847739.9 64… |
38010 | Annoisin-Chatelans | 686 | 14.8 | 44.4 | 54.2 | 113.3 | 64.1 | 78.9 | 113.3 | MULTIPOLYGON (((875494.4 65… |
38011 | Anthon | 1073 | 5.0 | 55.0 | 75.0 | 185.0 | 125.0 | 130.0 | 220.0 | MULTIPOLYGON (((866538.7 65… |
Exploration des représentations par packages : les classiques
Rappel : il s’agit ici de résoudre la question de la représentation de quantités brutes associées à des surfaces. Quatre possibilités parmi d’autres : les symboles proportionnels, la carte par point (taille unique ou plusieurs tailles), le cartogramme continu.
Rappel en carto “classique” avec le package mapsf
Le package mapsf permet de faire des cartes thématiques dans R. C’est le package qui succède au package cartography (qui n’est plus maintenu).
Dans ce package, trois solutions existent pour cartographier des quantités brutes associées à des surfaces (cartographie dite “classique”) :
la dot map
les points valués
les symboles proportionnels.
Création d’un template cartographique
library(mapsf)
= "#c291bc"
col = paste0("Bronner Anne-Christine & Nicolas Lambert, 2021\n",
credits "Source: IGN & INSEE, 2021")
= mf_theme(
theme x = "default",
bg = "#f0f0f0",
tab = FALSE,
pos = "center",
line = 2,
inner = FALSE,
fg = col,
mar = c(0, 0, 2, 0),
cex = 1.9
)
= function(title,
template
file,note = "",
basemap = TRUE,
scale = TRUE) {
mf_export(
communes,export = "png",
width = 1000,
filename = file,
res = 96,
theme = theme,
expandBB = c(-.02, 0, -.02, 0)
)
if (basemap == TRUE) {
mf_shadow(
x = communes,
col = "grey50",
cex = 1,
add = TRUE
)mf_map(
communes,col = "#CCCCCC",
border = "white",
lwd = 0.5,
add = TRUE
)
}
mf_title(title)
if (scale == TRUE) {
mf_scale(
size = 20,
pos = "bottomright",
lwd = 1.2,
cex = 1,
col = "#383838",
unit = "km"
)
}
mf_credits(
txt = credits,
pos = "bottomleft",
col = "#1a2640",
cex = 0.8,
font = 3,
bg = "#ffffff30"
)
if (note != "") {
mf_annotation(
x = c(885000, 6435000),
txt = note,
pos = "bottomleft",
cex = 1.2,
font = 2,
halo = TRUE,
s = 1.5
)
}
}
template("Template cartographique", "maps/template.png", note = "Département de\nl'Isère (38)")
mf_map(
communes,col = col,
border = "white",
lwd = 0.5,
add = TRUE
)dev.off()
Dot density map ou carte par points
Il existe plusieurs solutions pour réaliser une carte par points dans R. Ici, nous vous proposons de créer une fonction qui s’appuie sur st_sample() du package sf.
<- function(x,
dotdensitymap
var,onedot = 1,
radius = 1) {
<- x[, c("id", var, "geometry")]
x "v"] <- round(x[, var] %>% st_drop_geometry() / onedot, 0)
x[, <- st_sample(x, x$v, type = "random", exact = TRUE)
dots <- st_buffer(dots, dist = radius)
circles return (circles)
}
= 500
onedot = dotdensitymap(
dots x = communes,
var = "pop2018",
onedot = onedot,
radius = 300
)template("Carte par points",
"maps/dotdensity.png",
note = paste0("Un point =\n", onedot, " habitants"))
mf_map(
dots,col = col,
border = "#520a2c",
lwd = 0.5,
add = TRUE
)dev.off()
(Simili) Points Bertin ou points valués
Les points valués, appelés également “points Bertin” sont une variante de la dot map, avec deux-trois taille de points de référence pour s’adapter à de fortes disparités dans la distribution des effectifs. L’algorithme ci-après approche l’idée en distribuant les effectifs de chaque entité sur un ensemble de points qui se positionnent sur une grille et dont la taille va s’adapter à l’effectif.
# On cacule une grille régulière et on en éxtrait les centroides.
= st_make_grid(communes, cellsize = 1000, square = TRUE)
grid = st_sf(id = c(1:length(grid)), geometry = grid)
grid = communes[, "id"] %>% st_drop_geometry()
ids = st_centroid(grid)
ctr
# On effectue une jointure spatiale (point within polygon)
for (i in 1:nrow(grid)) {
= st_within(
x x = ctr[i, ],
y = communes,
sparse = TRUE,
prepared = TRUE
)= ids[unlist(x), ][1]
id if (length(id) == 0) {
= NA
id
}"id"] = id
grid[i,
}
# On élimine les points qui n'ont pas été intersectés
<- grid[!is.na(grid$id), ]
grid
# On affecte les valeurs aux points
for (i in 1:nrow(grid)) {
= as.character(grid[i, "id"])[1]
id = as.numeric(communes[communes$id == id, "pop2018"] %>% st_drop_geometry())
popAll = nrow(grid[grid$id == id, ])
count "pop"] = popAll / count
grid[i,
}
# Export des données
st_write(grid, "data/grid.geojson", delete_layer = TRUE)
La grille est maintenant disponible dans votre répertoire data.
= st_read("data/grid.geojson") grid
Vous pouvez aussi aller chercher la grille précalculée sur github.
=
grid st_read("https://raw.githubusercontent.com/transcarto/rcartograms/main/data/grid.geojson")
template("Points Bertin (grosso modo)", "maps/pointsbertin.png")
mf_map(
communes,col = "#CCCCCC",
border = "white",
lwd = 0.5,
add = FALSE
)mf_map(
grid,var = "pop",
col = col,
border = "#520a2c",
type = "prop",
inches = 0.07,
leg_title_cex = 1.2,
leg_val_cex = 0.8,
leg_title = "Nombre d'habitants, 2018"
)
dev.off()
Symboles proportionnels
C’est la représentation la plus usuelle pour représenter des données quantitatives absolues.
template("Symboles proportionnels (mapsf)", "maps/prop.png")
mf_map(
communes,var = "pop2018",
col = col,
border = "#6b4266",
type = "prop",
inches = 0.8,
leg_title_cex = 1.2,
leg_val_cex = 0.8,
symbol = "square",
leg_title = "Nombre d'habitants, 2018"
)dev.off()
template("Symboles proportionnels (mapsf)", "maps/prop2.png")
mf_map(
communes,var = "pop2018",
col = col,
border = "#6b4266",
type = "prop",
inches = 0.8,
leg_title_cex = 1.2,
leg_val_cex = 0.8,
leg_title = "Nombre d'habitants, 2018"
)dev.off()
Entre symboles proportionnels et cartogramme de Dorling : le package packcircles
Le package packcirles propose 3 algorithmes simples pour déplacer des disques sur un plan 2D de telle sorte qu’ils ne se supperposent pas. Il reprend le principe du cartogramme de Dorling et permet de gérer la superposition des cercles, améliorant la lisibilité générale de la carte par symboles proportionnels (D. Dorling 1996).
Création d’un ficher de données simplifié avec les coordonnées des centroïdes des communes.
= communes
dots st_geometry(dots) <-
st_centroid(sf::st_geometry(dots), of_largest_polygon = TRUE)
<- data.frame(dots$id, dots["pop2018"], st_coordinates(dots))
dots = dots[, c("dots.id", "X", "Y", "pop2018")]
dots colnames(dots) <- c("id", "x", "y", "v")
<- dots[!is.na(dots$v), ]
dots ::kable(dots[c(0:5), ], row.names = F, digits = 1) knitr
id | x | y | v |
---|---|---|---|
38001 | 902026.9 | 6495943 | 6336 |
38002 | 934555.0 | 6466630 | 1025 |
38003 | 845708.9 | 6472468 | 1127 |
38004 | 892892.6 | 6460697 | 1279 |
38005 | 937029.6 | 6456880 | 954 |
La fonction circleRepelLayout() prend un ensemble de cercles dans un cadre de données et utilise la répulsion itérative pour essayer de trouver un arrangement sans chevauchement où tous les centres des cercles se trouvent à l’intérieur d’un rectangle de délimitation. Si aucun arrangement de ce type ne peut être trouvé dans le nombre maximum d’itérations spécifié, la dernière tentative est renvoyée.
library("packcircles")
= 500 # pour ajuster la taille des cercles
k = 10 # nombre d'iterations
itermax
<- dots[, c("x", "y", "v")]
dat.init $v <- sqrt(dat.init$v * k)
dat.init<- circleRepelLayout(
simulation x = dat.init,
xysizecols = 1:3,
wrap = FALSE,
sizetype = "radius",
maxiter = itermax,
weights = 1
$layout
)::kable(simulation[c(0:5), ], row.names = F, digits = 1) knitr
x | y | radius |
---|---|---|
902120.7 | 6496140 | 1779.9 |
934555.0 | 6466630 | 715.9 |
845708.9 | 6472468 | 750.7 |
892892.6 | 6460697 | 799.7 |
937029.6 | 6456880 | 690.7 |
<- st_buffer(sf::st_as_sf(
circles
simulation,coords = c('x', 'y'),
crs = sf::st_crs(communes)
),dist = simulation$radius)
$v = dots$v
circles
template("Dorling (packcircles)", "maps/dorling1.png", scale = FALSE)
mf_map(
isere,col = "#CCCCCC",
border = "white",
lwd = 0.5,
add = TRUE
)mf_map(
circles,col = col,
border = "#6b4266",
lwd = 0.5,
add = TRUE
)dev.off()
Les 3 formes classiques avec le package cartogram
le package cartogram est développé par Sebastian Jeworutzki. Il propose trois méthodes : le cartogramme circulaire de Dorling, discontinu d’Olson et continu de Dougenik, Chrisman et Niemeyer).
library(cartogram)
Le cartogramme circulaire de Dorling
Proche d’un traitement graphique de l’information, l’algorithme de Danny Dorling permet de visualiser les grandes masses de population en prenant en compte les positions relatives des entités les unes par rapport aux autres.
L’algorithme packcircle permet d’approcher le cartogram de Dorling.
= cartogram_dorling(communes, "pop2018", k = 1.8) Dorling
template("Dorling (cartogram)", "maps/dorling2.png", scale = FALSE)
mf_map(
isere,col = "#CCCCCC",
border = "white",
lwd = 0.5,
add = TRUE
)mf_map(
Dorling,col = col,
border = "#6b4266",
lwd = 0.5,
add = TRUE
)dev.off()
Le cartogramme discontinu d’Olson
Judith Olson, en réaction aux premiers cartogrammes continus de Tobler et notamment les problèmes de lisibilité, propose une variation proportionnelle de la surface des entités tout en gardant la forme “géographique” (Olson 1976)
<- cartogram_ncont(communes, "pop2018", k = 1, inplace = TRUE) Olson
inplace : Si VRAI, chaque polygone est redimensionné à sa place initiale, si FAUX, les multi-polygones sont centrés sur leur centroïde initial.
template("Olson (cartogram)",
"maps/olson.png",
basemap = FALSE,
scale = FALSE)
mf_map(
isere,col = "#CCCCCC30",
border = "white",
lwd = 0.5,
add = TRUE
)mf_map(
Olson,col = col,
border = "#6b4266",
lwd = 0.5,
add = TRUE
)dev.off()
Le cartogramme continu Dougenik
James A. Dougenik, Nicholas R. Chrisman et Duane R.Niemeyer, sur la base des premiers travaux de W. Tobler, développent en 1985 un des deux algorithmes les plus implanté dans les outils (avec celui de Gastner et Newman développé en 2004). (Dougenik, Chrisman, and Niemeyer 1985)(Daniel Dorling 1995)
x : un objet sf.
weight : om de la variable.
itermax : nombre d’itérations maximum si maxSizeError n’est pas atteint.
maxSizeError : la déformation s’arrete si l’erreur moyenne est inférieur à cette valeur.
prepare : mettre “adjust” permer d’acceler le temps de calcul.
threshold : seuil pour la préparation des données.
<- cartogram_cont(communes, "pop2018", prepare = "none", itermax = 10, maxSizeError = 1.15) Dougenik
Calcul des erreurs
= sum(as.numeric(st_area(Dougenik)))
sumarea = sum(Dougenik$pop2018)
sumpop $error = (as.numeric(st_area(Dougenik)) / sumarea) / (Dougenik$pop2018 / sumpop) * 100
Dougeniksummary(Dougenik$error)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 25.68 97.10 109.49 144.87 131.42 2731.97
= c(min(Dougenik$error),
bks 70,
80,
90,
100,
110,
120,
max(Dougenik$error))
= c("#d53e4f",
cols "#f46d43",
"#fdae61",
"#fee08b",
"#e6f598",
"#abdda4",
"#66c2a5")
Affichage de la carte
template("Dougenik (cartogram)",
"maps/dougenik.png",
basemap = FALSE,
scale = FALSE)
mf_map(
x = Dougenik,
type = "choro",
var = "error",
pal = cols,
breaks = bks,
border = "#6b4266",
lwd = 0.5,
add = TRUE
)dev.off()
Les algos pour les cartogrammes continus avec le package cartogramR
library(cartogramR)
Le package cartogramR est développé par Pierre-Andre Cornillon et Florent Demoraes de l’université de Rennes. Le package propose les méthodes flow based cartogram (Gastner and Newman 2004) implanté dans Scapetoad, fast flow based cartogram (Gastner, Seguy, and More 2018) et rubber band based cartogram (Dougenik, Chrisman, and Niemeyer 1985). La méthode flow based cartogram est basée sur go_cart.
Attention, cartogramR n’est pas/plus sur le CRAN. L’archive est néanmoins disponible et permet l’installation.
la fonction precartogramR() aide à choisir la taille de la grille de déformation (plus elle est fine, plus le calcul sera long). On choisit donc a minima une grille telle que le minimum d’intersections est supérieur ou égal a un (ici un pas de grille de 256 peut faire l’affaire).
<- precartogramR(communes, method = "GastnerSeguyMore")
precarto summary(precarto)
## L256 L512 L1024 L2048
## Min. 2.00000 5.0000 20.0000 82.000
## 1st Qu. 13.00000 52.0000 208.7500 837.000
## Median 19.00000 77.0000 306.0000 1226.500
## Mean 26.16992 104.6953 418.6816 1674.701
## 3rd Qu. 29.00000 115.2500 460.7500 1850.250
## Max. 405.00000 1613.0000 6469.0000 25884.000
la fonction cartogramR() permet de calculer le cartogramme en tant que tel selon les 3 méthodes proposées : GastnerSeguyMore" (ou “gsm),”GastnerNewman" (ou “gn),”DougenikChrismanNiemeyer" (ou “dcn”). L’option L=256 permet de choisir la taille de la grille. L’option grid=TRUE (pour les méthodes “gsm” et “gn”) permet d’afficher la grille de déformation. L’option maxit=50 permet de définir le nombre d’itérations max (défaut = 50).
Test de l’algorithme de Gastener et Newman de 2004, équivalent à celui implanté dans la solution java scapetoad.
<-
GastnerSeguy cartogramR(
communes,count = "pop2018",
method = "GastnerSeguyMore",
options = list(L = 256, grid = TRUE, maxit = 5)
)
## WARNING criterion: 26.455251 > Objective: 0.010000
## Increase maxit or decrease Objective
La fonction make_layer() permet de récupérer la grille de calcul (mais aussi la grille d’origine, les centroides, etc)
<- make_layer(GastnerSeguy, type = c("final_graticule")) grid
L’affichage des résultats peut se réaliser avec plot (via sf) ou, comme précédemment, avec le package mapsf.
template(
"Gastner, Seguy & More",
"maps/gastnerseguy.png",
basemap = FALSE,
scale = FALSE
)mf_map(
$cartogram,
GastnerSeguycol = col,
border = "white",
lwd = 1,
add = TRUE
)mf_map(
grid,col = NA,
border = "#6b4266",
lwd = 0.5,
add = TRUE
)dev.off()
La fonction residuals() permet de calculer les erreurs liées à la déformation (erreur relative : taille finale / taille theorique * 100)
= cbind(
table $initial_data[, c("id", "pop2018")] %>% st_drop_geometry(),
GastnerSeguyorig_area = GastnerSeguy$orig_area,
final_area = GastnerSeguy$final_area,
errors = residuals(GastnerSeguy, type = "relative error") * 100
)::kable(table[c(0:10), ], row.names = F, digits = 1) knitr
id | pop2018 | orig_area | final_area | errors |
---|---|---|---|---|
38001 | 6336 | 27223532 | 39376361.8 | -0.2 |
38002 | 1025 | 16353553 | 5636619.1 | -11.7 |
38003 | 1127 | 8082831 | 7027719.4 | 0.2 |
38004 | 1279 | 10663444 | 7921991.8 | -0.5 |
38005 | 954 | 56515131 | 5540617.6 | -6.7 |
38006 | 4062 | 34980514 | 24517963.6 | -3.0 |
38008 | 28 | 4789504 | 95852.1 | -45.0 |
38009 | 1009 | 5121670 | 6220553.5 | -1.0 |
38010 | 686 | 13149817 | 4281634.4 | 0.3 |
38011 | 1073 | 8596299 | 6774560.2 | 1.4 |
Export au format sf
st_write(as.sf(GastnerSeguy),
"data/GastnerSeguy.geojson",
delete_layer = TRUE)
Variations : encore plus de cartogrammes
Dots Cartograms
La méthode des dots cartograms est une représentation cartographique à l’intersection des cartogrammes de Dorling et de la carte par points. Les premières cartes utilisant cette méthodes ont été réalisées sur les données du Covid (voir et voir). Article à paraitre. Voir aussi sur Observable
= function(x, var, itermax, onedot, radius) {
dotcartogram = sf::st_crs(x)
crs <-
coords st_coordinates(st_centroid(sf::st_geometry(x), of_largest_polygon = TRUE))
<- x[c("id", var)] %>% st_drop_geometry()
x <- data.frame(x, coords)
x colnames(x) <- c("id", "v", "x", "y")
$v <- round(x$v / onedot, 0)
x<- x[x$v > 0, ]
x <- x[x$v == 1, c("x", "y", "v")]
dots <- x[x$v > 1, c("x", "y", "v")]
rest
<- nrow(rest)
nb for (i in 1:nb) {
<- rest[i, ]
new $v <- 1
newfor (j in 1:rest$v[i]) {
<- rbind(dots, new)
dots
}
}
$x <- jitter(dots$x)
dots$y <- jitter(dots$y)
dots$v <- radius
dots
<- circleRepelLayout(
simulation x = dots,
xysizecols = 1:3,
wrap = FALSE,
sizetype = "radius",
maxiter = itermax,
weights = 1
$layout
)
<- st_buffer(sf::st_as_sf(simulation, coords = c('x', 'y'),
circles crs = crs), dist = radius)
return(circles)
}
= 1000
onedot = dotcartogram(
dc x = communes,
var = "pop2018",
itermax = 120,
onedot = onedot,
radius = 600
)
template("Dots Cartogram", "maps/dotcartogram.png", note = paste0("Un point =\n",onedot," personnes"), scale = FALSE)
mf_map(isere, col = "#CCCCCC", border = "white", lwd = 0.5, add = TRUE)
mf_map(dc, col = col, border = "#6b4266", lwd = 0.8, add = TRUE)
dev.off()
Mosaic cartogram
Ici, on essaie de reproduire les cartogrammes de type mosaïque en trichant un peu… La forme choisie est l’hexagone. On est sur un hexagonal cartogram [voir]
On récupère un fond transformé par un algorithme de cartogramme continu
= st_read("https://raw.githubusercontent.com/transcarto/rcartograms/main/data/GastnerSeguy.geojson") cartogram
Ou si vous l’avez calculé et sauvegardé en local
= st_read("data/GastnerSeguy.geojson") cartogram
Création d’une grille hexagonale, récupération des identificateurs des entités
= st_make_grid(cartogram, cellsize = 2000, square = FALSE)
grid = st_sf(id = rep("",length(grid)), geometry = grid)
grid
= st_centroid(grid)
ctr
= cartogram[,"id"] %>% st_drop_geometry()
ids
for(i in 1:nrow(grid)){
= st_within(x = ctr[i,], y = cartogram, sparse = TRUE, prepared = TRUE)
x = ids[unlist(x),][1]
id if (length(id) == 0){id = NA}
"id"] = id
grid[i,
}<- grid[!is.na(grid$id),] grid
Aggrégation des hexagones par communes
<- aggregate(x = grid,
gridcartogram by = list(grid$id),
FUN = min)
Estimation de la valeur de chaque hexagone (population totale / nombre de cellules)
= sum(cartogram$pop2018)
varmax = nrow(grid)
nbcell = round(varmax / nbcell) valcell
Création de la carte
template("Template cartographique", "maps/hexcartogram.png", note = paste0("Un hexagone ≈\n",valcell," habitants"),basemap = FALSE, scale = FALSE)
mf_shadow(x = grid, col = "grey50", cex = 1, add = TRUE)
mf_map(grid, col = col, border = "white", lwd = 0.5, add = TRUE)
mf_map(gridcartogram, col = NA, border = "#6b4266", lwd = 1, add = TRUE)
dev.off()
Hybride : tessellation et cartograme continu
Il s’agit d’articuler une simplification des tracés des unités adminsitratives en articuant une partition de Voronoï (de préférence calculé sur les centre des communes si l’on travaille sur les population) et le cartogramme continu.
NB : calculer les polygones de voronoi en R est un peu tricky. Voir et voir.
<- st_centroid(communes)
c <- st_voronoi(x = st_union(c))
v <- st_intersection(st_cast(v), st_union(communes))
v <- st_join(x = st_sf(v), y = c, join=st_intersects)
v <- st_cast(v, "MULTIPOLYGON") v
template("Polygones de voronoi", "maps/voronoi.png", basemap = FALSE, scale = FALSE)
mf_map(x = v, col = col, border = "#6b4266", lwd = 0.5, add = TRUE)
dev.off()
Ensuite, on peut calculer un cartogramme de Dougenik sur ces nouvelles géométries.
<- cartogram_cont(v, "pop2018", prepare = "none", itermax = 15, maxSizeError = 1.15) VDougenik
template("Dougenik & voronoi", "maps/dvoronoi.png", basemap = FALSE, scale = FALSE)
mf_map(x = VDougenik, col = col, border = "#6b4266", lwd = 0.5, add = TRUE)
dev.off()
L’impossibilité de réaliser un cartogramme rectangulaire avec le package recMap
A ce jour, il n’est pas possible d’automatiser la réalisation de cartogrammes de Demers ou rectangulaire, qui puissent construire et assembler un puzzle respectant surface, contiguité tout en recréant une forme globale à partir de formes carrées ou rectangulaires proportionnelles à des quantités brutes (stock). (Kreveld and Speckmann 2007)
Christian Panse a développé une première approche algorithmique que l’on trouve dans le package Recmap (Heilmann et al. 2004) (Panse 2016). L’algorithme va convertir les géométries des unités spatiales en rectangles dont la surface est définie par la variable thématique (stock). L’algorithme de RecMap est disponible ici. Développé en C++11, le package dépend des packages GA (>= 3.1), Rcpp (>= 1.0), sp(>= 1.3)
library(recmap)
Préparation des données
Création d’une fonction pour créer un fichier conforme à recmap
<- function(x, geometry, names = c("x","y")) {
sfc_as_cols if (missing(geometry)) {
<- sf::st_geometry(x)
geometry else {
} <- rlang::eval_tidy(enquo(geometry), x)
geometry
}stopifnot(inherits(x,"sf") && inherits(geometry,"sfc_POINT"))
<- sf::st_coordinates(geometry)
ret <- tibble::as_tibble(ret)
ret stopifnot(length(names) == ncol(ret))
<- x[ , !names(x) %in% names]
x <- setNames(ret,names)
ret ::bind_cols(x,ret)
dplyr }
Extraction des centroïdes long-lat sous forme d’une table XY
<- st_centroid(communes)
coords <- st_transform(coords, crs="+proj=longlat +datum=WGS84 +ellps=WGS84") coords
Création du fichier pour recmap
<- sfc_as_cols(coords) df_recmap
::kable(df_recmap[c(0:10),], row.names = F, digits = 1) knitr
id | name | pop2018 | agri | art | cadr | interm | emp | ouvr | retr | x | y | geometry |
---|---|---|---|---|---|---|---|---|---|---|---|---|
38001 | Les Abrets en Dauphiné | 6336 | 30.1 | 210.9 | 256.0 | 712.9 | 833.3 | 948.8 | 1397.2 | 5.6 | 45.5 | POINT (5.58891 45.53316) |
38002 | Les Adrets | 1025 | 0.0 | 53.0 | 168.7 | 163.9 | 82.0 | 67.5 | 159.1 | 6.0 | 45.3 | POINT (5.991151 45.25903) |
38003 | Agnin | 1127 | 0.0 | 28.9 | 72.2 | 168.6 | 139.7 | 134.9 | 192.6 | 4.9 | 45.3 | POINT (4.860446 45.33615) |
38004 | L’Albenc | 1279 | 25.0 | 30.0 | 65.0 | 215.0 | 130.0 | 175.0 | 210.0 | 5.5 | 45.2 | POINT (5.45788 45.21858) |
38005 | Allemond | 954 | 5.0 | 75.0 | 30.0 | 160.0 | 150.0 | 120.0 | 170.0 | 6.0 | 45.2 | POINT (6.017929 45.17045) |
38006 | Allevard | 4062 | 0.0 | 176.7 | 323.2 | 535.2 | 469.6 | 469.6 | 984.2 | 6.1 | 45.4 | POINT (6.11407 45.38024) |
38008 | Ambel | 28 | 10.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 10.0 | 5.9 | 44.8 | POINT (5.933199 44.79951) |
38009 | Anjou | 1009 | 10.0 | 54.9 | 60.0 | 124.9 | 100.0 | 115.0 | 278.5 | 4.9 | 45.3 | POINT (4.883146 45.34894) |
38010 | Annoisin-Chatelans | 686 | 14.8 | 44.4 | 54.2 | 113.3 | 64.1 | 78.9 | 113.3 | 5.3 | 45.8 | POINT (5.291367 45.76067) |
38011 | Anthon | 1073 | 5.0 | 55.0 | 75.0 | 185.0 | 125.0 | 130.0 | 220.0 | 5.2 | 45.8 | POINT (5.154166 45.78474) |
Calcul des rectangles
<- data.frame (x= df_recmap$x,
rec_cartogram y=df_recmap$y,
# make the rectangles overlapping by correcting lines of longitude distance
dx = sqrt(df_recmap$pop2018) / 90 / abs((0.65 * 60 * cos(df_recmap$y*pi/180))),
dy = sqrt(df_recmap$pop2018) / 90 / (0.65 * 60),
z = sqrt(df_recmap$pop2018),
name = df_recmap$id)
Création de la carte
template("RecMap", "maps/recmap1.png", basemap = FALSE, scale = FALSE)
plot.recmap(rec_cartogram, col = NA, border = col, lwd=4, col.text = col)
dev.off()
# cartog <- recmap(df)
# template("RecMap", "maps/recmap2.png", basemap = FALSE, scale = FALSE)
# plot(cartog[!cartog$name %in% c("IS","MT"),], col = col, border = "white")
# dev.off()
Attention, l’algo est incapable de déplacer les rectangles pour créer un cartogramme lorsque les entités sont très nombreuses. Seule solution : exporter en svg et les déplacer à la main.
Avec un nombre plus réduit d’unités, il est possible de mener le process jusqu’au bout. Sur l’Europe, cela donne de meilleurs résultats…
<-
europe st_read(
"https://raw.githubusercontent.com/transcarto/rcartograms/main/data/europe.geojson"
%>% st_transform(3035) )
= data.frame(st_coordinates(st_centroid(st_geometry(europe))))
coords <- lapply(st_geometry(europe), function(x) {
bb st_bbox(x)
})<- unlist(lapply(bb, function(x) {
dx 3] - x[1]
x[/ 2
})) <- unlist(lapply(bb, function(x) {
dy 4] - x[2]
x[/ 2
}))
<- data.frame(
df x = coords$X,
y = coords$Y,
dx = dx,
dy = dy,
z = europe$pop2008,
name = europe$id
)
::kable(df[c(0:10),], row.names = F, digits = 1) knitr
x | y | dx | dy | z | name |
---|---|---|---|---|---|
4632387 | 2727160 | 281872.0 | 144612.8 | 8318592 | AT |
3947526 | 3073013 | 130414.4 | 109371.6 | 10666866 | BE |
5561646 | 2310302 | 243408.4 | 187871.0 | 7640238 | BG |
4189065 | 2635281 | 179108.9 | 112221.5 | 7593494 | CH |
6426825 | 1653096 | 89911.7 | 81616.2 | 789269 | CY |
4709391 | 2971349 | 247155.0 | 137212.1 | 10381130 | CZ |
4351452 | 3108041 | 318053.7 | 427631.7 | 82217837 | DE |
4320739 | 3652882 | 224577.6 | 177722.3 | 5475791 | DK |
5207957 | 4048534 | 172686.1 | 126292.2 | 1340935 | EE |
3180502 | 2021032 | 535047.9 | 504067.3 | 45283259 | ES |
template("", "maps/recmap2.png", basemap = FALSE, scale = FALSE)
plot.recmap(
df,col = NA,
border = col,
lwd = 4,
col.text = col
)dev.off()
<- recmap(df)
cartog template("", "maps/recmap3.png", basemap = FALSE, scale = FALSE)
plot(cartog[!cartog$name %in% c("IS", "MT"), ], col = col, border = "white")
dev.off()
NB : IS et MT n’ont pu être placés. On les supprime à l’affichage.
Le cartogramme comme système de projection
S’il résoud la question de la variation de taille en implantation surfacique, le cartogramme permet également de “corriger” le fond de carte chroplèthe lorsque l’on cartographe des phénomènes socio-démographiques (cartographie électorale, données insee, de santé…).
Il suffit d’appeler lors de la cartographie de la variable attributaire le fond transformé. A priori, le fond transformé correspond à la valeur du dénominateur du taux cartographié (le nombre d’inscrits sur les listes électorales pour cartographier le taux d’abstention, la population active pour un taux de chômage, etc.). Néanmoins lors d’étude sur plusieurs variables socio-démo, on se base souvent sur un fond unique de référence (en général la population résidentielle).
Exemple cartographie de la part de cadres et d’ouvriers
Comparaison taux de cadres et ouvriers
- Calcul des taux et discrétisation Comme nous ne disposons pas de la population active, on se basera sur la population générale.
$ouvr_tx <- communes$ouvr / communes$pop2018 * 100
communes$cadr_tx <- communes$cadr / communes$pop2018 * 100
communes<- c(0, 0.5, 5, 10, 15, 20, 25, 100)
bks <-
cols c("#ffffff",
"#e5f5f9",
"#ccece6",
"#66c2a4",
"#238b45",
"#006d2c",
"#00441b")
71% de cadres à Oulles, 6 habitants ;-)
- Calcul des cartogrammes continu, discontinu et circulaire (fonds de carte)
<- cartogram_cont(communes, "pop2018", prepare = "none", itermax = 10, maxSizeError = 1.15)
Dougenik <- cartogram_ncont(communes, "pop2018", k = 1, inplace = TRUE)
Olson = cartogram_dorling(communes, "pop2018", k = 1.8) Dorling
Cartographies
Carte choroplèthe = quel fond de carte choisir pour représenter un taux ? géographique, cartogramme continu, discontinu ou de Dorling
Pour les ouvriers
par(mfrow = c(2, 2), mar = c(0,0,0,0))
mf_map(x=communes,var = "ouvr_tx", type = "choro", pal = cols, breaks = bks)
mf_map(x=Dougenik,var = "ouvr_tx", type = "choro", pal=cols, breaks = bks)
mf_map(x=Dorling,var = "ouvr_tx", type = "choro", pal=cols, breaks = bks)
mf_map(x=communes, col=NA)
mf_map(x=Olson,var = "ouvr_tx", type = "choro", pal=cols, breaks = bks, add = TRUE)
Pour les cadres
par(mfrow = c(2, 2), mar = c(0,0,0,0))
mf_map(x=communes,var = "cadr_tx", type = "choro", pal = cols, breaks = bks)
mf_map(x=Dougenik,var = "cadr_tx", type = "choro", pal=cols, breaks = bks)
mf_map(x=Dorling,var = "cadr_tx", type = "choro", pal=cols, breaks = bks)
mf_map(x=communes, col=NA)
mf_map(x=Olson,var = "cadr_tx", type = "choro", pal=cols, breaks = bks, add = TRUE)
Comparaison cadres-ouvriers sur fond transformé
par(mfrow = c(1, 2), mar = c(0,0,0,0))
mf_map(x=Dougenik,var = "cadr_tx", type = "choro", pal=cols, breaks = bks)
mf_map(x=Dougenik,var = "ouvr_tx", type = "choro", pal=cols, breaks = bks)
Changement d’échelle !
Cartogramme et échelle spatiale sont fondamentalement liés. La lecture de toute carte, comme du cartogramme est plus ou moins liée à la familiarité avec l’espace représenté. Dans le cas du cartogramme, l’étendue de l’espace cartographié, le niveaux d’agrégation et la méthode choisie permettent de chercher une solution adaptée au phénomène à représenter. Le cartogramme rectangulaire ou de Demers est probablement plus adaptés lorsque le nombre d’entité à cartographier est limité, alors que les cercles de Dorling s’adaptent à toute situation.
Cartogramme et étendue spatiale
Explorez les cartogrammes à l’échelle mondiale
Quel fond de carte pour la représentation du monde ?
<-
world st_read(
"https://raw.githubusercontent.com/transcarto/rcartograms/main/data/world_countries.geojson",
quiet = TRUE
%>%
) st_transform("+proj=bertin1953")
<- world[world$ISO3 != "ATA", ] world
::kable(world[c(0:10), ], row.names = F, digits = 1) knitr
ISO2 | ISO3 | ISONUM | NAMEen | NAMEfr | UNRegion | GrowthRate | PopDensity | PopTotal | JamesBond | geometry |
---|---|---|---|---|---|---|---|---|---|---|
GQ | GNQ | 226 | Equatorial Guinea | Guinée-Équatoriale | Africa | 2.9 | 30.1 | 845060 | 0 | MULTIPOLYGON (((-514804.2 -… |
TV | TUV | 798 | Tuvalu | Tuvalu | Oceania | 0.3 | 330.5 | 9916 | 0 | MULTIPOLYGON (((11846963 52… |
MV | MDV | 462 | Maldives | Maldives | Asia | 1.7 | 1212.2 | 363657 | 0 | MULTIPOLYGON (((5515581 -20… |
AD | AND | 20 | Andorra | Andorre | Europe | -1.9 | 149.9 | 70473 | 0 | MULTIPOLYGON (((-1013908 15… |
AG | ATG | 28 | Antigua and Barbuda | Antigua-et-Barbuda | America | 1.0 | 208.7 | 91818 | 0 | MULTIPOLYGON (((-6420562 59… |
AT | AUT | 40 | Austria | Autriche | Europe | 0.3 | 103.7 | 8544586 | 4 | MULTIPOLYGON (((27563.98 73… |
BE | BEL | 56 | Belgium | Belgique | Europe | 0.6 | 373.2 | 11299192 | 0 | MULTIPOLYGON (((-621349.5 1… |
BH | BHR | 48 | Bahrain | Bahren | Asia | 1.4 | 1812.2 | 1377237 | 0 | MULTIPOLYGON (((2804520 -10… |
BS | BHS | 44 | Bahamas | Bahamas | America | 1.2 | 38.8 | 388019 | 3 | MULTIPOLYGON (((-6563129 25… |
BB | BRB | 52 | Barbados | Barbade | America | 0.3 | 661.0 | 284215 | 0 | MULTIPOLYGON (((-6530926 70… |
Un premier point de vue sur le monde (projection Bertin, par Fil)
= "#c291bc"
col = "Vous, 2021"
credits = mf_theme(
theme x = "default",
bg = "#f0f0f0",
tab = FALSE,
pos = "center",
line = 2,
inner = FALSE,
fg = col,
mar = c(0, 0, 2, 0),
cex = 1.9
)
= function(title, file) {
templateworld mf_export(
world,export = "png",
width = 1000,
filename = file,
res = 96,
theme = theme,
expandBB = c(-.02, 0, -.02, 0)
)
mf_title(title)
mf_credits(
txt = credits,
pos = "bottomleft",
col = "#1a2640",
cex = 0.8,
font = 3,
bg = "#ffffff30"
) }
Votre carte de base
templateworld("Le monde en projection Bertin (thx Fil)", "maps/world.png")
mf_map(
world,col = col,
border = "white",
lwd = 0.5,
add = TRUE
)dev.off()
Lorsque l’on travaille avec les cartogrammes, les messages d’erreurs les plus courants sont liés à des valeurs absentes, des polygones mal fermés ou des contiguités imparfaites. Cela se discute, mais on peut considérer qu’il est plus “propre” de supprimer les entités avec des valeurs absentes ou nulles du jeu de données : en effet, elles ne sont pas concernées par le phénomène représenté.
= na.omit(world) world_sansNA
Cartogramme continu
templateworld("La population mondiale", "maps/worldDougenick.png")
mf_map(
x = cartogram_cont(world_sansNA, "PopTotal"),
col = col,
border = "white",
lwd = 0.5,
add = TRUE
)dev.off()
Cartogramme circulaire
templateworld("La population mondiale", "maps/worldDorling.png")
mf_map(
cartogram_dorling(world_sansNA, "PopTotal"),
col = col,
border = "white",
lwd = 0.5,
add = TRUE
)dev.off()
Cartogramme discontinu
templateworld("La population mondiale", "maps/worldOlson.png")
mf_map(
x = cartogram_ncont(world_sansNA, "PopTotal"),
col = col,
border = "white",
lwd = 0.5,
add = TRUE
)dev.off()
Explorez les cartogrammes à l’échelle européenne
<-
europe st_read(
"https://raw.githubusercontent.com/transcarto/rcartograms/main/data/europe.geojson"
%>% st_transform(3035) )
Les formes du monde (le fond de carte en question)
par(mfrow = c(2, 2), mar = c(0, 0, 0, 0))
mf_map(europe, col = col)
mf_map(x = cartogram_cont(europe, "pop2008"), col = col)
mf_map(x = cartogram_dorling(europe, "pop2008"), col = col)
mf_map(x = cartogram_ncont(europe, "pop2008"), col = col)
dev.off()
Cartogramme et étendue spatiale
A mettre en place lors des prolongations, cf. ci-dessous à vous de jouer …
A vous de jouer…
Pour prolonger cette exploration des formes géographiques, plusieurs pistes (n’hésitez pas à partager vos expériences avec nous !)
travailler l’habillage de la carte : comment automatiser l’ajout d’une légende (surface de référence) cruellement absente sur les représentations
améliorer la qualité du cartogramme continu (cf. mesure d’erreur) : repasser l’algorithme sur le fond transformé se heurte souvent à des erreurs de topologie créées par la transformation. Ceci nécessite de passer l’équivalent du “réparer les topologies” de QGIS sur les fonds transformés.
Variable et surface : côté didactique calculer le graphique part variable(%) X surface géo(%) et variable(%) X surface transformée(%). A chaque itération permet de mesurer l’amélioration du cartogramme.
tester le niveau de généralisation du fond de carte dans le cartogramme continu (du fond détaillé à Voronoï)
Un mot sur l’anticartogramme
Eduardo Dutenkefer (Dutenkefer 2018) a exploré l’esthétique des cartogrammes et notamment l’anti-cartogramme, qui inverse le poids des entités : la surface varie en proportion inverse à la valeur cartographiée, ce qui équivaut en quelque sorte à représenter des symboles proportionnels ponctuels ou linéaires sur la base 1/valeur de la variable.
La variation de taille a un sens bien défini en cartographie, basé sur la perception visuelle (la surface permet d’estimer la part de chaque ou d’un groupe d’entités sur le total) et l’objectif est de révéler les inégalités de répartition sur un territoire.
L’anticartogramme ne représente pas le “contraire” (excepté dans le cas où deux variables représenteraient la part d’un tout : p. ex. les votes exprimés entre deux candidats, chômeurs et actifs dans la population active, situation où nous sommes de facto en présence d’une relation inverse entre les deux variables).
Représenter le non poids des lieux ? Attention au sens véhiculé : un cartogramme de la non population ne représente pas nécessairement les zones dites rurales. A l’échelle mondiale, l’anti-cartogramme des riches n’est pas le cartogramme des pauvres. Pour révéler et analyser les zones où les effectifs sont faibles, les stocks peu élevés, le traitement de la donnée s’effectue par le filtrage.
Cependant, visualiser le “non poids,” créer la “non image” d’un espace peut soutenir un discours artistique, métaphorique sur un espace et prender sa place comme image illustrative.
Sa réalisation ? Calcul d’une nouvelle variable = 1/variable à “inverser” et application de l’une des méthodes précitée.