Part II: Classical Signal Processing and Feature Engineering
Chapter 7: Spectral and Time-Frequency Analysis

Power spectral density and coherence

"A single FFT tells you what one window happened to contain. A power spectral density tells you what the process keeps doing."

A Patient AI Agent

The big picture

The raw FFT of Section 7.1 answers a question about one particular record: which sinusoids reconstruct these exact samples. That is the wrong question for most sensor work, because your accelerometer or your EEG electrode is watching a random process, and a single spectrum of a random process is a noisy estimate whose variance never shrinks no matter how long you record. The power spectral density (PSD) reframes the goal: estimate how the average power of the process distributes across frequency, with a variance you can actually drive down. A single FFT describes one recording; a PSD describes the process behind it. Once you have a stable PSD for one channel, the same machinery extended to two channels gives you the cross-spectral density and its normalized cousin coherence, which measures, frequency by frequency, how linearly two signals are coupled.

This section assumes the sampling, aliasing, and windowing ideas from Chapter 3 and the leakage-versus-resolution tradeoff of Section 7.1. It also leans on the notion of a stationary random process and its autocorrelation from the probability primer in Chapter 4.

What a PSD is, and why the periodogram alone fails

For a wide-sense-stationary process \(x(t)\) with autocorrelation \(r_x(\tau) = \mathbb{E}[x(t)x(t+\tau)]\), the power spectral density is its Fourier transform (the Wiener-Khinchin theorem):

$$S_x(f) = \int_{-\infty}^{\infty} r_x(\tau)\, e^{-j 2\pi f \tau}\, d\tau .$$

The units are power per hertz: \((\text{units of }x)^2/\text{Hz}\), so an accelerometer PSD reads in \(g^2/\text{Hz}\) and integrating \(S_x(f)\) over a band returns the mean-square (the power) the signal carries in that band. That integral property is what makes a PSD a measurement rather than a picture: the area under a resonance peak is the energy that will fatigue a bracket or heat a coil.

The naive estimator is the periodogram, \(\hat{S}(f) = \tfrac{1}{N}\lvert X(f)\rvert^2\), the squared magnitude of the DFT. It is asymptotically unbiased, but it has a fatal defect: its variance does not decrease as the record length \(N\) grows. Doubling your data doubles the number of frequency bins but leaves each bin just as noisy, roughly 100% relative error, because each bin is essentially a single sample of a chi-squared random variable. A periodogram of a smooth process looks like grass: spiky, irreproducible, misleading, and no greener for more data.

Welch's method: trading resolution for a variance you can control

The cure is averaging, and the standard recipe is Welch's method. Split the record into \(K\) segments, overlapping (typically 50%), apply a window \(w[n]\) to each to control leakage, compute a periodogram per segment, and average them:

$$\hat{S}_{\text{Welch}}(f) = \frac{1}{K}\sum_{k=1}^{K} \frac{1}{U}\left\lvert \sum_{n} x_k[n]\, w[n]\, e^{-j2\pi f n}\right\rvert^2 ,$$

where \(U = \sum_n w[n]^2\) normalizes for the window's power so the PSD stays correctly scaled. Averaging \(K\) roughly independent periodograms cuts the variance by about \(K\). The catch is the classic bias-variance dial: shorter segments give more of them (lower variance) but coarser frequency resolution \(\Delta f = f_s / L\) for segment length \(L\), and a fatter window main lobe (more bias). Overlap recovers independence lost to windowing, letting you average more segments from the same data. You are, once again, spending resolution to buy stability: the exchange rate you pick depends on whether you need to resolve two close resonances or measure a broadband floor.

Key insight: a PSD is an estimate with a confidence interval

Because Welch averages \(K\) chi-squared periodograms, each PSD point behaves like a scaled \(\chi^2\) with about \(2K\) degrees of freedom (more when segments overlap and share window taper). That means every peak and every floor in your PSD carries a quantifiable uncertainty, and two PSDs that differ by less than that band are not actually different. Treating a PSD as a deterministic curve is the single most common spectral error in sensor pipelines: people read a 2 dB bump as a fault when the estimator's own noise is 3 dB. The habit that saves you is to always report the number of averages, so a reader can reconstruct the confidence band, in the same spirit as the uncertainty discipline of Chapter 4.

