La Campaña Rusa de Napoleon, C.J. Minard

Se trata de un mapa completo que muestra seis tipos de datos en dos dimensiones: el número de tropas de Napoleón; la distancia recorrida; la temperatura; la latitud y la longitud; la dirección del viaje; y la ubicación en relación con fechas concretas, sin hacer mención a Napoleón; el interés de Minard radica en las fatigas y los sacrificios de los soldados.

“La Carta figurativa de las sucesivas pérdidas de hombres de la armada francesa en la campaña de Rusia de Napoleón en 1812” (Carte figurative des pertes successives en hommes de l’Armée Française dans la campagne de Russie 1812-1813)

Activamos el entorno virtual

use_virtualenv("r-reticulate")
py_available(TRUE)
## [1] TRUE

Instalamos las librerias de Python necesarias

Cargamos las librerias

import pandas as pd
import altair as alt
import numpy as np

from vega_datasets import data

Cargamos el dataset de las ciudades

cities = pd.read_csv("data/minard_cities.txt", sep=" ", names=["lon", "lat", "city"])
cities
##      lon   lat            city
## 0   24.0  55.0           Kowno
## 1   25.3  54.7           Wilna
## 2   26.4  54.4        Smorgoni
## 3   26.8  54.3       Molodexno
## 4   27.7  55.2       Gloubokoe
## 5   27.6  53.9           Minsk
## 6   28.5  54.3      Studienska
## 7   28.7  55.5         Polotzk
## 8   29.2  54.4            Bobr
## 9   30.2  55.3         Witebsk
## 10  30.4  54.5          Orscha
## 11  30.4  53.9         Mohilow
## 12  32.0  54.8        Smolensk
## 13  33.2  54.9     Dorogobouge
## 14  34.3  55.2           Wixma
## 15  34.4  55.5           Chjat
## 16  36.0  55.5         Mojaisk
## 17  37.6  55.8          Moscou
## 18  36.6  55.3       Tarantino
## 19  36.5  55.0  Malo-Jarosewli

Cargamos el dataset de las temperaturas

temperatures = pd.read_csv("data/minard_temperature.txt", sep=" ", names=["lon", "temp", "days", "day"])
temperatures
##     lon  temp  days     day
## 0  37.6     0     6  Oct-18
## 1  36.0     0     6  Oct-24
## 2  33.2    -9    16   Nov-9
## 3  32.0   -21     5  Nov-14
## 4  29.2   -11    10     NaN
## 5  28.5   -20     4  Nov-28
## 6  27.2   -24     3   Dec-1
## 7  26.7   -30     5   Dec-6
## 8  25.3   -26     1   Dec-7

Cargamos el dataset de las tropas

troops = pd.read_csv("data/minard_troops.txt", sep=" ", names=["lon",  "lat", "survivors" , "direction", "division"])
troops.head()
##     lon   lat  survivors direction  division
## 0  24.0  54.9     340000         A         1
## 1  24.5  55.0     340000         A         1
## 2  25.5  54.5     340000         A         1
## 3  26.0  54.7     320000         A         1
## 4  27.0  54.8     300000         A         1

ETL del dataset temperaturas

temperatures["label"] = temperatures.fillna("").apply(
    axis=1, func=lambda row: "{}° {}".format(row[1], row[3].replace("-", " "))
)
temperatures.head()
##     lon  temp  days     day        label
## 0  37.6     0     6  Oct-18    0° Oct 18
## 1  36.0     0     6  Oct-24    0° Oct 24
## 2  33.2    -9    16   Nov-9    -9° Nov 9
## 3  32.0   -21     5  Nov-14  -21° Nov 14
## 4  29.2   -11    10     NaN        -11°

ETL del dataset tropas

troops = troops.sort_values(by=["division", "survivors"], ascending=False)
troops.head()
##      lon   lat  survivors direction  division
## 45  24.0  55.2      22000         A         3
## 46  24.5  55.3      22000         A         3
## 47  24.6  55.8       6000         A         3
## 48  24.6  55.8       6000         R         3
## 49  24.2  54.4       6000         R         3

Elementos de este mapa

Generamos el gráfico del volumen de tropoas

troops_chart = alt.Chart(troops).mark_trail().encode(
    x='lon:Q',
    y='lat:Q',
    size=alt.Size('survivors', scale=alt.Scale(range=[1, 75]), legend=None),
    color=alt.Color('direction')
)
troops_chart.save("data/troops_first_try.html")
htmltools::includeHTML("data/troops_first_try.html")

