Visualización interactiva de datos con Python usando Bokeh

Recientemente encontré ésta biblioteca, aprendí un poco sobre ella, la probé, por supuesto, y decidí compartir mis pensamientos.

Desde el sitio web oficial: “Bokeh es una biblioteca de visualización interactiva que se dirige a los navegadores web modernos para la presentación. Su objetivo es proporcionar una construcción elegante y concisa de gráficos versátiles, y extender esta capacidad con interactividad de alto rendimiento en conjuntos de datos muy grandes o de transmisión por secuencias. Bokeh puede ayudar a cualquier persona que quiera crear parcelas interactivas, paneles y aplicaciones de datos de forma rápida y sencilla.” Creo que la explicación es bastante clara, pero sería mejor verlo en acción, ¿no?

Antes de comenzar, asegúrese de tener Bokeh instalado en tu entorno; si no lo tienes, siga las instrucciones de instalación aquí.

Creé algún tipo de estudio de caso para mí mismo. Decidió visualizar los cambios en las emisiones de CO2 en el tiempo y en correlación con el PIB (y comprobar si existe esa correlación, porque nunca se sabe 😐 ).

Así que tomé dos archivos: uno con emisiones de CO2 de Gapminder.org y otro del curso en DataCamp (porque ese archivo ya estaba preprocesado 😀 yeeeeees, soy un bastardo perezoso 😀 ). También puedes descargar estos archivos desde aquí.

¿Cómo empezamos a analizar los datos? Correcto, importando los paquetes necesarios e importando los datos mismos (muy importante :D). Luego realizamos un análisis exploratorio de datos (EDA, por sus siglas en inglés) para comprender con qué estamos trabajando y después de eso, limpiar y transformar los datos en el formato necesario para el análisis. Muy claro. Como el artículo no se centra en estos pasos, solo insertaré el código a continuación con todas las transformaciones que he realizado.

import pandas as pd
import numpy as np

# Data cleaning and preparation
data = pd.read_csv('data/co2_emissions_tonnes_per_person.csv')
data.head()

gapminder = pd.read_csv('data/gapminder_tidy.csv')
gapminder.head()

df = gapminder[['Country', 'region']].drop_duplicates()
data_with_regions = pd.merge(data, df, left_on='country', right_on='Country', how='inner')
data_with_regions = data_with_regions.drop('Country', axis='columns')
data_with_regions.head()

new_df = pd.melt(data_with_regions, id_vars=['country', 'region'])
new_df.head()

columns = ['country', 'region', 'year', 'co2']
new_df.columns = columns

upd_new_df = new_df[new_df['year'].astype('int64') > 1963]
upd_new_df.info()
upd_new_df = upd_new_df.sort_values(by=['country', 'year'])
upd_new_df['year'] = upd_new_df['year'].astype('int64')

df_gdp = gapminder[['Country', 'Year', 'gdp']]
df_gdp.columns = ['country', 'year', 'gdp']
df_gdp.info()

final_df = pd.merge(upd_new_df, df_gdp, on=['country', 'year'], how='left')
final_df = final_df.dropna()
final_df.head()

np_co2 = np.array(final_df['co2'])
np_gdp = np.array(final_df['gdp'])
np.corrcoef(np_co2, np_gdp)

Por cierto, las emisiones de CO2 y el PIB se correlacionan, y de manera bastante significativa, de 0,78.

np.corrcoef(np_co2, np_gdp)
Out[138]:
array([[1. , 0.78219731],
[0.78219731, 1. ]])

Y ahora vamos a la parte de la visualización. Nuevamente, comenzamos con las importaciones necesarias. Les explicaré todos durante el articulo. Ahora, solo relájate e importa.

from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, CategoricalColorMapper, Slider
from bokeh.palettes import Spectral6
from bokeh.layouts import widgetbox, row

Comenzaremos con una preparación de diferentes detalles para nuestra aplicación de visualización interactiva. Primero, creamos un mapeador de colores para diferentes regiones del mundo, por lo que cada país tendrá un color diferente dependiendo de la región en la que se encuentre. Seleccionamos regiones únicas y las convertimos en una lista. Luego usamos CategoricalColorMapper para asignar un color diferente para cada región.