Cross-spectra and coherence: coupling between two channels

Sensor systems rarely have one channel. Given two stationary signals \(x\) and \(y\), the cross-spectral density \(S_{xy}(f)\) is the Fourier transform of their cross-correlation, and estimated exactly like a Welch PSD but multiplying one segment's DFT by the complex conjugate of the other's before averaging. From it comes the magnitude-squared coherence:

$$\gamma_{xy}^2(f) = \frac{\lvert S_{xy}(f)\rvert^2}{S_x(f)\, S_y(f)} , \qquad 0 \le \gamma_{xy}^2(f) \le 1 .$$

Coherence is the frequency-domain analogue of a squared correlation coefficient: at each frequency it reports how much of \(y\)'s power is linearly predictable from \(x\). A value near 1 means the two channels are tightly, linearly coupled at that frequency (a common source, or one driving the other through a linear path); a value near 0 means they are unrelated there. The cross-spectrum also carries phase, \(\angle S_{xy}(f)\), and for a pure delay \(y(t) = x(t-\tau)\) that phase grows linearly with frequency, \(\angle S_{xy}(f) = -2\pi f \tau\), so dividing the unwrapped phase by \(-2\pi f\) recovers \(\tau\) directly: a phase slope, not a single phase value, is the quantity you fit. That slope is trustworthy only where coherence is high, because phase is meaningless once noise dominates a bin (it becomes an average of arbitrary angles), and because a phase that changes quickly with frequency can wrap past \(\pm\pi\) and alias into a shorter, wrong apparent delay unless you unwrap across frequency first. Coherence-weighted, unwrapped phase is how you locate a leak by the lag between two acoustic sensors or estimate a transfer function \(H(f) = S_{xy}(f)/S_x(f)\) for a structure under vibration.

Misconception: phase is not delay until coherence says so

A common mistake is reading cross-spectral phase at a single bin as the delay between two sensors, even where the channels barely correlate. Low coherence makes the phase estimate meaningless, an average of noise that still returns some number. Fit the phase slope only where coherence clears several multiples of the \(1/K\) bias floor, and unwrap the phase across frequency first, or a wrapped phase can masquerade as a fast, wrong delay instead of the slow, correct one.

Warning: coherence without averaging is a lie

Plug a single segment (\(K = 1\)) into the coherence formula and it returns exactly 1 at every frequency, always, for any two signals, because \(\lvert S_{xy}\rvert^2 = S_x S_y\) identically when nothing is averaged. Coherence only becomes meaningful after averaging several segments, and its estimate is biased upward: with \(K\) averages, even two independent noise channels show an expected coherence of about \(1/K\). Always average enough segments that the bias floor sits well below the coupling you care about, and compare measured coherence against that \(1/K\) floor before believing it.

Practical example: sourcing a cabin boom in an electric vehicle

An EV NVH team hears a low-frequency boom in the cabin near 55 km/h. They mount an accelerometer on the rear subframe and a microphone at the driver's ear, log both at 8192 Hz during a steady cruise, and compute the coherence. Across most of the spectrum the two channels are unrelated, but at 38 Hz coherence spikes to 0.9: nine-tenths of the acoustic boom is linearly explained by that structural mode. The cross-spectral phase gives the propagation delay, and the transfer function quantifies decibels of cabin noise per unit of subframe vibration. The fix, a tuned damper on the subframe, is validated the same way: after the change, coherence at 38 Hz collapses. A plain PSD of either channel alone would have shown the 38 Hz peak but could never have proven the structural path was the cause, which is why root-cause work leans on coherence, not spectra alone, as in the interpretability methods of Chapter 67.

Computing it in practice

The code below estimates a PSD, a cross-spectrum, and coherence for two coupled signals: \(y\) is a filtered, delayed copy of a shared band-limited source plus independent noise. Welch handles the segmentation, windowing, overlap, and averaging; coherence peaks in the shared band and stays near the \(1/K\) floor elsewhere.

import numpy as np
from scipy import signal

fs = 8192
n = 200_000
rng = np.random.default_rng(0)
src = signal.lfilter(*signal.butter(4, [30, 45], "bandpass", fs=fs),
                     x=rng.standard_normal(n))          # shared 30-45 Hz source
x = src + 0.5 * rng.standard_normal(n)                  # channel 1 + noise
y = np.roll(src, 12) + 0.5 * rng.standard_normal(n)     # delayed copy + noise

f, Sxx = signal.welch(x, fs, nperseg=4096)              # PSD, units^2/Hz
f, Sxy = signal.csd(x, y, fs, nperseg=4096)             # cross-spectral density
f, Cxy = signal.coherence(x, y, fs, nperseg=4096)       # magnitude-sq coherence

band = (f > 30) & (f < 45)
print(f"mean coherence in 30-45 Hz band: {Cxy[band].mean():.2f}")
print(f"mean coherence outside band:     {Cxy[~band].mean():.2f}")
One shared band-limited source drives two noisy, relatively delayed channels. Welch, csd, and coherence share the same segmentation so their frequency axes align; coherence is high (near 1) in the coupled band and near the small 1/K bias floor everywhere else.

Library shortcut: scipy.signal.welch, csd, coherence

A correct-from-scratch Welch estimator (segmenting, windowing, power normalization by \(\sum w^2\), overlap bookkeeping, one-sided scaling, the \(f_s\) axis) is roughly 40 to 60 lines; the coherence version, with cross-spectral conjugation and per-bin normalization, adds another 20. SciPy collapses each to one call with vetted defaults (Hann window, 50% overlap, one-sided PSD). Keep nperseg and the window fixed across a dataset so segment counts and confidence bands stay comparable, in the leakage-safe spirit of Chapter 5. Reach for the from-scratch version only for a nonstandard estimator, such as a multitaper PSD.

When to reach for PSD and coherence

Use a PSD for a roughly stationary signal when you want a stable, reproducible view of power across frequency: a sensor's noise floor (Chapter 2), a vibration environment, or band-power features for the classifiers of Section 7.7 and Chapter 8. Use coherence when the question is coupling between two channels: EEG functional connectivity (Chapter 31), structural transfer functions, leak localization, or sensor-fusion checks that redundant sensors actually agree (Chapter 48). Skip both when the signal is strongly nonstationary within your averaging window, since averaging then blurs distinct regimes into a meaningless mean; segment first or move to the time-frequency tools of Section 7.2. And remember coherence measures only linear coupling; two channels can be perfectly dependent through a nonlinear law yet show low coherence.

Research frontier: coherence when nothing stays still

Classical coherence assumes the coupling itself is stationary over the averaging window, but EEG connectivity during a seizure, gait coupling during a fall, and vibration coupling during machine startup all shift faster than a comfortable Welch window allows. Active work on time-varying and wavelet coherence, adaptively weighted multitaper methods, and state-space models that track \(S_{xy}(f,t)\) as a filtered latent process aims to shrink the averaging window without pushing the \(1/K\) bias floor past usefulness. A related thread borrows the sequence models of Chapter 14 to learn coupling estimators from labeled events directly, trading Welch's clean guarantees for sensitivity to the short bursts of coupling that averaging would smear away.

Exercise

Generate 60 seconds of a 1000 Hz signal that is a 50 Hz sinusoid buried in white noise at 0 dB SNR. Estimate its PSD three ways: a single periodogram over the whole record, Welch with nperseg=1024, and Welch with nperseg=8192. Overlay them. Quantify the variance of the noise floor in each and relate it to the number of averages \(K\). Then explain why the 8192-point version resolves the 50 Hz line more sharply but has a noisier floor than the 1024-point version. Which would you ship for detecting a narrowband tone, and which for measuring broadband noise power?

Self-check

  1. Why does a periodogram's variance fail to decrease with record length, and what specifically does Welch averaging change to fix it?
  2. What are the units of an accelerometer PSD, and what physical quantity do you get by integrating it over a frequency band?
  3. You compute coherence from a single unaveraged segment and get 1.0 everywhere. Explain the mechanism, and state how many averages you would need so that an independent-noise coherence floor sits below 0.1.

What's Next

In Section 7.7, we step back from individual transforms and ask the design question that governs this whole chapter: given a modality and a task, which representation earns its keep. We will weigh the FFT, spectrogram, wavelet, Hilbert-Huang, cepstral, and PSD tools you have now met against the signal's stationarity, the compute budget, and what a downstream model can actually use, turning a menu of methods into a decision.