Construcción del Dashboard

Del dato crudo a la visualización interactiva en Tableau Public

Diagnóstico post-entrenamiento, pipeline de datos, migración y automatización

8Visualizaciones
297Modelos Prophet
4Llaves de join
3Avances iterativos
1Dataset integrado

Visión general

De boletines epidemiológicos a un dashboard interactivo embebido en la web

Objetivo del dashboard: Permitir a investigadores del IMSS y stakeholders del Tec de Monterrey explorar interactivamente datos históricos (2014-2026) y predicciones a 52 semanas de tres padecimientos neurológicos (Depresión F32, Parkinson G20, Alzheimer G30), desagregados por entidad federativa, sexo y semana epidemiológica.

El problema no estaba en Tableau

Tras la fase de entrenamiento, las fechas futuras desaparecían al filtrar por entidad o padecimiento en Tableau. La persistencia del error incluso en vistas simplificadas reveló que el defecto no era visual sino estructural: estaba en la integración de datos aguas abajo del entrenamiento. El diagnóstico completo, la reconstrucción del pipeline y la migración a un dataset único (tableau.csv) constituyen el núcleo de este reporte.

Arquitectura de datos

Pipeline completo: desde SINAVE hasta Tableau Public

SINAVE PDFs
~633 boletines
Extracción
Camelot + PyPDF
dataset_boletin.csv
make preprocess
filter → clean → transform → mapper
data_inegi_General.csv
Histórico + INEGI
make train
Prophet CV + modelos .pkl
make predict
predice.py + fallback híbrido
all_forecast.csv
~180 MB
make tableau
build_tableau.py
tableau.csv
Dataset integrado final
Tableau Public
8 visualizaciones embebidas
EpiDashboard.html
proyectointegrador.org
Principio rector: Pipeline reproducible → Publicación automatizada → Consumo visual. El dashboard es la consecuencia del pipeline, no un artefacto ad hoc.

Diagnóstico post-entrenamiento

El pipeline fallaba por inconsistencias en el dataset, no por Tableau

Semana 53 vs Semana 1

Se detectaron duplicados reales en el histórico INEGI en fechas específicas (2014-12-29 y 2025-12-29) por colisión entre Semana 53 y Semana 1. Esto rompía la unicidad esperada para los joins y provocaba filas fantasma que desaparecían al filtrar.

build_tableau.py # Fix determinista: preferir Semana 53 cuando existe if "Semana" in real.columns: special_ds = pd.to_datetime(["2014-12-29", "2025-12-29"]) is_special = real["ds"].isin(special_ds) if is_special.any(): b = real.loc[is_special].copy() b["_wk_pref"] = (b["Semana"] == 53).astype(int) b = b.sort_values(...).drop_duplicates(keep="first")

Política de NaN

Se decidió no inventar datos: no rellenar yhat donde no existe, no mezclar valores reales con forecast, y mantener NaN como ausencia real de información. El dataset se construye con outer join y una validación mínima correcta: prohibir NaN solo en llaves críticas.

Validación key_cols = ["ds", "padecimiento", "entidad", "meta_modo"] bad_keys = out[key_cols].isna().sum() if not bad_keys.empty: raise ValueError("NaN en llaves criticas")

Triple conteo de modos

Al expandir el histórico a general/hombres/mujeres, se evitó inflar SUM(Incrementos_*) en Tableau dejando poblado solo el incremento correspondiente al modo y poniendo NaN en los otros dos. Así, filtrar por meta_modo=hombres solo cuenta hombres.

expand_real_by_modo g = r.assign(meta_modo="general", y_real=r["incrementos_total"]) h = r.assign(meta_modo="hombres", y_real=r["incrementos_hombres"]) m = r.assign(meta_modo="mujeres", y_real=r["incrementos_mujeres"]) # Solo el incremento del modo queda poblado g = g.assign(incrementos_hombres=pd.NA, incrementos_mujeres=pd.NA)

Pipeline: build_tableau.py

8 pasos para construir el dataset integrado de visualización

A Lectura Histórico INEGI + all_forecast.csv load_inputs()
B Parse fechas Fecha→ds, mapeo columnas, limpieza prepare_inputs()
C Fix Sem. 53 Desambigua 2014-12-29 y 2025-12-29 drop_duplicates()
D Expansión Triplica a general / hombres / mujeres expand_real_by_modo()
E Validación Unicidad por 4 llaves en ambas entradas duplicated() check
F Outer merge Join histórico + forecast, validate m:1 merge(how="outer")
G RMSE Solo donde coexisten y_real y yhat groupby().mean()
H Escritura Fill, validación NaN, tableau.csv to_csv()

Contrato de llaves

La llave compuesta del dataset final es:

