Visualizando las elecciones de Puebla 2019

Un proyecto de @YaNiPaper y @tacosdedatos

In [1]:
import pandas as pd
from pathlib import Path
from herramientas import arbol
import arrow
import altair as alt
import geopandas as gpd
import gpdvega 
%matplotlib inline

hoy = arrow.now('local').format(fmt = 'DD-MMM-YY', locale = 'es')

hoy
Out[1]:
'12-jun-19'
In [2]:
DATOS_BRUTOS = Path("../datos/brutos/")
DATOS_INTERINOS = Path("../datos/interinos/")
DATOS_PROCESADOS = Path("../datos/procesados/")
DATOS_EXTERNOS = Path("../datos/externos/")
FIGURAS = Path("../reportes/figuras/")
In [3]:
arbol(DATOS_EXTERNOS)
+ ..\datos\externos
    + elecciones_puebla_por_municipio_computo_2019.csv
    + elecciones_puebla_por_seccion_2019.csv
    + mapas_puebla
        + MUNICIPIO.dbf
        + MUNICIPIO.prj
        + MUNICIPIO.qpj
        + MUNICIPIO.shp
        + MUNICIPIO.shx
        + SECCION.dbf
        + SECCION.prj
        + SECCION.qpj
        + SECCION.shp
        + SECCION.shx

Vamos a crear una visualización a nivel municipal y otra a nivel sección.

Estos datos han sido adquiridos, limpiados y manipulados por @YaNiPaper previo a esta visualización. Esto fue hecho con R, puedes visitar el repositorio de GitHub aquí


Nivel: Municipio

