FAIRE DES CARTES DE FLUX DANS R
L’objectif de cette séance est de présenter la construction de différentes cartes de flux décrivant des flux migratoires internationaux observés à l’échelle mondiale. Pour cela, nous mobilisons une matrice de flux origine-destination (OD) internationale (pays * pays) asymétrique, ainsi qu’un fond de carte des pays du monde. Ces données statistiques et géographiques vont être traitées afin d’enrichir la représentation. L’ensemble des étapes est codé dans R, sous une forme qui n’est pas toujours optimisée et beaucoup des traitements réalisés mobilisent {R base}. Le présent document présente l’ensemble de la chaîne de traitement : de la préparation des matrices OD, celle des fonds de carte à la représentation des flux du point de vue de lieux d’origine (ou de destination) et/ou des échanges entre OD, entraînant beaucoup de manipulations. L’idée n’est donc pas de commenter le code in extenso, mais d’expliciter une démarche, c’est-à-dire de montrer comment on peut réaliser des cartes de flux dans R selon différentes perspectives, dans le cadre démarche traçable, partageable et reproductible
- Créez un projet R et un script R.
- Créez un repertoire data pour stocker les données.
- Créez un répertoire maps dans lequel seront stockées les cartes
Ce document est accessible à l’adresse suivante https://transcarto.github.io/rflows/TRANSCARTO_flows.html
Le code source est disponible ici https://github.com/transcarto/rflows
Les packages
Avant de commencer, voici la liste des packages à installer et à charger. Les 3 packages les plus importants sont sf, mapsf et ttt.
install.packages("sf")
install.packages("remotes")
install.packages("smoothr")
install.packages("readxl")
install.packages("comparator")
install.packages("reshape2")
install.packages("dplyr")
library("remotes")
#install_github("riatelab/mapsf")
install.packages("mapsf")
install.packages("cartograflow")
install_github("tributetotobler/ttt")library("sf")
library("mapsf")
library("ttt")
library("readxl")
library("comparator")
library("reshape2")Les données
Données géométriques
Ici, nous utilisons des données géométriques sur mesure préparées en amont, qui permettent de coller exactement avec les données à cartographier. Il s’agit d’un fond de carte des pays du monde dont la nomenclature correspond à celle des données statistiques fournies par les Nations unies.
countries <- st_read("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/geom/countries.geojson")
graticule <- st_read("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/geom/graticule.geojson")
bbox <- st_read("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/geom/bbox.geojson")
crs <-
"+proj=aeqd +lat_0=90 +lon_0=50 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs "
countries <- st_transform(x = countries, crs = crs)
graticule <- st_transform(x = graticule, crs = crs)
bbox <- st_transform(x = bbox, crs = crs)
land <- st_union(countries)Réalisation d’un template cartographique avec mapsf
col = "#ffc524"
credit = paste0(
"Françoise Bahoken & Nicolas Lambert, 2021\n",
"Source: United Nations, Department of Economic\n",
"and Social Affairs, Population Division (2019)"
)
theme <- mf_theme(
x = "default",
bg = "#3b3b3b",
fg = "#ffc524",
mar = c(0, 0, 2, 0),
tab = TRUE,
pos = "left",
inner = FALSE,
line = 2,
cex = 1.9,
font = 3
)
template = function(title, file) {
mf_export(
countries,
export = "png",
width = 1000,
filename = file,
res = 96,
theme = theme,
expandBB = c(-.02, 0, -.02, 0)
)
mf_map(
bbox,
col = "#3b3b3b",
border = NA,
lwd = 0.5,
add = TRUE
)
mf_map(graticule,
col = "#FFFFFF50",
lwd = 0.5,
add = TRUE)
mf_map(
countries,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
# mf_map(links, col = NA,border = "#317691", lwd = 0.5, add = TRUE)
mf_credits(
txt = credit,
pos = "bottomright",
col = "#1a2640",
cex = 0.7,
font = 3,
bg = "#ffffff30"
)
mf_title(title)
}template("Template cartographique", "maps/template.png")
dev.off()Données de flux Origine-Destination
Nous utilisons un jeu de données sur les migrations internationales. Migration Stock at subregional level, 2019 Source : United Nations, Department of Economic and Social Affairs, Population Division (2019).
Celui-ci est proposé au format xls. Nous l’importons et le mettons en forme via le code ci-dessous.
Précisions sur les données : Ce jeu décrit l’effectif de populations partie ou acceuillie dans un pays autre que le leur, autorisant ainsi une représentation sous la forme de stocks et/ou sous la forme de flux de populations étrangères. Ces flux mettent en relation des pays de résidence (ceux de destination, placés en lignes dans le fichier .xls) avec des pays d’origine (ceux de départ, placés en colonnes dans le fichier .xls), correspondant à ceux dont les personnes recensées en tant qu’étrangères portent la nationalité. Il reconstruit par là une donnée origine-destination (OD) à partir d’effectifs de migrants que l’on est autorisés de représenter sous la forme de flux.
Téléchargez le fichier UN_MigrantStockByOriginAndDestination_2019.xlsx et placez-le dans votre répertoire data.
Cette opération peut se faire avec le code suivant :
data_url <-
"https://raw.githubusercontent.com/transcarto/rflows/master/data/world/UN_MigrantStockByOriginAndDestination_2019.xlsx"
file <- "data/UN_MigrantStockByOriginAndDestination_2019.xlsx"
if (!file.exists(file)) {
download.file(url = data_url, destfile = file)
} Choix de la feuille et de l’année de référence
sheet <- "Table 1"
year <- 2019Import et mise en forme
migr <- data.frame(read_excel(file, skip = 15, sheet = sheet))
migr <- migr[migr[, 1] == year, ]
migr <- migr[!is.na(migr[, 6]), ]
migr <-
subset(migr,
select = -c(...1, ...2, ...5, ...4, ...6, Total, Other.North, Other.South))
colnames(migr)[1] <- "i"
migr <- migr[order(migr[, "i"], decreasing = FALSE), ]
for (i in 2:length(colnames(migr))) {
migr[, i] <- as.numeric(migr[, i])
}Affectation des codes ISO du fond du carte en ligne et en colonne
ctr <- countries[,2:4] %>% st_drop_geometry()
ctr <- ctr[order(ctr[,"label"], decreasing =FALSE),]
codes <- ctr$adm0_a3_is
# Verification manuelle
ctr$rows <- migr[,"i"]
ctr$cols <- colnames(migr)[-1]
for(i in 1:nrow(ctr)){
ctr$rows_test[i] = LCS(similarity = TRUE)(ctr$label[i], ctr$rows[i]) / ((nchar(ctr$label[i]) + nchar(ctr$rows[i])) / 2) * 100
ctr$cols_test[i] = LCS(similarity = TRUE)(ctr$label[i], ctr$cols[i]) / ((nchar(ctr$label[i]) + nchar(ctr$cols[i])) / 2) * 100
}knitr::kable(ctr[c(0:10),], row.names = F, digits = 1)| un_a3 | adm0_a3_is | label | rows | cols | rows_test | cols_test |
|---|---|---|---|---|---|---|
| 4 | AFG | Afghanistan | Afghanistan | Afghanistan | 100 | 100.0 |
| 8 | ALB | Albania | Albania | Albania | 100 | 100.0 |
| 12 | DZA | Algeria | Algeria | Algeria | 100 | 100.0 |
| 16 | ASM | American Samoa | American Samoa | American.Samoa | 100 | 92.9 |
| 20 | AND | Andorra | Andorra | Andorra | 100 | 100.0 |
| 24 | AGO | Angola | Angola | Angola | 100 | 100.0 |
| 660 | AIA | Anguilla | Anguilla | Anguilla | 100 | 100.0 |
| 28 | ATG | Antigua and Barbuda | Antigua and Barbuda | Antigua.and.Barbuda | 100 | 89.5 |
| 32 | ARG | Argentina | Argentina | Argentina | 100 | 100.0 |
| 51 | ARM | Armenia | Armenia | Armenia | 100 | 100.0 |
rownames(migr) <- codes
colnames(migr) <- c("i",codes)
migr <- migr[,-1]knitr::kable(migr[c(0:15),c(0:15)], row.names = T, digits = 1)| AFG | ALB | DZA | ASM | AND | AGO | AIA | ATG | ARG | ARM | ABW | AUS | AUT | AZE | BHS | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| AFG | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| ALB | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| DZA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| ASM | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| AND | NA | NA | NA | NA | NA | NA | NA | NA | 727 | NA | NA | 69 | NA | NA | NA |
| AGO | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| AIA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| ATG | NA | NA | NA | NA | NA | NA | 40 | NA | NA | NA | 5 | 7 | NA | NA | 2 |
| ARG | 9 | 67 | 105 | NA | 1 | 9 | NA | NA | NA | 570 | 1 | 279 | 1039 | NA | NA |
| ARM | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | 78478 | NA |
| ABW | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA | NA |
| AUS | 59798 | 3872 | 1745 | 0 | 31 | 592 | NA | 63 | 17583 | 1629 | 74 | NA | 18091 | 740 | 327 |
| AUT | 20561 | 3715 | 1522 | NA | 2 | 402 | NA | 8 | 1797 | 3601 | NA | 2939 | NA | 1270 | 25 |
| AZE | 178 | NA | NA | NA | NA | NA | NA | NA | NA | 142650 | NA | NA | NA | NA | NA |
| BHS | NA | NA | NA | NA | NA | NA | NA | 14 | 117 | NA | NA | 88 | 42 | NA | NA |
Transposition de la matrice
migr <- t(migr) Passage de la matrice du format large (wide) au format long (liste) i, j, fij
migr <- melt(migr)
colnames(migr) = c("i", "j", "fij")
migr = migr[!is.na(migr$fij), ]
migr = migr[migr$fij > 0, ]
migr = migr[order(migr$fij, decreasing = TRUE), ]knitr::kable(migr[c(0:10),], row.names = F, digits = 1)| i | j | fij |
|---|---|---|
| MEX | USA | 11489684 |
| SYR | TUR | 3743494 |
| IND | ARE | 3419875 |
| RUS | UKR | 3308515 |
| UKR | RUS | 3269248 |
| BGD | IND | 3103664 |
| CHN | USA | 2899267 |
| IND | USA | 2661470 |
| KAZ | RUS | 2559711 |
| RUS | KAZ | 2458414 |
Sauvegarder du fichier mis en forme au format .csv
write.csv(migr, "data/migr.csv", row.names = FALSE)Ce fichier de données correctement formaté est dorénavant accessible comme ceci.
migr <- read.csv("data/migr.csv")Calculs d’indicateurs avec la package cartograflow. Ce package, développé par Françoise Bahoken, contient des fonctions permettant de préparer et de filtrer la matrice origine-destination à des fins de cartographie thématique des flux.
Vérification si la matrice est carrée, car le calcul des indicateurs s’appuie sur la propriété générale de symétrie de la matrice par rapport à la diagonale principale**
library("cartograflow")tabflow <- migr
#Verification si la matrice est carree et fermee
matflow <- flowtabmat(tabflow, matlist = "M")
# Rendre la matrice carree
#---------------------
# on cree une liste des codes ISO de l'ensemble des entités
library("dplyr")
liste <- countries %>% select(adm0_a3_is)
liste <- as.data.frame(liste$adm0_a3_is)
#on s'en sert pour creer la matrice
tabflow2 <- flowcarre(
tab = tabflow,
liste = liste,
origin = "i",
dest = "j",
valflow = "fij",
format = "L",
diagonale = TRUE,
empty.sq = FALSE
)## 'data.frame': 232 obs. of 1 variable:
## $ liste$adm0_a3_is: chr "BGR" "MMR" "BDI" "BLR" ...
#on renomme les variables correctement
colnames(tabflow2) <- c("i", "j", "fij")
tabflow2$i <- as.character(tabflow2$i)
tabflow2$j <- as.character(tabflow2$j)
tabflow2$fij <- as.numeric(tabflow2$fij)
# On calcule des indicateurs fondés sur la symétrie
# Volume bilateral as Tobler
flow_vol2 <- flowtype(
tabflow2,
origin = "i",
destination = "j",
fij = "fij",
format = "L",
x = "bivolum"
)
# solde/balance bilateral as Tobler
flow_net2 <- flowtype(
tabflow2,
origin = "i",
destination = "j",
fij = "fij",
format = "L",
x = "bibal"
)
#calcul de plusieurs indicateurs
flow_indic <-
flowtype(
tabflow2,
origin = "i",
destination = "j",
fij = "fij",
format = "L",
x = "alltypes"
)
#suppression des NA car divisions par zero
for (i in 1:nrow(flow_indic))
for (j in 1:ncol(flow_indic))
{
if (is.na.data.frame(flow_indic[i, j]) == TRUE) {
flow_indic[i, j] <- 0
}
}
head(flow_indic)## i j Fij Fji FSij FBij FAij minFij maxFij rangeFij FDij
## 1 ABW ABW 0 0 0 0 0 0 0 0 0
## 2 ABW AFG 0 0 0 0 0 0 0 0 0
## 3 ABW AGO 0 0 0 0 0 0 0 0 0
## 4 ABW AIA 0 0 0 0 0 0 0 0 0
## 5 ABW ALB 0 0 0 0 0 0 0 0 0
## 6 ABW AND 0 0 0 0 0 0 0 0 0
Sauvegarde de la matrice carree et fermée avec des zéros. C’est important car permet de calculer la densité de la matrice (tx de remplissage) = nb de liens renseignes / nb de liens theoriques. Ce qui est un premier indicateur**
Sauvegarde du fichier (matrice carrée incluant les zero) mis en forme au format .csv
write.csv(tabflow2, "data/migr2.csv", row.names = FALSE)Ce fichier de données correctement formaté est dorénavant accesible comme ceci.
tabflow2 <- read.csv("data/migr2.csv")Tout est prêt. Avançons …
Premières explorations
On considère la matrice migr : elle est formée de 11305 couples d’OD et de 3 variables.
migr <- read.csv("data/migr.csv")
dim(migr)## [1] 11305 3
L’effet Spaghetti
Création d’une couche de liens correspondant aux 11305 lignes de la matrice migr
links <-
mf_get_links(
x = countries,
df = migr,
x_id = "adm0_a3_is",
df_id = c("i", "j")
)Cartographie de ces liens en utilisant le template. La carte spaghetti.png est enregistrée dans le répertoire ./maps
template("L'effet Spaghetti ", "maps/spaghetti.png")
mf_map(links, col = col, add = TRUE)
mf_map(land,
col = NA,
border = "#3b3b3b",
add = TRUE)
dev.off()Pour simplifier l’image de ces flux (réduire le nombre de signes représentés), plusieurs possibilités sont envisageables. L’une d’entre elles consiste à sélectionner les signes à représenter en raisonant soit sur les lignes/colonnes de la matrice (donc sur les lieux d’origine/destination) et/ou soit sur le coeur de la matrice (sur les relations).
Commençons par raisonner sur les lieux.
Choix d’un pays de référence
Pour simplifier la carte, choisissons un seul pays de référence depuis/vers lequel on observe les flux.
ISO3 <- "FRA"
label = "France"Création d’une sous-matrice migrFRA des flux à destination de la France, jointure et mise en forme des données
countr <- countries[, c("adm0_a3_is", "label")]
migrFRA <- migr[migr$j == ISO3, ]
migrFRA$fij <- as.numeric(migrFRA$fij)
maxval = max(migrFRA$fij)
total = round(sum(migrFRA$fij) / 1000000,1)
countr <-
merge(
x = countr,
y = migrFRA,
by.x = "adm0_a3_is",
by.y = "i",
all.x = TRUE
)
countr <- countr[-3]
colnames(countr) <- c("id", "label", "fij", "geometry")knitr::kable(countr[c(0:10),], row.names = F, digits = 1)| id | label | fij | geometry |
|---|---|---|---|
| ABW | Aruba | 11 | MULTIPOLYGON (((-7476945 42… |
| AFG | Afghanistan | 6887 | MULTIPOLYGON (((2474775 -53… |
| AGO | Angola | 23438 | MULTIPOLYGON (((-4917506 -1… |
| AIA | Anguilla | 10 | MULTIPOLYGON (((-7351488 31… |
| ALB | Albania | 7371 | MULTIPOLYGON (((-2639654 -4… |
| AND | Andorra | 1079 | MULTIPOLYGON (((-3952645 -3… |
| ARE | United Arab Emirates | 862 | MULTIPOLYGON (((785851 -712… |
| ARG | Argentina | 14253 | MULTIPOLYGON (((-14113355 7… |
| ARM | Armenia | 21012 | MULTIPOLYGON (((-348529.3 -… |
| ASM | American Samoa | 1 | MULTIPOLYGON (((7561304 878… |
Réalisation d’une première carte de stocks sur le nombre d’étrangers présents en France : ; elle correspond à la somme marginale de la colonne iso3=FRA.
template(paste0("En 2019, il y avait ",total, " millions d'étrangers en France"),
"maps/prop1.png")
#mf_map(countr[countr$id == ISO3,], col = col, border = "red", lwd = 2, add = TRUE)
mf_map(
countr[countr$id != ISO3, ],
var = "fij",
col = col,
border = "white",
type = "prop",
val_max = maxval,
inches = 0.4,
leg_title_cex = 1.2,
leg_val_cex = 0.8,
leg_pos = "bottomleft",
leg_title = "Nombre de personnes"
)
mf_map(
countr[countr$id == ISO3, ],
col = NA,
border = "#e36019",
lwd = 2,
add = TRUE
)
dev.off()Création d’une sous-matrice migrFRA des flux originaires de la France, jointure et mise en forme des données
countr <- countries[, c("adm0_a3_is", "label")]
migrFRA <- migr[migr$i == ISO3, ]
migrFRA$fij <- as.numeric(migrFRA$fij)
total = round(sum(migrFRA$fij) / 1000000,1)
countr <-
merge(
x = countr,
y = migrFRA,
by.x = "adm0_a3_is",
by.y = "j",
all.x = TRUE
)
countr <- countr[-3]
colnames(countr) <- c("id", "label", "fij", "geometry")Réalisation d’une seconde carte de stocks sur le nombre de français à l’étranger ; elle correspond à la somme marginale de la ligne iso3=FRA.
template(paste0("En 2019, il y avait ",total, " millions de Français à l'étranger"),
"maps/prop2.png")
mf_map(
countr[countr$id != ISO3, ],
var = "fij",
col = col,
border = "white",
type = "prop",
val_max = maxval,
inches = 0.4,
leg_title_cex = 1.2,
leg_val_cex = 0.8,
leg_pos = "bottomleft",
leg_title = "Nombre de personnes"
)
mf_map(
countr[countr$id == ISO3, ],
col = NA,
border = "#e36019",
lwd = 2,
add = TRUE
)
dev.off()Raisonnons maintenant sur les relations entre les lieux.
Choix des relations avec un pays de référence
Réalisation d’une troisième carte mettant en relation la France avec les pays d’origine des étrangers qui résident sur le territoire national, et pondération de ces liens en fonction du nombre de personnes concernées.
Préparation d’une sous-matrice migrtoFRA formée des liens entrant dans la colonne iso3=FRA.
ISO3 <- "FRA"
label = "France"
migrtoFRA <- migr[migr$j == ISO3,]
migrtoFRA$fij <- as.numeric(migrtoFRA$fij)Création de la couche de liens correspondante
links <-
mf_get_links(
x = countries,
df = migrtoFRA,
x_id = "adm0_a3_is",
df_id = c("i", "j")
)Cartographie de ces liens avec une dimension de largeur proportionnelle à l’effectif de fij
template(
paste0("Origine des personnes étrangères vivant en ", label, " en 2019"),
"maps/links1.png"
)
mf_map(
links,
var = "fij",
col = col,
border = "white",
type = "prop",
inches = 10,
leg_title_cex = 1.2,
leg_val_cex = 0.8,
leg_pos = "bottomleft",
leg_title = "Nombre de personnes"
)
mf_map(
countries[countries$adm0_a3_is == ISO3, ],
col = "#4e4f4f",
border = col,
lwd = 1.5,
add = TRUE
)
dev.off()La matrice étant orientée, il est possible de raisonner selon les flux entre les lieux, qu’il convient de préparer
Filtrages et indicateurs
Le principe général est de sélectionner les liens à représenter pour ne représenter que ceux jugés “significatifs”. Le filtrage peut être soit global (on applique une valeur unique en deça/au-dessus de laquelle les flux ne seront pas représentés) ou soit local, concernant les lieux (on filtre les flux en fonction de considérations sur les l’origie et/ou la destination).
Application d’un critère global
Tobler indique que seuls les flux supérieurs à la moyenne devront être représentés. On peut faire un test. Ce critère n’est pas toujours suffisant, car dépend de la densité de la matrice. Il est plus prudent de sélectionner un critère selon les quantile.
tabflow2 <- read.csv("data/migr2.csv")
fij <- (tabflow2$fij)
mean <- mean(fij) #as Tobler
#Q3<-quantile(flow$fij,0.75) #25% of the most important migrations
#Q95<-quantile(flow$fij, 0.95) # 5% of the most important migrations
#Q98<-quantile(flow$fij, 0.98) # 2% of the most important migrationsFlux supérieurs à la moyenne
Vers des cartes un peu plus graphiques
Une carte un peu plus sophistiquée avec packcircles
Avec le code ci-dessous, on cherche à réaliser une carte à la façon de cette application interactive.
ISO3 <- "FRA"
label = "France"
migrFRA <- migr[migr$j == ISO3,]
migrFRA$fij <- as.numeric(migrFRA$fij)
migrFRA <-
rbind.data.frame(migrFRA, c(
i = ISO3,
j = ISO3,
fij = sum(as.numeric(migrFRA$fij))
))
countr <- countries[, "adm0_a3_is"]
countr <-
merge(
x = countr,
y = migrFRA,
by.x = "adm0_a3_is",
by.y = "i",
all.x = TRUE
)
colnames(countr) <- c("i", "j", "fij", "geometry")knitr::kable(countr[c(0:10),], row.names = F, digits = 1)| i | j | fij | geometry |
|---|---|---|---|
| ABW | FRA | 11 | MULTIPOLYGON (((-7476945 42… |
| AFG | FRA | 6887 | MULTIPOLYGON (((2474775 -53… |
| AGO | FRA | 23438 | MULTIPOLYGON (((-4917506 -1… |
| AIA | FRA | 10 | MULTIPOLYGON (((-7351488 31… |
| ALB | FRA | 7371 | MULTIPOLYGON (((-2639654 -4… |
| AND | FRA | 1079 | MULTIPOLYGON (((-3952645 -3… |
| ARE | FRA | 862 | MULTIPOLYGON (((785851 -712… |
| ARG | FRA | 14253 | MULTIPOLYGON (((-14113355 7… |
| ARM | FRA | 21012 | MULTIPOLYGON (((-348529.3 -… |
| ASM | FRA | 1 | MULTIPOLYGON (((7561304 878… |
Cercles avec packcircles (Dorling style)
library("packcircles")dots = countr
st_geometry(dots) <-
st_centroid(sf::st_geometry(dots), of_largest_polygon = TRUE)
dots <- data.frame(dots$i, dots["fij"], st_coordinates(dots))
dots = dots[, c("dots.i", "X", "Y", "fij")]
colnames(dots) <- c("id", "x", "y", "v")
dots <- dots[!is.na(dots$v), ]
k = 700000 # pour ajuster la taille des cercles
itermax = 10 # nombre d'iterations
delta = 35000
dat.init <- dots[, c("x", "y", "v", "id")]
dat.init$v <- sqrt(as.numeric(dat.init$v) * k)
simulation <- circleRepelLayout(
x = dat.init,
xysizecols = 1:3,
wrap = FALSE,
sizetype = "radius",
maxiter = itermax,
weights = 1
)$layout
circles <- st_buffer(sf::st_as_sf(
simulation,
coords = c('x', 'y'),
crs = sf::st_crs(countries)
),
dist = simulation$radius - delta)
circles$v = dots$v
circles$id = dots$idLinks
# Links
dots$j = "FRA"
links <-
mf_get_links(
x = circles,
df = migrFRA,
x_id = "id",
df_id = c("i", "j")
)
links$fij = as.numeric(links$fij)Réalisation de la carte
template("Les étrangers en France, 2019", "maps/migrexplorer1.png")
col2 = "#4e4f4f"
mf_map(
land,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(
links,
var = "fij",
col = col,
border = "#3b3b3b",
type = "prop",
lwd_max = 160,
leg_pos = "n",
add = TRUE
)
mf_map(
circles[circles$id != ISO3, ],
var = "fij",
col = col,
border = "#3b3b3b",
lwd = 1.5,
add = TRUE
)
mf_map(
circles[circles$id == ISO3, ],
var = "fij",
col = col2,
border = col,
lwd = 2.5,
add = TRUE
)
t = circles[circles$id != ISO3, ]
mf_label(
t,
var = "id",
halo = FALSE,
cex = sqrt(as.numeric(t$v) / 1200000),
col = col2,
overlap = TRUE,
lines = FALSE
)
t = circles[circles$id == ISO3, ]
mf_label(
t,
var = "id",
halo = FALSE,
cex = sqrt(as.numeric(t$v) / 1200000),
col = col,
overlap = TRUE,
lines = FALSE
)
dev.off()Comme précédemment, on peut faire la carte des français présents à l’étranger, en regardant la destination
ISO3 <- "FRA"
label = "France"
migrFRA <- migr[migr$i == ISO3,] # ici
migrFRA$fij <- as.numeric(migrFRA$fij)
migrFRA <-
rbind.data.frame(migrFRA, c(
i = ISO3,
j = ISO3,
fij = sum(as.numeric(migrFRA$fij))
))
countr <- countries[, "adm0_a3_is"]
countr <-
merge(
x = countr,
y = migrFRA,
by.x = "adm0_a3_is",
by.y = "j", # là
all.x = TRUE
)
colnames(countr) <- c("i", "j", "fij", "geometry")dots = countr
st_geometry(dots) <-
st_centroid(sf::st_geometry(dots), of_largest_polygon = TRUE)
dots <- data.frame(dots$i, dots["fij"], st_coordinates(dots))
dots = dots[, c("dots.i", "X", "Y", "fij")]
colnames(dots) <- c("id", "x", "y", "v")
dots <- dots[!is.na(dots$v), ]
k = 700000 # pour ajuster la taille des cercles
itermax = 10 # nombre d'iterations
delta = 35000
dat.init <- dots[, c("x", "y", "v", "id")]
dat.init$v <- sqrt(as.numeric(dat.init$v) * k)
simulation <- circleRepelLayout(
x = dat.init,
xysizecols = 1:3,
wrap = FALSE,
sizetype = "radius",
maxiter = itermax,
weights = 1
)$layout
circles <- st_buffer(sf::st_as_sf(
simulation,
coords = c('x', 'y'),
crs = sf::st_crs(countries)
),
dist = simulation$radius - delta)
circles$v = dots$v
circles$id = dots$idLinks
# Links
dots$j = "FRA"
links <-
mf_get_links(
x = circles,
df = migrFRA,
x_id = "id",
df_id = c("i", "j")
)
links$fij = as.numeric(links$fij)Réalisation de la carte
template("Les français à l'étranger, 2019", "maps/migrexplorer2.png")
col2 = "#4e4f4f"
mf_map(
land,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(
links,
var = "fij",
col = col,
border = "#3b3b3b",
type = "prop",
lwd_max = 160,
leg_pos = "n",
add = TRUE
)
mf_map(
circles[circles$id != ISO3, ],
var = "fij",
col = col,
border = "#3b3b3b",
lwd = 1.5,
add = TRUE
)
mf_map(
circles[circles$id == ISO3, ],
var = "fij",
col = col2,
border = col,
lwd = 2.5,
add = TRUE
)
t = circles[circles$id != ISO3, ]
mf_label(
t,
var = "id",
halo = FALSE,
cex = sqrt(as.numeric(t$v) / 1200000),
col = col2,
overlap = TRUE,
lines = FALSE
)
t = circles[circles$id == ISO3, ]
mf_label(
t,
var = "id",
halo = FALSE,
cex = sqrt(as.numeric(t$v) / 1200000),
col = col,
overlap = TRUE,
lines = FALSE
)
dev.off()Ces cartes, on peut les retrouver dans l’application MigrExplorer mise en ligne via R shiny.
https://gitlab.huma-num.fr/nlambert/migrexplorer/-/tree/master
Changer de maillage
Contrairement aux cartes réalisées à l’échelle internationale (pays * pays), cartographier les flux au niveau régional permet parfois de mieux percevoir la logique d’ensemble de ces mobilités internationales. Cette carte, pas très élégante, a été réalisée et présentée par François Héran dans ses cours au Collège de France.
Et si on essayait de la reproduire avec R ?
Pour cela, nous construisons des données de migrations internationales, en agrégeant les flux internationaux au niveau subrégional, à partir d’une clé d’aggrégation contenue dans le fichier countries.
knitr::kable(countries[c(0:10),c("adm0_a3_is", "label","Code2","Label2")], row.names = F, digits = 1)| adm0_a3_is | label | Code2 | Label2 | geometry |
|---|---|---|---|---|
| BGR | Bulgaria | 923 | Eastern Europe | MULTIPOLYGON (((-1882818 -4… |
| MMR | Myanmar | 920 | South-Eastern Asia | MULTIPOLYGON (((5416951 -56… |
| BDI | Burundi | 910 | Eastern Africa | MULTIPOLYGON (((-3418256 -9… |
| BLR | Belarus | 923 | Eastern Europe | MULTIPOLYGON (((-1406024 -3… |
| KHM | Cambodia | 920 | South-Eastern Asia | MULTIPOLYGON (((7198820 -51… |
| DZA | Algeria | 912 | Northern Africa | MULTIPOLYGON (((-3911770 -4… |
| CMR | Cameroon | 911 | Middle Africa | MULTIPOLYGON (((-5196562 -7… |
| CAN | Canada | 918 | Northern America | MULTIPOLYGON (((-2925928 15… |
| CPV | Cabo Verde | 914 | Western Africa | MULTIPOLYGON (((-7996256 -2… |
| CYM | Cayman Islands | 915 | Caribbean | MULTIPOLYGON (((-5899896 51… |
Géométries
subregions <-
aggregate(countries, by = list(countries$Code2), FUN = head, 1)
subregions <- subregions[, c("Code2", "Label2")]
st_geometry(subregions) <-
st_cast(subregions$geometry, "MULTIPOLYGON")
colnames(subregions) <- c("id", "label", "geometry")template("Subregions", "maps/subregions.png")
mf_map(
subregions,
col = "#4e4f4f",
border = col,
lwd = 0.5,
add = TRUE
)
mf_label(
x = subregions,
var = "label",
halo = TRUE,
bg = "#4e4f4f",
cex = 0.8,
col = col,
overlap = TRUE,
lines = FALSE
)
dev.off()Données attributaires
keys <- data.frame(countries[, c("adm0_a3_is", "Code2")])
keys$geometry <- NULL
migr <- merge(x = migr,
y = keys,
by.x = "i",
by.y = "adm0_a3_is")
colnames(migr)[4] <- "subreg_i"
migr <- merge(x = migr,
y = keys,
by.x = "j",
by.y = "adm0_a3_is")
colnames(migr)[5] <- "subreg_j"
migr$id <- paste0(migr$subreg_i, "_", migr$subreg_j)
migr2 <- aggregate(migr$fij, by = list(migr$id), FUN = sum)
migr2$i <- sapply(strsplit(migr2$Group.1, "_"), "[", 1)
migr2$j <- sapply(strsplit(migr2$Group.1, "_"), "[", 2)
migr2 <- migr2[, c("i", "j", "x")]
colnames(migr2)[3] <- "fij"
migr2$fij <- round(migr2$fij / 1000, 0)knitr::kable(migr2[c(0:10),], row.names = F, digits = 1)| i | j | fij |
|---|---|---|
| 5500 | 5500 | 483 |
| 5500 | 5501 | 12 |
| 5500 | 906 | 28 |
| 5500 | 912 | 4 |
| 5500 | 913 | 0 |
| 5500 | 914 | 2 |
| 5500 | 915 | 0 |
| 5500 | 916 | 0 |
| 5500 | 918 | 137 |
| 5500 | 922 | 95 |
On ajoute au fond de carte les flux intrarégionaux
flowsintra <- migr2[migr2$i == migr2$j,c("i","fij")]
colnames(flowsintra) <- c("id","intra")
subregions <- merge(x = subregions, y = flowsintra, by = "id")knitr::kable(subregions[c(0:10),], row.names = F, digits = 1)| id | label | intra | geometry |
|---|---|---|---|
| 906 | Eastern Asia | 5202 | MULTIPOLYGON (((6806372 -37… |
| 910 | Eastern Africa | 5330 | MULTIPOLYGON (((1248754 -12… |
| 911 | Middle Africa | 1537 | MULTIPOLYGON (((-4917506 -1… |
| 912 | Northern Africa | 351 | MULTIPOLYGON (((-1719098 -7… |
| 913 | Southern Africa | 715 | MULTIPOLYGON (((-3989376 -1… |
| 914 | Western Africa | 6625 | MULTIPOLYGON (((-9723391 -6… |
| 915 | Caribbean | 864 | MULTIPOLYGON (((-8064806 28… |
| 916 | Central America | 641 | MULTIPOLYGON (((-7187560 54… |
| 918 | Northern America | 1114 | MULTIPOLYGON (((-1050627 -4… |
| 920 | South-Eastern Asia | 6856 | MULTIPOLYGON (((7061625 -67… |
Calcul du volume bilatéral d’interactions inter régionales (A -> B) + (B -> A) ou (fij)+(fji)
migr2 <- migr2[migr2$i != migr2$j,]
for (k in 1:length(migr2$i)) {
val1 <- migr2$fij[k]
val2 <-
migr2[migr2$i == migr2$j[k] & migr2$j == migr2$i[k], "fij"]
migr2$interaction[k] <- sum(val1, val2)
}
# Suppression des doublons
interactions = data.frame(matrix(
ncol = 3,
nrow = 0,
dimnames = list(NULL, c("i", "j", "interaction"))
))
for (k in 1:length(migr2$i)) {
idi = migr2$i[k]
idj = migr2$j[k]
test = length(interactions[(interactions$i == idi &
interactions$j == idj) |
(interactions$i == idj & interactions$j == idi), "interaction"])
if (test == 0) {
interactions <-
rbind(interactions, data.frame(
i = idi,
j = idj,
interaction = migr2$interaction[k]
))
}
}knitr::kable(interactions[c(0:10),], row.names = F, digits = 1)| i | j | interaction |
|---|---|---|
| 5500 | 5501 | 28 |
| 5500 | 906 | 130 |
| 5500 | 912 | 4 |
| 5500 | 913 | 0 |
| 5500 | 914 | 2 |
| 5500 | 915 | 0 |
| 5500 | 916 | 0 |
| 5500 | 918 | 137 |
| 5500 | 922 | 261 |
| 5500 | 923 | 9999 |
Choix du critère de sélection global
On décide d’éliminer les petits flux, c’est-à-dire les signes graphiques présentant des valeurs de flux faibles pour ne pas se trouver face à un effet spaghetti.
Tobler recommande en première intention de ne représenter que les flux qui sont supérieurs au flux moyen
mean<-mean(interactions$interaction)Cependant, ce seuil à 829 personnes (flux interregional moyen) ne nous apparaît pas assez élevé car 1) il n’éclaircit pas suffisament la figure et 2) il ne nous permet pas de coller suffisament à la carte de F. Héran.
Nous changeons de logique et raisonnons selon les posiions, les déciles, pour ne représenter que la part en % de flux qui contribuent à une visualisation d’une part importante de l’interaction totale.
Nous décidons alors de ne représenter que les 10% de signes qui contribuent à l’interaction totale la plus forte
Q90<-quantile(interactions$interaction, 0.90) #10% des migrations les plus importantesPour simplifier, on fixe le critère de sélection à 2000
threshold <- 2000
interactions <- interactions[interactions$interaction >= threshold,]construction des liens inter regionaux
links <-
mf_get_links(
x = subregions,
df = interactions,
x_id = "id",
df_id = c("i", "j")
)Cartographie
template("L'Afrique, un continent encore isolé dans la mondialisation", "maps/heran.png")
col2 = "#4e4f4f"
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(
links,
var = "interaction",
col = col,
border = "#3b3b3b",
type = "prop",
lwd_max = 25,
leg_pos = "bottomleft",
leg_title = paste0("Migratons INTER régionales (interactions)\n(A -> B) + (B -> A)\nSeuil : ",threshold, "\nen milliers de personnes"),
add = TRUE
)
mf_map(
subregions,
var = "intra",
col = "#3b3b3b",
border = col,
lwd = 1.5,
type = "prop",
symbol = "square",
leg_pos = "topright",
leg_title = "Migrations INTRA\nrégionale nen 2019\n(en milliers)",
add = TRUE
)
mf_label(
subregions,
var = "intra",
halo = FALSE,
cex = sqrt(as.numeric(subregions$intra) / 12000),
col = col,
overlap = TRUE,
lines = FALSE
)
mf_label(
links,
var = "interaction",
halo = TRUE,
cex = 0.5,
col = col2,
bg = col,
r = 0.1,
overlap = FALSE,
lines = FALSE
)
dev.off()Problème : avec seulement mapsf, on a du mal à représenter des flèches et surtout, des flèches bilatérales A -> B et B -> A qui respectent les principes de la sémiologie cartographique des flux en étant parallèles et convergentes/divergentes vers/depuis les lieux. La solution : Flowmapper 👍
Flowmapper
flowmapper() est une fonction du package ttt (en cours de développement) dédié à la ré-écriture/extension de programmes initialement proposés par Tobler.
library("ttt")Les données
Dans le package ttt, il y a des données d’exemple au niveau subrégional. Chargeons-les.
#subregions <- st_read(system.file("subregions.gpkg", package="flowmapper")) %>% st_transform(crs)
#migr <- read.csv(system.file("migrantstocks2019.csv", package="flowmapper"))
subregions <- st_read(system.file("subregions.gpkg", package="ttt")) %>%
st_transform(crs)
migr <- read.csv(system.file("migrantstocks2019.csv", package="ttt"))Sélection des flux les plus importants (environ 1/3 des plus forts)
#mean_migr<-mean(migr$fij)
Q65_migr<-quantile(migr$fij, 0.65) #1/3 des migrations les plus importantesthreshold <- 1500
migr <- migr[migr$fij >= threshold, ]knitr::kable(migr[c(0:10),], row.names = F, digits = 1)| i | j | fij |
|---|---|---|
| 5500 | 923 | 5603 |
| 5501 | 5501 | 11177 |
| 5501 | 918 | 5334 |
| 5501 | 920 | 1666 |
| 5501 | 922 | 18402 |
| 5501 | 924 | 2551 |
| 906 | 906 | 5202 |
| 906 | 918 | 5700 |
| 910 | 910 | 5330 |
| 910 | 913 | 1538 |
flows <- ttt_flowmapper(
x = subregions,
xid = "id",
df = migr,
dfid = c("i", "j"),
dfvar = "fij",
plot = FALSE
)Liens
template("ttt_flowmapper$links", "maps/ttt_links.png")
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(flows$links,
col = col,
lwd = 3,
add = TRUE)
dev.off()Cercles
template("ttt_flowmapper$circles", "maps/ttt_circles.png")
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(flows$circles, col = col, add = TRUE)
dev.off()Flèches
template("ttt_flowmapper$flows", "maps/ttt_arrows.png")
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(flows$flows, col = col, add = TRUE)
dev.off()Visualisation par défaut
template("Visulaisation par défaut", "maps/ttt_flows.png")
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
flows <- ttt_flowmapper(
x = subregions,
xid = "id",
type = "arrows",
df = migr,
dfid = c("i", "j"),
dfvar = "fij",
col = col,
border = "#424242",
border2 = col,
add = TRUE
)
dev.off()La VV taille, c’est aussi la surface
template("La surface des fleches", "maps/ttt_surface.png")
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
ttt_flowmapper(
x = subregions,
xid = "id",
type = "arrows",
size = "area",
df = migr,
dfid = c("i", "j"),
dfvar = "fij",
col = col,
border = "#424242",
border2 = col,
add = TRUE
)
dev.off()Epaisseur vs Surface
Interactions (type = “rect”)
migr2 <- data.frame(i = integer(), j = integer(), fij = integer())
for (k in 1:length(migr$i)) {
val1 <- migr$fij[k]
val2 <- migr[migr$i == migr$j[k] & migr$j == migr$i[k], "fij"]
val <- sum(val1, val2)
idi = migr$i[k]
idj = migr$j[k]
test <-
length(migr2[(migr2$i == idi &
migr2$j == idj) | (migr2$i == idj & migr2$j == idi), "fij"])
if (test == 0) {
migr2 <- rbind(migr2, data.frame(i = idi, j = idj, fij = val))
}
}
migr2 <- migr2[migr2$i != migr2$j, ] head(migr2)## i j fij
## 1 5500 923 9999
## 3 5501 918 5334
## 4 5501 920 3221
## 5 5501 922 18402
## 6 5501 924 2551
## 8 906 918 5700
template("Interactions", "maps/ttt_interactions.png")
c <- ttt_flowmapper(
x = subregions,
xid = "id",
size = "thickness",
type = "rect",
df = migr2,
dfid = c("i", "j"),
dfvar = "fij",
col = col,
border = "#424242",
border2 = col,
add = TRUE
)
dev.off()Combiner flux intra et flux inter
intra <- migr[migr$i == migr$j, ]
intra <- intra[, c("i", "fij")]
colnames(intra) <- c("id", "nb")
knitr::kable(intra, row.names = F, digits = 1)Calcul des flux (plot = FALSE)
flows <- ttt_flowmapper(
x = subregions,
xid = "id",
df = migr,
dfid = c("i", "j"),
dfvar = "fij",
size = "thickness",
type = "arrows",
decreasing = FALSE,
add = TRUE,
lwd = 1,
col = col,
border = "#424242",
k = NULL,
k2 = 60,
df2 = intra,
df2id = "id",
df2var = "nb",
col2 = "#3b3b3b",
border2 = col,
plot = FALSE
)Affichage de la carte avec mapsf
template("Flux inter et flux intra", "maps/interintra.png")
mf_shadow(x = flows$flows, col = "grey70", cex = 0.2, add = TRUE)
mf_map(
flows$flows,
var = "fij",
col = col,
border = "#3b3b3b",
leg_pos = "n",
add = TRUE
)
mf_map(
flows$circles,
var = "fij",
col = "#3b3b3b",
border = col,
lwd = 1.5,
leg_pos = "n",
add = TRUE
)
mf_label(
flows$circles,
var = "nb",
halo = FALSE,
cex = sqrt(as.numeric(flows$circles$nb) / 18000),
#cex = 1,
col = col,
overlap = TRUE,
lines = FALSE
)
mf_label(
flows$flows,
var = "fij",
halo = TRUE,
cex = 0.7,
col = col2,
bg = col,
r = 0.1,
overlap = FALSE,
lines = FALSE
)
# La fonction légende :-)
ttt_flowmapperlegend(
x = flows,
title = "Flux inter",
title2 = "flux intra",
col = "#4e4f4f",
txtcol = col
)
dev.off()Reprojection
1 - calcul en projection polaire
tmp <- ttt_flowmapper(
x = subregions,
xid = "id",
type = "arrows",
df = migr,
dfid = c("i", "j"),
dfvar = "fij",
col = "#ffc524",
border = "#424242",
border2 = "#ffc524",
plot = FALSE
)2 - reprojection & nouveau template
crs <-
"+proj=ortho +lat_0=42.5333333333 +lon_0=-72.53333333339999 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs"
flows <- smoothr::densify(tmp$flows, n = 30) %>% st_transform(crs)
dots <- st_transform(tmp$circles, crs)
subregions <- st_transform(subregions, crs)
graticule <- st_transform(graticule, crs)
bbox <- st_transform(bbox, crs)3 - affichage
title = "Flux sur Globe"
file = "maps/ttt_globe.png"
mf_export(
subregions,
export = "png",
width = 1000,
filename = file,
res = 96,
theme = theme,
expandBB = c(-.02, 0,-.02, 0)
)
mf_map(
bbox,
col = "#3b3b3b",
border = NA,
lwd = 0.5,
add = TRUE
)
mf_map(graticule,
col = "#FFFFFF50",
lwd = 0.5,
add = TRUE)
mf_map(
subregions,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_credits(
txt = credit,
pos = "bottomright",
col = "#1a2640",
cex = 0.7,
font = 3,
bg = "#ffffff30"
)
mf_map(flows, col = col, add = TRUE)
mf_map(dots, col = col, add = TRUE)
mf_title(title)
dev.off()A vous de jouer
Et si on essayait de faire des cartes de flux sur un fond de carte déformé. Ici, par la population en 2019.
Les données
migrCountries <- read.csv("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/cartogram/migr2019_T.csv")
migrSubregions <- read.csv("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/cartogram/migrSubregions2019_T.csv")Les géométries
countriesPop <- st_read("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/cartogram/countriesPop.geojson")
subregionsPop <- st_read("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/cartogram/regionsPop.geojson")
gridPop <- st_read("https://raw.githubusercontent.com/transcarto/rflows/master/data/world/cartogram/grid.geojson")Quelques variables d’affichage
col = "#ffc524"
votrenom = "Miles Davis & Frances Taylor, kings of cool, 1965"credit = paste0(
votrenom,"\n",
"Source: United Nations, Department of Economic\n",
"and Social Affairs, Population Division (2019)"
)
theme <- mf_theme(
x = "default",
bg = "#3b3b3b",
fg = "#ffc524",
mar = c(0, 0, 2, 0),
tab = TRUE,
pos = "left",
inner = FALSE,
line = 2,
cex = 1.9,
font = 3
)
template = function(title, file) {
mf_export(
countriesPop,
export = "png",
width = 1000,
filename = file,
res = 96,
theme = theme,
expandBB = c(-.02, 0, -.02, 0)
)
mf_map(gridPop,
col = "#FFFFFF70",
lwd = 0.4,
add = TRUE)
mf_map(
countriesPop,
col = "#4e4f4f",
border = "#3b3b3b",
lwd = 0.5,
add = TRUE
)
mf_map(
subregionsPop,
col = "NA",
border = col,
lwd = 0.5,
add = TRUE
)
# mf_map(links, col = NA,border = "#317691", lwd = 0.5, add = TRUE)
mf_credits(
txt = credit,
pos = "bottomright",
col = "#1a2640",
cex = 0.5,
font = 3,
bg = "#ffffff30"
)
mf_title(title)
}A vous de jouer…
template("World Population, 2019", "maps/cartogram.png")
# METTEZ DES TRUCS ICI !
dev.off()