regions_list = final_df.region.unique().tolist()
color_mapper = CategoricalColorMapper(factors=regions_list, palette=Spectral6)

A continuación, prepararemos una fuente de datos para nuestra aplicación. Bokeh acepta una gran cantidad de diferentes tipos de datos como fuente de gráficos y elementos visuales: proporciona datos directamente utilizando listas de valores, marcos de datos y series de pandas, matrices de números y así sucesivamente. Pero el núcleo de la mayoría de los gráficos de Bokeh es ColumnDataSource.

En el nivel más básico, un objeto ColumnDataSource es simplemente una asignación entre los nombres de las columnas y las listas de datos. ColumnDataSource toma un parámetro data que es un dict, con nombres de columna de cadena como claves y listas (o matrices) de valores de datos como valores. Si se pasa un argumento posicional al inicializador ColumnDataSource, se tomará como data. (desde el sitio web oficial).

# Make the ColumnDataSource: source
source = ColumnDataSource(data={
'x': final_df.gdp[final_df['year'] == 1964],
'y': final_df.co2[final_df['year'] == 1964],
'country': final_df.country[final_df['year'] == 1964],
'region': final_df.region[final_df['year'] == 1964],
})

Comenzamos con una muestra de nuestros datos solo por un año. Básicamente creamos un diccionario de valores para x, y, país y región.

El siguiente paso es establecer límites para nuestros ejes. Podemos hacerlo encontrando valores mínimos y máximos para x e y.

# Save the minimum and maximum values of the gdp column: xmin, xmax
xmin, xmax = min(final_df.gdp), max(final_df.gdp)

# Save the minimum and maximum values of the co2 column: ymin, ymax
ymin, ymax = min(final_df.co2), max(final_df.co2)

Después de eso creamos nuestra figura, donde colocaremos todos nuestros objetos de visualización. Le damos un título, ajuste el ancho y la altura y también establecemos los ejes. (El eje Y está configurado del tipo 'log' solo para una mejor vista; se probaron unos tipos y este dio el mejor resultado)

# Create the figure: plot
plot = figure(title='Gapminder Data for 1964',
plot_height=600, plot_width=1000,
x_range=(xmin, xmax),
y_range=(ymin, ymax), y_axis_type='log')

Bokeh usa una definición de glifo para todas las formas visuales que pueden aparecer en la trama. La lista completa de glifos incorporados en Bokeh se presenta a continuación (sin inventar nada, toda la información de la página oficial):

Todos estos glifos comparten una interfaz mínima común a través de su clase base Glyph

No vamos a profundizar demasiado con todas estas formas y usaremos los círculos como una de las más básicas. Si deseas jugar más con otros glifos, tienes toda la documentación y los enlaces necesarios.

# Add circle glyphs to the plot
plot.circle(x='x', y='y', fill_alpha=0.8, source=source, legend='region',
color=dict(field='region', transform=color_mapper),
size=7)

Entonces, ¿cómo añadimos estos círculos? Asignamos nuestra fuente al parámetro “source” del glifo de círculo, especificamos datos para x e y, agregamos leyenda para los colores y aplicamos ColorMapper creado previamente al parámetro “color”, “fill_alpha” establece un poco de transparencia y “Tamaño” es el tamaño de los círculos que aparecerán en la trama.

A continuación, agregamos afinar el aspecto de nuestra trama configurando la ubicación de la leyenda y dando algunas explicaciones a nuestros ejes.

# Set the legend.location attribute of the plot
plot.legend.location = 'bottom_right'

# Set the x-axis label
plot.xaxis.axis_label = 'Income per person (Gross domestic product per person adjusted for differences in '
purchasing power in international dollars, fixed 2011 prices, PPP based on 2011 ICP)'

# Set the y-axis label
plot.yaxis.axis_label = 'CO2 emissions (tonnes per person)'

