tutoriales.com

Visualizando Series Temporales Interactivas con Bokeh en Python: El Clima de tu Ciudad

Este tutorial te guiará paso a paso en la creación de visualizaciones interactivas de series temporales utilizando la biblioteca Bokeh en Python. Aprenderás a cargar datos, preprocesarlos y diseñar gráficos dinámicos para explorar patrones y tendencias en datos climáticos reales.

Intermedio15 min de lectura7 views
Reportar error

¡Bienvenido a este tutorial sobre visualización interactiva de series temporales con Bokeh! 📈 En el mundo de la Ciencia de Datos, comprender la evolución de los fenómenos a lo largo del tiempo es crucial. Las series temporales nos permiten observar patrones, tendencias, estacionalidades y anomalías, proporcionando una visión profunda sobre el comportamiento de sistemas complejos.

En esta guía, nos centraremos en los datos climáticos, un ejemplo perfecto de series temporales, para ilustrar cómo Bokeh puede transformar datos estáticos en experiencias interactivas y explorables. Prepárate para darle vida a tus datos con herramientas poderosas y visualizaciones impactantes.

🎯 ¿Por qué Bokeh para Series Temporales?

Bokeh es una biblioteca de visualización interactiva para navegadores web modernos. A diferencia de otras bibliotecas más orientadas a gráficos estáticos, Bokeh se especializa en crear visualizaciones ricas e interactivas que pueden ser incrustadas en dashboards, aplicaciones web o simplemente exploradas en un Jupyter Notebook. Su capacidad para manejar grandes conjuntos de datos y su enfoque en la interactividad la hacen ideal para el análisis exploratorio de series temporales.

💡 Consejo: La interactividad es clave para el análisis de series temporales. Poder hacer zoom, panear, seleccionar rangos de fechas y ver tooltips detallados mejora enormemente la experiencia de exploración de datos.

🛠️ Herramientas Necesarias

Antes de sumergirnos en el código, asegúrate de tener las siguientes herramientas instaladas en tu entorno Python:

  • Python 3.x: La base de nuestro proyecto.
  • Jupyter Notebook o JupyterLab: Para ejecutar el código de forma interactiva.
  • Pandas: Para la manipulación y preprocesamiento de datos.
  • Bokeh: La estrella de nuestro show para la visualización.

Si aún no las tienes, puedes instalarlas fácilmente usando pip:

pip install pandas bokeh jupyterlab

📊 Preparación de los Datos: El Clima de tu Ciudad

Para este tutorial, utilizaremos un conjunto de datos simulado de temperaturas y precipitaciones diarias de una ciudad imaginaria. El objetivo es simular la estructura de datos que encontrarías con datos reales de estaciones meteorológicas.

💾 Carga y Exploración Inicial de Datos

Primero, vamos a crear un DataFrame de Pandas con algunos datos de ejemplo. En un escenario real, cargarías un archivo CSV o de otro formato.

import pandas as pd
import numpy as np

# Generar datos de ejemplo
dates = pd.date_range(start='2022-01-01', end='2023-12-31', freq='D')
temps_mean = 15 + 10 * np.sin(np.linspace(0, 3 * np.pi, len(dates))) + np.random.randn(len(dates)) * 3
temps_min = temps_mean - np.random.rand(len(dates)) * 5 - 2
temps_max = temps_mean + np.random.rand(len(dates)) * 5 + 2
precip = np.random.rand(len(dates)) * 10
precip[precip < 2] = 0 # Muchos días sin lluvia

data = pd.DataFrame({
    'date': dates,
    'temp_min': temps_min,
    'temp_max': temps_max,
    'temp_avg': temps_mean,
    'precipitation': precip
})

# Asegurar que 'date' es un tipo de dato datetime
data['date'] = pd.to_datetime(data['date'])

print(data.head())
print(data.info())

Observarás la estructura de los datos: una columna de fecha y varias columnas numéricas que representan diferentes métricas climáticas. Es crucial que la columna de fecha sea de tipo datetime para que Bokeh la interprete correctamente.

🧹 Preprocesamiento de Datos

Aunque nuestros datos de ejemplo son bastante limpios, en un escenario real, podrías necesitar:

  • Manejar valores faltantes (NaN).
  • Convertir unidades (ej. Celsius a Fahrenheit).
  • Resamplear los datos (ej. de diario a mensual o anual).

Para este tutorial, nos aseguraremos de que los datos estén listos para Bokeh.

# Establecer la columna 'date' como índice (opcional pero útil para series temporales)
data = data.set_index('date')

# Resampling (ejemplo: promedio mensual de temperatura máxima)
data_monthly = data['temp_max'].resample('M').mean().reset_index()
data_monthly.columns = ['date', 'avg_temp_max_monthly']

print(data_monthly.head())

