Suavizado exponencial: de la media móvil a Holt-Winters
En el capítulo anterior ajustaste un SARIMA a la serie de ventas de una librería online y conseguiste un pronóstico sólido. El director, satisfecho, vuelve con una petición nueva: quiere el mismo tipo de pronóstico, pero para cada una de las 200 categorías de producto de la tienda, actualizado cada semana.
Ajustar un SARIMA por categoría —identificar p, d, q, revisar ACF/PACF, diagnosticar residuos— funciona perfectamente para una serie. Para 200 series cortas y ruidosas, se convierte en un proceso lento, frágil y difícil de mantener: cada serie exige su propio ciclo de identificación, y muchas de ellas ni siquiera tienen historia suficiente para que ADF o ACF digan algo útil.
Necesitas una familia de modelos distinta: más simple de ajustar, más rápida de ejecutar en lote, y con una intuición tan directa que puedas explicársela a ese mismo director en dos frases. Esa familia es el suavizado exponencial.
La idea central: pesar más el presente
Ya conoces la media móvil del capítulo 1: promediar las últimas k observaciones para estimar el nivel de la serie. Tiene un problema evidente: todas las observaciones de la ventana pesan lo mismo. Un dato de hace 11 meses cuenta igual que el de ayer.
El suavizado exponencial corrige exactamente eso: pondera el presente más que el pasado, y ese peso decae de forma exponencial cuanto más atrás miras. En lugar de fijar una ventana rígida, usas un único parámetro que controla cuánta memoria tiene el modelo.
Antes de tocar la fórmula, compara los dos enfoques sobre la misma serie. Mueve la ventana de la media móvil y fíjate en lo brusco que resulta que un dato salga de la ventana y dejé de contar de golpe:
Es la misma serie de ventas del capítulo 2. Con ventanas pequeñas, la media reacciona rápido pero es ruidosa; con ventanas grandes, se suaviza pero se retrasa. El suavizado exponencial busca ese mismo equilibrio de una forma más elegante: sin ventana fija, con un solo parámetro continuo.
Suavizado exponencial simple (SES)
La fórmula recursiva es la pieza central de toda la familia:
En palabras: el pronóstico de mañana es una mezcla entre el valor observado hoy y el pronóstico que ya tenías para hoy. El parámetro α (entre 0 y 1) decide esa mezcla:
- α cercano a 1 → confías casi todo en el último dato observado. El modelo reacciona rápido pero es sensible al ruido.
- α cercano a 0 → confías casi todo en tu pronóstico anterior. El modelo es estable pero lento para reaccionar a cambios reales.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
# Reconstruimos la serie de ventas del capítulo 2 (72 meses)
np.random.seed(42)
fechas = pd.date_range(start="2018-01-01", periods=72, freq="ME")
t = np.arange(72)
tendencia = 1500 + 12 * t
estacional = 200 * np.sin(2 * np.pi * (t - 2) / 12)
ruido = np.random.normal(0, 40, 72)
serie = pd.Series(tendencia + estacional + ruido, index=fechas, name="ventas_libreria")
train = serie.iloc[:-12]
test = serie.iloc[-12:]
# SES: statsmodels optimiza alpha por mínimos cuadrados si no lo fijas
modelo_ses = SimpleExpSmoothing(train, initialization_method="estimated").fit()
print(f"Alpha óptimo: {modelo_ses.params['smoothing_level']:.3f}")
prediccion_ses = modelo_ses.forecast(12)
Ejecuta esto y verás algo revelador: statsmodels elige α = 1.0. Dicho en claro: el optimizador concluyó que la mejor estrategia es ignorar todo el pasado y repetir el último valor observado. SES se ha convertido en un naive disfrazado, y su error de pronóstico (MAE ≈ 248 €) es literalmente el mismo que el del naive.
Este es precisamente el límite que resuelve el siguiente método. El subcapítulo 3.1 profundiza en SES sobre una serie donde sí es la herramienta correcta — sin tendencia, solo nivel y ruido — para que veas también su cara buena.
Método de Holt: añadiendo tendencia
Holt extiende SES con una segunda ecuación que rastrea la pendiente de la serie, además de su nivel. Ahora hay dos parámetros de suavizado: α para el nivel y β para la tendencia.
from statsmodels.tsa.holtwinters import Holt
modelo_holt = Holt(train, initialization_method="estimated").fit()
print(f"alpha = {modelo_holt.params['smoothing_level']:.3f}")
print(f"beta = {modelo_holt.params['smoothing_trend']:.3f}")
prediccion_holt = modelo_holt.forecast(12)
Con la tendencia incorporada, el modelo ya no se rinde: el MAE baja a ≈227 €, mejor que el naive puro pero todavía lejos de ARIMA/SARIMA. Falta una pieza: Holt no sabe nada de estacionalidad, y nuestra serie tiene un pico anual muy marcado en septiembre-diciembre.
Holt-Winters: la pieza que faltaba
Holt-Winters añade una tercera ecuación para la estacionalidad, con su propio parámetro de suavizado γ. Existen dos variantes, exactamente el mismo dilema que ya viste con seasonal_decompose en el capítulo 1:
- Aditiva: la amplitud estacional es constante en el tiempo (
Y = Nivel + Tendencia + Estacionalidad). - Multiplicativa: la amplitud estacional crece con el nivel (
Y = (Nivel + Tendencia) × Estacionalidad).
from statsmodels.tsa.holtwinters import ExponentialSmoothing
# Aditivo: la amplitud del pico de diciembre no depende del nivel general
modelo_hw_add = ExponentialSmoothing(
train,
trend="add",
seasonal="add",
seasonal_periods=12,
initialization_method="estimated"
).fit()
# Multiplicativo: la amplitud del pico crece proporcionalmente al nivel
modelo_hw_mul = ExponentialSmoothing(
train,
trend="add",
seasonal="mul",
seasonal_periods=12,
initialization_method="estimated"
).fit()
print(f"AIC aditivo : {modelo_hw_add.aic:.1f}")
print(f"AIC multiplicativo: {modelo_hw_mul.aic:.1f}")
Con nuestra serie (generada de forma aditiva) el resultado es claro: AIC aditivo ≈ 450.5 frente a AIC multiplicativo ≈ 462.2. El modelo aditivo gana, tal y como esperaríamos sabiendo cómo se construyó la serie.
Puedes inspeccionar los tres componentes que Holt-Winters extrajo — nivel, estacionalidad y el residual que queda sin explicar — con el mismo visor que usaste para seasonal_decompose en el capítulo 1:
Y_t = T_t + S_t + ε_t El residual se mueve de forma irregular alrededor de cero, sin arrastrar el patrón anual que ya capturó el componente estacional — exactamente lo que confirma el test de Ljung-Box a continuación.
from statsmodels.stats.diagnostic import acorr_ljungbox
residuos_hw = train - modelo_hw_add.fittedvalues
ljung = acorr_ljungbox(residuos_hw, lags=[10], return_df=True)
print(ljung)
# p-valor ≈ 0.78 → sin autocorrelación significativa, residuos limpios
El marco ETS: nombrar cada combinación
Con nivel, tendencia (con o sin amortiguar) y estacionalidad (aditiva, multiplicativa o ausente), hay muchas combinaciones posibles. El marco ETS (Error, Tendencia, Estacionalidad) les da un nombre sistemático a todas:
| Letra | Significa | Valores típicos |
|---|---|---|
| E | Error | N (ninguno), A (aditivo), M (multiplicativo) |
| T | Tendencia | N, A (aditiva), Ad (aditiva amortiguada) |
| S | Estacionalidad | N, A (aditiva), M (multiplicativa) |
Así, ETS(A,A,A) es exactamente el Holt-Winters aditivo que acabas de ajustar; ETS(A,N,N) es SES; ETS(A,A,N) es Holt sin estacionalidad.
from statsmodels.tsa.exponential_smoothing.ets import ETSModel
# ETS(A,A,A): error aditivo, tendencia aditiva, estacionalidad aditiva
modelo_ets = ETSModel(
train,
error="add",
trend="add",
seasonal="add",
seasonal_periods=12
).fit(disp=False)
print(modelo_ets.summary())
Holt-Winters vs. SARIMA: ¿cuál eliges?
Con la misma serie y el mismo split (60 meses de entrenamiento, 12 de test), esta es la comparación real:
| Modelo | MAE aprox. | Coste de ajuste |
|---|---|---|
| Naive | 248 € | Ninguno |
| Seasonal naive | 148 € | Ninguno |
| SES | 248 € (degenerado) | Bajo |
| Holt | 227 € | Bajo |
| ARIMA(2,1,2) | 74 € | Medio — requiere identificar p,d,q |
| SARIMA(1,1,1)(1,1,1,12) | 33 € | Alto — identificación + parte estacional |
| Holt-Winters aditivo | 30 € | Bajo — 3 parámetros, sin ADF ni ACF/PACF |
Autoevaluación
Ajustas SimpleExpSmoothing sobre una serie con tendencia creciente y el optimizador devuelve alpha = 1.0. ¿Qué está pasando?
¿Qué distingue al método de Holt de Holt-Winters?
Comparas AIC entre Holt-Winters aditivo (450.5) y multiplicativo (462.2) sobre la misma serie. ¿Qué modelo eliges y por qué?
Necesitas pronosticar 200 series cortas de ventas por categoría cada semana, con un pipeline automatizado. ¿Qué familia de modelos es el punto de partida más razonable?
Reto de código
Resumen
- El suavizado exponencial pondera el presente más que el pasado, con un peso que decae exponencialmente — a diferencia de la media móvil, que trunca de golpe.
- SES modela solo el nivel. Funciona bien en series estables; en series con tendencia, degenera hacia un naive (α → 1), y esa degeneración es en sí misma un diagnóstico útil.
- Holt añade una ecuación de tendencia (β). La variante amortiguada (
damped_trend, φ) evita que esa tendencia se extrapole en línea recta para siempre. - Holt-Winters añade una tercera ecuación de estacionalidad (γ), en versión aditiva o multiplicativa — mismo dilema que la descomposición del capítulo 1.
- El marco ETS nombra cada combinación (Error, Tendencia, Estacionalidad) y se selecciona igual que un ARIMA: comparando AIC y validando residuos con Ljung-Box.
- Frente a SARIMA, Holt-Winters puede alcanzar una precisión similar con muchísimo menos esfuerzo de ajuste — la elección natural cuando necesitas escalar a muchas series.
En los subcapítulos profundizamos en cada pieza:
- 3.1 — Suavizado exponencial simple, paso a paso: por qué los pesos decaen geométricamente y cuándo SES es realmente la herramienta correcta.
- 3.2 — El método de Holt y la tendencia amortiguada: qué ocurre cuando extrapolas una tendencia demasiado lejos, y cómo φ lo corrige.
- 3.3 — Holt-Winters de principio a fin: el caso completo, con selección de modelo, diagnóstico e intervalos de predicción.