Anomaly Detection Across Correlated Sensor Streams
10 min read · updated August 11, 2026
A pump whose bearing is failing draws slightly more current at unchanged flow. Neither number leaves its normal range. What has changed is the relationship between them, and that is a different detection problem from scoring either series on its own.
The failure single-series scoring misses
Per-channel anomaly detection — thresholds, z-scores, a seasonal decomposition with a residual bound — asks whether this value is unusual for this sensor. It is the right question for a stuck valve or a blown fuse, and it is cheap. Its blind spot is any fault whose signature is distributed: each channel moves a little, all stay inside their own envelope, and the joint state is somewhere the system has never been.
A concrete version. A chiller has a suction pressure that normally runs between 3.0 and 4.5 bar and a compressor current that normally runs between 11 and 18 A, and in healthy operation the two track each other closely because both follow load. A refrigerant leak lowers pressure and lowers current, but not in the usual proportion: pressure falls faster. Reading 3.2 bar and 16 A, both individually unremarkable, is a combination that has essentially never occurred. No per-channel detector will fire, and the state is plainly wrong to anyone who has ever plotted the two against each other.
The same argument explains why adding sensors does not by itself improve detection. Ten independent detectors on ten channels produce ten times the alert volume and detect the same class of fault. What the extra channels actually buy is the joint structure between them, and using it requires modelling it explicitly.
Monitor the residual, not the reading
The general construction is: build a model that predicts one channel from the others, then treat the prediction error — the residual — as the series you monitor. If the relationship holds, the residual is small, roughly zero-mean, and roughly stationary, which is exactly the well-behaved signal that simple univariate methods are good at. If the relationship breaks, the residual moves even when neither raw channel does.
# fit on a window known to be healthy current_hat = a * pressure + b # ordinary least squares r_t = current_t - current_hat_t # the monitored residual # healthy window statistics mu_r = mean(r_t) sigma_r = std(r_t) # score z_t = (r_t - mu_r) / sigma_r # alarm on |z_t| sustained > 3
Two details make the difference between this working and generating noise. First, alarm on a sustained excursion, not a single sample: requiring the score to stay outside the bound for k consecutive windows converts a per-sample false-positive rate into something far smaller, at the cost of k windows of detection delay. That trade is the one real tuning decision. Second, the healthy window has to actually be healthy — a fit contaminated by a fault learns the fault as normal, which is the reason this needs the same careful ground-truth work described in labelling industrial IoT data.
Doing it for many channels at once
Pairwise regressions do not scale: twenty channels give 190 pairs, most of them uninformative. The standard multivariate treatment is to model the whole covariance structure once.
Principal component analysis on a healthy window splits the variation into a few directions that capture almost all of it and a remainder. Healthy operation lives close to the subspace spanned by the first few components; a fault that breaks the correlation structure pushes the point off it. Two statistics come out of this and they mean different things. Hotelling’s T² measures distance from the centre within the retained subspace — unusual but structurally normal operation, like an unusually heavy load. The squared prediction error, sometimes written Q or SPE, measures distance off the subspace — the relationship itself has changed. For fault detection the second is usually the interesting one, and reporting only a combined score throws that distinction away.
The same idea in Mahalanobis form uses the inverse covariance matrix directly, which is what scikit-learn’s EllipticEnvelope fits. It is exact for jointly Gaussian data and it needs enough samples to estimate the covariance stably: with d channels the matrix has d(d+1)/2 free parameters, so twenty channels need at least a few hundred independent samples and preferably thousands. Sensor data is heavily autocorrelated, so “a thousand samples” at 10 Hz is not a thousand independent observations; it is closer to however many distinct operating episodes are in the window.
When the relationship is not linear
Many physical relationships are not lines. Fan power goes roughly with the cube of speed, flow through an orifice with the square root of differential pressure. A linear residual monitor on a cubic relationship has a residual that is a function of operating point, so it fires whenever the machine runs hard.
Three approaches, in increasing order of what they demand of you. Fit the physics: if the relationship is known to be a power law, regress in log space and the linear machinery works again. Fit a nonlinear regressor per target channel — a gradient-boosted tree on the other channels predicts one channel well and its residual is still just a series. Or fit an autoencoder on the whole vector and use reconstruction error, which is the same off-subspace idea as PCA’s SPE with a nonlinear manifold; it needs far more healthy data and it is much harder to explain to the person who has to act on the alert.
Isolation Forest, from Liu, Ting and Zhou’s 2008 ICDM paper, is often reached for here and is worth understanding correctly: it scores how few random axis-aligned splits are needed to isolate a point. That makes it fast and distribution-free, and it means it is weak at exactly this problem, because a point inside every channel’s marginal range but off the correlation ridge is not easy to isolate with axis-aligned cuts. Use it for gross outliers, not for a broken relationship.
Four ways this goes wrong in production
- Misaligned timestamps manufacture false correlations and destroy real ones. A residual model fitted on channels that are 300 ms apart learns a relationship that is partly an artefact of the lag. Fix the alignment first; clock offset correction is a prerequisite for this whole method, not a refinement.
- One dead channel poisons every score. If a sensor flatlines, the multivariate distance explodes and every alert afterwards is about that sensor. Detect the failing sensor first and drop the channel from the model rather than letting it dominate.
- Regime changes are not faults. A plant that switches between two products has two correlation structures. One model averaged over both fits neither. Either condition the model on the regime label or fit one model per regime.
- The alert has to name a channel. A scalar distance tells an operator that something is wrong and nothing else. Report the per-channel contributions to the score — for SPE that is just each channel’s squared residual as a share of the total — so the alert says which relationship broke.