ds padecimiento entidad meta_modo

meta_modo indica la segmentación por sexo: general (ambos sexos), hombres o mujeres. Cada combinación de padecimiento, entidad y modo genera un modelo independiente.

El pipeline exige unicidad por esta llave en ambas entradas (histórico expandido y forecast) antes de hacer el merge. El merge es outer para no perder histórico ni forecast, y se usa validate="m:1" para controlar cardinalidad.

Segmentación por sexo — meta_modo

El campo meta_modo es la tercera dimensión de la llave compuesta que permite segmentar los datos y las predicciones por sexo. Cada valor genera un modelo Prophet independiente:

general

Ambos sexos combinados. Usa la columna incrementos_total del histórico. Es la vista por defecto del dashboard.

hombres

Solo población masculina. Usa la columna incrementos_hombres del histórico.

mujeres

Solo población femenina. Usa la columna incrementos_mujeres del histórico.

Cada combinación padecimiento × entidad × meta_modo genera un modelo Prophet independiente. Esto significa que Jalisco-Depresión produce 3 modelos distintos, cada uno entrenado con su propia serie de incidencia.

Triple conteo (expand_real_by_modo): La función expand_real_by_modo() triplica el histórico INEGI generando una fila por cada modo. Solo el incremento correspondiente al modo queda poblado; los otros dos se dejan como NaN. Esto permite que Tableau filtre por meta_modo sin inflar las sumas agregadas (SUM(incrementos_*)).

Ejemplo: una sola fila de Jalisco-Depresión en el histórico se expande en 3 registros independientes:

padecimiento entidad meta_modo y_real Modelo Prophet (.pkl)
Depresión Jalisco general incrementos_total Prophet_Depresion_Jalisco_general.pkl
Depresión Jalisco hombres incrementos_hombres Prophet_Depresion_Jalisco_hombres.pkl
Depresión Jalisco mujeres incrementos_mujeres Prophet_Depresion_Jalisco_mujeres.pkl
PasoFunciónDescripción
Aload_inputs()Lee data_inegi_General.csv (histórico INEGI) y all_forecast.csv (predicciones)
Bprepare_inputs()Parsea Fechads, mapea columnas del forecast, elimina fechas inválidas
CFix Semana 53Desambigua 2014-12-29 y 2025-12-29 prefiriendo Semana 53 sobre Semana 1
Dexpand_real_by_modo()Triplica histórico en 3 modos; solo el incremento correspondiente queda poblado
EValidación de duplicadosVerifica unicidad en [ds, padecimiento, entidad, meta_modo] en ambas entradas
Fouter mergeJoin histórico + forecast por las 4 llaves, validate="m:1"
GRMSE por modeloCalcula RMSE solo donde coexisten y_real y yhat
HEscrituraRenombra, fill numérico (excluyendo incrementos), valida llaves, escribe tableau.csv

Métricas: modelo original vs usado

Trazabilidad completa del fallback híbrido en predice.py

Modelo propio

Para los 256 modelos con confianza suficiente, archivo_modelo_original y archivo_modelo_usado apuntan al mismo .pkl. Las métricas original y usado son idénticas.

Fallback regional

Para los 41 modelos insuficientes, el forecast viene del modelo regional pero se desnormaliza con la población estatal individual. Las métricas se desdoblan:

  • rmse_original, mase_original — del modelo estatal propio
  • rmse_usado, mase_usado — del modelo regional fallback
  • confianza_original = "insuficiente"
  • confianza_usado = "suficiente"

Distribución de modelos por tipo

297 modelos estatales + 15 regionales de fallback

Tooltips informativos en Tableau: Los tooltips despliegan RMSE, MAE, MAPE, MASE y modelo utilizado, manteniendo la visualización principal limpia mientras el detalle técnico queda disponible bajo demanda. El MASE se clasifica categóricamente: excelente (<0.7), bueno (0.7-1.0), requiere mejora (>1.0).

Las 8 visualizaciones

Embebidas en EpiDashboard.html via Tableau Public JavaScript API

01

Tabla de datos

Tabla interactiva con datos epidemiológicos por entidad, año, semana y sexo. Se incorpora información demográfica del INEGI. Filtrable por año, género y padecimiento.
Tabla de datos · viz_epiforecastmx
02

Mapa de México

Variables demográficas y territoriales por entidad: densidad poblacional, superficie, casos desagregados por género. Se aplica normalización min-max para gradientes de color coherentes al alternar variables.
Mapa de México · viz_epiforecastmx
03

México en categorías

Categorizaciones analíticas: región geográfica, región socio-urbana, densidad poblacional, extensión territorial y ratio por género. Incluye rangos fijos y percentiles para comparaciones estructurales.
México en categorías · viz_epiforecastmx
04