Este paso de resampling es útil cuando quieres ver tendencias a una escala temporal diferente, como promedios mensuales o anuales.

📌 Nota: Bokeh funciona mejor con DataFrames de Pandas, así que la preparación con Pandas es un paso fundamental.

✨ Creando tu Primera Visualización Interactiva con Bokeh

Ahora que tenemos nuestros datos listos, es hora de usar Bokeh para visualizarlos. Empezaremos con un gráfico de línea simple para la temperatura promedio diaria.

📈 Gráfico de Línea Básico

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, DatetimeTickFormatter
from bokeh.io import output_notebook

# Esto es para mostrar los gráficos directamente en Jupyter Notebook
output_notebook()

# Crear un ColumnDataSource para Bokeh
source = ColumnDataSource(data)

# Crear una nueva figura con un título y ejes adecuados
p = figure(
    title="Temperatura Promedio Diaria (2022-2023)",
    x_axis_label="Fecha",
    y_axis_label="Temperatura (°C)",
    x_axis_type="datetime", # Muy importante para series temporales
    height=350, width=800,
    tools="pan,wheel_zoom,box_zoom,reset,save"
)

# Añadir una línea al gráfico
p.line(x='date', y='temp_avg', source=source, line_width=2, color='blue', legend_label='Temp. Promedio')

# Formatear el eje X para que muestre fechas de manera legible
p.xaxis.formatter = DatetimeTickFormatter(
    days=['%Y-%m-%d'],
    months=['%Y-%m'],
    years=['%Y']
)

# Añadir leyenda interactiva
p.legend.location = "top_left"
p.legend.click_policy="hide"

show(p)

Este código crea un gráfico de línea interactivo. Observa el parámetro x_axis_type="datetime" en figure(), que es crucial para que Bokeh interprete las fechas correctamente. Los tools permiten interactuar con el gráfico (zoom, paneo, etc.).

Evolución de la Temperatura Promedio + - 10°C 15°C 20°C 25°C 30°C Ene Feb Mar Abr May Jun 28.5°

🎨 Personalizando y Añadiendo Interactividad Avanzada

Vamos a hacer nuestro gráfico más interesante añadiendo rangos de temperatura (mínima y máxima) y tooltips para mostrar información detallada al pasar el ratón.

from bokeh.models import HoverTool

# Crear una nueva figura
p_advanced = figure(
    title="Temperaturas Diarias (Mín., Máx., Promedio)",
    x_axis_label="Fecha",
    y_axis_label="Temperatura (°C)",
    x_axis_type="datetime",
    height=400, width=900,
    tools="pan,wheel_zoom,box_zoom,reset,save"
)

# Añadir líneas para temp_min, temp_max y temp_avg
p_advanced.line(x='date', y='temp_min', source=source, line_width=1, color='lightgreen', legend_label='Temp. Mínima')
p_advanced.line(x='date', y='temp_max', source=source, line_width=1, color='salmon', legend_label='Temp. Máxima')
p_advanced.line(x='date', y='temp_avg', source=source, line_width=2, color='blue', legend_label='Temp. Promedio')

# Añadir un HoverTool para mostrar detalles al pasar el ratón
hover = HoverTool(
    tooltips=[
        ("Fecha", "@date{%Y-%m-%d}"),
        ("Temp. Mínima", "@temp_min{0.00} °C"),
        ("Temp. Máxima", "@temp_max{0.00} °C"),
        ("Temp. Promedio", "@temp_avg{0.00} °C")
    ],
    formatters={'@date': 'datetime'} # Formatear la fecha en el tooltip
)
p_advanced.add_tools(hover)

# Formatear el eje X
p_advanced.xaxis.formatter = DatetimeTickFormatter(
    days=['%Y-%m-%d'],
    months=['%Y-%m'],
    years=['%Y']
)

# Configurar leyenda
p_advanced.legend.location = "top_left"
p_advanced.legend.click_policy="hide"

show(p_advanced)

El HoverTool es una característica poderosa de Bokeh que permite a los usuarios obtener información contextual al interactuar con el gráfico. Hemos definido los tooltips para mostrar la fecha y las diferentes temperaturas con un formato específico.


🌧️ Añadiendo Datos de Precipitación: Ejes Duales

A menudo, querrás visualizar diferentes tipos de datos que tienen escalas muy distintas en el mismo gráfico. Para esto, Bokeh permite usar ejes Y secundarios.

📊 Gráfico con Ejes Y Secundarios

Vamos a añadir la precipitación a nuestro gráfico de temperatura. Dado que las escalas son muy diferentes (temperatura en grados Celsius, precipitación en milímetros), usaremos un eje Y a la derecha para la precipitación.

from bokeh.models import LinearAxis, Range1d

# Crear una figura principal
p_dual_axis = figure(
    title="Temperatura y Precipitación Diaria (2022-2023)",
    x_axis_label="Fecha",
    y_axis_label="Temperatura (°C)",
    x_axis_type="datetime",
    height=450, width=1000,
    tools="pan,wheel_zoom,box_zoom,reset,save"
)

# Configurar el rango del eje Y izquierdo (temperatura)
p_dual_axis.y_range = Range1d(start=data['temp_min'].min() - 5, end=data['temp_max'].max() + 5)

# Añadir la línea de temperatura promedio
p_dual_axis.line(x='date', y='temp_avg', source=source, line_width=2, color='blue', legend_label='Temp. Promedio')

# Crear un segundo eje Y para la precipitación
p_dual_axis.extra_y_ranges = {"precip_range": Range1d(start=0, end=data['precipitation'].max() * 1.2)}
p_dual_axis.add_layout(LinearAxis(y_range_name="precip_range", axis_label="Precipitación (mm)"), 'right')

# Añadir un gráfico de barras para la precipitación en el segundo eje Y
p_dual_axis.vbar(
    x='date', top='precipitation', source=source, 
    width=pd.Timedelta(days=0.9), # Ancho de la barra
    color='grey', alpha=0.4, legend_label='Precipitación',
    y_range_name="precip_range" # Asignar al eje Y secundario
)

# Añadir HoverTool (adaptado para ambos tipos de datos)
hover_dual = HoverTool(
    tooltips=[
        ("Fecha", "@date{%Y-%m-%d}"),
        ("Temp. Promedio", "@temp_avg{0.00} °C"),
        ("Precipitación", "@precipitation{0.00} mm")
    ],
    formatters={'@date': 'datetime'} 
)
p_dual_axis.add_tools(hover_dual)

# Formatear el eje X
p_dual_axis.xaxis.formatter = DatetimeTickFormatter(
    days=['%Y-%m-%d'],
    months=['%Y-%m'],
    years=['%Y']
)

# Configurar leyenda
p_dual_axis.legend.location = "top_left"
p_dual_axis.legend.click_policy="hide"

show(p_dual_axis)

Aquí, definimos un extra_y_ranges con un nombre ("precip_range") y luego creamos un LinearAxis que se asocia a ese rango y se añade a la derecha del gráfico. La clave es usar y_range_name="precip_range" al dibujar el glifo de precipitación (vbar).

Climatología Mensual 10° 20° 30° Temperatura (°C) 0 50 100 150 Precipitación (mm) Ene Feb Mar Abr May Jun Mes: Abril Temp: 18°C Prec: 60mm Temperatura Precipitación
🔥 Importante: Al usar ejes duales, asegúrate de que los rangos de ambos ejes estén bien definidos para evitar que un conjunto de datos domine visualmente al otro.

🗓️ Agregación y Comparación de Periodos

Una técnica común en el análisis de series temporales es agregar datos para ver tendencias a largo plazo o comparar diferentes periodos (ej. año a año, mes a mes).

📈 Gráfico de Temperaturas Mensuales Promedio por Año

Vamos a calcular la temperatura promedio mensual y luego graficar varios años en un solo gráfico para ver la estacionalidad y las diferencias interanuales.

# Calcular la temperatura promedio mensual
data['month'] = data.index.month
data['year'] = data.index.year
monthly_avg_temp = data.groupby(['year', 'month'])['temp_avg'].mean().reset_index()

# Pivotear los datos para tener los años como columnas y los meses como filas
pivoted_data = monthly_avg_temp.pivot(index='month', columns='year', values='temp_avg')

# Crear un ColumnDataSource para los datos pivoteados
source_pivoted = ColumnDataSource(pivoted_data.reset_index())

# Lista de colores para los años
from bokeh.palettes import Category10
colors = Category10[len(pivoted_data.columns) - 1]

# Crear la figura
p_comparison = figure(
    title="Temperatura Promedio Mensual por Año",
    x_axis_label="Mes",
    y_axis_label="Temperatura Promedio (°C)",
    height=400, width=700,
    x_range=[str(i) for i in range(1, 13)], # Rango de meses como strings
    tools="pan,wheel_zoom,box_zoom,reset,save"
)

# Añadir una línea para cada año
for i, year in enumerate(pivoted_data.columns):
    if year != 'month': # Evitar la columna 'month' si está ahí
        p_comparison.line(
            x='month', y=str(year), source=source_pivoted,
            line_width=2, color=colors[i], legend_label=str(year)
        )

# Configurar el eje X para mostrar nombres de meses (opcional)
month_names = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']
p_comparison.xaxis.ticker = list(range(1, 13))
p_comparison.xaxis.major_label_overrides = {i: name for i, name in enumerate(month_names, 1)}

p_comparison.legend.location = "top_left"
p_comparison.legend.click_policy="hide"

show(p_comparison)

