Croiser des identifiants - Couper des données - Afficher la légende

L'essentiel de cette page...

J'ai des valeurs correspondant à chaque pays du monde... ou presque... Mes pays sont identifiés par un code à deux lettres... Comment vais-je faire pour mettre une couleur à chaque pays proportionnelle à la couleur MAIS SURTOUT faire le lien entre code et zone à coloriser PUIS afficher une légende...

C'est un problème classique de cartographie.

Option 1 : ggplot2 fera tout ce travail de façon automatisée. La reprise de la carte et sa personnalisation risque d'être un beau casse-tête. En revanche, ggplot2 va vous rendre fou si vous devez jonglé avec des identifiants qui ne correspondent pas entre vos données et le fond de carte.

Option 2 : je reste dans l'environnement classique de R. Suivez le chemin...

1- D'abord mes données...

Disons que j'ai fait un traitement en amont. Et j'ai un tableau déjà chargé...

iso2 <- c("GB","US","RU","UA","AU","CA","CN","FR","BR","IQ", "SY","NZ","DE","ZA","JP","IL","ES","IN","EG","NG","MY","PS", "MX","IR","KR","IT","TR","TH","AF", "BE", "PK", "LY","HK","CF", "SD", "SA","YE","VA","AE", "PH","NL","AR","GR","KE", "ID","VE","CH", "LK","CU","LB","SO", "CO", "PL","AT","LR", "MM","GN","ML","TN", "BD", "SE","IE","JM","DZ","CL","PE","KP","QA", "SL","PR","BH","DK", "RS","ZW","RO","PT","UG","CY","MA","CZ","DO", "SG","UY","NO","CG","SN","SV","VN","JO", "HT","BA","ET","CR", "GT","PA","TW","KH","KW","LT","KV","MW","CI", "HR","MZ","RW","BF", "GH","NP","HU", "IS","MC","BI", "DJ","ER","KM", "MG","MU","RE", "SC","TZ","YT","ZM","AO","CD","CM","GA","GQ","ST","TD","EH","SS","BW","LS","NA","SZ", "BJ","CV", "GM","GW","MR", "NE","SH","TG", "AG","AI","AW", "BB","BL","BQ", "BS","CW","DM", "GD","GP","KN", "KY","LC","MF", "MQ","MS","SX", "TC","TT","VC", "VG","VI","BZ", "HN","NI","BO", "EC","FK","GF", "GY","PY","SR", "BM","GL","PM", "KG","KZ","TJ",  "TM","UZ","MN","MO","BT","MV","BN","LA","TL","AM","AZ","GE","OM","BG","BY","MD","SK","AX","EE","FI","FO","GG","IM","JE","LV","SJ","AD","AL","GI","ME","MK","MT","SI","SM","LI","LU","CK","NF","FJ","NC","PG","SB","VU","FM","GU","KI","MH","MP","NR","PW","AS","NU","PF","PN","TK","TO","TV", "WF","WS")
occ <- c(671,607,511,481,457,404,403,338,328,327,313,260,259,256,217,212,210,202,179,169,155,155,150,149,138,138,120,119,112,98, 95,81,78,68,67,66,58,58, 57,56,55,54,53, 50,49,48,45,43,41,39,37,35,32,32,28, 28,27,26,25,24,24,23,20,17,17,16,15,15,14,14,14,12, 12,11,11,11,10, 10,9,9,8,8,7,7,6,6, 6,6,6,5,5,3,3,3,3,3, 3,3,3,3,2,2,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0) 
mes_datas <- data.frame(iso2,occ)

2- Maintenant, il me faut un fond de carte du monde...

Option 1 - J'ai un super PC et je veux un fond de carte hyper lourd à charger (mais magnifique)

library("raster")
WorldMap <- getData('countries')
names(WorldMap)

On voit ici que les codes à deux lettres des pays sont stockés dans ce fond dans la liste WorldMap$ISO2

Option 2 - on va faire le choix de la légèreté

library(rworldmap)
WorldMap  <- getMap()
summary(WorldMap) # ou names

Ici, les codes ISO2 apparaissent dans la liste WorldMap $ISO_A2

Maintenant, il va falloir faire coller ma liste de pays (dans le désordre) avec l'autre liste de pays (qui en a plus que moi !)

code_ISO2_fond_carte <- as.character(WorldMap$ISO_A2) # parfois une conversion avec as.character est nécessaire si une des liste est reconnue comme facteurs et pas l'autre
# Contrôle de sécurité que les codes ISO2 (2 lettres) sont dans le bon ordre par rapport au fond de carte
setequal(mes_datas[,1],code_ISO2_fond_carte) # Si FALSE, les deux listes sont différentes sinon elle peuvent être identiques mais d'ordres différents !
length(unique(mes_datas[,1]))==length(mes_datas[,1]) # Si FALSE, notre vecteur d'ID est mal conçu (des pays en double...). Ce problème arrive souvent si on travaille sur des espaces fragmentés : on peut se retrouver avec deux communes ayant le même ID.

Bon, clairement, je vais devoir changer l'ordre de mes valeurs pour le faire coller à l'ordre du fond de carte.

# Mise en correspondance des valeurs associés à chaque pays au code ISO selon leur disposition dans le fond de carte
occurences_pays <- rep(NA,length(code_ISO2_fond_carte)) # On crée d'abord une liste vide.
indices <- which(!is.na(code_ISO2_fond_carte)) # On récupère les indices de chaque code pays (on se débarrasse des valeurs vides qui correspondent aux "pays" qui n'ont pas de code pays ISO2 (parfois NA ou parfois "-99")