Casos por año

Tasa anual de incremento normalizada por población (por 100K hab.), desagregada por género. Recorte visual de extremos preservando valores reales en el detalle.
Casos por año · viz_epiforecastmx
05

Casos semanales

Tasa semanal de incremento normalizada, permitiendo analizar la dinámica de corto plazo y detectar variaciones intra-anuales. Recorte por percentiles para legibilidad.
Casos por semana · viz_epiforecastmx
06

Predicción e incertidumbre

Trayectoria estimada del modelo con rango de variación (intervalo de confianza). Filtrable por año. Incluye línea de referencia COVID y banda asociada. En desarrollo
Predicciones · En migración al dashboard unificado
07

Dash Predicciones

Dashboard integrado: desempeño del modelo (observado vs predicho) + KPIs de confianza + modelo Prophet utilizado. Contrasta puntos observados con línea de predicción continua.
Dash Predicciones · viz_epiforecastmx
08

Últimas 52 semanas

Ventana temporal de 52 semanas observadas + 52 proyectadas. Resumen del comportamiento reciente y su extensión esperada, clave para la toma de decisiones. En desarrollo
Últimas 52 semanas · viz_epiforecastmx

Migración al dataset integrado

De dos fuentes fragmentadas a un único tableau.csv con llave homogénea

Antes: arquitectura fragmentada

  • Dos fuentes de datos separadas (histórico + forecast)
  • Joins implícitos en Tableau (Relationships)
  • Filtros sobre campos del histórico excluían filas futuras
  • Fechas futuras desaparecían al filtrar por entidad
  • Cálculos LOD dependían de la separación de fuentes
  • Métricas agregadas infladas por duplicados

Después: dataset único

  • Un solo tableau.csv con llave compuesta
  • Todos los registros responden a los mismos filtros
  • NaN semántico: ausencia real, no error
  • Histórico y forecast conviven sin joins implícitos
  • Expresiones LOD recalibradas para una fuente
  • Acciones de filtro sin dependencias residuales

Reconstrucción del workbook

La migración no fue un simple "cambiar fuente": requirió reconstruir sistemáticamente múltiples componentes:

Fuente de datos

Todas las hojas apuntadas al nuevo CSV con tipos validados

Campos calculados

Condiciones logicas basadas en meta_modo, WINDOW_* revisadas

Agregaciones LOD

SUM, AVG, FIXED recalibrados para evitar duplicaciones

Filtros y parámetros

Globales remapeados, acciones de resaltado verificadas

Decisiones UX/UI

Diseño centrado en interpretabilidad, no en decoración

Contexto temporal

Línea de referencia COVID-19 y ajuste a fecha continua (condición necesaria para bandas en Tableau). Títulos con referencias temporales claras sin sobrecargar.

MASE categórico

Clasificación interpretable: Excelente (<0.7), Bueno (0.7-1.0), Mejora (>1.0). El usuario entiende sin interpretar la métrica cruda.

Tooltips informativos

RMSE, MAE, MAPE, MASE y modelo utilizado bajo demanda. La visualización principal queda limpia; el detalle técnico aparece al hover.

Dashboard integrado

Worksheets de predicciones y desempeño se integraron en un único dashboard "Predicciones" con una nueva worksheet "KPIs" (confianza + modelo Prophet).

Normalización de mapas

Normalización min-max para variables con magnitudes muy diferentes (densidad, superficie, casos). El gradiente de color es consistente al alternar variables.

Ventana 2 años

52 semanas históricas + 52 proyectadas. Controlada por parámetro de selección única (workaround a limitación de Tableau). En desarrollo

Tableau - Normalización para mapas // Campo calculado en Tableau: // Normaliza cualquier variable al rango [0,1] para coherencia // visual al alternar entre densidad, superficie y casos. (SUM([Variable seleccionada]) - WINDOW_MIN(SUM([Variable seleccionada]))) / (WINDOW_MAX(SUM([Variable seleccionada])) - WINDOW_MIN(SUM([Variable seleccionada])))

Evolución del dashboard

Tres avances iterativos: de tablas básicas a un pipeline integrado

Avance 2 — Semana 3
Fundamentos: tablas, mapas y series

Primeras 5 visualizaciones: tabla de datos interactiva, mapa de México, vista por categorías, casos anuales y semanales. Dashboard ejecutivo comparativo con los 3 padecimientos. Modelo baseline Prophet con cross-validation temporal.

Tabla de datos Mapa de México Categorías Casos anuales Casos semanales
Avance 3 — Semana 4-5
Normalización, modelos por estado, dashboard integral