In [4]:
datos_municipio = pd.read_csv(DATOS_EXTERNOS / 'elecciones_puebla_por_municipio_computo_2019.csv')
In [5]:
datos_municipio.head()
Out[5]:
ID_MUNICIPIO MUNICIPIO ALBERTO_JIMENEZ ENRIQUE_CARDENAS MIGUEL_BARBOSA NO_REGISTRADOS NULOS TOTAL_VOTOS LISTA_NOMINAL aj_porc ec_porc mb_porc no_reg_porc nulos_porc parti_porc abst_porc GANADOR PORC_GANADOR CVE_GANADOR
0 1 ACAJETE 3724 2917 3675 6 303 10625 45097 35.05 27.45 34.59 0.06 2.85 23.56 76.44 ALBERTO_JIMENEZ 35.05 1
1 2 ACATENO 503 226 2335 4 87 3155 6978 15.94 7.16 74.01 0.13 2.76 45.21 54.79 MIGUEL_BARBOSA 74.01 3
2 3 ACATLAN 1777 1246 4455 6 215 7699 28427 23.08 16.18 57.86 0.08 2.79 27.08 72.92 MIGUEL_BARBOSA 57.86 3
3 4 ACATZINGO 2558 2778 3106 20 229 8691 37475 29.43 31.96 35.74 0.23 2.63 23.19 76.81 MIGUEL_BARBOSA 35.74 3
4 5 ACTEOPAN 283 98 729 0 54 1164 2333 24.31 8.42 62.63 0.00 4.64 49.89 50.11 MIGUEL_BARBOSA 62.63 3
In [6]:
datos_municipio.shape
Out[6]:
(219, 19)
In [7]:
puebla_municipios = gpd.read_file(DATOS_EXTERNOS / 'mapas_puebla' / 'MUNICIPIO.shp', encoding = 'utf-8')
In [8]:
# # un poco mas de limpieza por las malditas ñ's en los nombres
# puebla_municipios['nombre'] = puebla_municipios['nombre'].str.replace("CA?ADA", "CAÑADA")
In [9]:
puebla_municipios.head()
Out[9]:
gid id entidad municipio nombre control geometry1_ geometry
0 1 66.0 21 1 ACAJETE 67.0 None POLYGON ((607552.153001085 2102577.75040513, 6...
1 2 169.0 21 2 ACATENO 175.0 None POLYGON ((687604.536279954 2218598.67690477, 6...
2 3 25.0 21 3 ACATLAN 25.0 None (POLYGON ((606149.074408127 2018062.39644271, ...
3 4 102.0 21 4 ACATZINGO 103.0 None POLYGON ((625483.645090082 2096032.04553089, 6...
4 5 127.0 21 5 ACTEOPAN 131.0 None POLYGON ((531458.765347764 2080441.15751206, 5...
In [10]:
puebla_municipios.plot();

De este GeoDataFrame solo nos interesa las columnas:

  1. municipio
  2. nombre
  3. geometry
In [11]:
puebla_municipios.drop(columns = ['gid', 'id', 'entidad', 'control', 'geometry1_'], inplace = True)
In [12]:
puebla_con_datos = puebla_municipios.merge(datos_municipio, left_on = ['municipio', ], right_on = ['ID_MUNICIPIO',],)
In [13]:
puebla_con_datos.drop(columns = ['municipio',], inplace = True)
In [14]:
puebla_con_datos.head()
Out[14]:
nombre geometry ID_MUNICIPIO MUNICIPIO ALBERTO_JIMENEZ ENRIQUE_CARDENAS MIGUEL_BARBOSA NO_REGISTRADOS NULOS TOTAL_VOTOS ... aj_porc ec_porc mb_porc no_reg_porc nulos_porc parti_porc abst_porc GANADOR PORC_GANADOR CVE_GANADOR
0 ACAJETE POLYGON ((607552.153001085 2102577.75040513, 6... 1 ACAJETE 3724 2917 3675 6 303 10625 ... 35.05 27.45 34.59 0.06 2.85 23.56 76.44 ALBERTO_JIMENEZ 35.05 1
1 ACATENO POLYGON ((687604.536279954 2218598.67690477, 6... 2 ACATENO 503 226 2335 4 87 3155 ... 15.94 7.16 74.01 0.13 2.76 45.21 54.79 MIGUEL_BARBOSA 74.01 3
2 ACATLAN (POLYGON ((606149.074408127 2018062.39644271, ... 3 ACATLAN 1777 1246 4455 6 215 7699 ... 23.08 16.18 57.86 0.08 2.79 27.08 72.92 MIGUEL_BARBOSA 57.86 3
3 ACATZINGO POLYGON ((625483.645090082 2096032.04553089, 6... 4 ACATZINGO 2558 2778 3106 20 229 8691 ... 29.43 31.96 35.74 0.23 2.63 23.19 76.81 MIGUEL_BARBOSA 35.74 3
4 ACTEOPAN POLYGON ((531458.765347764 2080441.15751206, 5... 5 ACTEOPAN 283 98 729 0 54 1164 ... 24.31 8.42 62.63 0.00 4.64 49.89 50.11 MIGUEL_BARBOSA 62.63 3

5 rows × 21 columns

Limpiamos un poco mas los datos:

In [15]:
puebla_con_datos.columns = [col.replace("_", " ").lower() for col in puebla_con_datos.columns]
In [16]:
puebla_con_datos = puebla_con_datos.rename(columns = {
    "enrique cardenas": "Enrique Cárdenas Sánchez",
    "alberto jimenez": "Alberto Jiménez Merino",
    "miguel barbosa": "Miguel Barbosa Huerta",
    "no registrados": "No registrados",
    "nulos": "Votos nulos",
    "total votos": "Total de votos",
    "ganador": "Ganador",
    "porc ganador": "% del ganador",
    "lista nominal": "Lista nominal",
    "aj porc": "% AJ",
    "ec porc": "% EC",
    "mb porc": "% MB",
    "no reg porc": "% No registrados",
    "nulos porc": "% Nulos",
    "parti porc": "% de participación",
    "abst porc": "% de abstencionismo",
})
In [17]:
puebla_con_datos['Municipio'] = puebla_con_datos['municipio'].str.title()
puebla_con_datos.head()
Out[17]:
nombre geometry id municipio municipio Alberto Jiménez Merino Enrique Cárdenas Sánchez Miguel Barbosa Huerta No registrados Votos nulos Total de votos ... % EC % MB % No registrados % Nulos % de participación % de abstencionismo Ganador % del ganador cve ganador Municipio
0 ACAJETE POLYGON ((607552.153001085 2102577.75040513, 6... 1 ACAJETE 3724 2917 3675 6 303 10625 ... 27.45 34.59 0.06 2.85 23.56 76.44 ALBERTO_JIMENEZ 35.05 1 Acajete
1 ACATENO POLYGON ((687604.536279954 2218598.67690477, 6... 2 ACATENO 503 226 2335 4 87 3155 ... 7.16 74.01 0.13 2.76 45.21 54.79 MIGUEL_BARBOSA 74.01 3 Acateno
2 ACATLAN (POLYGON ((606149.074408127 2018062.39644271, ... 3 ACATLAN 1777 1246 4455 6 215 7699 ... 16.18 57.86 0.08 2.79 27.08 72.92 MIGUEL_BARBOSA 57.86 3 Acatlan
3 ACATZINGO POLYGON ((625483.645090082 2096032.04553089, 6... 4 ACATZINGO 2558 2778 3106 20 229 8691 ... 31.96 35.74 0.23 2.63 23.19 76.81 MIGUEL_BARBOSA 35.74 3 Acatzingo
4 ACTEOPAN POLYGON ((531458.765347764 2080441.15751206, 5... 5 ACTEOPAN 283 98 729 0 54 1164 ... 8.42 62.63 0.00 4.64 49.89 50.11 MIGUEL_BARBOSA 62.63 3 Acteopan

5 rows × 22 columns

In [18]:
puebla_con_datos['Ganador'] = puebla_con_datos['Ganador'].str.replace("_", ' ').str.title()
In [19]:
# centroides
puebla_con_datos['centroide_x'] = puebla_con_datos['geometry'].centroid.x
puebla_con_datos['centroide_y'] = puebla_con_datos['geometry'].centroid.y

Podemos revisar rápidamente con lo que estamos trabajando con .plot(); en GeoPandas.

Nota Estoy agregando un ";" a .plot(); solo para evitar el mensaje de matplotlib que dice algo como matplotlib.axes._subplots.AxesSubplot at 0xc281b70
In [20]:
puebla_con_datos.plot(column = 'Ganador');

Primera visualización

Inspirada por este tuit de Ben Welsh (Editor en jefe de Datadesk de Los Angeles Times) aka @palewire

In [21]:
%run theme.py
<Figure size 432x288 with 0 Axes>
In [22]:
mapa_base = alt.Chart(puebla_con_datos).mark_geoshape(
    stroke = '#C7C7C7',
    strokeWidth = 0.1,
).encode().properties(
    width = 600,
    height = 800,
)
In [23]:
mapa_resultados = alt.Chart(puebla_con_datos).mark_geoshape(
    fill = 'lightgray',
    stroke = '#C7C7C7',
).encode(
    color = alt.Color(
        'Ganador:N',
        legend = alt.Legend(title = "Ganador", orient = 'none', legendY=300, legendX = 600)
    ),
    tooltip = ['Municipio:N','Enrique Cárdenas Sánchez:Q', 'Alberto Jiménez Merino:Q', 'Miguel Barbosa Huerta:Q', 'Votos nulos:Q', 'No registrados:Q', 'Lista nominal:Q', '% de abstencionismo']
)
In [24]:
ganador_por_municipio = (mapa_base + mapa_resultados).properties(
    title = 'Elección de gobernador - Puebla 2019',
    background = '#F5F1E4'
).configure_view(
    strokeWidth = 0,
).configure_title(
    fontSize=36,
)
In [25]:
ganador_por_municipio
Out[25]:
In [26]:
# checkpoint
municipio_path = FIGURAS.joinpath(f"ganador_por_municipio-{hoy}.html")
municipio_path = str(municipio_path)
ganador_por_municipio.save(municipio_path)

Segunda visualización

In [27]:
secciones = gpd.read_file(DATOS_EXTERNOS / 'mapas_puebla' / 'SECCION.shp')

secciones = secciones[['seccion', 'geometry']]

secciones.head()
Out[27]:
seccion geometry
0 113 POLYGON ((624220.121241881 2218231.45630332, 6...
1 1319 POLYGON ((588649.611591479 2106827.2561998, 58...
2 114 POLYGON ((625131.004189697 2218154.0604571, 62...
3 115 POLYGON ((622838.703358738 2221492.84485929, 6...
4 502 POLYGON ((581656.828105251 2237838.39969905, 5...
In [28]:
secciones_datos = pd.read_csv(DATOS_EXTERNOS / 'elecciones_puebla_por_seccion_2019.csv')

secciones_datos.head()
Out[28]:
SECCION ALBERTO_JIMENEZ ENRIQUE_CARDENAS MIGUEL_BARBOSA NO_REGISTRADOS NULOS TOTAL_VOTOS LISTA_NOMINAL aj_porc ec_porc mb_porc no_reg_porc nulos_porc parti_porc abst_porc GANADOR PORC_GANADOR CVE_GANADOR
0 1 161 203 283 0 17 664 2377 24.25 30.57 42.62 0.00 2.56 27.93 72.07 MIGUEL_BARBOSA 42.62 3
1 10 89 211 251 0 8 559 2494 15.92 37.75 44.90 0.00 1.43 22.41 77.59 MIGUEL_BARBOSA 44.90 3
2 100 22 107 328 0 9 466 1340 4.72 22.96 70.39 0.00 1.93 34.78 65.22 MIGUEL_BARBOSA 70.39 3
3 1000 48 641 208 2 13 912 1715 5.26 70.29 22.81 0.22 1.43 53.18 46.82 ENRIQUE_CARDENAS 70.29 2
4 1001 60 259 210 0 15 544 1420 11.03 47.61 38.60 0.00 2.76 38.31 61.69 ENRIQUE_CARDENAS 47.61 2
In [29]:
secciones_con_datos = secciones.merge(secciones_datos, left_on = 'seccion', right_on = 'SECCION')
In [30]:
secciones_con_datos = secciones_con_datos.rename(columns = {
    "ENRIQUE_CARDENAS": "Enrique Cárdenas Sánchez",
    "ALBERTO_JIMENEZ": "Alberto Jiménez Merino",
    "MIGUEL_BARBOSA": "Miguel Barbosa Huerta",
    "NO_REGISTRADOS": "No registrados",
    "NULOS": "Votos nulos",
    "TOTAL_VOTOS": "Total de votos",
    "GANADOR": "Ganador",
    "PORC_GANADOR": "% del ganador",
    "abst_porc": "% de abstencionismo"
})
In [31]:
secciones_con_datos['Ganador'] = secciones_con_datos['Ganador'].str.replace("_", ' ').str.title()
In [32]:
secciones_con_datos = secciones_con_datos[['Alberto Jiménez Merino', 'Enrique Cárdenas Sánchez', 'Miguel Barbosa Huerta', 'No registrados', 'Votos nulos', 'Total de votos', 'LISTA_NOMINAL', 'aj_porc', 'ec_porc', 'mb_porc', 'no_reg_porc', 'nulos_porc', 'parti_porc', "% de abstencionismo", 'Ganador','% del ganador', 'CVE_GANADOR', 'geometry',]]
In [33]:
secciones_con_datos = secciones_con_datos.to_crs(epsg=4326)
In [34]:
# centroides
secciones_con_datos['centroide_x'] = secciones_con_datos['geometry'].centroid.x
secciones_con_datos['centroide_y'] = secciones_con_datos['geometry'].centroid.y
In [35]:
secciones_con_datos.plot(column = 'Ganador');
In [36]:
base_secciones = alt.Chart(secciones_con_datos).mark_geoshape(
    stroke='black',
    strokeWidth=0.1,
    fill = '#FCF9F0'
).properties(
    width = 600,
    height = 800,
).encode(
    tooltip = ['Enrique Cárdenas Sánchez', 'Alberto Jiménez Merino', 'Miguel Barbosa Huerta', 'Votos nulos', 'Total de votos', '% de abstencionismo']
)
In [37]:
participacion = alt.Chart(secciones_con_datos).mark_circle().encode(
    longitude='centroide_x:Q',
    latitude='centroide_y:Q',
    size=alt.Size(
        "Total de votos:Q",
        title="Total de votos",
        legend = alt.Legend(orient = 'none', legendY=400, legendX = 600)
    ),
    tooltip = ['Enrique Cárdenas Sánchez', 'Alberto Jiménez Merino', 'Miguel Barbosa Huerta', 'Votos nulos', 'Total de votos', '% de abstencionismo'],
    color = alt.Color('Ganador:N',legend = alt.Legend(title = "Ganador", orient = 'none', legendY=300, legendX = 600)),
)
In [38]:
mapa_participacion = (base_secciones + participacion).properties(
    title="Elección de gobernador - Puebla 2019",
    background = '#F5F1E4',
).configure_view(
    strokeWidth=0,
).configure_title(
    fontSize = 36,
)
In [39]:
mapa_participacion
Out[39]:
In [40]:
participacion_path = str(FIGURAS / f"participacion-{hoy}.html")
mapa_participacion.save(participacion_path)