Dirección

En este apartado se muestra la dirección de ataque y retirada del ejercito napoleonico.

troops_chart = alt.Chart(troops).mark_trail().encode(
    longitude='lon:Q',
    latitude='lat:Q',
    size=alt.Size(
        'survivors', 
        scale=alt.Scale(range=[1, 75]), 
        legend=None
    ),
    detail='division',
    color=alt.Color(
        'direction', 
        scale=alt.Scale(
            domain=['A', 'R'],
            range=['#EBD2A8', '#888888']
        ), 
        legend=None
    ),
).project(
    type="mercator"
)
troops_chart.save("data/troops.html")
htmltools::includeHTML("data/troops.html")

Volumen

Aquí se muestran el volukmen de efectivos de las tropas.

ETL del dataset Tropas

# Small manipulation to scatter the troop size text a bit
troops_text = troops.iloc[::2, :].copy()
troops_text["lon"] += 0.13 * (troops_text["division"])
troops_text["lat"] += troops_text["direction"].replace({"A": 0.35, "R": -0.21})
troops_text.head()
##       lon    lat  survivors direction  division
## 45  24.39  55.55      22000         A         3
## 47  24.99  56.15       6000         A         3
## 49  24.59  54.19       6000         R         3
## 28  24.26  55.45      60000         A         2
## 30  25.76  55.05      60000         A         2

Generamos gráfica de número de tropas por ubicación

troops_text_chart = alt.Chart(troops_text).mark_text(
    font='Cardo',
    fontSize=7,
    fontStyle='italic',
    angle=280
).encode(
    longitude='lon:Q',
    latitude='lat:Q',
    text='survivors'
).project(
    type="mercator"
)

Grabamos en html

troops_text_chart.save("data/troops_text.html")
htmltools::includeHTML("data/troops_text.html")

Número de soldados

Aquí se muestran el número de efectivos de las tropas.

Generamos la gráfica de las localidades donde atraviesa la ruta.

cities_chart = alt.Chart(cities).mark_text(
    font='Cardo',
    fontSize=11,
    fontStyle='italic',
    dx=-3
).encode(
    longitude='lon:Q',
    latitude='lat:Q',
    text='city',
).project(
    type="mercator"
)

Grabamos en html

cities_chart.save("data/cities.html")
htmltools::includeHTML("data/cities.html")

Geografía

En este apartado se muestran las ciudades por las que recorrió el ejercito entre la frontera polaca y Moscú.

x_encode = alt.X(
    'lon:Q', 
    scale=alt.Scale(
        domain=[cities["lon"].min(), cities["lon"].max()]
    ),
    axis=None
)
y_encode = alt.Y(
    'temp',
    axis=alt.Axis(
        title="Temperatura durante la Retirada", 
        grid=True, 
        orient='right'
    )
)
y_encode
## Y({
##   axis: Axis({
##     grid: True,
##     orient: 'right',
##     title: 'Temperatura durante la Retirada'
##   }),
##   shorthand: 'temp'
## })

Generamos las gráficas de la temperatura

temperatures_chart = alt.Chart(temperatures).mark_line(
    color="#888888"
).encode(
    x=x_encode,
    y=y_encode
) + alt.Chart(temperatures).mark_text(
    dx=5, 
    dy=20, 
    font='Cardo', 
    fontSize=10
).encode(
    x=x_encode,
    y=y_encode,
    text='label'
)

Grabamos en html

temperatures_chart.save("data/temperature.html")
htmltools::includeHTML("data/temperature.html")

Temperatura

Muestra la temperatura en cada momento de paso de las tropas.

temperatures_chart = temperatures_chart.properties(
    height=100
)
temperatures_chart
## alt.LayerChart(...)

Sumamos los mapas

# sumamos los mapas
map_chart = troops_chart + cities_chart + troops_text_chart
# Resultado final
final_chart = alt.vconcat(map_chart, temperatures_chart).configure_view(
    width=900,
    height=400,
    strokeWidth=0
).configure_axis(
    grid=False,
    labelFont="Cardo",
    titleFont="Cardo"
)

Grabamos en html

final_chart.save("data/minard_chart.html")
htmltools::includeHTML("data/minard_chart.html")

Resultado final

Resultado final juntando todos los mapas