A partir de ahora tenemos un gráfico básico y estático para el año 1964, pero el título del artículo tiene una palabra que no encaja con esta situación – “Interactiva” O_O. ¡Así que vamos a añadir un poco de interactividad!

Para ello, agregaremos un slider con años, por lo que al final tendremos una visualización para cada año disponible. ¡Guay! ¿no es así?

Anteriormente importábamos la clase Slider, ¡ahora es el momento de usarlo! Así que creamos el objeto de esta clase, siendo el año mínimo, final, máximo, valor predeterminado – año mínimo nuevamente, paso (qué tan rápido están cambiando los valores en el slider) – 1 año y el título.

También creamos un callback para cualquier cambio que ocurra en este slider. Los callbacks en Bokeh siempre tienen los mismos parámetros de entrada: attr, old, new. Vamos a actualizar nuestra fuente de datos en función del valor del slider. Así que creamos un nuevo diccionario que corresponderá al año del control deslizante y, a partir de esto, actualizamos nuestra trama. También actualizamos el título en consecuencia.

# Make a slider object: slider
slider = Slider(start=min(final_df.year), end=max(final_df.year), step=1, value=min(final_df.year), title='Year')


def update_plot(attr, old, new):
# set the `yr` name to `slider.value` and `source.data = new_data`
yr = slider.value

new_data = {
'x': final_df.gdp[final_df['year'] == yr],
'y': final_df.co2[final_df['year'] == yr],
'country': final_df.country[final_df['year'] == yr],
'region': final_df.region[final_df['year'] == yr],
}
source.data = new_data

# Add title to figure: plot.title.text
plot.title.text = 'Gapminder data for %d' % yr


# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)

Con esta cantidad de puntos de datos, la trama se desordena muy rápidamente. Así que para agregar más claridad a cada pequeño círculo que se presentará aquí, decidí incluir HoverTool en esta figura.

# Create a HoverTool: hover
hover = HoverTool(tooltips=[('Country', '@country'), ('GDP', '@x'), ('CO2 emission', '@y')])

# Add the HoverTool to the plot
plot.add_tools(hover)

HoverTool acepta una lista de tuplas con el primer valor como etiqueta y el segundo valor de detalle del origen de datos.

Lo hemos hecho con todos los componentes de nuestra pequeña aplicación, solo unas pocas líneas finales de código para crear un diseño y agregarlo al documento actual.

# Make a row layout of widgetbox(slider) and plot and add it to the current document
layout = row(widgetbox(slider), plot)
curdoc().add_root(layout)

¡Y hemos terminado! ¡Felicidades! Ejecutamos este código y … Nada. No hay errores (o tal vez algunos errores, pero luego los arreglas y no hay errores) y no hay aplicación, y no hay visualización O_o. ¿Por qué demonios pasé todo ese tiempo para crear una trama genial y no obtengo nada? Ni siquiera la explicación de lo que hice mal?

Ese fue mi primer pensamiento cuando intenté ejecutar la aplicación. Pero luego recordé un truco en el que primero tienes que iniciar un servidor que será un servidor para esta visualización.

Entonces, lo siguiente y lo último que debe hacer es ejecutar el código siguiente desde su línea de comando:

bokeh serve --show my_python_file.py

Y la visualización se abrirá automáticamente en una nueva pestaña del navegador.

A pesar de ser el más popular, matplotlib no es la herramienta de visualización de datos más fácil de usar y tiene sus propias limitaciones, yyyyyy… realmente no me gusta. Así que Bokeh es una solución posible si perteneces a la misma cohorte de personas que yo. Pruébalo y déjame saber qué piensas.

¡Gracias por tu atención, espero que esta pequeña introducción a Bokeh sea útil y que tengas un gran día! (o noche si estás leyendo esto antes de ir a la cama: D)

P.S. También quiero probar plotly, he visto muchos comentarios positivos.

P.S.S. Código en Github.


Foto de Yosh Ginsu en Unsplash

Karma +1 when you share it:

Leave a Reply

Your email address will not be published. Required fields are marked *