Descomposición STL
Cualquier serie de tiempo puede entenderse como la suma de unas pocas piezas más simples: una tendencia subyacente, un patrón estacional que se repite y lo que queda sin explicar. La descomposición STL (Seasonal-Trend decomposition using Loess) es la forma estándar y robusta de dividir una serie exactamente en estos tres componentes. Esta entrada se apoya en los componentes ya introducidos en qué es una serie de tiempo, y encaja de forma natural con comprobar la estacionariedad, ya que eliminar la tendencia y la estacionalidad suele ser el primer paso hacia una serie estacionaria.
El modelo de descomposición aditiva
\[y_t = T_t + S_t + R_t\]
donde \(T_t\) es el componente de tendencia (el nivel subyacente de evolución lenta), \(S_t\) es el componente estacional (un patrón que se repite con un periodo fijo y conocido, por ejemplo cada 12 meses), y \(R_t\) es el residuo (lo que la tendencia y la estacionalidad no explican).
Existe también una alternativa multiplicativa, \(y_t = T_t \times S_t \times R_t\), que se usa cuando las oscilaciones estacionales crecen proporcionalmente con el nivel de la serie en lugar de mantener un tamaño absoluto constante. Equivale a aplicar una descomposición aditiva sobre \(\log(y_t)\).
Descomposición clásica: la referencia más simple
El enfoque clásico al estilo decompose() funciona en dos pasos: estima la tendencia con una media móvil cuya ventana coincide con el periodo estacional, la resta, y después promedia los valores destendenciados dentro de cada posición estacional (por ejemplo, promediar todos los eneros) para obtener un único componente estacional fijo que se repite. Lo que queda tras eliminar ambos es el residuo.
Esto tiene dos debilidades reales, y la STL corrige ambas:
- La estimación de la tendencia pierde el primer y el último medio periodo de observaciones: una media móvil centrada necesita datos a ambos lados, así que no puede calcularse justo en los extremos de la serie.
- El componente estacional está obligado a ser perfectamente idéntico en cada ciclo, no puede cambiar de forma gradual con el tiempo. Un patrón estacional real que deriva poco a poco, por ejemplo unas ventas de verano cada vez más dominantes año tras año, no puede capturarse en absoluto.
STL: qué la hace diferente
La STL sustituye la media móvil de la tendencia y el promedio estacional fijo por un suavizado de regresión local (LOESS) para ambos componentes, aplicado de forma iterativa. Esto ofrece dos ventajas concretas frente a la descomposición clásica:
- El componente estacional puede cambiar lentamente de forma con el tiempo en lugar de quedar congelado en un único ciclo repetitivo.
- La STL es robusta frente a valores atípicos: una observación extrema puede recibir menos peso para que no arrastre la estimación de la tendencia o de la estacionalidad, quedando aislada en el residuo en su lugar.
Ejemplo resuelto: descomponer 6 años de ventas mensuales
Datos de ventas mensuales simuladas, 72 meses (6 años), con una tendencia ascendente, un ciclo estacional anual claro y ruido:
set.seed(3)
n_months <- 72
t <- 1:n_months
trend <- 500 + 8*t
seasonal <- 150*sin(2*pi*t/12) + 60*cos(2*pi*t/12)
noise <- rnorm(n_months, 0, 40)
y <- trend + seasonal + noise
ts_y <- ts(y, frequency = 12, start = c(2019, 1))
fit_stl <- stl(ts_y, s.window = "periodic")
Valores reales del primer año (12 meses) y los componentes STL resultantes:
| Mes | \(y_t\) (ventas) | Tendencia \(T_t\) | Estacional \(S_t\) | Residuo \(R_t\) |
|---|---|---|---|---|
| 1 | 596,48 | 497,10 | 119,52 | -20,14 |
| 2 | 664,20 | 505,96 | 152,40 | 5,84 |
| 3 | 684,35 | 514,82 | 157,62 | 11,91 |
| 4 | 585,82 | 523,43 | 97,48 | -35,09 |
| 5 | 570,87 | 532,03 | 31,88 | 6,96 |
| 6 | 489,20 | 540,49 | -87,32 | 36,04 |
| 7 | 432,46 | 548,94 | -110,15 | -6,34 |
| 8 | 448,76 | 557,41 | -142,95 | 34,30 |
| 9 | 373,25 | 565,88 | -164,35 | -28,29 |
| 10 | 530,79 | 573,61 | -91,43 | 48,61 |
| 11 | 535,17 | 581,35 | -20,49 | -25,69 |
| 12 | 610,75 | 588,52 | 57,77 | -35,55 |
La identidad aditiva se cumple exactamente para el mes 1: \(497{,}10 + 119{,}52 - 20{,}14 = 596{,}48\).
A lo largo de los 72 meses completos, la tendencia sube de unos 497 a unos 1.072, el componente estacional oscila entre aproximadamente -164 y +158 (un ciclo anual claro con máximo hacia marzo y mínimo hacia septiembre), y el residuo tiene una desviación típica de 31,1. La tendencia y la estacionalidad juntas explican en torno al 97,6% de la varianza total de la serie (varianza del residuo de 965,8 sobre una varianza total de 39.782,2), solo un 2,4% queda como ruido sin explicar, lo que confirma que la descomposición capturó bien los dos patrones sistemáticos.

