Bitácora del Modelado Prophet

El recorrido completo v1 → v6 · EpiForecast-MX · IMSS × Tec de Monterrey

6Versiones
-64%RMSE Depresión
100%Cobertura Estatal

Timeline del Modelado

Seis versiones, cada una construyendo sobre los hallazgos de la anterior

Versión 1
Normalización a tasa por 100K
El primer paso crítico: dejar de modelar conteos absolutos. CDMX tiene 9M de habitantes, Colima 730K — comparar conteos crudos no tiene sentido. Normalizamos a tasa por 100,000 habitantes usando datos de población del INEGI. Por primera vez, los modelos de todos los estados eran comparables entre sí.
y = incidencia / población × 100K
Versión 2
Log-transform + modo aditivo
El descubrimiento que cambió todo. La varianza de Depresión era enorme: RMSE hasta 2.448. Aplicamos log(1+y) y el RMSE medio cayó de 0.586 a 0.210 (-64%). También descubrimos que el modo aditivo mejoraba en Depresión, mientras multiplicativo dominaba en Alzheimer.
-64% RMSE Depresión
Versión 3
Filtro insuficientes + holiday Tabasco
Identificamos que estados con menos de 1 caso/semana promedio producían modelos planos e inútiles. Los marcamos como «insuficientes» y dejamos de mostrar sus predicciones. También descubrimos un cambio de régimen en Tabasco-Depresión (2023) y lo absorbimos como holiday (-6.2% RMSE).
Cobertura: 72% (213 modelos)
Versión 4
Grids por padecimiento + CV ponderada
Analizamos los 297 modelos v3 para construir grids especializados. Alzheimer se simplificó a 6 combos (solo multiplicativo), Depresión mantuvo 24, Parkinson exploró 18. Los folds de CV se ponderaron: el más reciente (2023-2024) pesa 1.25x, el post-COVID solo 0.5x.
Grids: 6 / 24 / 18 combos
Versión 5
Anti-Newton: 3 capas de protección
Prophet caía a Newton optimizer en series difíciles, 100-500x más lento que L-BFGS. Chihuahua-Depresión tardaba 39 minutos. Implementamos 3 capas: sort CP descendente, timeout por fold (35s), y threshold Newton-prone. Resultado: 39 min → 4 min.
Chihuahua: 39 min → 4 min
Versión 6
Modo híbrido + MASE
El gran salto final: en lugar de descartar 41 estados insuficientes, entrenamos modelos regionales INEGI de fallback. Cada estado usa el modelo de su región pero se desnormaliza con su propia población. Agregamos MASE como métrica escala-independiente. Resultado: 100% cobertura, 312 modelos.
100% cobertura · MASE < 1

Feature Engineering

Las transformaciones que convirtieron datos crudos en features modelables

Detección de Outliers (Z-score)

Los datos epidemiológicos son ruidosos: errores de captura, rezagos de reporte, brotes reales. Usamos Z-score con umbral=3, agrupado por Padecimiento × Entidad, reemplazando outliers con la mediana del grupo.

¿Por qué Z-score y no IQR? Z-score es más robusto para distribuciones no-normales cuando el umbral es alto (3). IQR es demasiado agresivo para series epidemiológicas con variación estacional natural.

tratamiento_outliers: metodo: 'zscore' umbral: 3 reemplazo: "mediana" agrupacion: - Padecimiento - Entidad

Lag Features (12 y 52 semanas)

Dos perspectivas temporales críticas para Prophet como regresores adicionales:

Lag-12 (quarter-over-quarter): captura tendencias trimestrales. Útil para detectar aceleraciones o desaceleraciones recientes en la incidencia.

Lag-52 (year-over-year): captura la estacionalidad anual directamente. Particularmente valioso para Depresión, que tiene un patrón estacional marcado con picos en invierno.

Rolling Window (26 semanas)

Un suavizado semestral usando media móvil de 26 semanas. Esto produce una tendencia semestral limpia que filtra el ruido semanal pero preserva cambios de nivel importantes.

26 semanas = medio año. Es el balance óptimo: suficiente para suavizar variación estacional intrasemestral, pero no tanto como para perder cambios de régimen reales (como el de Tabasco en 2023).

Incrementos Negativos

El SINAVE reporta incidencia acumulada por año. Cuando el acumulado baja de una semana a otra, es un error de captura o corrección retroactiva. Dos estrategias:

Redistribución: los decrementos se redistribuyen proporcionalmente en las semanas previas del mismo año.

Extrapolación 3 semanas: para las últimas semanas del año (que a veces llegan con rezago), extrapolamos usando la tendencia de las 3 semanas anteriores.

Normalización y Transformación del Target

El pipeline que transforma conteos crudos en el espacio óptimo para Prophet

Tasa por 100K habitantes (v1)

El problema fundamental: CDMX reporta ~500 casos/semana de Depresión, Colima solo ~5. Esto no significa que CDMX tenga 100x más incidencia — tiene 12x más población. Sin normalizar, Prophet sobreajustaría en estados grandes y subajustaría en pequeños.

La solución: dividir la incidencia semanal entre la población del estado y multiplicar por 100,000. Ahora la escala es comparable: ~5.5 por 100K en CDMX vs ~6.8 por 100K en Colima.

Python # Normalización a tasa por 100K y_tasa = (incidencia / población) * 100_000

Log-transform (v2): el cambio que valió oro

Incluso después de normalizar, la varianza de Depresión era heterogénea: estados con tasas altas tenían fluctuaciones mucho más grandes. log(1+y) estabiliza la varianza y comprime los picos extremos. El +1 evita log(0) cuando la incidencia es cero.

Python # Log-transform para estabilizar varianza y = np.log(1 + y_tasa) # Inversión al predecir y_pred = np.exp(y_hat) - 1

Pipeline completo de transformación

Conteos
SINAVE
Tasa 100K
y / pop × 100K
Log-transform
log(1+y)
Prophet
entrena
exp(y)-1
inversión
Desnormalizar
× pop / 100K

Configuración Prophet

Cada parámetro fue probado y justificado con datos

Fourier Custom (estacionalidad)

Prophet modela estacionalidad con series de Fourier. Usamos period=52.18 semanas (365.25/7) para capturar el ciclo anual exacto.

ParámetroNacionalRegional/Estatal
fourier_order53
n_changepoints25 (default)12
JustificaciónSeries largas, complejidad altaSeries cortas, evitar overfitting

¿Por qué order=3 regional? Con fourier_order=5 en series estatales cortas, Prophet sobreajustaba oscilaciones espurias — especialmente en Depresión estatal. Order=3 reduce los parámetros de estacionalidad de 10 a 6, produciendo curvas más suaves y generalizables.

Holiday COVID-19

La pandemia distorsionó drásticamente las series epidemiológicas. La modelamos como un holiday que abarca 913 días (~2.5 años) desde el 23 de marzo de 2020.

PadecimientoEfectoImpacto
DepresiónCaída brusca seguida de reboteSin holiday, Prophet trata el rebote como tendencia
ParkinsonCaída sostenida, recuperación lentaHoliday absorbe el periodo sin distorsionar tendencia
AlzheimerCaída moderadaMenor impacto pero aún mejora el fit
peridos_atípicos: - holiday: pandemia_covid ds: "2020-03-23" upper_window: 913 # 2.5 años

Cambios de Régimen: el caso de Tabasco

Probamos holidays para cambios de nivel en 5 estados. Solo uno pasó el filtro:

EntidadPadecimientoΔ RMSEVeredicto
TabascoDepresión-6.2%Aprobado
NayaritDepresión+7.1%Rechazado
ColimaDepresión+5.3%Rechazado
DurangoGeneral+8.9%Rechazado
Baja California SurGeneral+4.7%Rechazado

Lección clave: Prophet holidays modelan eventos temporales. Nayarit, Colima, Durango y BCS tienen cambios permanentes (step functions) que Prophet no puede absorber como holidays — los trata como eventos que eventualmente regresan al nivel anterior, empeorando el forecast.

n_changepoints regional: 12 vs 25

Prophet coloca 25 changepoints por default para detectar cambios de tendencia. En series estatales (más cortas, menos datos), esto produce overfitting.

Con n_changepoints=12, los modelos regionales y estatales capturan los cambios de tendencia principales (COVID, recuperación post-pandemia) sin sobreajustar fluctuaciones menores.

La lógica: 12 changepoints en ~500 observaciones (10 años semanales) = 1 changepoint cada ~10 meses. Suficiente para cambios anuales, no tanto como para capturar ruido.

Grid Search y Cross-Validation

De un grid genérico a grids optimizados por padecimiento, con CV ponderada y MASE

Grids Actuales (v5+)

Alzheimer — 6 combos

El grid más reducido. Multiplicativo domina al 100%; additive tenía +51% RMSE.

