Cuando surge la necesidad de explorar datos o generar visualizaciones relacionadas con entidades geográficas (el polígono de un país o puntos de coordenadas geográficas), resulta bastante razonable pensar en mapas. En ocasiones, puede resultar igualmente válido usar visualizaciones más sencillas como gráficos de barras o de líneas. En cada caso, el gráfico más adecuado dependerá de cuánta información y de con qué precisión se pretenda visualizar. Sin embargo, la representación de muchas categorías en gráficos de barras o de líneas complica su legibilidad; alternativamente, la representación sobre mapas puede facilitarla. Además, al representar entidades geográficas sobre planos geográficos se está incluyendo implícitamente cierta dimensión contextual o posicional de la entidad.
Normalmente, cuando se quieren representar gráficos para distintos valores de una variable categórica tendemos a generar varios gráficos estáticos, uno por cada una de las categorías existentes. Una alternativa sencilla e intuitiva puede ser generar gráficos que nos permitan filtrar o seleccionar las categorías de manera dinámica. La interactividad sobre gráficos (botones de radio, animaciones, etc.) puede ser una herramienta que facilite la exploración de conjuntos de datos, en especial cuando existen muchas categorías. También la representación de mapas puede incluir cierto grado de interactividad.
Algunas de las herramientas de visualización más conocidas que permiten representar mapas interactivos son PowerBI de Microsoft, Looker de Google y Tableau de Salesforce. El uso de alguna de ellas puede ser especialmente interesante en escenarios en los que ya se esté trabajando con ella y se necesiten generar mapas como parte de dashboards. En caso de que la necesidad se dé en otros contextos, o cuando sea requisito una mayor flexibilidad o exportabilidad, lo más adecuado podría ser usar alguna librería de graficación dinámica. Python y R son dos lenguajes de uso común en estos casos. En este post, se exploran algunas librerías de Python.
Conjuntos de datos
Para ilustrar el uso de las librerías se echará mano de dos conjuntos de datos abiertos:
- Datos de situación de las estaciones pluviométricas de la AEMET, que se usará para la representación de puntos de coordenadas. Tras leer el archivo .dbf solo será necesario convertir las coordenadas geográficas de EPSG:25830 (sinónimo de ETRS89) a EPSG:4326 con Transformer del paquete
pyproj
.
- Datos del parque de vehículos de Mallorca entre 2013 y 2020 del Observatorio de Turismo Sostenible del Consell de Mallorca, que se utilizará para la representación de datos municipales. Se ha transformado el conjunto de datos para poder representar la cantidad de vehículos matriculados por cada 1000 habitantes, desglosados por tipo de vehículo. Se han renombrado y seleccionado campos para que tenga la siguiente estructura:
Cuando sea necesario, se utilizará esta representación en .json (GeoJson) de polígonos de términos municipales de Mallorca. Es importante que exista un identificador común entre el conjunto de datos y el archivo GeoJson. A falta de éste, se ha utilizado la cadena de caracteres del nombre de los municipios, homogeneizado para ambas fuentes.
Visualización interactiva de mapas: librerías
Altair
Altair es una librería declarativa de visualización de gráficos basada en Vega y Vega-Lite (de JS), que a su vez están basadas en D3.js.
Por lo que respecta a la representación de datos sobre mapas, todavía permite una variedad muy limitada de interacciones, básicamente, la inclusión de globos (tooltips). Aunque, de manera interesante, permite asociar mapas cruzados con otro tipo de gráficos como en el ejemplo siguiente adaptado de la galería oficial de la librería:
Como a fecha de escritura de este post Altair no permite añadir opciones interactivas para filtro o selección de categorías (o, por lo menos, este autor no ha podido encontrarlas) para la representación del parque de vehículos, sería necesario generar un mapa distinto por categoría. Por brevedad, a continuación se genera sólo un gráfico por tipo de vehículo con datos del año 2013.
Altair permite exportar todo tipo de gráficos en formato html con el método Chart.save()
, pero no guarda los datos GeoJson de los mapas base, con lo cual parece que sólo es posible exportar capturas en .png o .svg. También cabe decir que es compatible con algunos paquetes de dashboarding como Dash
.
Plotly
Otra opción a tener en cuenta es Plotly, otra librería declarativa de visualización de gráficos basada en la librería homónima de JavaScript. Al igual que Altair, esta librería por debajo está basada en D3.js, aunque también toma ciertos elementos de Stack.gl.
Plotly permite representar mapas con interacciones similares a las que ofrece Altair, con acceso, además, a mapas de Carto y OpenStreet a través de la API de mapbox.
Plotly permite exportar gráficos interactivos, mapas incluidos:
Para poder utilizar algunas de las herramientas de mapbox, es necesario crear una cuenta para obtener un token de acceso, que tiene un tier gratuito de unas 100,000 peticiones.
Anteriormente, se ha mencionado Dash. Dash es, básicamente, un marco para la construcción de aplicaciones web, basado en Flask, Plotly.js y React.js, que se utiliza mucho para visualización de datos con Python. Como servidor web, Flask es una aplicación WSGI por detrás y, por tanto, síncrona por defecto. Aunque Flask no es stateless, Dash sí se diseñó para serlo.
El mapa interactivo siguiente, integrado con Dash, incluye interacciones extra que permiten explorar las cifras de distintos vehículos a lo largo de los años.
Bokeh
Bokeh es una librería de Python que permite crear visualizaciones interactivas de JS y que no está basada en D3.js. Si Plotly se integraba con Dash, que incluye un servidor web Flask, Bokeh se sirve de un servidor web Tornado, que usa WebSockets por detrás. WebSockets es stateful y asíncrono.
Para la visualización de la situación de los pluviómetros, esta vez se transformarán las coordenadas a UTM:
Para representar el parque de coches de Mallorca, será necesario juntar el DataFrame con el archivo GeoJson previamente transformado a GeoDataFrame. Además, Bokeh no puede tratar con columnas de tipo MULTIPOLYGON, así que se hace necesario generar dos columnas de listas de latitudes y longitudes, que luego se transformarán a latitud y longitud en sistema UTM.
Es necesario efectuar transformaciones adicionales sobre los datos de coordenadas para representar la información. Véase el anexo al final de este post para el detalle del preprocesamiento.
Bokeh ofrece distintas opciones de visualización de sus gráficos:
- Usando
output_notebook()
se genera en el propio notebook. - También puede ejecutarse
bokeh serve myfile.py
desde la CLI para abrir un puerto en el que se visualice el gráfico. La implementación es ligeramente distinta y deben programarse otras funciones callback si se pretenden representar datos en streaming. - También es posible insertarlos como parte de una página web.
Características a tener en cuenta
- Altair destaca por su simpleza, pero el grado de interactividad es todavía muy limitado para mapas.
- Bokeh es un poco más complejo que Plotly.
- En cuanto a aspectos de visualización e interacciones:
- Plotly se considera visualmente más atractivo, con más tipos de visualizaciones y una comunidad más grande.
- Bokeh permite más flexibilidad en la implementación de las interacciones.
- Por las características de su servidor web:
- Plotly se integra con Dash (stateless y síncrono) a través de un framework de alto nivel.
- La integración del servidor web con Bokeh está más estrechamente integrada con la librería misma. Su naturaleza stateful le permite agilizar las interacciones cuando se requieren procesos costosos sobre los datos, aunque los estados intermedios requerirán memoria extra.
- En ambos casos las aplicaciones pueden integrarse con tecnologías cloud. Por ejemplo, Plotly con AWS y Bokeh con GCP.
- Respecto de las estructuras de datos que necesitan:
- Plotly con Dash tiene una mayor compatibilidad con pandas, lo cual la convierte en una buena alternativa si se quiere hacer análisis exploratorio de manera ágil.
- Bokeh requiere un solo conjunto de datos en un objeto
ColumnDataSource
, menos flexible que un DataFrame de pandas pero potencialmente reutilizable en gráficos cruzados de manera interactiva.
- En cuanto al volumen de datos:
- Plotly y Bokeh por sí solas pueden ser útiles para representar sobre mapas conjuntos pequeños de datos.
- Bokeh es más ágil que Plotly con conjuntos de datos más grandes.
- Ambas librerías soportan datos en streaming, aunque curiosamente hay más información al respecto para Bokeh.
- De acuerdo con Van Der Donckt et al. (2022), Plotly y Bokeh no escalan bien comparados con librerías no interactivas (existe un trade-off entre escalabilidad e interactividad). Para volúmenes masivos, tanto Plotly como Bokeh ofrecen integración con cudf.pandas (una librería de Python que emula pandas para GPUs de NVIDIA utilizando tecnologías del entorno RAPIDS) que puede ayudar a paliar esta limitación.
Conclusión
En este post, se han explorado varias librerías de Python para la representación de mapas interactivos. Como casi siempre en ciencia y arquitectura de datos, la opción óptima dependerá de las necesidades del caso de uso.
- Altair puede ser una opción en casos de uso muy simples, pero en la mayoría de los casos Plotly y Bokeh son opciones preferibles.
- Plotly es algo más fácil y está más extendido que Bokeh, así que puede representar una opción por defecto, especialmente con conjuntos de datos pequeños o medianos con pocas transformaciones.
- Bokeh es más flexible en la implementación de las interacciones y está más optimizado para trabajar con conjuntos de datos más grandes o que requieren transformaciones complejas.
- Cuando el caso lo requiera, debe estudiarse bien cómo alojar una aplicación concreta de Plotly/Dash o Bokeh en un proveedor cloud y valorar si una visualización interactiva va a escalar adecuadamente antes de implementarla o desplegarla.
Anexo (transformación de datos para Bokeh)
Para el tratamiento de los tipos MULTIPOLYGON, se han adaptado funciones de este curso de introducción a Sistemas de Información Geográfica con Python.
Para la transformación a coordenadas UTM se ha adaptado la función transform_to_mercator
para que funcione sobre columnas de listas. Finalmente, se ha particionado el DataFrame filtrando por año y vehículo y se han metido las particiones en un diccionario.