La mayor parte del taller utilizaremos principalmente la librería Pandas para el análisis de datos, esta librería viene incluida en el stack que nos provee automáticamente COlab. Sin embargo, para poder leer datos geográficos y hacer mapas, vamos a necesitar instalar Geopandas, que es una extensión de Pandas para el manejo de datos geográficos, y un par de librerías más para hacer mapas interactivos.
La instalación de dependencias en Colab es relativamente sencilla, el caso de Geopandas tiene elguna complicación porque requiere de la instalación de un par de librerías del sistema (es decir, librerías que no son sólo de Python). La siguiente celda contiene las instrucciones (al sistema operativo debajo de Colab, noten el símbolo !
al inicio de cada instrucción) para instalar todas las dependencias que necesitamos.
# Important library for many geopython libraries
!apt install gdal-bin python-gdal python3-gdal
# Install rtree - Geopandas requirment
!apt install python3-rtree
# Install Geopandas
!pip install git+git://github.com/geopandas/geopandas.git
# Install descartes - Geopandas requirment
!pip install descartes
# Install Folium for Geographic data visualization
!pip install folium
# Install plotlyExpress
!pip install plotly_express
!pip install mapclassify
Con todas las librerías instaladas, podemos importar lo que vamos a utilizar
import os
import glob
import itertools
from pathlib import Path
import zipfile
import numpy as np
import pandas as pd
import geopandas as gpd
from datetime import timedelta, date, datetime
import csv
import openpyxl
import requests
import matplotlib.pyplot as plt
import mapclassify
import folium
import logging
En el taller anterior aprendimos a utilizar Geopandas para manejar y visualizar datos geográficos. En esta sección vamos a hacer algunos mapas sobre la evolución del COVID en México.
Vamos a partir de la base de datos ya preprocesada, en este caso contiene sólo los agregados de casos nuevos por semana para todos los municipios del país. Para simplificar el ejercicio y centrarnos en la producción de mapas, vamos a bajar los datos de Dropbox como ya lo hemos hecho antes (la ruta es para Google Colab):
url = "https://www.dropbox.com/s/kf9dldnqgo4eidu/agregados_semana_municipio.pkl?dl=1"
r = requests.get(url, allow_redirects=True)
open('/content/semana_municipio.pkl', 'wb').write(r.content)
Ya con los datos, ahora vamos a necesitar las geometrías de los municipios de México para empezar a hacer mapas, esos los vamos a bajar de:
url = "https://www.dropbox.com/s/2zw0fh3vdl0rxh4/municipios_pob_2020_simple.json?dl=1"
r = requests.get(url, allow_redirects=True)
open('/content/municipios_pob_2020_simple.json', 'wb').write(r.content)
Ahora ya tenemos el shape, vamos a usar GeoPandas para leerlo en un GeoDataFrame
municipios = gpd.read_file('data/municipios_pob_2020_simple.json')
municipios
id | oid | municipio_cvegeo | municipio | pob2020 | pob_0a4 | pob_0a9 | pob60ym | entidad_cvegeo | geometry | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 827 | 525 | 16046 | Juárez | 15290 | 1557 | 3122 | 1911 | 16 | POLYGON ((-100.45693 19.33414, -100.45818 19.3... |
1 | 828 | 209 | 16047 | Jungapeo | 22358 | 2470 | 4920 | 2608 | 16 | POLYGON ((-100.44063 19.51413, -100.44814 19.5... |
2 | 829 | 564 | 16048 | Lagunillas | 5862 | 550 | 1111 | 844 | 16 | POLYGON ((-101.38329 19.59813, -101.38279 19.6... |
3 | 830 | 524 | 16049 | Madero | 18769 | 2049 | 4136 | 2055 | 16 | POLYGON ((-101.11644 19.53327, -101.11713 19.5... |
4 | 67 | 44 | 05035 | Torreón | 744247 | 65682 | 129805 | 85778 | 05 | MULTIPOLYGON (((-102.98871 24.79622, -102.9930... |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2452 | 2452 | 2388 | 32053 | Villa González Ortega | 13945 | 1481 | 2926 | 1599 | 32 | POLYGON ((-101.94821 22.65201, -101.95269 22.6... |
2453 | 2453 | 2417 | 32054 | Villa Hidalgo | 20177 | 2078 | 4191 | 2036 | 32 | POLYGON ((-101.65599 22.51381, -101.65651 22.5... |
2454 | 2454 | 1407 | 32055 | Villanueva | 31804 | 2738 | 5540 | 5324 | 32 | POLYGON ((-102.69428 22.62230, -102.69370 22.6... |
2455 | 2455 | 2411 | 32056 | Zacatecas | 155533 | 12609 | 25488 | 15549 | 32 | POLYGON ((-102.58542 22.81149, -102.58522 22.8... |
2456 | 2457 | 2457 | 32058 | Santa María de la Paz | 2855 | 257 | 505 | 536 | 32 | POLYGON ((-103.19774 21.58534, -103.21258 21.5... |
2457 rows × 10 columns
Como pueden ver un GeoDataFrame es bastante similar a un DataFrame, la única diferencia es la columna especial geometry
. En general cualquier cosa que se puede hacer con un DataFrame de Pandas se puede hacer con un GeoDataFrame (aunque claro, no todas las operaciones regresan algo con geometría!).
Lo primero que vamos a hacer es un mapa rápido de población a nivel municipal. Para eso vamos a usar el métdo plot de geopandas que nos da chance de hacer mapas bien fácil
municipios.plot(column='pob2020', cmap='OrRd',figsize=(15, 10), scheme="quantiles", legend=True)
<AxesSubplot:>
Si no nos gusta cómo se ve el mapa, es fácil hacer modificaciones de estilo
fig, ax = plt.subplots(1, figsize=(20,10))
municipios.plot(column='pob2020', cmap='OrRd',figsize=(15, 10), scheme="quantiles", legend=True, ax=ax)
ax.set_axis_off()
fig.suptitle("Población por municipio") # A través de la función '.suptitle()' aplicada a la figura se coloca el título.
plt.show()
El primer paso para mapear los casos de covid es leerlos y unirlos a las geometrías de los municipios, empecemos por leer la misma base que hemos usado ya antes
df = pd.read_pickle("data/semana_municipio.pkl")
df.head()
Nuevos Casos | Defunciones | |||
---|---|---|---|---|
FECHA_SINTOMAS | CLAVE_MUNICIPIO_RES | MUNICIPIO_RES | ||
2020-01-05 | 01001 | AGUASCALIENTES | 11 | 0 |
01006 | PABELLÓN DE ARTEAGA | 1 | 0 | |
01009 | TEPEZALÁ | 1 | 0 | |
02001 | ENSENADA | 1 | 0 | |
02002 | MEXICALI | 11 | 0 |
Estos datos corresponden a toda la serie de tiempo, entonces para cada municipio hay toda una serie de valores. Para empezar a hacer mapas entonces vamos a seleccionar una fecha en específico, lo más fácil es seleccionar la última disponible (vamos a seleccionar por FECHA_INGRESO
, pero podríamos usar cualquier otra)
ultima_fecha = df.reset_index().loc[df.reset_index()['FECHA_SINTOMAS'] == df.reset_index()['FECHA_SINTOMAS'].max()]
ultima_fecha.head()
FECHA_SINTOMAS | CLAVE_MUNICIPIO_RES | MUNICIPIO_RES | Nuevos Casos | Defunciones | |
---|---|---|---|---|---|
159076 | 2022-01-23 | 01001 | AGUASCALIENTES | 10 | 0 |
159077 | 2022-01-23 | 01007 | RINCÓN DE ROMOS | 1 | 0 |
159078 | 2022-01-23 | 02001 | ENSENADA | 9 | 0 |
159079 | 2022-01-23 | 02002 | MEXICALI | 12 | 0 |
159080 | 2022-01-23 | 02004 | TIJUANA | 12 | 0 |
Tenemos la lista de los municipios que tuvieron casos en la fecha que estamos analizando, para hacer un mapa necesitamos unir estos datos a la geometría de los municipios.
Primero vamos a seleccionar, a partir del GeoDataFrame que ya tenemos sólo los municipios de la entidad que estamos analizando. A partir de eso podemos realizar una unión via la clave del municipio, sólo tenemos que tener cuiaddo de utilizar el tipo de unión adecuada para no dejar fuera los municipios sin casos.
casos_municipio = (municipios
.merge(ultima_fecha, left_on='municipio_cvegeo', right_on='CLAVE_MUNICIPIO_RES', how='left') # Unimos con los municipios
.drop(columns=['CLAVE_MUNICIPIO_RES', 'MUNICIPIO_RES']) # eliminamos dos columnas que ya no vamosd a usar
.fillna(0) # Los municipios sin casos deben tener 0 en lugar de NaN
)
casos_municipio
id | oid | municipio_cvegeo | municipio | pob2020 | pob_0a4 | pob_0a9 | pob60ym | entidad_cvegeo | geometry | FECHA_SINTOMAS | Nuevos Casos | Defunciones | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 827 | 525 | 16046 | Juárez | 15290 | 1557 | 3122 | 1911 | 16 | POLYGON ((-100.45693 19.33414, -100.45818 19.3... | 0 | 0.0 | 0.0 |
1 | 828 | 209 | 16047 | Jungapeo | 22358 | 2470 | 4920 | 2608 | 16 | POLYGON ((-100.44063 19.51413, -100.44814 19.5... | 0 | 0.0 | 0.0 |
2 | 829 | 564 | 16048 | Lagunillas | 5862 | 550 | 1111 | 844 | 16 | POLYGON ((-101.38329 19.59813, -101.38279 19.6... | 0 | 0.0 | 0.0 |
3 | 830 | 524 | 16049 | Madero | 18769 | 2049 | 4136 | 2055 | 16 | POLYGON ((-101.11644 19.53327, -101.11713 19.5... | 0 | 0.0 | 0.0 |
4 | 67 | 44 | 05035 | Torreón | 744247 | 65682 | 129805 | 85778 | 05 | MULTIPOLYGON (((-102.98871 24.79622, -102.9930... | 2022-01-23 00:00:00 | 13.0 | 0.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2452 | 2452 | 2388 | 32053 | Villa González Ortega | 13945 | 1481 | 2926 | 1599 | 32 | POLYGON ((-101.94821 22.65201, -101.95269 22.6... | 0 | 0.0 | 0.0 |
2453 | 2453 | 2417 | 32054 | Villa Hidalgo | 20177 | 2078 | 4191 | 2036 | 32 | POLYGON ((-101.65599 22.51381, -101.65651 22.5... | 0 | 0.0 | 0.0 |
2454 | 2454 | 1407 | 32055 | Villanueva | 31804 | 2738 | 5540 | 5324 | 32 | POLYGON ((-102.69428 22.62230, -102.69370 22.6... | 0 | 0.0 | 0.0 |
2455 | 2455 | 2411 | 32056 | Zacatecas | 155533 | 12609 | 25488 | 15549 | 32 | POLYGON ((-102.58542 22.81149, -102.58522 22.8... | 2022-01-23 00:00:00 | 67.0 | 0.0 |
2456 | 2457 | 2457 | 32058 | Santa María de la Paz | 2855 | 257 | 505 | 536 | 32 | POLYGON ((-103.19774 21.58534, -103.21258 21.5... | 0 | 0.0 | 0.0 |
2457 rows × 13 columns
fig, ax = plt.subplots(1, figsize=(20,10))
casos_municipio.plot(column='Nuevos Casos', cmap='OrRd',figsize=(15, 10), legend=True, ax=ax)
ax.set_axis_off()
fig.suptitle("Nuevos Casos por municipio") # A través de la función '.suptitle()' aplicada a la figura se coloca el título.
plt.show()
m = folium.Map(location=[19.4326018, -99.1332049], zoom_start=5) # Creamos la instancia de folium
folium.Choropleth( # Instanciamos un mapa de coropletas
geo_data=casos_municipio[["municipio_cvegeo", "Nuevos Casos", "geometry"]], # Pasamos la geometría de los municipios
data=casos_municipio[["municipio_cvegeo", "Nuevos Casos"]], # Las variables que vamos a usar en el mapa
columns=["municipio_cvegeo", "Nuevos Casos"], # La primera columna une geometrías y datos, la segunda es la variable que vamos a mapear
key_on="feature.properties.municipio_cvegeo", # Cómo se unen los datos
bins=4, # Cuántos intervalos iguales queremos en la clasificación
fill_color="OrRd", # La escala de colores
fill_opacity=0.7, # Opacidad del relleno
line_opacity=0.2, # opacidad de la línea
legend_name="Nuevos Casos",
).add_to(m)
m