Remarque : il est recommandé de vérifier que mes noms sont écrits de la même façon dans mon tableau et mon fond de carte avec la commande setdiff().

for (i in indices) {
  sortie <- mes_datas[,2][mes_datas[,1]==code_ISO2_fond_carte[i]]
 if (length(sortie)>1) {cat("Problème : plusieurs valeurs pour un même pays : ",code_ISO2_fond_carte[i],"\n") 
 } else if (length(sortie)==0) {cat("Pas de valeurs pour le pays suivant : ",code_ISO2_fond_carte[i],"\n")
  } else {occurences_pays[i] <- sortie}
}

Remarque : je peux aussi utiliser la commande left_join() de dplyr ou, mieux encore, la commande match() (cf. chercher sur ce site en cliquant sur la loupe en haut à droite).

Enfin, ma liste de valeurs suit maintenant le même ordre que les pays ordonnés dans le fond de carte.

Faut maintenant, affubler à chaque pays une valeur de couleur proportionnelle à sa valeur attribuée.

J'ai plusieurs options.

Option 1 : découpe automatique en fonction des valeurs

n= 10 # nombre de niveaux souhaités
## Niveaux de couleurs : j'utilise la fonction cut
niveaux <- cut(occurences_pays, n, label = FALSE)

Je vais choisir ma palette de couleurs :


## Couleurs - option 1.1. un dégradé avec heat.colors
colors <- rev(heat.colors(n))
## Couleurs - option 1.2. un dégradé où je choisis mes couleurs : je veux aller ici du rouge vers le vert en passant par l'orange !
colfunc<-colorRampPalette(c("red","orange","green"))
colors <- (colfunc(n))

Et maintenant, en fonction de son niveau, chaque valeur de chaque pays aura sa couleur :

couleurs <- rep("#FFFFFF",nrow(dat_country))
for (i in c(1:n)) {couleurs[niveaux==i] <- colors[i]}

Option 2 : je reprends le contrôle et je fais ma découpe manuellement !

summary(occurences_pays)
categories <- c(0,0,8,32,128,256,512,1000) # C'est là que je définis mon découpage
colfunc<-colorRampPalette(c("red","orange","green"))
colors <- (colfunc(length(categories)-1))
couleurs <- rep("#FFFFFF",nrow(mes_datas))
for (i in c(1:length(colors))) {
  couleurs[occurences_pays>categories[i]&occurences_pays<=categories[i+1]] <- colors[i] 
}

Option 3 : le compromis parfait

Je n'ai pas le temps d'étudier cet exemple mais la fonction cut() présentée option1 et disponible à travers la librairie raster() permet de faire le découpage selon les catégories de son choix !

categories <- c(0,8,16,64,256,400,700)
niveaux <- cut(occurences_pays,categories)

On peut utiliser directement les niveaux découpés comme couleurs mais ce n'est pas satisfaisant. Il va falloir réfléchir à sa conversion mais c'est à peu près le même code qu'en option 1.

Bon, je meure d'envie de voir ce que ça donne : affichons la carte !

Découpe automatique

plot(WorldMap,col=couleurs)

Ce n'est pas TOP...

Découpe manuelle

C'est mieux !

Déjà, j'ai mis du blanc au pays qui avaient 0 ou qui n'étaient pas répertoriés.

Ensuite, 'j'ai mis une bordure grise plus fine...

plot(WorldMap,col=couleurs,border="grey")

Mais j'y pense ! Il faut une légende...

Encore une fois, deux options se présentent à moi.

Option 1 : des petits carrés. C'est très classique et pas beau. Mais ça suffit quand on fait des tests et puis cela reste un grand classique.

# PREPARER LA LEGENDE en reprenant le cas où j'ai découpé manuellement
texte=c()
for (i in c(1:(length(categories )-1))) {
  if (i < (length(categories )-1)) {
  if (categories [i] == categories [i+1]) {temp <-categories [i]
  }else {temp <- paste(">",categories [i],"- <= ",categories [i+1],sep="")}
  }
 if (i == (length(categories )-1)){
  temp <- paste(">=",categories [i],sep="")
 }
  texte <- c(texte,temp)
 }
texte
legend(x="left", legend=texte, fill=colors,cex=1) # Et hop, ça s'ajoute (cf. ci-dessous)

Option 2 : un dégradé continue, c'est la classe (mais avouons que ggplot2 le faisait déjà automatiquement)

Je ne connais que cette façon de faire (mais il y a certainement mieux).

# Je trace ma carte sur la partie gauche et je réserve la partie droite pour la légende (0 à 0,8 ==> Je me suis gardé 20% de la place de 0,8 à 1)
par(fig=c(0,0.8,0,1), mar = c(0,2,0,2), new=TRUE) ; dev.new()
plot(WorldMap,col=couleurs,border="grey")
# J'ajoute la légende constituée d'une image à coller
par(fig=c(0.8,1,0.1,0.9), mar = c(2,2,2,0),new=T) # je peux jouer sur sa place en changeant ces paramètres
legend_image <- as.raster(matrix(rev(colors), ncol=1))
# Tracer un graphique blanc à la place de la légende
plot(c(0,2),c(0,1),type = 'n', axes = F,xlab = '', ylab = '', main = 'legend title',cex.main=0.5)
# Tracer les graduations de la légende, ici le paramétrage en est strictement manuel 5 paliers entre 0 et 650
text(x=1.5, y = seq(0,1,l=5), labels = seq(0,650,l=5),cex=0.8) # 5 graduations de 0 à 600...
# Ajouter l'image de gradient
rasterImage(legend_image, 0, 0, 1,1)

Légende classique

Option 1

Légende en gradient

Option 2