R: la campagne de Russie par Minard

Le billet précédent m’a donné envie de m’intéresser un peu plus longuement à la visualisation de données - ou dataViz dans les milieux autorisés. Depuis quelques temps je m’intéresse en particulier à R. Il s’agit à l’origine d’un logiciel libre de traitement statistique de données. Cependant, grâce aux multiples extensions développées par ses utilisateurs, il est devenue très puissant pour tout type de traitement de données. Il existe actuellement un grand nombre d’extensions qui permettent très facilement de traiter des informations géographiques. Il s’avère que la représentation de Charles Minard de la campagne de Russie de 1812, 1813 fait partie des exercices classiques d’utilisation de R.
Comme les exemples disponibles ne sont pas très beaux, je vous propose dans ce billet ma version et le code qui permet de générer l’image ci-dessous :

Campagne de Russie par Minard avec R

Les données

Les données proviennent de 3 fichiers où la première ligne sert à identifier les données par nom.

Le premier fichier contient une position géographique, le nombre de soldats, la direction (A pour aller, R pour retour) et un groupe qui représente les corps d’Armée : 1 pour l’armée principale de Napoléon, 2 pour le Xème corps d’Armée de MacDonald qui va échouer devant Riga, 3 pour le IIème corps d’Armée d’Oudinot qui reste figé à Polotsk.

long  lat survivors  direction group
24.0  54.9   340000        A     1
24.5  55.0   340000        A     1
25.5  54.5   340000        A     1
....

Le second contient la position des villes.

long  lat city
24.0  55.0  Kowno
25.3  54.7  Wilna
26.4  54.4  Smorgoni
....

Le dernier contient la température en fonction d’une longitude avec une indication de date.

long   temp   month day date
37.6     0   Oct 18  18OCT1812
36.0     0   Oct 24  24OCT1812
....

Traiter les données et générer le graphique

Lorsque les données sont décrites, il ne reste plus qu’à écrire le script de traitement et d’affichage.

R permet de traiter les données sous forme de fichier texte (un script).
Cette forme permet de facilement commenter ou modifier le script pour corriger les défauts et comparer les modifications. Ce type de traitement est impossible avec un logiciel qui ne permet que de cliquer.

Voici l’intégralité du code :

# Chargement des bibliothèques nécessaires
library(ggplot2)
library(ggmap)
library(grid)

scale = 3         # 6 pour générer une grande image

# Lecture des données
# ===================              
troops <- read.table("minard-troops.txt",header=TRUE)
cities <- read.table("minard-cities.txt",header=TRUE)
temps <- read.table("temps.txt", header=TRUE)

# Adaptation des données
# ======================

# la Bérézina coule à proximité de Studienska
berezina <- cities[cities$city == 'Studienska',]

# traduction des titres de colonnes
colnames(troops) <- c("long","lat","Survivants","Direction","group")

# astuce pour que ce groupe soit dessiné en dernier
# cette ligne sera tracée au dessus des autres
troops$group[troops$group == 1] <- 4

# Les éléments 
# ============
# chemins, points, textes, carte 
p_troops <- geom_path(aes(long, lat, size = Survivants,
                   color = Direction, group = group),
                   lineend = "square", linejoin = "bevel",
                   data = troops)
p_text <- geom_text(aes(long,lat,label = city), size = scale,
                   family="Times", fontface="italic",
                   hjust=0, vjust=-1, color="black",
                   data = cities)
p_point <- geom_point(aes(long,lat), colour = "black",
                   size = scale, data = cities )
# carte Stamen de la Russie en noir et blanc
russie <- ggmap(
           get_map(location = c(left=22, bottom=53.5, right=39, top=56.5),
           zoom=6, maptype="toner", source="stamen"))

# Graphique principal
# ===================

# assemblage du graphique
p <- russie + p_troops
# on ajoute le point berezina
p <- p + geom_text(aes(long,lat,label ="Bérézina"), size = scale*2,
      hjust=0, vjust=1.2, family="Times", fontface="italic",
      color="red", data = berezina)
p <- p  + geom_point(aes(long,lat), colour = "red",
       size = scale*2, data = berezina)
# on ajoute les villes
p <- p + p_text + p_point

# on enlève les légendes des axes
p <- p + xlab(NULL) + ylab(NULL)

# échelles des lignes et légende, couleurs
p <- p + scale_size(range = c(1, scale*4),
                  breaks = c(90000, 50000, 10000, 4000),
                  labels = c(90000, 50000, 10000, 4000) )
p <- p + scale_colour_manual(values = c("bisque2", "grey50"))

# Thème graphique
# ================
theme_minard <- theme(
    panel.background = element_rect(fill="white"),
    panel.border = element_blank(),
    legend.key = element_rect(fill="white"),
    legend.key.size = unit(3, "line"),
    axis.text.y = element_text(colour="black"),
    axis.text.x = element_text(colour="black"),
    text = element_text(size=scale*4, family="Times")
    )

p <- p + theme_minard
p <- p + theme(axis.text.y = element_blank())

# Graphique des températures
# ==========================
t <- qplot(long, temp, data=temps, geom="line") +
  geom_text(aes(label = paste(day, month)),
  size = scale, family="Times", fontface="italic", vjust=1.5)

t <- t + theme_minard
t <- t + theme(panel.grid.major = element_line(size=.2,colour="black"))
t <- t + xlab("Longitude") + ylab("Température")
t <- t + scale_x_continuous(limits = c(23, 38))

# Dessin final
# ============ 
vplayout <- function(x, y)
   viewport(layout.pos.row = x, layout.pos.col = y)

#png("r-minard.png",width = 1200, height = 600)

# sur une grille de 3 lignes et 6 colonnes
pushViewport(viewport(layout = grid.layout(3, 6)))
print(p, vp = vplayout(1:2, 1:6))
print(t, vp = vplayout(3, 1:5))

#dev.off()

# Pour enregistrer une image png, décommentez les lignes "png(..." et "dev.off()"
# Vous pouvez également changer de format : svg, jpg, pdf, ...

Par rapport à l’original de Minard, j’ai ajouté un fond de carte récent où apparaît les frontières de la Lituanie ou de la Biélorussie. J’ai également fait apparaître clairement la position de la Bérézina. Il serait élémentaire d’y ajouter la position des principales batailles. Il faudrait éventuellement corriger quelques défauts de marge ou d’alignement.

# graphique rapide des survivants en fonction de la longitude
qplot( long, Survivants, data = troops, geom="path",
   # légende et groupement par corps d'armée
   color=as.factor(group),group = group) + 
   # thème noir et blanc 
   theme_bw() + 
    # re nommage de la légende
   scale_colour_grey(name="Armée",labels=c("MacDonald","Oudinot","Principale"))

# enregistrement de l'image png
ggsave(file="troops.png",scale=2,dpi = 87, width=4, height=2)

Le code ci-dessus permet de générer un autre graphique

Avec R, il est également très facile de créer très rapidement des graphiques classiques comme ci-dessous :

R survivants en fonction de la longitude

Il s’agit uniquement des pertes de nos 3 groupes en fonction de la longitude.

Ressources supplémentaires:

Complément

08-06-2019: Ce graphique et billet sont cités dans un article très intéressant sur l’enseignement de la statistique à l’aide de données historiques dans la revue //Statistique et Enseignement// de la //Société Française de Statistique//.

Jonathan EL METHNI, «Data visualisation et enseignement de la statistique au travers d’exemples historiques en R», Statistique et Enseignement - Vol. 9 novembre 2018, consulté le 8 juin 2019, URL:http://statistique-et-enseignement.fr/article/view/696