ACF and PACF of an AR process
correlationSet the two AR coefficients and watch the ACF decay while the PACF cuts off after lag 2, the rule that reads model order off the data.
Time-series & spectral analysis · vibration-based condition monitoring
A field guide to the indicators that turn a raw vibration signal into a diagnosis: its correlation structure, its frequency content, its impulsiveness, and where in time a transient lives.
A turning machine writes its condition into its vibration. The art is reading it: pulling a fault's signature out of noise and other healthy motion, and tying it to the kinematics so a peak becomes a named defect. The indicators below organize by the question they answer:
What periodicity and memory does the signal carry, and what model order describes it?
Which lines and bands hold the energy, and which match a shaft, mesh, or bearing defect rate?
Is the signal impulsive or nonlinear, and where in time does a transient occur?
Each card gives the idea in plain words, a system-health example, a figure, and runnable Python. Five live explorers, a worked case study on a bearing fault, and a closing "going further" set, automatic band selection, order tracking, synchronous averaging, and the cyclic spectrum, round it out. A decision map keys every method back to the question it answers.
Three live tools. Shape an AR process and watch the PACF read off its order, sweep a bearing defect frequency through noise and watch the envelope spectrum recover it, or add impacts and watch kurtosis climb.
Set the two AR coefficients and watch the ACF decay while the PACF cuts off after lag 2, the rule that reads model order off the data.
Impulsive bursts ring a resonance at the defect rate. Sweep the defect frequency and the noise, and watch the envelope spectrum recover the line and its harmonics, or lose it in noise.
Add sharp impacts to a near-Gaussian signal and watch the kurtosis and crest factor climb past their healthy values, the early sign of a bearing or gear defect.
Before the frequency domain, the autocorrelation says how a signal relates to its own past. That structure reveals periodicity, sets the order of a model, and is the basis of the whiteness checks that model-based detectors rely on.
The correlation of a signal with lagged copies of itself, lag by lag, with a white-noise band. It exposes periodicity and memory; for a moving-average process it cuts off at the order, and a slowly decaying ACF means strong serial dependence.
The autocorrelation function asks, for each lag, how much a sample looks like the sample that many steps earlier. A pure random signal correlates with nothing but itself at lag zero, so every other bar sits inside a narrow band around zero. Real signals carry memory: a rotating machine repeats, so its ACF shows peaks at the rotation period and its multiples, and a damped resonance shows a decaying oscillation. The shape is diagnostic, a sharp cutoff after a few lags points to a moving-average structure, a slow decay to a long memory, and any bar that clears the band is correlation worth modeling.
A gearbox vibration signal repeats once per shaft revolution. Its autocorrelation shows a clear peak at the shaft period and smaller peaks at the harmonics, confirming the dominant periodicity before any spectrum is computed and giving the lag at which to look for sidebands.
import numpy as np
def acf(x, nlags=40):
x = np.asarray(x, float) - np.mean(x); n = len(x)
c0 = x @ x / n
r = [ (x[:n-k] @ x[k:]) / n / c0 for k in range(nlags+1) ]
band = 1.96 / np.sqrt(n) # white-noise 95% band
return np.array(r), band
r, band = acf(vibration, 40)
Remove the mean and any trend before computing, or low-frequency drift dominates the early lags. Read the band as a guide, not a verdict, since at many lags a few bars clear it by chance. For a fault indicator, trend the autocorrelation at the kinematic lag rather than scanning all lags each time.
Box, Jenkins & Reinsel (2008), Time Series Analysis; Brockwell & Davis (1991), Time Series.
The autocorrelation at a lag after removing the effect of all shorter lags. A sharp cutoff after lag p is the fingerprint of an order-p autoregressive structure, and an information criterion confirms the order.
A plain autocorrelation at lag three is inflated by the chain of correlations running through lags one and two. The partial autocorrelation strips those out, reporting only the direct relationship at each lag, computed efficiently by the Durbin-Levinson recursion. That makes it the natural order-selection tool: an autoregressive process of order p has a partial autocorrelation that is large up to lag p and then drops inside the band. To confirm the choice rather than read it by eye, fit autoregressive models of increasing order and score them with an information criterion, where AIC is efficient but tends to over-fit and BIC is parsimonious, taking the order at the minimum.
A bearing housing behaves like a lightly damped resonator driven by noise, well described by a low-order autoregressive model. The partial autocorrelation clears the band only at the first two lags, and BIC bottoms out at order two, so a compact AR(2) captures the dynamics and its residual becomes the input to a whiteness-based detector.
import numpy as np
def pacf(x, nlags=40):
x = np.asarray(x, float) - np.mean(x); n = len(x)
c0 = x @ x / n
r = np.array([(x[:n-k] @ x[k:])/n/c0 for k in range(nlags+1)])
phi = np.zeros((nlags+1, nlags+1)); pac = np.zeros(nlags+1)
phi[1,1] = r[1]; pac[1] = r[1]
for k in range(2, nlags+1):
num = r[k] - sum(phi[k-1,j]*r[k-j] for j in range(1,k))
den = 1 - sum(phi[k-1,j]*r[j] for j in range(1,k))
phi[k,k] = num/den
for j in range(1,k):
phi[k,j] = phi[k-1,j] - phi[k,k]*phi[k-1,k-j]
pac[k] = phi[k,k]
return pac[1:], 1.96/np.sqrt(n)
The partial autocorrelation is for autoregressive order, the autocorrelation for moving-average order; use them together to read a mixed model. Confirm the visual cutoff with an information criterion, preferring BIC for a parsimonious order, and remember both assume a stationary series, so analyze within one operating regime.
Box, Jenkins & Reinsel (2008), Time Series Analysis; Akaike (1974), IEEE TAC 19; Schwarz (1978), Ann. Statist. 6.
A fault advertises itself at a frequency tied to the machine kinematics: a shaft rate, a gear-mesh frequency, a bearing defect rate. The frequency-domain tools move those signatures into view, including the ones, like a bearing rate, that hide as modulation rather than a line.
The distribution of power over frequency, estimated by averaging the periodograms of overlapping windowed segments for a smooth, low-variance result. The basic frequency-domain condition indicator.
A single Fourier transform of a noisy signal gives a spectrum so erratic that real peaks are hard to separate from scatter. Welch's method tames that by splitting the record into overlapping segments, windowing each to control leakage, transforming them, and averaging the resulting power spectra. The price is coarser frequency resolution; the reward is a stable estimate in which a genuine line stands clear of the floor. In condition monitoring the lines sit at known kinematic frequencies, so a new line, a rising harmonic, or a growing band is read directly as a developing fault.
An induction motor develops a broken rotor bar, which plants sidebands around the supply frequency at twice the slip frequency. Averaged over many segments, the Welch spectrum resolves those small sidebands from the noise floor, where a raw periodogram would bury them in variance.
from scipy import signal
def psd_welch(x, fs, nperseg=1024):
f, Pxx = signal.welch(x, fs=fs, nperseg=nperseg)
return f, Pxx
f, Pxx = psd_welch(vibration, fs=12000, nperseg=1024)
# inspect Pxx at kinematic frequencies (shaft, mesh, sidebands)
The segment length trades resolution against variance: longer segments resolve close lines but average fewer, so the estimate is noisier. Always window to control leakage, and read peaks at the machine's kinematic frequencies rather than hunting blindly. Express axes in orders of shaft speed when the speed varies, so a line does not smear.
Welch (1967), IEEE Trans. Audio Electroacoust. 15; Oppenheim & Schafer (2010), Discrete-Time Signal Processing.
The frequency-by-frequency linear relationship between two signals. Coherence near one at a frequency means a shared source, which helps trace a fault from where it is sensed back to where it originates.
A single sensor cannot say whether two peaks at the same frequency on two channels come from one source or two coincidences. The cross-spectrum and its normalized form, the magnitude-squared coherence, answer that: coherence runs between zero and one and measures how consistently the phase between the channels holds at each frequency. A value near one means the two are linearly linked there, sharing a common source or a transmission path; a value near zero means they are independent. It is the tool for path analysis, deciding which of several measurement points actually drives a resonance and which merely feel it.
Two accelerometers on a pump both show a peak near a structural resonance. High coherence between them at that frequency confirms a single shared excitation traveling through the structure, rather than two local sources, focusing the search for the root cause on the common path.
from scipy import signal
import numpy as np
def coherence_xy(x, y, fs, nperseg=1024):
f, Pxy = signal.csd(x, y, fs=fs, nperseg=nperseg)
fc, Cxy = signal.coherence(x, y, fs=fs, nperseg=nperseg)
return f, np.abs(Pxy), Cxy
f, csd_mag, coh = coherence_xy(ch1, ch2, fs=12000)
Coherence needs averaging over several segments to be meaningful; a single segment gives a coherence of one everywhere and tells you nothing. A low coherence can mean independence or simply too little signal-to-noise, so check the cross-power magnitude alongside it, and remember coherence finds linear association, not causation or direction.
Bendat & Piersol (2010), Random Data; Carter (1987), Proc. IEEE 75.
The inverse transform of the log spectrum. By turning an evenly spaced family of harmonics or sidebands into a single peak at their spacing, it isolates periodic structure within the spectrum itself, the signature of gear sidebands and echoes.
Some faults do not add a single line but a whole comb: a gear with a local defect modulates the mesh frequency, planting a family of evenly spaced sidebands around it. Reading the spacing off a crowded spectrum is hard. The cepstrum takes the logarithm of the spectrum and transforms it again, so a regularly spaced family of peaks in frequency becomes one peak in the new axis, called quefrency, located at the reciprocal of the spacing. It collapses a sideband family or a repeated echo into a single, easy-to-track number, which is why it is a staple of gearbox diagnostics.
A cracked gear tooth modulates the mesh frequency, scattering sidebands at the shaft rate across the spectrum. The cepstrum gathers that whole family into one rahmonic at the shaft period, turning a smear of small sidebands into a single peak that can be trended over time.
import numpy as np
def real_cepstrum(x):
spec = np.fft.rfft(x * np.hanning(len(x)))
logmag = np.log(np.abs(spec) + 1e-12)
ceps = np.fft.irfft(logmag)
return ceps # peak at quefrency = 1 / spacing
ceps = real_cepstrum(vibration)
# a peak at quefrency q means a spectral spacing of fs/len * (1/q)
Window before transforming and work with the log magnitude, since the log is what turns a multiplicative harmonic family into an additive peak. Strong single tones and the overall spectral shape produce low-quefrency content that can mask the rahmonics you want, so lifter (filter in quefrency) to suppress them. A cepstral peak gives the spacing, not which sideband family it belongs to.
Bogert, Healy & Tukey (1963), Proc. Symp. Time Series; Randall (2017), Mech. Syst. Signal Process. 97.
The workhorse of bearing diagnostics. A localized defect rings the structure once per impact, so the fault rate appears as the modulation of a high resonance, not as a line. Band-passing the resonance and transforming its Hilbert envelope brings the defect frequency into plain view.
A spalled bearing does not hum at its defect frequency; it strikes, and each strike rings a high structural resonance that decays before the next. The defect rate is therefore the rate at which the resonance is amplitude-modulated, invisible as a line in the ordinary spectrum, which only shows the resonance. Envelope analysis recovers it: band-pass the signal around the resonance to isolate the rung region, take the magnitude of its analytic signal with the Hilbert transform to get the envelope that traces the impacts, and transform that envelope. The bearing defect frequencies, computed from the geometry as the outer-race, inner-race, ball, and cage rates, then appear as clear lines and harmonics.
An outer-race spall on a fan bearing produces impacts at the outer-race defect frequency, each ringing a resonance near three kilohertz. The raw spectrum shows only the resonance hump; band-passing it and taking the envelope spectrum reveals a sharp line at the outer-race frequency and its harmonics, identifying the fault and its location.
import numpy as np
from scipy import signal
def envelope_spectrum(x, fs, band):
b, a = signal.butter(4, [band[0]/(fs/2), band[1]/(fs/2)], "band")
xb = signal.filtfilt(b, a, x)
env = np.abs(signal.hilbert(xb)); env -= env.mean()
f = np.fft.rfftfreq(len(env), 1/fs)
return f, np.abs(np.fft.rfft(env * np.hanning(len(env))))
def bearing_frequencies(fr, n, d, D, phi=0.0):
r = (d/D)*np.cos(phi)
return dict(BPFO=0.5*n*fr*(1-r), BPFI=0.5*n*fr*(1+r),
BSF=(D/(2*d))*fr*(1-r**2), FTF=0.5*fr*(1-r))
Everything depends on choosing the right resonance band to demodulate; pick it where the impulsiveness is highest, which a spectral-kurtosis or kurtogram scan finds automatically. Compute the defect frequencies from the actual geometry and shaft speed, and expect the measured lines to sit a percent or two off because of slip. Inner-race and ball faults are modulated by shaft and cage rotation, adding sidebands, so look for families, not single lines.
Randall & Antoni (2011), Mech. Syst. Signal Process. 25; McFadden & Smith (1984), J. Sound Vib. 96.
The mean and variance describe a Gaussian signal completely; a faulty one is rarely Gaussian. Higher moments measure the impulsiveness that bearing and gear defects create, and the third-order spectrum measures the nonlinear coupling they introduce.
Scalar shape indicators of the amplitude distribution. Kurtosis and crest factor climb sharply when a defect makes a signal impulsive, often well before RMS energy moves, which makes them sensitive early indicators of bearing and gear faults.
A healthy machine's vibration is close to Gaussian, and its kurtosis, the normalized fourth moment, sits near three. A developing bearing or gear defect adds brief, sharp impacts that barely raise the overall energy but fatten the tails of the amplitude distribution, so kurtosis rises well above three and the crest factor, the ratio of peak to RMS, grows with it. Skewness, the third moment, reports asymmetry, useful when a fault pushes the signal preferentially one way. These are single numbers computed straight from the waveform, cheap to trend, and they react to impulsive faults earlier than energy-based indicators like RMS, which a few short spikes hardly change.
A bearing in early spalling produces occasional sharp impacts in an otherwise normal signal. The RMS is almost unchanged, so an energy threshold stays silent, but the kurtosis jumps from about three to six and the crest factor rises, flagging the impulsive defect at an early stage.
import numpy as np
from scipy import stats
def time_stats(x):
x = np.asarray(x, float)
rms = np.sqrt(np.mean(x**2)); peak = np.max(np.abs(x))
return dict(rms=rms, peak=peak, crest=peak/rms,
kurtosis=stats.kurtosis(x, fisher=False), # normal = 3
skewness=stats.skew(x))
s = time_stats(vibration) # trend s['kurtosis'] and s['crest']
Kurtosis is sensitive but not specific and is easily corrupted by a single outlier or electrical spike, so clean the signal and confirm with the envelope spectrum before calling a fault. It also saturates: once a fault is well developed and impacts are frequent, kurtosis can fall back toward three even as damage grows, so trend it alongside RMS rather than alone.
Dyer & Stewart (1978), J. Mech. Design 100; Antoni (2006), Mech. Syst. Signal Process. 20 (spectral kurtosis).
The third-order spectrum, nonzero only when frequency components are quadratically phase-coupled. Where the power spectrum treats two tones and their sum as independent, the bispectrum reveals whether they share a phase relationship, a fingerprint of nonlinear faults.
The power spectrum discards phase, so it cannot tell whether a component at the sum of two frequencies arose independently or was generated by a nonlinear interaction between them. The bispectrum keeps that information: it correlates the phases of three frequencies, one being the sum of the other two, and is large only when they are phase-locked, the signature of quadratic coupling. Many mechanical and electrical faults are nonlinear, looseness, rub, and certain electrical asymmetries generate exactly this kind of coupling, so a bispectral peak at a pair of fault-related frequencies is evidence of a nonlinear mechanism that a linear spectrum would miss.
A loose mechanical joint rectifies and mixes two running frequencies, generating a phase-coupled component at their sum. The power spectrum shows three separate lines with no hint they are related; the bispectrum lights up at the corresponding frequency pair, revealing the nonlinear looseness behind them.
import numpy as np
def bispectrum(x, nfft=128, noverlap=None):
x = np.asarray(x, float); noverlap = noverlap or nfft//2
step = nfft - noverlap; win = np.hanning(nfft); half = nfft//2
B = np.zeros((half, half), complex); count = 0
for s in range(0, len(x)-nfft+1, step):
X = np.fft.fft((x[s:s+nfft]-x[s:s+nfft].mean())*win)
for i in range(half):
for j in range(half-i):
B[i,j] += X[i]*X[j]*np.conj(X[i+j])
count += 1
return np.abs(B/max(count,1))
The bispectrum needs many segments to average down its variance and is expensive to compute, so restrict the frequency grid to the region of interest. It detects quadratic phase coupling specifically, not every nonlinearity, and a peak shows association, not mechanism, so pair it with a physical hypothesis about what could couple those frequencies.
Nikias & Mendel (1993), IEEE Signal Process. Mag. 10; Collis, White & Hammond (1998), Mech. Syst. Signal Process. 12.
A Fourier transform assumes the spectrum holds for the whole record. A fault transient does not: it happens at a moment. Time-frequency transforms map how the spectrum evolves, localizing a transient in both time and frequency.
The short-time Fourier transform: slide a window along the signal and transform each piece, mapping how the spectrum evolves in time. The first tool for a non-stationary signal where a fault lives at a particular time and frequency.
The ordinary spectrum collapses the whole record into one frequency picture, which is wrong the moment the signal changes in time, during a run-up, a load change, or a transient fault. The short-time Fourier transform restores time by sliding a window along the signal and transforming each windowed piece, producing a spectrogram, a map of frequency against time. Its one compromise is fixed resolution: a short window localizes events in time but blurs frequency, a long window does the reverse, and you must choose. Within that limit it is the standard way to see a frequency sweep during start-up or a fault that appears only under certain conditions.
A variable-speed drive is run up from rest while vibration is recorded. The spectrogram shows the order lines sweeping upward with shaft speed and a resonance band staying fixed, so the speed at which an order crosses the resonance, the critical speed, is read directly off the time-frequency map.
from scipy import signal
def spectrogram_stft(x, fs, nperseg=256, noverlap=None):
noverlap = noverlap or nperseg*3//4
f, t, S = signal.spectrogram(x, fs=fs, nperseg=nperseg,
noverlap=noverlap)
return f, t, S # short window: time-sharp; long: freq-sharp
f, t, S = spectrogram_stft(runup, fs=12000, nperseg=256)
The window length is the whole trade-off: short for sharp timing of transients, long for resolving close frequencies, and you cannot have both at once. Overlap the windows to smooth the time axis, window each segment to limit leakage, and for rotating machinery order-track against speed so a run-up sweep does not blur a fixed resonance.
Allen & Rabiner (1977), Proc. IEEE 65; Cohen (1995), Time-Frequency Analysis.
The continuous wavelet transform with a Morlet wavelet. Unlike the fixed window of the STFT, the wavelet narrows at high frequency and widens at low, so it localizes a short broadband transient in time while still resolving slow components. The right lens for impulsive, non-stationary faults.
The short-time Fourier transform must use one window length for the whole signal, which is a poor fit when a record holds both slow oscillations and sharp impacts. The wavelet transform adapts: it correlates the signal with scaled copies of a single prototype wave, the Morlet wavelet being a common choice, so high-frequency analysis uses short wavelets that pinpoint impacts in time and low-frequency analysis uses long ones that resolve slow components in frequency. The result, a scalogram, gives sharp time resolution exactly where impulsive faults need it. It is the natural tool for bearing impacts, gear transients, and any fault whose signature is a brief, broadband burst.
A chipped gear tooth produces a brief impact once per revolution, smeared across frequency. On a fixed-window spectrogram each impact is blurred; the wavelet scalogram resolves each one as a sharp vertical ridge at its instant, so the impacts can be counted and timed against the shaft angle to locate the damaged tooth.
import numpy as np
def cwt_morlet(x, fs, freqs, w0=6.0):
x = np.asarray(x, float); n = len(x); X = np.fft.fft(x)
omega = 2*np.pi*np.fft.fftfreq(n, 1/fs)
scalo = np.zeros((len(freqs), n))
for k, fc in enumerate(freqs):
s = w0/(2*np.pi*fc)
psi = np.pi**-0.25 * np.sqrt(2) * (omega>0) * \
np.exp(-0.5*(s*omega - w0)**2)
scalo[k] = np.abs(np.fft.ifft(X * psi))
return scalo
Choose the analysis frequencies and the wavelet's bandwidth from the transient you expect; a higher Morlet center frequency sharpens frequency at the cost of time. The continuous transform is redundant and can be heavy, so limit the frequency range. Mind edge effects at the start and end of the record, where the wavelet runs off the data and the scalogram is unreliable.
Mallat (2008), A Wavelet Tour of Signal Processing; Peng & Chu (2004), Mech. Syst. Signal Process. 18.
Start from the question you actually have. The badge marks the domain: correlation, spectral, higher-order, or time-frequency.
| If you are asking... | Reach for | Domain |
|---|---|---|
| What is the correlation structure or AR order? | ACF / PACF | correlation |
| Which frequencies carry the energy? | Welch PSD | spectral |
| Do two channels share a source or path? | cross-spectrum / coherence | spectral |
| Is there a harmonic or sideband family? | cepstrum | spectral |
| What is a rolling-element bearing's defect rate? | envelope analysis | spectral |
| Is the signal impulsive (early bearing/gear)? | kurtosis / crest factor | higher-order |
| Are frequencies nonlinearly phase-coupled? | bispectrum | higher-order |
| How does the spectrum evolve during a run-up? | STFT spectrogram | time-frequency |
| Where is a short broadband transient? | wavelet (CWT) | time-frequency |
Heuristics that hold across most vibration-based condition monitoring, before the method-specific detail.
| Scenario | Pick & why | Domain |
|---|---|---|
| Find the dominant periodicity and model order | ACF / PACFcorrelation structure and AR order | correlation |
| Spot a new line or rising harmonic | Welch PSDsmooth, low-variance spectrum | spectral |
| Decide if two sensors share a source | coherencelinear association per frequency | spectral |
| Track a gear sideband family | cepstrumcollapses the family to one peak | spectral |
| Find a rolling-bearing defect frequency | envelope analysisdemodulates the resonance | spectral |
| Catch an early impulsive bearing fault | kurtosis / crest factorimpulsiveness before RMS moves | higher-order |
| Expose nonlinear frequency coupling | bispectrumphase coupling the PSD misses | higher-order |
| Analyze a variable-speed run-up | STFT spectrogramspectrum evolving in time | time-frequency |
| Localize a brief broadband transient | wavelet (CWT)adaptive time-frequency resolution | time-frequency |
The indicators split by domain: correlation structure, frequency content, distribution shape, and time-frequency.
| Method | Domain | Reveals | Output | Key assumption |
|---|---|---|---|---|
| ACF | lag | periodicity, memory | correlation vs lag | stationary |
| PACF | lag | AR order | partial corr + order | stationary |
| Welch PSD | frequency | spectral lines and bands | power vs frequency | stationary in window |
| Coherence | frequency | shared source between channels | 0 to 1 vs frequency | needs averaging |
| Cepstrum | quefrency | harmonic / sideband spacing | peak at spacing | periodic structure |
| Envelope | frequency | bearing defect rate | defect lines + harmonics | resonance band chosen |
| Kurtosis / crest | amplitude | impulsiveness | scalar indicator | outlier-sensitive |
| Bispectrum | bifrequency | quadratic phase coupling | magnitude on (f1,f2) | needs many segments |
| STFT | time-frequency | spectrum over time | spectrogram | fixed resolution |
| Wavelet | time-frequency | localized transients | scalogram | adaptive resolution |
A fan bearing develops an outer-race spall. From its geometry the defect frequencies are BPFO 96.9 Hz, BPFI 143.1 Hz, BSF 75.1 Hz, and cage 12.1 Hz. Every number below is computed by the engine on a 1-second record sampled at 12000 Hz.
The cheap indicators move first. Kurtosis rises from 2.8 on the healthy signal to 3.8 on the faulty one, and the crest factor from 3.7 to 5.3, while RMS barely changes (0.45 to 0.48). The signal has become impulsive, the hallmark of a developing bearing defect, even though its energy is almost unchanged.
The raw Welch spectrum is dominated by the shaft line and a broadband resonance near 3393 Hz, with no line at any bearing rate: the defect is hidden as modulation. Scanning candidate bands by the kurtosis of their envelope picks the band around the resonance, where impulsiveness peaks at a band kurtosis of 6.0, as the one to demodulate.
Band-passing that resonance, taking the Hilbert envelope, and transforming it moves the defect rate into plain view: the envelope spectrum peaks at 97.0 Hz, within a percent of the computed BPFO of 96.9 Hz, with harmonics following. The match to the outer-race frequency, rather than the inner-race or ball rate, identifies both the fault and its location on the bearing.
Scalar higher-order statistics flagged an impulsive change that energy thresholds missed; a spectral-kurtosis scan chose the resonance band; and envelope analysis turned a hidden modulation into a labeled outer-race defect frequency. The reflex order for vibration diagnostics is: screen with kurtosis, locate the resonance, demodulate with the envelope, and read the answer at the kinematic frequencies. The time domain says how impulsive; the right transform says which fault.
Beyond the core indicators, a set of methods that complete the bearing, run-up, gear, and cyclostationary stories: automatic band selection, angular resampling, synchronous averaging, the cyclic spectrum, and tools for decomposition, deconvolution, and severity. Two more explorers first.
Average more revolutions and watch the gear mesh and the once-per-rev fault rise out of noise and non-synchronous content, as the noise falls with the square root of the count.
Increase the speed variation and watch the raw spectral peak smear while the order-domain peak, after resampling to shaft angle, stays sharp at its order.
Envelope analysis only works if you demodulate the right band. The kurtogram automates that choice, the one judgement call the envelope card left open.
Spectral kurtosis measures impulsiveness frequency by frequency; the kurtogram scans center frequency and bandwidth to find the single most impulsive band, the optimal one to band-pass before envelope analysis.
Envelope analysis depends on demodulating the resonance a bearing's impacts excite, and choosing that band by eye is unreliable. Spectral kurtosis turns the choice into a measurement: for each frequency it takes the kurtosis of the short-time spectral magnitude over time, large where the signal is intermittently impulsive, near zero where it is stationary. The kurtogram extends this over a grid of center frequencies and bandwidths, so the brightest cell names both where to filter and how wide. Feeding that band to the envelope spectrum is the modern, automated bearing pipeline.
A gearbox bearing fault rings a resonance whose frequency is not known in advance. The kurtogram lights up a band near three kilohertz as the most impulsive; band-passing there and enveloping recovers the defect frequency a guessed band would have missed.
import numpy as np
from scipy import signal, stats
def fast_kurtogram(x, fs, nlevel=4):
nyq = fs/2; best = dict(kurt=-np.inf)
for lvl in range(1, nlevel+1):
bw = nyq/2**lvl; lo = 0.0
while lo <= nyq - bw + 1:
loc, hic = max(lo, fs*0.004), min(lo+bw, nyq*0.998)
if hic-loc >= fs*0.004:
b, a = signal.butter(3, [loc/nyq, hic/nyq], "band")
env = np.abs(signal.hilbert(signal.filtfilt(b, a, x)))
k = stats.kurtosis(env, fisher=False)
if k > best["kurt"]:
best = dict(kurt=k, band=(loc, hic))
lo += bw/2
return best # band -> feed to envelope_spectrum
The kurtosis can be maximized by a band so narrow it cannot resolve the defect-frequency sidebands, so cap the level or require the bandwidth to exceed several times the expected defect rate. Strong deterministic components like gear-mesh lines distort the kurtogram, so remove them first by synchronous averaging or cepstrum editing.
Antoni (2006), Mech. Syst. Signal Process. 20; Antoni & Randall (2006), MSSP 20.
A Fourier spectrum assumes constant speed. On a run-up the kinematic lines sweep and smear. Order tracking resamples to shaft angle so they stand still.
Resample a signal from equal time steps to equal increments of shaft angle, using the instantaneous speed, so a shaft-locked component becomes a fixed order regardless of how the speed varies. It un-smears a run-up.
When shaft speed changes, every component locked to the shaft changes frequency with it, so a Fourier spectrum of a run-up smears each order into a broad hump and resolves nothing. Order tracking removes the speed: using a tachometer or an estimate of instantaneous phase, it resamples the signal at equal increments of shaft angle rather than equal time. In that angle domain a shaft-order component sits at a fixed number of cycles per revolution no matter how the speed varies, so the smeared sweeps collapse back into sharp order lines. Structural resonances, fixed in frequency, do the opposite and smear, which itself helps tell a forced order from a resonance.
A turbocharger is run up from idle while vibration is recorded. In the raw spectrum the blade-pass order is an unreadable smear; after order tracking it is a sharp line at the blade count per revolution, and a fixed resonance band that now smears is exposed as structural rather than forced.
import numpy as np
def order_track(x, fs, f_inst, spr=64):
theta = np.cumsum(f_inst)/fs # revolutions vs time
t = np.arange(len(x))/fs
ang = np.arange(0, int(theta[-1]), 1.0/spr)
xr = np.interp(np.interp(ang, theta, t), t, x) # resample to angle
X = np.abs(np.fft.rfft((xr-xr.mean())*np.hanning(len(xr))))
orders = np.fft.rfftfreq(len(xr), 1.0/spr)
return orders, X, xr
Order tracking needs an accurate angle reference; phase errors blur the orders just as speed variation blurred the spectrum. Resample with enough samples per revolution to cover the highest order of interest without aliasing. Remember resonances stay at fixed frequencies and therefore smear in the order domain, the mirror image of the time-domain picture.
Fyfe & Munck (1997), Mech. Syst. Signal Process. 11; Bossley et al. (1999), MSSP 13.
A gear signal is buried in noise and the vibration of everything else on the shaft. Averaging synchronized to rotation extracts it, and its residual yields the classical gear indicators.
Averaging the signal synchronized to shaft rotation keeps the part that repeats every revolution and averages away noise and non-synchronous content. The residual left after removing the regular mesh yields FM4, M6A, and M8A.
Everything locked to a shaft repeats every revolution; everything else does not. Time-synchronous averaging exploits that by resampling to shaft angle and averaging over many revolutions, so the gear-mesh signal and any once-per-revolution fault reinforce while noise and components from other shafts average toward zero. The averaged revolution is a clean picture of that gear. Subtracting its regular mesh harmonics leaves a residual that is flat for a healthy gear but spikes where a local defect, a cracked or spalled tooth, disturbs a few teeth; the normalized fourth, sixth, and eighth moments of that residual, FM4, M6A, and M8A, turn the disturbance into trendable numbers.
One gear in a multi-stage box has a cracked tooth. Synchronous averaging at that shaft's rate pulls its mesh pattern out of the mixture, and the once-per-revolution disturbance from the crack drives FM4 well above its healthy value of three while the overall spectrum looks unchanged.
import numpy as np
def time_synchronous_average(x, fs, fr, spr=256):
total = int((len(x)/fs)*fr)
ang = np.arange(total*spr)/spr
xr = np.interp(ang/fr, np.arange(len(x))/fs, x)
return xr.reshape(total, spr).mean(axis=0) # one clean revolution
def gear_indicators(tsa, n_teeth, n_harm=3):
X = np.fft.rfft(tsa); reg = np.zeros_like(X)
for h in range(1, n_harm+1):
if n_teeth*h < len(X): reg[n_teeth*h] = X[n_teeth*h]
d = tsa - np.fft.irfft(reg, len(tsa)); v = np.mean(d**2)
return dict(FM4=np.mean(d**4)/v**2, M6A=np.mean(d**6)/v**3)
TSA needs an accurate once-per-rev trigger and many revolutions, since noise falls only as the square root of the count. The indicators need a healthy baseline: FM4 and NA4 are meant to be trended against a learned normal, since their absolute value depends on the gear. In a multi-stage box, average at each shaft's own rate to separate the gears.
McFadden (1987), Mech. Syst. Signal Process. 1; Zakrajsek et al. (1993), NASA TM 105950.
Envelope analysis is a special case of a deeper idea: a bearing fault is cyclostationary, its energy modulated at the defect rate. The cyclic spectrum makes that structure explicit and separates random bearing content from deterministic gears.
A fast estimator of the spectral correlation. It maps energy against two frequencies, the carrier f and the cyclic modulation frequency alpha, so a bearing fault appears as a ridge at alpha equal to the defect rate across its resonance, separating random impulsive content from deterministic lines.
A bearing fault is not stationary and not periodic but cyclostationary: the random ringing is repeated, with statistics that cycle at the defect rate. The cyclic modulation spectrum captures that by asking, for each carrier frequency, how strongly its energy is modulated and at what rate. The result is a map over the carrier frequency f and the cyclic frequency alpha, on which a bearing fault is a vertical ridge at alpha equal to its defect frequency, spread across the resonance band in f. Deterministic gear and shaft lines sit at their own cyclic frequencies and so separate out, which is why the cyclic approach succeeds on mixed signals where a plain envelope spectrum blends everything together.
A signal carries both a strong gear mesh and a weak bearing fault. The envelope spectrum is dominated by the gear and its sidebands; the cyclic modulation spectrum places the gear content at the shaft cyclic frequency and reveals the bearing as a separate ridge at its defect rate across the resonance.
import numpy as np
from scipy import signal
def cyclic_modulation_spectrum(x, fs, nperseg=256):
noverlap = nperseg - max(8, nperseg//16) # small hop -> wide alpha
f, t, Z = signal.stft(x, fs, nperseg=nperseg, noverlap=noverlap)
S = np.abs(Z)**2; S = S - S.mean(axis=1, keepdims=True)
C = np.abs(np.fft.rfft(S*np.hanning(S.shape[1]), axis=1))
alpha = np.fft.rfftfreq(S.shape[1], t[1]-t[0])
return alpha, f, C # ridge at alpha = defect rate
The cyclic frequency range is set by the analysis frame rate, so use enough overlap to reach the defect frequencies and their harmonics. Deterministic components can still mask faint faults, so the normalized form, the cyclic spectral coherence, is preferred for weak signatures. It costs more than envelope analysis but separates sources the envelope blends.
Antoni (2009), Mech. Syst. Signal Process. 23; Borghesani & Antoni (2018), MSSP 105.
Four shorter entries that round out the kit: an adaptive decomposition, a blind deconvolution, a spectrum-cleaning edit, and a standard severity scale.
Without assuming any basis, EMD peels a signal into a few intrinsic mode functions, fastest to slowest, plus a trend. It adapts to non-stationary, nonlinear signals, separating a fast fault transient from slow background.
Where the wavelet uses a fixed prototype, empirical mode decomposition builds its basis from the data itself. It repeatedly identifies the local extrema, fits smooth envelopes through the maxima and minima, and subtracts their mean, peeling off the fastest oscillation as the first intrinsic mode function, then the next, down to a slow residual trend. The result is a data-driven filter bank that follows a non-stationary or nonlinear signal, so a brief fault transient lands in an early mode separated from the slow operating background. Combined with the Hilbert transform it gives instantaneous frequencies, the Hilbert-Huang view.
A vibration record mixes a slow load oscillation, a steady tone, and a short impact. EMD separates them: the impact appears alone in the first intrinsic mode, where it can be enveloped or timed, while the slow load lands in the trend.
# see scripts/spectral.py: emd(x, max_imf=6)
# sift: extrema -> cubic-spline envelopes -> subtract the mean
# repeat per mode; returns [IMF1, IMF2, ..., residual]
imfs = emd(vibration, max_imf=4)
transient = imfs[0] # fastest mode isolates the impact
EMD is adaptive but fragile: mode mixing can split one component across modes or merge two, and it is sensitive to noise and to endpoint handling. Ensemble EMD and variational mode decomposition are more robust. Treat the modes as a filter bank to inspect, not as physically labeled components.
Huang et al. (1998), Proc. R. Soc. A 454; Lei et al. (2013), Mech. Syst. Signal Process. 35.
A fault impulse train is blurred by the path it travels to the sensor. MED estimates the inverse filter that makes the output as impulsive as possible, by maximizing output kurtosis, recovering the buried impacts before envelope analysis.
By the time a bearing impact reaches the accelerometer it has been smeared by the transmission path, its sharp edge spread into a ring. Minimum entropy deconvolution tries to undo that: it searches for the finite impulse-response filter that, applied to the signal, makes the output as impulsive, as low-entropy, as possible, which it measures by kurtosis and maximizes by a short iteration. The deconvolved signal restores the periodic spikes the path had blurred, sharpening a faint impulse train so the subsequent envelope spectrum reads cleanly.
A bearing fault is so smeared by a long structural path that its impacts are invisible and its kurtosis barely raised. MED estimates the inverse path, the deconvolved signal shows a clean train of spikes at the defect period, and the kurtosis jumps, confirming the fault.
# see scripts/spectral.py: med_deconvolve(x, L=40, iters=15)
# iterate: y = X f ; f = R^-1 (X^T y^3) ; normalize (R = X^T X)
out = med_deconvolve(vibration, L=50, iters=20)
sharpened = out["deconvolved"] # then envelope_spectrum(sharpened, ...)
MED maximizes kurtosis, so it favors one large impulse and can lock onto a single outlier rather than the periodic train; maximum correlated kurtosis deconvolution, which targets a specified period, is more robust for bearings. Choose the filter length long enough to invert the path but short enough to stay stable, and follow with envelope analysis rather than reading the deconvolved signal directly.
Wiggins (1978), Geophys. Prospect. 26; McDonald et al. (2012), Mech. Syst. Signal Process. 33.
A regular harmonic or sideband family is a single peak in the cepstrum, so notching that rahmonic removes the whole family from the spectrum, cleaning a dominant gear-mesh comb to expose weaker structure underneath.
The cepstrum gathers an evenly spaced family of spectral lines into one peak at their spacing, which makes it a precise editing tool: zero that peak and its multiples, transform back, and the entire family vanishes from the spectrum while everything else is untouched. This liftering is the standard way to strip a dominant gear-mesh comb that would otherwise swamp a weak bearing signature, a prewhitening step that lets the spectral kurtosis and the envelope work on what remains.
A strong gear mesh and its sidebands bury a small bearing fault. Liftering the mesh family out in the cepstral domain removes the comb and leaves the bearing's resonance, which the kurtogram and envelope then pick up cleanly.
# see scripts/spectral.py: cepstrum_edit(x, q_remove)
# real cepstrum -> zero the rahmonic at q = fs / spacing -> back to spectrum
ce = cepstrum_edit(vibration, [int(fs/sideband_spacing)])
cleaned_spectrum = ce["log_edited"] # mesh family removed
Editing the real cepstrum removes magnitude structure but discards phase, so to reconstruct a time signal use the complex cepstrum, which preserves phase but needs careful unwrapping. Liftering a dominant mesh family is a common prewhitening step before bearing analysis, removing the deterministic comb that would otherwise dominate the kurtosis and the envelope.
Randall (2017), Mech. Syst. Signal Process. 97; Borghesani et al. (2013), MSSP 36.
Map a broadband vibration velocity RMS to a standard severity zone, A newly commissioned, B unrestricted operation, C short-term only, D damaging. It turns a measured level into an actionable alarm.
Diagnosis says what is wrong; severity assessment says how urgent it is. The ISO 20816 family (formerly 10816) defines velocity RMS boundaries that sort a machine into four zones from newly commissioned through damaging, set by the machine's size, power, and mounting. A single broadband number placed against those boundaries gives operations a clear, standardized action: keep running, plan maintenance, or stop. It is the coarse overall gauge that sits above the diagnostic indicators, telling you when the detailed analysis becomes urgent.
A pump's overall velocity RMS climbs from one millimetre per second into zone C over several months. The level alone does not say why, but it triggers the diagnostic chain, where the envelope spectrum then identifies a bearing outer-race fault as the cause.
def iso10816_zone(v_rms_mm_s, machine_class="medium"):
table = {"small": (0.71, 1.8, 4.5), "medium": (1.4, 2.8, 7.1),
"large_rigid": (2.3, 4.5, 11.0), "large_soft": (3.5, 7.1, 18.0)}
ab, bc, cd = table[machine_class]
z = "A" if v_rms_mm_s<ab else "B" if v_rms_mm_s<bc else \
"C" if v_rms_mm_s<cd else "D"
return z, dict(AB=ab, BC=bc, CD=cd)
The boundaries depend on machine size, power, and mounting, rigid or flexible, so use the class that matches; the zones here are illustrative. Broadband velocity RMS is a coarse gauge, not a diagnosis: it can sit in zone A while a localized bearing fault, which kurtosis and the envelope catch, develops. Trend the overall level and the diagnostic indicators together.
ISO 20816-1 (2016); ISO 10816-3 (2009).
The same content is packaged as a VS Code Copilot skill in the progressive-disclosure layout: a routing SKILL.md, per-topic reference docs, a tested spectral.py engine that implements every indicator with numpy and scipy only, and Copilot integration files (a vibration-analyst chat mode, a /diagnose prompt, and path-scoped instructions). Drop the folder into your kit, or point Copilot at the skill directly.
Link resolves when the zip sits beside this file. Run python scripts/spectral.py for the self-test on a synthetic bearing signal.