Curso

En este tutorial conocerás dos paquetes muy populares para trabajar con datos geoespaciales: geopandas y Shapely. Después aplicarás ambos paquetes para leer datos geoespaciales con Python y representar la trayectoria del huracán Florence del 30 de agosto al 18 de septiembre.
¿Qué son los datos geoespaciales?
Datos espaciales, datos geoespaciales, datos GIS o geodatos son nombres para datos numéricos que identifican la ubicación geográfica de un objeto físico, como un edificio, una calle, un pueblo, una ciudad, un país, etc., según un sistema de coordenadas geográficas. A partir de los datos espaciales puedes conocer no solo la ubicación, sino también la longitud, el tamaño, el área o la forma de cualquier objeto. Un ejemplo de dato espacial que puedes obtener son las coordenadas de un objeto: latitud, longitud y altitud. Se pueden usar Sistemas de Información Geográfica (GIS) u otras aplicaciones especializadas para acceder, visualizar, manipular y analizar datos geoespaciales.
¿Por qué datos geoespaciales?
Los datos geoespaciales tienen infinidad de aplicaciones en el día a día. Un ejemplo es la app de Mapas que usas para ir de un lugar a otro. Otro que seguramente ves a menudo es el parte meteorológico.

Sí, los datos geoespaciales se usan para representar la posición de algo en relación con lo que lo rodea: tu casa en el mapa de la ciudad, el huracán en el mapa del mundo, etc. En este tutorial, vas a echar un vistazo al destructivo huracán Florence y seguir su trayectoria.
Requisitos de paquetes
No pases de largo. Esta parte es importante. Si no estás seguro de cumplir los requisitos, revisa de nuevo. Ante todo, necesitarás tener instalados todos los paquetes de abajo.
- Pandas: estructuras de datos y herramientas de análisis
- Numpy: paquete fundamental para computación científica con Python
- SciPy: (se pronuncia «sai pai») ecosistema en Python de software open source para matemáticas, ciencia e ingeniería
- RTree: envoltorio ctypes en Python de libspatialindex que ofrece indexación espacial avanzada
- GDAL: biblioteca traductora para formatos geoespaciales ráster y vectoriales
- Fiona: lectura y escritura de ficheros de datos espaciales
- Shapely: objetos geométricos, predicados y operaciones
- GeoPandas: amplía los tipos de datos de pandas para permitir operaciones espaciales sobre tipos geométricos
- PySAL: librería de funciones de análisis espacial en Python para construir aplicaciones de alto nivel
- Matplotlib: librería de gráficos 2D en Python
- Missingno: módulo de visualización de datos ausentes para Python
Instala todos los paquetes en el orden anterior para asegurarte de que todo funciona. Algunos son prerequisitos de otros; por ejemplo, para instalar GeoPandas necesitas Shapely, y para instalar Shapely deben estar instalados RTree, GDAL y Fiona. La forma más sencilla de instalar un paquete es: pip install PACKAGE_NAME
Si usas Windows y no puedes instalar el paquete así, consulta este sitio web para descargar los paquetes correspondientes y ejecuta pip install PATH_TO_PACKAGE.
Sobre los datos
Como sabrás, el temible huracán Florence cruzó parte de la costa este de Estados Unidos y dejó unos daños estimados en 17 mil millones de USD. Este tutorial te ayudará a descubrir de dónde vino, cuándo y dónde se intensificó, entender mejor este fenómeno y analizarlo en Python. Hay muchos sitios que ofrecen información sobre este huracán. Por ejemplo, este sitio web ofrece datos de varias tormentas y huracanes en EE. UU. desde 1902 hasta 2018, así que tendrás un montón de datos con los que trabajar más adelante. Para este tutorial, usarás solo los datos del huracán Florence
También usarás los datos geoespaciales del mapa de EE. UU. disponibles en internet. La entrada de blog de Eric Celeste recopila todo tipo de ficheros de límites de condados y estados de EE. UU. En este tutorial se usa el fichero GeoJSON «US States, 5m».
# Load all importance packages
import geopandas
import numpy as np
import pandas as pd
from shapely.geometry import Point
import missingno as msn
import seaborn as sns
import matplotlib.pyplot as plt
% matplotlib inline
Primero, veamos el primer geodataframe: geodatos de los estados de EE. UU.
# Getting to know GEOJSON file:
country = geopandas.read_file("data/gz_2010_us_040_00_5m.json")
country.head()