alzheimer: seasonality_mode: [multiplicative] changepoint_prior_scale: [0.01, 0.03] seasonality_prior_scale: [0.05, 0.1, 0.5]

Depresión — 24 combos

El grid más grande. Ambos modos compiten (47% vs 53%). El rango de SP es el más amplio.

depresion: seasonality_mode: [additive, multiplicative] changepoint_prior_scale: [0.01, 0.03, 0.05] seasonality_prior_scale: [0.025, 0.05, 0.1, 0.5]

Parkinson — 18 combos

Multiplicativo domina (71%) pero additive gana en algunas entidades. CP=0.01 eliminado (Newton-prone).

parkinson: seasonality_mode: [multiplicative, additive] changepoint_prior_scale: [0.03, 0.04, 0.05] seasonality_prior_scale: [0.1, 0.5, 1.0]

Hiperparámetros Ganadores

Cross-Validation Ponderada

No todos los folds de CV son igual de relevantes. El fold post-COVID (2020-2021) es un periodo atípico que no representa el futuro. El fold más reciente (2023-2024) es el más representativo del forecast. Los pesos reflejan esta realidad:

FoldPeriodoPesoJustificación
12020-20210.50Post-COVID, periodo atípico, menos relevante
22021-20220.75Recuperación, patrón transicional
32022-20231.00Estabilización, patrón normal
42023-20241.25Más reciente, más representativo del forecast
Python # CV ponderada: np.average en vez de np.mean rmse_ponderado = np.average( fold_rmses, weights=[0.5, 0.75, 1.0, 1.25] )

MASE: Mean Absolute Scaled Error

Agregado en v6 como métrica principal. MAPE (porcentual) es problemático en epidemiología: cuando el denominador (casos reales) es pequeño, MAPE explota. MASE compara contra un baseline naive estacional (repetir lo de hace 52 semanas):

Formula MASE = MAE_modelo / MAE_naive(lag=52) # MASE < 1 → mejor que repetir el año pasado # MASE = 0.74 → 26% mejor que naive

Protección Anti-Newton

Cómo evitamos que Prophet cayera al optimizador Newton (100-500x más lento que L-BFGS)

El problema

Prophet usa Stan para optimizar. El optimizador default es L-BFGS (rápido, O(n) por iteración). Pero cuando L-BFGS no converge, Stan cae silenciosamente a Newton (O(n³) por iteración). En Chihuahua-Depresión, un solo fold tardaba hasta 25 minutos con Newton, acumulando 39 minutos para los 3 modos de sexo.

El trigger: changepoint_prior_scale bajo (0.01, 0.03) combinado con series volátiles. CP bajo = regularización fuerte de cambios de tendencia, lo que dificulta la convergencia de L-BFGS.

1

Sort CP Descendente

Las combinaciones se ordenan por changepoint_prior_scale de mayor a menor. CP alto converge rápido con L-BFGS. Si encontramos una buena solución temprano, podemos podar las combinaciones lentas.

2

Timeout por Fold (35s)

_fit_with_timeout() usa ThreadPoolExecutor para cortar un fold que exceda 35 segundos. Si un fold tarda más de 35s, con alta probabilidad cayó a Newton.

3

Threshold Newton-prone

Si una combinación con CP=X hace timeout, todas las combinaciones con CP < X se saltan automáticamente. CP más bajo = más probabilidad de Newton. Poda agresiva y segura.

Python def _fit_with_timeout(self, model, df, timeout): "Entrena Prophet con timeout. Si excede, retorna None." with ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(model.fit, df) try: return future.result(timeout=timeout) except TimeoutError: logger.warning("Timeout — probable Newton") return None

Fallback final

Si todas las combinaciones hacen timeout (raro, pero posible), usamos parámetros default con el CP más alto del grid. Esto garantiza que siempre obtenemos un modelo, aunque no sea el óptimo de CV.

Modo Híbrido (v6)

De 72% cobertura a 100%: el fallback regional que cambió el juego

El problema: 41 estados sin predicción útil

En v3-v5, 41 combinaciones estado-padecimiento-sexo tenían un promedio menor a 0.5 casos/semana. Sus modelos Prophet producían líneas planas — predicciones técnicamente válidas pero inservibles. Descartarlas significaba perder cobertura: solo el 72% de los estados tenían al menos un modelo usable en v3.

La solución: pedir prestado a los vecinos