En este ejemplo, primero agregamos los datos por mes y año. Luego, usamos pivot para reorganizar el DataFrame, lo que facilita graficar cada año como una línea separada. Esto es excelente para identificar patrones estacionales y comparar el comportamiento de la temperatura entre diferentes años.

¿Por qué el uso de `str(i)` y `str(year)` en el bucle? Al pivotear el DataFrame, los nombres de las columnas de los años (`2022`, `2023`, etc.) se convierten en enteros. Sin embargo, para usarlos como identificadores de columnas en el `ColumnDataSource` de Bokeh, es más seguro convertirlos a cadenas (`str`) para evitar posibles conflictos o interpretaciones erróneas.

🚀 Creando un Dashboard Interactivo Simple

Bokeh no solo permite crear gráficos individuales, sino también combinarlos en diseños complejos y dashboards interactivos. Aquí, mostraremos cómo combinar nuestros gráficos en un diseño simple.

🖼️ Combinando Gráficos con gridplot

Podemos usar gridplot para organizar múltiples gráficos en una cuadrícula. Esto es útil para presentar diferentes aspectos de los datos en un solo vistazo.

from bokeh.layouts import gridplot

# Reutilizamos los gráficos p_advanced y p_dual_axis creados anteriormente.
# Si los ejecutaste, ya están definidos en tu sesión.

# Crear una cuadrícula de gráficos
# Puedes ajustar las dimensiones de cada gráfico si es necesario
dashboard_layout = gridplot([[p_advanced], [p_dual_axis]], width=900, height=450)

# Mostrar el dashboard
show(dashboard_layout)

Este código simple organiza los dos gráficos interactivos que hemos creado en una disposición vertical. Puedes experimentar con diferentes diseños de gridplot (ej. [[p_advanced, p_dual_axis]] para una disposición horizontal, o [[p1, p2], [p3, p4]] para una cuadrícula 2x2).

Temperaturas Diarias (°C) Máx Media Mín 30° 20° 10° Lun Mar Mié Jue Vie Sáb Dom Climatología Semanal Temp Prec. 40° 25° 10° 100mm 50mm 25mm 0mm Lun Mar Mié Jue Vie Sáb Dom
📌 Nota: Para dashboards más complejos con widgets interactivos (sliders, selectores), Bokeh ofrece el `Bokeh Server`, que va más allá del alcance de este tutorial pero es una opción muy potente.

✅ Conclusión y Próximos Pasos

¡Felicidades! 🎉 Has aprendido los fundamentos para crear visualizaciones interactivas de series temporales con Bokeh en Python. Hemos cubierto desde la preparación de datos hasta la creación de gráficos de línea avanzados con tooltips, el uso de ejes duales y la combinación de gráficos en un dashboard simple.

La visualización de series temporales es una habilidad invaluable en Ciencia de Datos, permitiéndote desentrañar patrones ocultos y comunicar tus hallazgos de manera efectiva. Sigue explorando las capacidades de Bokeh, ¡es una herramienta muy versátil!

🚀 Ideas para Seguir Explorando:

  • Añadir Widgets: Incorpora sliders, selectores o botones de Bokeh para permitir a los usuarios filtrar o agregar datos de forma dinámica. Por ejemplo, un slider para seleccionar un rango de fechas específico.
  • Conectar Gráficos: Haz que un gráfico filtre o actualice otro gráfico, creando una experiencia de usuario aún más inmersiva.
  • Integración con Bokeh Server: Aprende a construir aplicaciones web de datos interactivas con Bokeh Server para compartir tus visualizaciones con otros.
  • Más Tipos de Glifos: Experimenta con otros glifos de Bokeh como area, scatter o quad para representar diferentes aspectos de tus series temporales.
  • Datos Reales: Descarga datos climáticos reales (ej. de NOAA, Kaggle o APIs meteorológicas) y aplica las técnicas aprendidas.
# Ejemplo de cómo se vería un slider simple (requiere Bokeh Server para interactividad completa en un entorno web, 
# pero puedes definirlo para un Jupyter notebook con `interact` de ipywidgets si se instala ipywidgets)
# from bokeh.models import CustomJS, Slider
# from bokeh.layouts import column

# start_date_slider = Slider(start=data.index.min().timestamp() * 1000, 
#                            end=data.index.max().timestamp() * 1000, 
#                            value=data.index.min().timestamp() * 1000, 
#                            step=pd.Timedelta(days=1).total_seconds() * 1000,
#                            title="Fecha de Inicio")

# layout = column(start_date_slider, p_advanced)
# show(layout)

Esperamos que este tutorial te haya proporcionado una base sólida para comenzar tu viaje en la visualización interactiva de series temporales con Bokeh. ¡Ahora es tu turno de experimentar y crear tus propias historias con datos! 🌟

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!