Se implementó normalización min-max para mapas con variables de magnitudes dispares. Modelo expandido a nivel estatal (99 modelos por padecimiento). Dashboard integral con heatmaps y bump charts de rendimiento.

Min-max maps 99 modelos/pad Dashboard integral
Avance 4 — Semana 6
Pipeline integrado, migración y automatización

Diagnóstico post-entrenamiento, corrección de Semana 53, política de NaN explícitos, contrato de llaves, expansión por modo, build_tableau.py. Migración a dataset único. Experimento de automatización GitHub Actions → Google Sheets → Tableau Public.

build_tableau.py Dataset único Métricas orig/usado Automatización

Componentes del dashboard por avance

Crecimiento acumulativo de funcionalidad

Automatización: GitHub Actions + Google Sheets

Validación del principio pipeline → publicación → consumo

Arquitectura del experimento

GitHub Actions
cron: 0 * * * * (cada hora)
update_sheet.py
gspread + service account
Google Sheets
Dataset público
auto-refresh ~24h
Tableau Public
Fuente: Google Sheets
Web embed
proyectointegrador.org

Identidad no interactiva

Service account de Google Cloud como identidad técnica. Credenciales en GitHub Secrets (no versionadas). El Sheet se comparte con el correo de la service account.

ComponenteResponsabilidad
Google Cloud / IAMControl de acceso
GitHub ActionsEjecución automatizada
Google SheetsPersistencia de datos

Resultados y limitaciones

La escritura a Google Sheets funciona correctamente en cada ejecución. La incertidumbre está en el lado de Tableau Public: no ofrece control sobre triggers ni schedules de refresh.

Limitacion estructural: Tableau Public no gestiona credenciales de datos protegidos por diseño. Para una arquitectura robusta y gobernada, la solución correcta es Tableau Cloud o Tableau Server.

Tableau Public: costos y limitaciones

Excelente para difusión, no diseñada como plataforma de automatización

ProductoCosto aprox.Refresh programadoUso principal
Tableau PublicGratisSolo Google Sheets (~1x/día)Publicación abierta
Tableau Desktop~70 USD/mesNoDesarrollo y autoría
Tableau Cloud (Creator)~75 USD/mesDesarrollo + publicación
Tableau Cloud (Explorer)~42 USD/mesExploración interactiva
Tableau Cloud (Viewer)~15 USD/mesConsumo de dashboards
Agradecimiento: Se agradece la colaboración de la Dra. Grettel Barceló Alonso, quien facilitó el acceso a una licencia de Tableau Desktop para el desarrollo del workbook viz_epiforecastmx.twb.

Lecciones aprendidas

Del diagnóstico del Avance 4 al estado actual del pipeline

Semántica sobre estética

Los errores en Tableau no se resolvieron con ajustes visuales. El problema estaba en la estructura del dataset. Los NaN no son errores: son semántica. La visualización es consecuencia de datos bien construidos.

Contrato > convencion

Definir un contrato explícito de llaves (ds, padecimiento, entidad, meta_modo) y validarlo con validate="m:1" previene errores silenciosos que solo se manifiestan en producción.

Trazabilidad end-to-end

El desdoblamiento original vs usado en métricas permite auditar exactamente qué modelo generó cada predicción. Con 41 modelos en fallback, la transparencia es crítica.

Pipeline reproducible

make tableau es un único comando que genera el dataset final. Cualquier miembro del equipo puede reconstruir tableau.csv desde cero en cualquier momento.

Anatomia del dataset final: tableau.csv

Columnas clave agrupadas por propósito

Ficha técnica

Resumen de scripts, archivos y comandos del pipeline de visualización

ComponenteArchivo / ComandoPropósito
Predicciónscripts/predice.py / make predictGenera all_forecast.csv con yhat + métricas orig/usado
Dataset Tableauscripts/build_tableau.py / make tableauIntegra histórico + forecast en tableau.csv
Workbookviz/viz_epiforecastmx.twb8 visualizaciones interactivas en Tableau
Web embedEpiDashboard.htmlPágina web con embeds Tableau Public JS API
Automatizaciónhourly.yml + update_sheet.pyExperimento GitHub Actions → Google Sheets
Notebooknotebooks/Avance4_Luis_Reconstruido.ipynbDocumentación completa del proceso (6 secciones)
# Flujo completo del dashboard make train # 1. Entrenar 297 modelos Prophet make models-push # 2. Subir modelos a S3 (DVC) make predict # 3. Generar all_forecast.csv + fallback híbrido make tableau # 4. Construir tableau.csv (dataset integrado) make forecast-push # 5. Subir forecast a S3 # 6. Actualizar Tableau Desktop con nuevo CSV # 7. Publicar en Tableau Public # 8. Verificar embeds en EpiDashboard.html