Si compruebas el tipo de dataframe que acabas de cargar, verás que es un GeoDataFrame, que mantiene todas las características habituales de un DataFrame de pandas.
type(country)
geopandas.geodataframe.GeoDataFrame
Al comprobar el tipo de datos de la columna que contiene las coordenadas, verás que es una GeoSeries.
type(country.geometry)
geopandas.geoseries.GeoSeries
Cada valor de la GeoSeries es un objeto de Shapely. Puede ser:
- Point
- Line
- Polygon
- MultiPolygon
Cada objeto se usa para un tipo distinto de entidad física: Point para un edificio, Line para una calle, Polygon para una ciudad y MultiPolygon para un país con múltiples polígonos. Para más información sobre cada objeto geométrico, lee este artículo: https://shapely.readthedocs.io/en/stable/manual.html#geometric-objects
type(country.geometry[0])
shapely.geometry.multipolygon.MultiPolygon
Igual que un DataFrame de pandas, un GeoDataFrame también tiene el atributo plot, que aprovecha la geometría dentro del dataframe para dibujar un mapa:
country.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x1cfe68c1358>

Como ves, el mapa de EE. UU. queda relativamente pequeño respecto al lienzo. Es porque la información incluye Alaska, Hawái y Puerto Rico, que aparecen dispersos. Para este tutorial, puedes excluir Alaska y Hawái, ya que el huracán no se acercó a esos estados. Además, puedes ajustar el tamaño de la figura y el color para personalizar tu gráfico:
# Exclude Alaska and Hawaii for now
country[country['NAME'].isin(['Alaska','Hawaii']) == False].plot(figsize=(30,20), color='#3B3C6E');

¡Nada mal! Ya tienes el mapa de EE. UU.; ahora carguemos los datos del huracán:
florence = pd.read_csv('data/florence.csv')
florence.head()

Análisis exploratorio de datos
Esto es lo primero que debes hacer al cargar cualquier conjunto de datos:
- Revisar la información y los tipos de datos
- Detectar valores ausentes
- Explorar estadísticas básicas
florence.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 105 entries, 0 to 104
Data columns (total 11 columns):
AdvisoryNumber 105 non-null object
Date 105 non-null object
Lat 105 non-null float64
Long 105 non-null float64
Wind 105 non-null int64
Pres 105 non-null int64
Movement 105 non-null object
Type 105 non-null object
Name 105 non-null object
Received 105 non-null object
Forecaster 104 non-null object
dtypes: float64(2), int64(2), object(7)
memory usage: 9.1+ KB
Comprobación de valores ausentes con el paquete missingno. Es muy útil para visualizar datos faltantes. Como ves abajo, solo hay un valor ausente en la columna «Forecaster», que no necesitas para este tutorial. Puedes ignorarlo por ahora.
# Notice you can always adjust the color of the visualization
msn.bar(florence, color='darkolivegreen');

Échale un ojo a algunas estadísticas; pueden ser muy útiles, como la velocidad media del viento, el máximo y el mínimo de este huracán, etc.
# Statistical information
florence.describe()
| Lat | Long | Wind | Pres | |
|---|---|---|---|---|
| count | 105.000000 | 105.000000 | 105.000000 | 105.000000 |
| mean | 25.931429 | 56.938095 | 74.428571 | 981.571429 |
| std | 7.975917 | 20.878865 | 36.560765 | 22.780667 |
| min | 12.900000 | 18.400000 | 25.000000 | 939.000000 |
| 25% | 18.900000 | 41.000000 | 40.000000 | 956.000000 |
| 50% | 25.100000 | 60.000000 | 70.000000 | 989.000000 |
| 75% | 33.600000 | 76.400000 | 105.000000 | 1002.000000 |
| max | 42.600000 | 82.900000 | 140.000000 | 1008.000000 |
En la mayoría de los casos tendrás que limpiar los datos y quedarte con lo necesario. Aquí solo necesitas el tiempo, las coordenadas (latitud y longitud), la velocidad del viento, la presión y el nombre. Movement y Type son opcionales; el resto se puede eliminar.
# dropping all unused features:
florence = florence.drop(['AdvisoryNumber', 'Forecaster', 'Received'], axis=1)
florence.head()

Normalmente, si representas los datos por sí solos, no necesitas prestar más atención a las coordenadas. Sin embargo, si quieres que se vean como en el mapa, es importante revisar la longitud y la latitud. Aquí la longitud es oeste, así que tendrás que añadir un «-» delante del número para trazar correctamente los puntos:
# Add "-" in front of the number to correctly plot the data:
florence['Long'] = 0 - florence['Long']
florence.head()

Después puedes combinar latitud y longitud para crear las coordenadas del huracán, que convertirás en GeoPoint para visualizarlas.
# Combining Lattitude and Longitude to create hurricane coordinates:
florence['coordinates'] = florence[['Long', 'Lat']].values.tolist()
florence.head()

# Change the coordinates to a geoPoint
florence['coordinates'] = florence['coordinates'].apply(Point)
florence.head()

