3. Graph construction from time-series sensor data
In industrial fault localization, the graph is not a given — it must be inferred from the data and the plant itself. A chemical process unit, a wind turbine drivetrain, or a power distribution feeder rarely ships with a clean adjacency matrix. Instead, we have hundreds of time-series tags sampled at 1 Hz to 10 kHz, plus a P&ID, a wiring diagram, and maintenance logs. The quality of the resulting graph determines whether a GNN can localize a fault to a specific bearing, valve, or busbar, or whether it merely reports "something is wrong somewhere."
This section covers three complementary construction strategies — correlation-based, causality-based, and physical-connectivity-based — and how to combine them into a single weighted adjacency matrix suitable for message passing.
3.1 Correlation-Based Edges: Fast but Directionless
The simplest edge criterion is statistical dependence. Given N sensor channels with T samples, compute the pairwise Pearson correlation matrix R ∈ ℝN×N. An edge exists between nodes i and j if |Rij| exceeds a threshold τ. For non-linear relationships, replace Pearson with Spearman rank correlation or mutual information.
A practical pitfall: raw correlation on non-stationary industrial signals is dominated by common trends (e.g., ambient temperature, load level). Always detrend and normalize first. A common recipe is to difference the signal, apply a rolling z-score with a 10-minute window, then compute correlation on residuals.
import numpy as np
from scipy.stats import zscore
def correlation_adjacency(X, tau=0.6, window=600):
# X: (T, N) raw sensor matrix
Xd = np.diff(X, axis=0) # detrend by differencing
Xz = zscore(Xd, axis=0) # per-channel normalization
R = np.corrcoef(Xz.T) # (N, N)
A = (np.abs(R) >= tau).astype(float)
np.fill_diagonal(A, 0)
return A, R
Correlation edges are symmetric and undirected. They capture co-movement but not influence: a downstream pressure sensor and an upstream flow controller may correlate strongly, yet only one drives the other. For fault localization this matters, because propagation direction determines which node is the root cause.
3.2 Causality-Based Edges: Direction and Lag
To recover direction, use Granger causality or transfer entropy. Granger causality tests whether past values of Xj improve the prediction of Xi beyond Xi's own history. For each ordered pair (i, j), fit two autoregressions and compare residual variances via an F-test:
from statsmodels.tsa.stattools import grangercausalitytests
def granger_edges(X, max_lag=5, alpha=0.01):
N = X.shape[1]
A = np.zeros((N, N))
for i in range(N):
for j in range(N):
if i == j: continue
data = np.column_stack([X[:, i], X[:, j]])
res = grangercausalitytests(data, maxlag=max_lag, verbose=False)
p = min(res[l][0]['ssr_ftest'][1] for l in range(1, max_lag+1))
if p < alpha:
A[j, i] = 1.0 # j Granger-causes i
return A
Note the index convention: A[j, i] = 1 means information flows from j to i. This asymmetry is exactly what we want for fault propagation. In a real compressor skid, for example, vibration at the drive-end bearing typically Granger-causes discharge temperature with a lag of 2–4 samples, while the reverse is not significant.
3.3 Physical Connectivity: The Prior You Already Have
Piping and instrumentation diagrams, electrical single-line diagrams, and network topology files encode hard constraints. Two sensors on the same header are physically coupled; two sensors on isolated branches are not. This prior is invaluable because it is static, reliable, and sparse.
Represent physical connectivity as a binary matrix Aphys where Aphysij = 1 if a pipe, wire, or shaft directly connects the components monitored by sensors i and j. In practice, this is extracted from a tag-to-equipment mapping table and an equipment-to-equipment connection list.
3.4 Fusion: Building the Final Adjacency Matrix
Each source has different reliability and semantics. A robust approach is a weighted sum with a physical mask:
A = Aphys ⊙ (α·Acorr + β·Acause + γ·I)
where ⊙ is element-wise multiplication, I is the identity (self-loops), and α, β, γ are tunable weights. The physical mask ensures that a spurious correlation between two hydraulically isolated lines never becomes an edge. Typical starting values: α = 0.4, β = 0.5, γ = 0.1. These are example defaults; tune them on a validation set of labeled faults.
After fusion, symmetrize if the GNN uses undirected message passing, or keep the directed version for directed GNN variants. Normalize rows to sum to 1 (or use symmetric normalization D−1/2AD−1/2) to stabilize training.
3.5 A Worked Example: Compressor Skid
Consider a three-stage reciprocating compressor with 12 tags: suction pressure (P1), inter-stage pressures (P2, P3), discharge pressure (P4), four bearing vibrations (V1–V4), motor current (I1), lube-oil temperature (T1), and ambient temperature (T2). Sampling at 10 Hz for 8 hours yields 288,000 samples per channel.
Step 1 — Preprocess. Remove startup/shutdown transients, difference each channel, and z-score with a 5-minute rolling window.
Step 2 — Correlation. With τ = 0.6, we obtain edges such as V1–V2 (r = 0.82), P2–P3 (r = 0.74), and I1–P4 (r = 0.68). Ambient T2 correlates with everything weakly (r ≈ 0.3) and is excluded.
Step 3 — Causality. Granger tests with max_lag = 5 and α = 0.01 reveal directed edges: V1 → T1 (p = 0.003), V1 → P4 (p = 0.007), I1 → P4 (p = 0.001). The reverse directions are non-significant.
Step 4 — Physical mask. The P&ID shows V1 and V2 on the same crankshaft (edge kept), V3 on a separate shaft (edge removed even though correlation was 0.55), and T1 in the oil circuit connected to all bearings (edges kept).
Step 5 — Fuse and normalize. Apply α = 0.4, β = 0.5, γ = 0.1, multiply by the physical mask, then row-normalize. The resulting 12×12 matrix has 23 non-zero off-diagonal entries — sparse enough for efficient message passing.
3.6 Practical Checklist
| Stage | Action | Typical Parameter | Failure Symptom |
|---|---|---|---|
| Preprocess | Detrend, z-score, remove transients | Window = 300–600 s | Spurious edges from common trends |
| Correlation | Compute |R|, threshold | τ = 0.5–0.7 | Too dense (τ too low) or disconnected (τ too high) |
| Causality | Granger F-test per ordered pair | max_lag = 3–10, α = 0.01 | Spurious edges on non-linear loops |
| Physical mask | Map tags to equipment, build binary matrix | — | Missing edges if tag mapping is incomplete |
| Fusion | Weighted sum, mask, normalize | α = 0.4, β = 0.5, γ = 0.1 | Over-smoothing if weights too uniform |
The graph you build here becomes the fixed input to every subsequent GNN layer. Getting the edge semantics right — direction, weight, and physical plausibility — is often more impactful than swapping one GNN architecture for another. In the next section, we will see how these graphs feed into spectral and spatial convolution operators.