📊 Time Series Decomposition: Trend, Seasonality & Residuals
Decompose a synthetic time series into trend, seasonality and residuals in real time: moving-average trend extraction, cycle-averaged seasonality, a live DFT periodogram, and STL as the modern robust standard.
About Time Series Decomposition
Time series decomposition splits an observed sequence Y(t) into three interpretable parts: a trend capturing the long-term direction, a seasonal component capturing a pattern that repeats at a fixed period, and a residual — whatever is left after removing both. The classical additive model is Y(t) = Trend(t) + Seasonal(t) + Residual(t). When the size of the seasonal swings grows in proportion to the trend level (e.g. retail sales whose December spike gets bigger as the business grows), a multiplicative model Y(t) = Trend(t) × Seasonal(t) × Residual(t) is the better fit — it is mathematically equivalent to applying the additive model to log Y(t).
This simulator generates a synthetic series from a trend you control (linear or curved), a sinusoidal seasonal pattern of adjustable amplitude and period length, and Gaussian noise — so the ground truth is always known and you can judge exactly how well the decomposition recovers it. The trend is extracted with a real centred moving average (adjustable window size); the seasonal component is extracted by cycle-averaging — averaging the detrended values that fall at the same phase of the cycle, then centring that average pattern to zero mean; the residual is simply what remains: Y − Trend − Seasonal.
A small periodogram panel runs a real discrete Fourier transform (DFT) on the detrended series and plots the magnitude of each frequency component against its corresponding period length. The tallest spike marks the dominant cycle length actually present in the data — compare it to the true seasonal period slider to see how noise, a short series, or a badly chosen moving-average window can bias the naive decomposition. The modern, more robust standard is STL (Seasonal-Trend decomposition using Loess), which replaces the fixed-window moving average with an iterative, locally-weighted regression smoother (Loess) and lets the seasonal pattern itself change gradually over time, rather than assuming it repeats identically forever.
Frequently Asked Questions
What is the difference between additive and multiplicative decomposition?
In the additive model, Y(t) = Trend(t) + Seasonal(t) + Residual(t), the seasonal swings stay roughly constant in absolute size regardless of the trend level. In the multiplicative model, Y(t) = Trend(t) × Seasonal(t) × Residual(t), the seasonal swings scale with the trend — appropriate when, say, holiday sales spikes get proportionally bigger as a business grows. Taking logs turns a multiplicative series into an additive one: log Y(t) = log Trend(t) + log Seasonal(t) + log Residual(t), which is why many implementations simply log-transform the data first.
How does moving-average trend extraction actually work?
A centred moving average replaces each value with the mean of the values in a symmetric window around it — for a window of size w = 2k+1, TrendHat(t) is the average of Y(t−k) … Y(t+k). This averages out both the seasonal oscillation (if the window spans a whole number of cycles) and short-term noise, leaving the slower-moving trend. Near the edges of the series, where a full symmetric window isn't available, this simulator shrinks the window to whatever data exists on each side rather than leaving the trend undefined.
How is the seasonal component estimated from the detrended data?
After subtracting the trend estimate from the series, what remains (the "detrended" series) still contains the seasonal pattern plus noise. Cycle-averaging groups all the detrended points that share the same position in the cycle — e.g. all "week 3 of the cycle" points — and averages them together. Averaging over many cycles cancels out the noise, leaving a clean estimate of the repeating seasonal shape, which is then centred to have zero mean so it doesn't leak into the trend level.
What does the periodogram panel actually compute?
It runs a discrete Fourier transform (DFT) directly — for each candidate frequency k it sums Y(t)·e^(−2πikt/N) over all t — on the detrended series and plots the resulting magnitude against the corresponding period length N/k. Peaks in this "periodogram" mark cycle lengths that are strongly present in the data. Detrending first avoids the huge low-frequency spike a raw trending series would otherwise produce, which would otherwise swamp the true seasonal peak.
Why might the detected period differ from the true seasonal period I set?
With a short series, a large noise level, or a seasonal period that isn't a clean divisor of the sample length, the periodogram's frequency resolution (Δperiod ≈ N/k²) is coarse enough that the peak can land a few steps away from the true value. Very high noise can also make a spurious frequency temporarily out-power the true one. This is a genuine, well-known limitation of periodogram-based period detection, not a simulator bug — it's exactly the kind of uncertainty real analysts have to account for.
How do I choose a good moving-average window size?
The window should ideally match (or be a multiple of) the true seasonal period, so that each window averages over a whole number of cycles and the seasonal ripple cancels out of the trend estimate cleanly. A window that's too short leaves seasonal wiggle in the "trend," while a window that's too long smooths away genuine trend curvature — try setting the window equal to the true seasonal period and watch the trend panel become noticeably smoother.
What is STL and how does it improve on this simulator's method?
STL (Seasonal-Trend decomposition using Loess) iterates between a Loess (locally-weighted polynomial regression) smoother for the trend and cycle-subseries smoothing for the seasonal component, refining both over several passes. Unlike a fixed-window moving average and a single static seasonal pattern, STL lets the seasonal shape drift slowly over time, is robust to outliers via optional weighting, and produces smoother, more statistically defensible trend and seasonal estimates — it's the standard tool in production forecasting pipelines (e.g. R's `stl()`, Python's `statsmodels.tsa.seasonal.STL`).
What does "variance explained" mean here?
It is 1 − Var(Residual)/Var(Y), expressed as a percentage — the fraction of the original series' variance that the trend and seasonal components together account for. A value near 100% means the residual is nearly pure noise (a good decomposition); a low value means either the noise level is genuinely high, or the trend/seasonal extraction is a poor fit to the true underlying pattern.
Why use a discrete Fourier transform instead of just eyeballing the chart?
For a clean, low-noise signal with an obvious period, eyeballing works fine. But as noise grows, or when several periodicities overlap, the human eye struggles to pick out the dominant cycle reliably. The DFT decomposes the signal into a precise sum of sinusoids at each possible frequency, giving an objective, quantitative ranking of which cycle lengths actually carry the most energy — the same mathematical machinery behind audio spectrum analysers and JPEG compression.
Decompose a synthetic time series into trend, seasonality and residuals in real time — moving-average trend extraction, cycle-averaged seasonality, and a live DFT periodogram.
2D · HTML5 Canvas 2D · 60 FPS target · runs fully client-side, no install