Comprobando el tipo del dataframe florence y de la columna coordinates de florence. Son un DataFrame de pandas y una Series de pandas.
type(florence)
pandas.core.frame.DataFrame
type(florence['coordinates'])
pandas.core.series.Series
Tras convertir los datos en geoespaciales, volvemos a comprobar el tipo del dataframe florence y de la columna coordinates. Ahora son GeoDataFrame y GeoSeries.
# Convert the count df to geodf
florence = geopandas.GeoDataFrame(florence, geometry='coordinates')
florence.head()

type(florence)
geopandas.geodataframe.GeoDataFrame
type(florence['coordinates'])
geopandas.geoseries.GeoSeries
Fíjate en que, aunque ahora son GeoDataFrame y GeoSeries, siguen comportándose como un DataFrame y una Series normales. Puedes seguir filtrando, agrupando por columnas o calculando mínimos, máximos o medias.
# Filtering from before the hurricane was named.
florence[florence['Name']=='Six']

# Groupping by name to see how many names it has in the data set:
florence.groupby('Name').Type.count()
Name
FLORENCE 6
Florence 85
SIX 4
Six 10
Name: Type, dtype: int64
Calculando la velocidad media del viento del huracán Florence:
print("Mean wind speed of Hurricane Florence is {} mph and it can go up to {} mph maximum".format(round(florence.Wind.mean(),4),
florence.Wind.max()))
Mean wind speed of Hurricane Florence is 74.4286 mph and it can go up to 140 mph maximum
Así, la velocidad media del viento del huracán Florence es de 74,43 millas por hora (119,78 km/h) y la máxima de 140 millas por hora (225,308 km/h). Para hacerte una idea de lo que significa, la escala de Beaufort, desarrollada por la Marina Real británica, muestra cómo se manifiesta el viento en el agua y en tierra. Con velocidades de 48 a 55 millas por hora ya puede romper y arrancar árboles y causar «daños estructurales considerables».

No querrías estar ahí en ese momento.
Visualización
Igual que un DataFrame de pandas, un GeoDataFrame también tiene el atributo .plot. Sin embargo, este atributo usa las coordenadas del GeoDataFrame para situarlas en el mapa. Veámoslo:
florence.plot(figsize=(20,10));

¿Qué ha pasado? Solo ves un montón de puntos sin referencia. ¿Hay algo mal?
No, todo está bien. Como este dataframe solo tiene la información de coordenadas (ubicación) del huracán Florence en cada instante, lo único que podemos dibujar es su posición sobre un lienzo en blanco.
El siguiente paso es situar la posición del huracán sobre el mapa de EE. UU. para ver dónde impactó y con qué intensidad en cada momento. Para ello, usarás las coordenadas del mapa de EE. UU. (los datos que cargamos al principio) como base y representarás encima las posiciones del huracán Florence.
# Plotting to see the hurricane overlay the US map:
fig, ax = plt.subplots(1, figsize=(30,20))
base = country[country['NAME'].isin(['Alaska','Hawaii']) == False].plot(ax=ax, color='#3B3C6E')
# plotting the hurricane position on top with red color to stand out:
florence.plot(ax=base, color='darkred', marker="*", markersize=10);

¡Tiene buena pinta! Ahora, terminemos añadiendo más detalles, como:
- Añadir título
- Colorear la posición del huracán según la velocidad del viento para ver la intensidad en cada ciudad
- Quitar ejes
- Añadir leyenda
- Guardar el resultado como imagen para usarlo después
fig, ax = plt.subplots(1, figsize=(20,20))
base = country[country['NAME'].isin(['Alaska','Hawaii']) == False].plot(ax=ax, color='#3B3C6E')
florence.plot(ax=base, column='Wind', marker="<", markersize=10, cmap='cool', label="Wind speed(mph)")
_ = ax.axis('off')
plt.legend()
ax.set_title("Hurricane Florence in US Map", fontsize=25)
plt.savefig('Hurricane_footage.png',bbox_inches='tight');

El huracán fue más intenso en alta mar, cerca de la costa este. Al acercarse a tierra empezó a perder fuerza, pero con vientos entre 60 y 77 millas por hora aún puede causar daños terribles.
Conclusión
¡Buen trabajo! Has aprendido los pasos básicos para trabajar con datos geoespaciales en Python. También has visto cómo representarlos y cómo personalizar la forma, el color y las superposiciones para contar una historia. Si quieres practicar, hay muchísimos datos geoespaciales disponibles online para que te atrevas con ellos. Un ejemplo es el sitio del tiempo que te hemos dejado arriba.
Si buscas referencias, échale un vistazo a este libro: «Python Geospatial Development Essentials» de Karim Bahgat (2015), para profundizar más.
Si quieres ponerte en contacto conmigo, puedes escribirme a dqvu.ubc@gmail.com o conectar en LinkedIn. ¡A disfrutar aprendiendo y cuídate!
Si te gustaría aprender más sobre datos geoespaciales en Python, haz el curso de DataCamp Visualizing Geospatial Data in Python.
Debajo tienes los datos usados en este tutorial:
