JA EN
LearnTime Series
·★ MEMBER·PAPER·11 min read

Time-Series Anomaly Detection — The Math Behind the Alerts

An alert should fire on the gap between what you observed and what that moment predicted — not on the raw size of a number. Four rulers for measuring that gap (robust statistics, forecast residuals, subsequence distance, changepoints), built up from analogy to formula, then the two things that actually break in production: how you pick the threshold and how you evaluate.

ModalitytextTaskforecasting

Bayesian Online Changepoint Detection


2 a.m., and the dashboard turns red

Fire an alert when CPU goes above 90%. That is where monitoring almost always starts. Run it for a month and this almost always happens: a heavy batch job runs early every Sunday, CPU crosses 90%, and the on-call engineer is woken up for nothing. So you raise the threshold to 95% — and now a real weekday incident plateaus at 92% and nobody notices.

The number isn't the problem. The problem is that you defined "anomaly" purely by magnitude. 90% at 3 a.m. on Sunday is normal; 70% on Tuesday afternoon might not be. In a time series, an anomaly is not a value — it is a gap between what you observed and what that moment led you to expect. This article builds four rulers for measuring that gap: summary statistics, forecast residuals, subsequence distance, and changepoints. All you need to bring is a memory of what a mean and a standard deviation are.

Anomalies come in three shapes

A point anomaly is a single value sitting far away from the rest — a sensor glitch that reads 9999 for one sample.

A contextual anomaly is a perfectly ordinary value that should not happen at that moment: heating load spiking in midsummer, or 3 a.m. traffic matching the lunchtime peak. Nothing about it is unusual in the marginal distribution, so no amount of staring at a histogram will surface it.

A subsequence anomaly is a stretch where every individual value is ordinary but the shape is wrong: one irregular beat in an ECG, a vibration sensor whose period wobbles. Look at points independently and you will never see it.

The last two are what make time series distinct, and they are also the two that hurt in production.

Ruler 1: statistical outlyingness

Start with the classic. Measure the distance from the mean in units of standard deviation — the z-score.

zt=xtμσz_t = \frac{x_t - \mu}{\sigma}
(1)

Here xtx_t is the observation at time tt, μ\mu the mean, σ\sigma the standard deviation. In words: how many standard deviations away from average is this? Under a normal distribution z>3|z| > 3 happens roughly 0.3% of the time, which is where the familiar "three sigma" convention comes from.

Simple — and broken in two ways.

First, an anomaly hides itself. You computed μ\mu and σ\sigma from data that contains the anomaly. One enormous spike inflates σ\sigma, which shrinks its own zz. The bigger the anomaly, the harder it is to see; statisticians call this masking. The fix is to swap in statistics that outliers cannot drag around: the median, and MAD (median absolute deviation).

MAD=median(xtmedian(x)),ztrob=xtmedian(x)1.4826MAD\mathrm{MAD} = \mathrm{median}\big(|x_t - \mathrm{median}(x)|\big), \qquad z^{\text{rob}}_t = \frac{x_t - \mathrm{median}(x)}{1.4826 \cdot \mathrm{MAD}}
(2)

In words: use "the median of the deviations from the median" as your measure of spread. The 1.48261.4826 is only a scaling constant that makes this agree with σ\sigma for normally distributed data — there is no deeper meaning to it. Because the median barely moves until nearly half the data is contaminated, a handful of spikes cannot bend the ruler.

Second, the mean is not constant. With a trend, last year's average is no baseline for today; with a daily cycle, "the average day" describes neither noon nor midnight. The fix is to compute μ\mu and σ\sigma over a recent window rather than all of history. Manufacturing has been running control charts on exactly this idea for the better part of a century.

import numpy as np

def robust_z(x, window=288):                 # 288 = one day at 5-minute resolution
    med = np.median(x[-window:])
    mad = np.median(np.abs(x[-window:] - med))
    return (x[-1] - med) / (1.4826 * mad + 1e-9)

The window length is your first hyperparameter. Short windows are responsive, but a sustained anomaly quietly becomes the new normal. Long windows are sluggish and keep alerting on legitimate change. That tug-of-war reappears, in some disguise, in every method below.

Ruler 2: forecast, then look at what's left over

Flip the framing. Build a model that predicts the series when nothing is wrong, and use the gap between prediction and observation — the residual — as your anomaly score.

et=xtx^te_t = x_t - \hat{x}_t
(3)

x^t\hat{x}_t is the prediction. In words: anomaly detection has been decomposed into a forecasting problem plus some cleanup.

That decomposition earns its keep because it pushes trend, seasonality, and calendar effects entirely into the model. Go back to the Sunday batch job: if the forecast already knows "Sunday pre-dawn runs at 90%," then observing 90% leaves a residual near zero and nothing fires. Tuesday's 70%, against a forecast of 40%, produces a large residual. That is exactly the goal we set at the start — measuring deviation from context instead of magnitude. For building the forecaster itself, see Time-Series Forecasting from Scratch.

There is a trap here, though, and it is a big one. A forecaster that is too flexible will reproduce the anomalies too. Train a high-capacity model until the residuals vanish and it will predict the spikes along with everything else, leaving your detector permanently silent. An anomaly-detection forecaster should be deliberately blunt.

FIG 1Raise the degree and the curve traces the training data ever more faithfully. In anomaly detection that fidelity is the enemy — once the model predicts the anomalies too, the residuals go to zero and the detector goes quiet

The most common mistake after obtaining residuals is to declare "anomalous if |residual| > 5." Residual spread varies by time of day: overnight traffic is small so residuals are small, while at the daytime peak the same *proportional* deviation produces a much larger absolute residual. A fixed threshold gives you a det

What's behind this

§

Members-only from here

371 walkthroughs, 26 textbook chapters, 48 student units and 6 close readings — all included for $4.99/mo, with three new explainers every day. Cancel any time; access runs to the end of the period.

Already a member? Sign in to keep reading

References

  1. Bayesian Online Changepoint Detection. arXiv:0710.3742Paper page·PDF
  2. Towards a Rigorous Evaluation of Time-series Anomaly Detection. arXiv:2109.05257Paper page·PDF
  3. Current Time Series Anomaly Detection Benchmarks are Flawed and are Creating the Illusion of Progress. arXiv:2009.13807Paper page·PDF

This article is written from the source paper above. Where they differ, the original is authoritative.

Comments

Sign in to comment