La tendencia (verde) sube de forma suave y sostenida. El componente estacional (naranja) se repite cada 12 meses con una forma estable. El residuo (gris) no muestra ningún patrón visible, exactamente lo que debe dejar una buena descomposición.
⚠️ Un residuo grande o con patrón indica que la descomposición se dejó algo por explicar
El residuo debe parecer ruido sin estructura: sin tendencia visible, sin patrón repetitivo, sin agrupaciones de residuos inusualmente grandes. Si el residuo sigue mostrando un patrón claro, una deriva lenta que queda sin capturar, u oscilaciones mayores en ciertos meses cada año, normalmente significa que el periodo estacional se especificó mal, que hay un segundo ciclo estacional que el modelo no está capturando (por ejemplo un patrón semanal Y otro anual a la vez en datos diarios), o que existe una ruptura estructural, un cambio de régimen repentino, que una descomposición suave simplemente no puede representar.
Representa siempre el residuo gráficamente e inspecciónalo. No te fíes solo de que los paneles de tendencia y estacionalidad parezcan razonables.
Elegir la ventana estacional (s.window)
El argumento s.window controla cuánto se permite que cambie el componente estacional con el tiempo. s.window = "periodic", usado en el ejemplo anterior, obliga a un patrón estacional perfectamente fijo e invariable, equivalente al paso estacional de la descomposición clásica. Es una opción por defecto segura cuando no hay motivo para esperar que el propio patrón estacional evolucione.
Un valor numérico impar, por ejemplo s.window = 7, permite en cambio que la forma estacional derive lentamente con el tiempo, útil para series donde el patrón estacional cambia realmente de un año a otro.
Ejecutarlo en R
ts_y <- ts(y, frequency = 12, start = c(2019, 1))
# Descomposición STL
fit_stl <- stl(ts_y, s.window = "periodic")
plot(fit_stl)
components <- fit_stl$time.series # matriz con las columnas seasonal, trend, remainder
# Descomposición clásica para comparar
fit_classical <- decompose(ts_y, type = "additive")
plot(fit_classical)
# Serie ajustada estacionalmente (la original menos el componente estacional)
seasonally_adjusted <- ts_y - components[, "seasonal"]
💡 STL frente a descomposición clásica: cuál usar
Usa STL por defecto. Maneja un patrón estacional cambiante, es robusta frente a valores atípicos, y no pierde observaciones al principio y al final de la serie como sí hace la media móvil de la descomposición clásica. La descomposición clásica sigue siendo útil hoy como primer vistazo rápido y sencillo, o cuando alguna herramienta posterior espera específicamente su formato de salida.
La STL solo maneja un único periodo estacional a la vez (la frequency fijada en el objeto ts). Para series con varios ciclos estacionales, por ejemplo un patrón semanal y otro anual a la vez en datos diarios, usa mstl() del paquete forecast, que extiende la STL a estacionalidades múltiples.