Para cada región INEGI de salud mental, entrenamos un modelo regional que agrega datos de todos los estados de la zona. Estos modelos tienen muchos más datos y producen predicciones robustas. Cuando un estado es «insuficiente», usamos el modelo de su región pero desnormalizamos con la población del estado individual.

Estado insuficiente

Colima — Alzheimer
< 0.5 casos/semana

Región INEGI

Urbana media
(9 estados)

Modelo regional

Prophet entrenado
con datos agregados

Desnormalización

Población de Colima
(no de la región)

Evolución de Resultados v1 → v6

El progreso medido: cada versión mejoró al menos una métrica

Resumen por versión

VersiónRMSE Dep.RMSE Alz.RMSE Park.CoberturaModelosCambio principal
v10.5860.0300.070100%297Normalización tasa 100K
v20.2100.0290.063100%297Log-transform (-64% RMSE)
v30.2100.0290.06372%213Filtro insuficientes + holidays
v50.2060.0330.06487%257Anti-Newton + umbral 0.5
v60.1830.0270.057100%312Híbrido + MASE + grids v5

Entrenamiento con Serie Completa

CV evalúa, pero el modelo final usa todos los datos disponibles

Un error común es entrenar el modelo final solo con el split de entrenamiento de CV. Pero CV ya cumplió su función: seleccionar los mejores hiperparámetros. Una vez seleccionados, el modelo final debe aprovechar todos los datos disponibles para maximizar la precisión en producción.

Nuestro flujo: CV usa 4 splits temporales para evaluar → selecciona los mejores HP → el .pkl final se entrena con toda la serie (2014-2025). Esto captura los patrones más recientes sin desperdiciar datos.

Python # Después de CV (evaluación) best_params = seleccionar_mejor_combo(cv_results) # Modelo final: entrenado con TODA la serie modelo_final = Prophet(**best_params) modelo_final.fit(self.serie) # toda la data, no solo train split # Guardar para producción save_model(modelo_final, f"models/Prophet_{pad}_{estado}_{modo}.pkl")

Lecciones Aprendidas

10 takeaways de 6 versiones y 297+ modelos

Lección 01

Normalizar es imprescindible

Modelar conteos crudos cuando las poblaciones varían 12x no tiene sentido. Tasa por 100K convierte todos los estados a una escala comparable. Sin esto, nada funciona.

Lección 02

Log-transform vale oro

Un solo log(1+y) redujo el RMSE de Depresión 64%. La lección: antes de tunear hiperparámetros, asegúrate de que el target esté en el espacio correcto. Estabilizar la varianza importa más que el grid search.

Lección 03

Holidays son temporales, no step functions

Prophet holidays modelan eventos que empiezan y terminan. Un cambio permanente de nivel (Nayarit subiendo 3x y quedándose ahí) no es un holiday — Prophet lo tratará como algo que debe «regresar» y empeorará.

Lección 04

Grids por padecimiento > genérico

Alzheimer necesita solo multiplicativo. Depresión compite entre aditivo y multiplicativo. Un grid único para todos desperdicia tiempo en combinaciones inútiles o pierde opciones valiosas.

Lección 05

Newton es el enemigo silencioso

Stan no avisa cuando cae a Newton optimizer. Un modelo que tardaba 30 segundos de repente tarda 25 minutos. Sin monitoreo de tiempos, nunca lo habríamos detectado.

Lección 06

Mejor pedir prestado que no predecir

Un modelo regional con desnormalización individual es mejor que «sin datos suficientes». Los stakeholders necesitan cobertura completa, no modelos perfectos con lagunas.

Lección 07

MASE > MAPE para epidemiología

MAPE explota cuando los valores reales son cercanos a cero. MASE compara contra un baseline real (naive lag-52) y es escala-independiente. Alzheimer con MASE 0.74 es 26% mejor que repetir el año pasado.

Lección 08

Folds recientes importan más

El fold post-COVID no representa el futuro. Ponderar con [0.5, 0.75, 1.0, 1.25] refleja esta realidad. Sin pesos, el fold 2020-2021 distorsiona la selección de hiperparámetros.

Lección 09

Entrenar con todo después de CV

CV selecciona hiperparámetros. El modelo final debe usar toda la data disponible. Desperdiciar el 25% de datos en un test set permanente es un lujo que series de 10 años no se pueden dar.

Lección 10

Fourier order bajo para series cortas

Fourier order=5 funciona para series nacionales con 500+ puntos. Para series estatales con menos datos, order=3 evita sobreajustar patrones estacionales espurios — especialmente crítico en Depresión estatal.