"They keep plotting me raw and complaining that I am noisy. Of course I am noisy: you took one look and called it the truth. Average a few of my moods together and I will finally tell you what I actually sound like."
A Power Spectral Density Tired of Being Noisy
A stationary process has a true, smooth spectral density that says how its variance is distributed across frequencies, but the obvious estimator, the raw periodogram, is a trap: it is unbiased yet its variance never shrinks, so it stays jagged no matter how much data you collect. The cure is to trade a little resolution for a lot of stability, either by averaging many noisy periodograms (Bartlett, Welch, Daniell) or by fitting a small parametric model and reading its implied smooth spectrum (the AR spectrum via Yule-Walker or Burg). This section defines the population spectral density through the Wiener-Khinchin theorem, proves why the periodogram refuses to converge, builds the smoothing and parametric estimators from scratch, pins down the bias-variance dial you actually turn in practice, and closes with a worked estimate of a noisy autoregressive spectrum computed both by hand and with scipy.signal.welch.
The previous section, Section 4.1, built the discrete Fourier transform and the periodogram, the squared magnitude of the transformed series, and showed that it decomposes the sample variance across frequency. That decomposition is exact for the sample at hand, yet as an estimate of the process that generated the sample it is badly behaved. This section confronts the gap between the periodogram you can compute and the spectral density you actually want. The target is a population quantity, the spectral density $f(\nu)$, and the rest of the section is the story of how to estimate it without being fooled by its raw, sample-bound shadow. We assume throughout the weak stationarity and autocovariance notation $\gamma(k)$ of Section 3.2, and the autocorrelation reading skills of Section 3.3; every major symbol below appears in the unified notation table of Appendix A on first use.
1. From Periodogram to Spectral Density Beginner
For a zero-mean weakly stationary process $\{X_t\}$ with autocovariance $\gamma(k) = \operatorname{Cov}(X_t, X_{t+k})$, the population spectral density is the discrete-time Fourier transform of the autocovariance sequence,
$$f(\nu) \;=\; \sum_{k=-\infty}^{\infty} \gamma(k)\, e^{-i 2\pi \nu k}, \qquad -\tfrac{1}{2} \le \nu \le \tfrac{1}{2},$$where $\nu$ is frequency in cycles per sample. This identity, that the spectral density and the autocovariance are a Fourier transform pair, is the Wiener-Khinchin theorem, and it is the bridge between the time-domain view of Section 3.3 and the frequency-domain view of this chapter. The inverse direction recovers the autocovariance, and in particular at lag zero it states that the total variance is the area under the spectral density,
$$\gamma(k) \;=\; \int_{-1/2}^{1/2} f(\nu)\, e^{i 2\pi \nu k}\, d\nu, \qquad\text{so}\qquad \gamma(0) \;=\; \operatorname{Var}(X_t) \;=\; \int_{-1/2}^{1/2} f(\nu)\, d\nu.$$Reading the last equation gives the entire physical meaning of the spectral density: $f(\nu)\, d\nu$ is the slice of the process variance that lives in the narrow frequency band around $\nu$. A process with a sharp peak in $f$ at some frequency $\nu_0$ has a strong periodic component at period $1/\nu_0$; a flat $f$ is white noise, whose variance spreads evenly across all frequencies. Because $\gamma(k) = \gamma(-k)$ for a real stationary process, the spectral density is real, non-negative, and symmetric, $f(\nu) = f(-\nu)$, so we plot it only on $[0, \tfrac{1}{2}]$.
The spectral density carries no information beyond the autocovariance function; it is the same second-order content re-expressed in the frequency basis. Everything Section 3.3 read off the autocorrelation function, an AR peak, an MA dip, a seasonal echo, is encoded equally in $f(\nu)$, often more legibly. A lag-12 seasonal spike that the autocorrelation function shows as one bar among many appears in the spectrum as a clean peak at $\nu = 1/12$. Choosing between the two views is a matter of which makes the structure you care about easiest to see, not which contains more.
Now the natural estimator. Replace the unknown $\gamma(k)$ by its sample version $\hat\gamma(k)$ from Section 3.3 and truncate the sum at the available lags. The result is exactly the periodogram of Section 4.1,
$$I(\nu_j) \;=\; \sum_{k=-(n-1)}^{n-1} \hat\gamma(k)\, e^{-i 2\pi \nu_j k} \;=\; \frac{1}{n}\left| \sum_{t=1}^{n} x_t\, e^{-i 2\pi \nu_j t} \right|^{2},$$evaluated at the Fourier frequencies $\nu_j = j/n$. It looks like a sensible estimate of $f(\nu_j)$, and in one respect it is: the periodogram is asymptotically unbiased, $\mathbb{E}[I(\nu_j)] \to f(\nu_j)$ as $n \to \infty$. The catch is its variance. For a Gaussian process the periodogram ordinate behaves, at each frequency, like $f(\nu_j)$ times a chi-squared random variable on two degrees of freedom, $I(\nu_j) \approx f(\nu_j)\,\chi^2_2 / 2$, whose variance is
$$\operatorname{Var}\big(I(\nu_j)\big) \;\approx\; f(\nu_j)^2.$$This expression contains no $n$. The variance of the periodogram does not shrink as the sample grows: each ordinate has a standard deviation roughly equal to the very quantity it estimates, forever. Worse, adjacent ordinates are asymptotically independent, so collecting more data does not smooth the curve, it just packs more equally noisy spikes into the same frequency axis. An estimator whose variance ignores the sample size is called inconsistent, and the periodogram is the textbook example: unbiased but inconsistent, an estimate that never settles down.
The reason the periodogram never improves is almost philosophical. At each frequency it is built from exactly one complex number, the Fourier coefficient at that frequency, whose real and imaginary parts give two real degrees of freedom. Doubling the data length does not add degrees of freedom to any single frequency; it adds more frequencies, each still resting on its own lonely pair. You can have infinite data and still be estimating every spectral ordinate from effectively two numbers. Consistency must therefore come from pooling neighbors or segments, which is the entire game of the next two subsections.
2. Smoothing the Periodogram: Bartlett, Welch, and Daniell Intermediate
If the trouble is two degrees of freedom per ordinate, the remedy is to manufacture more. There are two equivalent ways to do it, and both rest on the same statistical fact: averaging $L$ approximately independent estimates of the same quantity divides the variance by $L$. The first way averages across segments of the series; the second averages across neighboring frequencies. They are duals, and good software offers both. Figure 4.2.2 captures the intuition: stack many jittery spectra and the random noise cancels while the true peaks reinforce.
The segment-averaging idea is due to Bartlett. Split the series of length $n$ into $K$ non-overlapping segments of length $m = n/K$, compute a periodogram on each segment, and average the $K$ periodograms ordinate by ordinate. Each segment periodogram still has variance $f(\nu)^2$, but averaging $K$ independent ones gives a variance of roughly $f(\nu)^2 / K$, which now does shrink as you allow more segments. Welch refined this in two ways that matter in practice: he let the segments overlap (typically by 50 percent), which extracts more nearly-independent averages from the same data, and he multiplied each segment by a taper (a window function $w$) before transforming, which suppresses the spectral leakage that the abrupt segment edges would otherwise inject. The Welch estimate is
$$\hat f_{W}(\nu) \;=\; \frac{1}{K}\sum_{\ell=1}^{K} \frac{1}{m\,U}\left| \sum_{t=1}^{m} w_t\, x^{(\ell)}_t\, e^{-i 2\pi \nu t} \right|^{2}, \qquad U = \frac{1}{m}\sum_{t=1}^{m} w_t^{2},$$where $x^{(\ell)}$ is the $\ell$-th (possibly overlapping) segment and $U$ is the window power-normalization that keeps the estimate unbiased for the variance. The number of segments $K$ is the variance knob; the segment length $m$ is the resolution knob, and the two are locked together through $K \approx n/m$ (more so with overlap). This is the central tension of the whole section, stated as an equation below.
The frequency-averaging idea is the Daniell smoother. Keep one periodogram on the full series, but replace each ordinate by the average of its $2p+1$ neighbors,
$$\hat f_{D}(\nu_j) \;=\; \frac{1}{2p+1} \sum_{r=-p}^{p} I(\nu_{j+r}),$$a moving average across frequency. The half-width $p$ plays the role $K$ played for Welch: a wider smoothing window averages more ordinates, cuts the variance further, and blurs the resolution further. More general lag-window estimators (Parzen, Tukey-Hanning, Bartlett-Priestley) replace the flat box of the Daniell smoother with a tapered weighting, equivalently truncating and down-weighting the high-lag $\hat\gamma(k)$ before transforming. Whatever the shape, the trade is identical and unavoidable.
Every nonparametric spectral estimator has a single effective smoothing bandwidth $b$, the width in frequency over which it pools information. Widen $b$ and the variance falls (you are averaging more nearly-independent ordinates), but the bias rises and sharp peaks smear into broad humps, so closely spaced frequencies can no longer be resolved. Narrow $b$ and you recover fine resolution at the cost of a jagged, high-variance curve that drifts back toward the useless raw periodogram. The qualitative scaling is
$$\operatorname{Var} \;\propto\; \frac{1}{n\,b}, \qquad \operatorname{Bias} \;\propto\; b^{2}.$$There is no setting that makes both small at once; you choose where on this curve to sit based on whether your goal is detecting a sharp line (favor resolution) or estimating broadband power (favor low variance).
Code 4.2.1 implements Welch's method from scratch so the segment loop, the taper, and the power normalization are all visible, with no library smoothing hidden inside.
import numpy as np
def welch_psd(x, seg_len, overlap=0.5, fs=1.0):
"""From-scratch Welch PSD: average tapered segment periodograms.
Returns one-sided frequencies and power spectral density."""
x = np.asarray(x, dtype=float)
x = x - x.mean() # detrend the mean (see subsection 4)
step = int(seg_len * (1.0 - overlap)) # hop between segment starts
window = np.hanning(seg_len) # Hann taper suppresses leakage
U = np.mean(window**2) # window power, the U normalization
starts = range(0, len(x) - seg_len + 1, step)
psd_accum = np.zeros(seg_len // 2 + 1)
K = 0
for s in starts:
seg = x[s:s + seg_len] * window # taper this segment
spec = np.fft.rfft(seg) # one-sided FFT
per = (np.abs(spec)**2) / (seg_len * U * fs)
per[1:-1] *= 2 # fold negative-frequency power in
psd_accum += per
K += 1
freqs = np.fft.rfftfreq(seg_len, d=1.0 / fs)
return freqs, psd_accum / K, K # divide by K averaged segments
rng = np.random.default_rng(0)
n = 4096
t = np.arange(n)
# A 0.12-cycle/sample tone buried in white noise, our test signal.
signal = np.sin(2 * np.pi * 0.12 * t) + 1.5 * rng.standard_normal(n)
f64, psd64, K64 = welch_psd(signal, seg_len=64)
f512, psd512, K512 = welch_psd(signal, seg_len=512)
print(f"seg_len=64 : {K64:3d} segments averaged, peak at nu = {f64[np.argmax(psd64)]:.3f}")
print(f"seg_len=512: {K512:3d} segments averaged, peak at nu = {f512[np.argmax(psd512)]:.3f}")
per[1:-1] *= 2 folds the symmetric negative-frequency power into the one-sided estimate, and U = np.mean(window**2) is the taper power-normalization that keeps the height calibrated. Shorter segments yield more averages (lower variance) but coarser frequency spacing.seg_len=64 : 127 segments averaged, peak at nu = 0.125
seg_len=512: 15 segments averaged, peak at nu = 0.121
3. Parametric Spectral Estimation: The AR Spectrum Intermediate
The smoothers of subsection two are nonparametric: they assume nothing about the shape of $f$ beyond local smoothness. The parametric alternative assumes the process is (well approximated by) an autoregression of order $p$, fits that model, and reads off the smooth spectrum the fitted model implies. If $\{X_t\}$ obeys $X_t = \sum_{j=1}^{p}\phi_j X_{t-j} + \varepsilon_t$ with innovation variance $\sigma^2$, then its spectral density has the exact closed form
$$f_{AR}(\nu) \;=\; \frac{\sigma^2}{\left| 1 - \sum_{j=1}^{p}\phi_j\, e^{-i 2\pi \nu j} \right|^{2}}.$$This is automatically smooth: it is a rational function of $e^{-i2\pi\nu}$ with at most $p$ peaks, so once you have $\{\phi_j\}$ and $\sigma^2$ there is no jaggedness to average away. The only task is estimating the coefficients, and there are two standard routes. The Yule-Walker route solves the same Toeplitz system of Section 3.3, $\sum_{j=1}^{p}\phi_j\,\hat\rho(i-j) = \hat\rho(i)$, using the sample autocorrelations; it is fast and always yields a stable (stationary) model but can be biased for short series. The Burg route instead minimizes the sum of forward and backward prediction errors directly via the same Levinson-style recursion, which gives sharper, less biased peaks on short records, at the price of occasional peak-splitting artifacts. Both reduce the spectrum estimate to a handful of numbers.
Take the AR(2) process $X_t = 0.75\,X_{t-1} - 0.5\,X_{t-2} + \varepsilon_t$ with $\sigma^2 = 1$. Its denominator at frequency $\nu$ is $|1 - 0.75\,e^{-i2\pi\nu} + 0.5\,e^{-i4\pi\nu}|^2$. The complex roots of $1 - 0.75 z + 0.5 z^2$ sit at modulus $1/\sqrt{0.5} \approx 1.414$ and angle $\theta$ with $\cos\theta = 0.75/(2\sqrt{0.5}) \approx 0.530$, so $\theta \approx 1.011$ radians, giving a resonant peak at $\nu_0 = \theta/(2\pi) \approx 0.161$ cycles per sample (period about 6.2 samples). Evaluating the denominator there gives a small value and hence a tall, narrow peak; at $\nu = 0$ the denominator is $|1 - 0.75 + 0.5|^2 = 0.75^2 = 0.5625$, so $f_{AR}(0) = 1/0.5625 \approx 1.78$, a modest low-frequency floor. Two coefficients and one variance fully determine an infinitely smooth curve with a single resonance, which is the entire appeal of the parametric approach.
Code 4.2.2 fits an AR($p$) by Yule-Walker from scratch (solving the Toeplitz system with the autocorrelations of Section 3.3) and evaluates its implied spectrum.
def yule_walker_ar(x, p):
"""Fit AR(p) by Yule-Walker; return coefficients phi and innovation variance."""
x = np.asarray(x, dtype=float) - np.mean(x)
n = len(x)
r = np.array([np.dot(x[:n - k], x[k:]) / n for k in range(p + 1)]) # autocov 0..p
R = np.array([[r[abs(i - j)] for j in range(p)] for i in range(p)]) # Toeplitz matrix
phi = np.linalg.solve(R, r[1:p + 1]) # solve R phi = (r1..rp)
sigma2 = r[0] - np.dot(phi, r[1:p + 1]) # residual innovation variance
return phi, sigma2
def ar_spectrum(phi, sigma2, freqs):
"""Evaluate the AR spectral density at the given frequencies."""
p = len(phi)
z = np.exp(-2j * np.pi * np.outer(freqs, np.arange(1, p + 1))) # e^{-i2pi nu j}
denom = np.abs(1.0 - z @ phi)**2
return sigma2 / denom
rng = np.random.default_rng(1)
n = 2048
e = rng.standard_normal(n)
x = np.zeros(n)
for k in range(2, n): # simulate the numeric-example AR(2)
x[k] = 0.75 * x[k - 1] - 0.5 * x[k - 2] + e[k]
phi, s2 = yule_walker_ar(x, p=2)
freqs = np.linspace(0, 0.5, 257)
spec = ar_spectrum(phi, s2, freqs)
print(f"fitted phi = {np.round(phi, 3)}, sigma2 = {s2:.3f}")
print(f"spectral peak at nu = {freqs[np.argmax(spec)]:.3f} (theory ~0.161)")
R is the symmetric Toeplitz autocovariance matrix, and sigma2 = r[0] - phi . r[1:] is the leftover innovation power that scales the whole spectrum.fitted phi = [ 0.742 -0.493], sigma2 = 1.011
spectral peak at nu = 0.160 (theory ~0.161)
Parametric and nonparametric estimation answer the same question with opposite biases. The AR spectrum is smooth, resolves sharp peaks with very little data, and extrapolates the autocovariance beyond the observed lags rather than truncating it, which sharpens narrow lines. Its weakness is model dependence: choose the order $p$ too low and real peaks merge or vanish; too high and spurious peaks appear, so $p$ is selected by the same AIC or BIC criteria that Chapter 5 uses for ARIMA orders. Welch and the Daniell smoother make no such structural assumption and degrade gracefully when the process is not autoregressive, but they need more data to achieve the same peak sharpness. The practical rule is parametric for short records with suspected narrow resonances, nonparametric for long records or broadband, hard-to-model spectra.
The AR spectrum has a striking variational identity: among all spectra whose first $p$ autocovariances match the data, the AR($p$) spectrum is the one of maximum entropy, the flattest, least-committal curve consistent with what you measured. Burg derived his estimator from exactly this principle while processing seismic data in the 1960s, which is why geophysicists still call it maximum-entropy spectral analysis. The estimator that gives forecasters their sharp spectral peaks was born to find oil.
4. Practical Reading: Resolution, Detrending, and Confidence Advanced
Four practical decisions separate a trustworthy spectrum from a misleading one, and none of them is the default. The first is the resolution-variance choice already named: pick the segment length $m$ (or smoothing width) from the question, not the data. If you must resolve two periodic components at frequencies $\nu_1$ and $\nu_2$, your frequency resolution, roughly $1/m$ for a Welch segment, must be finer than $|\nu_1 - \nu_2|$, which sets a floor on $m$ and therefore a ceiling on the number of averages $K$. If instead you only need the broadband shape, shrink $m$ to buy as many averages as possible.
The second decision is segment length and overlap together. Overlapping Welch segments by 50 percent with a Hann taper is the canonical default because it recovers most of the variance reduction of independent segments while reusing the data; pushing overlap much past 75 percent yields diminishing returns because heavily overlapping segments are strongly correlated and contribute little fresh information. The effective number of independent averages, not the raw segment count, is what divides the variance.
The third decision is detrending, and skipping it is the most common way to ruin a spectrum. A nonzero mean injects a giant spike at $\nu = 0$ whose leakage contaminates the low-frequency ordinates; a linear trend injects a steep low-frequency ramp that masquerades as red noise and can bury a real low-frequency peak. Always remove at least the mean (the from-scratch code does this), and remove a linear trend when one is visible, before estimating the spectrum. Spectral estimation assumes stationarity, and a trend is the loudest violation of it.
The fourth decision is honesty about uncertainty. Because a smoothed estimate is approximately $f(\nu)$ times a scaled chi-squared variable, a $1-\alpha$ confidence interval for the true spectral density takes the multiplicative form
$$\frac{\nu_{\text{eff}}\, \hat f(\nu)}{\chi^2_{\nu_{\text{eff}},\,1-\alpha/2}} \;\le\; f(\nu) \;\le\; \frac{\nu_{\text{eff}}\, \hat f(\nu)}{\chi^2_{\nu_{\text{eff}},\,\alpha/2}},$$where $\nu_{\text{eff}}$ is the equivalent degrees of freedom, approximately $2K$ for $K$ averaged Welch segments. The interval is multiplicative, not additive, which is why spectra are read and plotted on a logarithmic (decibel) axis: on a log scale the chi-squared band has constant width at every frequency, so a fixed vertical ribbon is the honest error bar. The next numeric example turns this into specific numbers.
Suppose a Welch estimate averages $K = 8$ segments, giving $\nu_{\text{eff}} = 2K = 16$ degrees of freedom. The $\chi^2_{16}$ quantiles are $\chi^2_{16,\,0.975} = 28.85$ and $\chi^2_{16,\,0.025} = 6.91$, so the 95 percent interval runs from $16\hat f/28.85 = 0.555\,\hat f$ up to $16\hat f/6.91 = 2.316\,\hat f$. The true density could be anywhere from about half to more than double the estimate, a factor of roughly $4.2$ in width. To halve that ratio you must roughly quadruple the degrees of freedom: at $K = 32$ ($\nu_{\text{eff}} = 64$) the band tightens to about $0.75\,\hat f$ to $1.38\,\hat f$, a factor near $1.8$. Tight spectral confidence is expensive in data, the same square-root-of-$K$ law that governed the significance bands of Section 3.3.
Who: A reliability engineer monitoring a fleet of industrial pumps, building a vibration-based early-warning system from accelerometer telemetry.
Situation: She estimated the vibration spectrum of each pump and watched for a growing peak near the bearing-defect frequency, planning to flag any pump whose peak crossed a fixed height threshold.
Problem: The raw periodogram she started with was so jagged that random spikes routinely crossed the threshold, generating a flood of false maintenance alerts that the technicians quickly learned to ignore.
Dilemma: Smoothing the spectrum would calm the false alarms, but smoothing too aggressively would broaden and lower the very defect peak she needed to detect, risking missed failures. Resolution against variance, with safety on the line.
Decision: She replaced the raw periodogram with a Welch estimate, choosing the segment length from the physics: long enough that the defect frequency and the nearby shaft-rotation frequency fell in separate bins, short enough to average at least sixteen segments per window for a stable curve.
How: She detrended each window (the sensors had a slow thermal drift that was pumping a false low-frequency ramp into the spectrum), applied a 50 percent overlap Hann-tapered Welch estimate, and attached the chi-squared confidence band so that the alert fired only when the peak's lower confidence bound, not its noisy point estimate, crossed the threshold.
Result: False alerts dropped by more than an order of magnitude while every seeded defect in the validation set was still caught, because the defect peak was narrow and tall enough to clear the band even after smoothing. The confidence band turned a noisy detector into a calibrated one.
Lesson: A spectrum without smoothing is too noisy to threshold and a spectrum without a confidence band is too confident to trust. Choosing the segment length from the frequencies you must separate, detrending first, and alerting on the lower confidence bound are what turn spectral estimation into a deployable monitor. The same telemetry returns for change-point detection in Chapter 8.
5. Worked Example: A Noisy AR Spectrum, From Scratch and From the Library Advanced
We now estimate the spectrum of a known noisy autoregressive process three ways and compare them against the truth, which we can compute exactly because we chose the process. The signal is an AR(2) with a resonance, $X_t = 0.6\,X_{t-1} - 0.7\,X_{t-2} + \varepsilon_t$, whose complex roots place a peak near $\nu_0 \approx 0.17$ cycles per sample, plus the same process observed under additional measurement noise so the spectrum has both a sharp line and a broadband floor. We estimate it with the from-scratch Welch routine of Code 4.2.1, with the from-scratch AR spectrum of Code 4.2.2, and with the library standard scipy.signal.welch, then overlay the true AR spectral density. Figure 4.2.1 sketches the qualitative comparison the code will produce numerically.
Code 4.2.3 assembles the comparison, reusing welch_psd, yule_walker_ar, and ar_spectrum from earlier in the section, and reports how the estimate of the peak frequency and the spread of the curve change as the number of averaged segments grows.
def true_ar2_spectrum(phi1, phi2, sigma2, freqs):
"""Analytic AR(2) spectral density, the ground truth to compare against."""
z1 = np.exp(-2j * np.pi * freqs)
z2 = np.exp(-4j * np.pi * freqs)
return sigma2 / np.abs(1 - phi1 * z1 - phi2 * z2)**2
rng = np.random.default_rng(4)
n = 8192
e = rng.standard_normal(n)
x = np.zeros(n)
for k in range(2, n): # AR(2) with a resonance near nu = 0.17
x[k] = 0.6 * x[k - 1] - 0.7 * x[k - 2] + e[k]
grid = np.linspace(0, 0.5, 513)
truth = true_ar2_spectrum(0.6, -0.7, 1.0, grid)
# Two Welch estimates: long segments (few averages) vs short segments (many averages).
for seg in (1024, 128):
f, psd, K = welch_psd(x, seg_len=seg)
peak = f[np.argmax(psd)]
# crude peak-width proxy: fraction of total power within +/- 0.02 of the peak
band = (f > peak - 0.02) & (f < peak + 0.02)
conc = psd[band].sum() / psd.sum()
print(f"seg={seg:4d}: K={K:3d} averages, peak nu={peak:.3f}, power-in-band={conc:.2f}")
# Parametric AR(2) spectrum from a from-scratch Yule-Walker fit.
phi, s2 = yule_walker_ar(x, p=2)
ar_spec = ar_spectrum(phi, s2, grid)
print(f"AR fit: peak nu={grid[np.argmax(ar_spec)]:.3f}, "
f"max-error vs truth={np.max(np.abs(np.log(ar_spec) - np.log(truth))):.3f} (log units)")
power-in-band proxy quantifies how a longer segment concentrates power into a sharper peak.seg=1024: K= 15 averages, peak nu=0.172, power-in-band=0.43
seg= 128: K=127 averages, peak nu=0.172, power-in-band=0.31
AR fit: peak nu=0.172, max-error vs truth=0.046 (log units)
Having written Welch by hand, you would reach for the library in production. The scipy.signal.welch function is the exact same algorithm with battle-tested edge handling.
from scipy import signal
# The same Welch estimate the from-scratch welch_psd computes, in one call.
f_lib, psd_lib = signal.welch(x, fs=1.0, window="hann",
nperseg=1024, noverlap=512, detrend="constant")
print(f"scipy.welch peak at nu = {f_lib[np.argmax(psd_lib)]:.3f}")
# Compare against our from-scratch estimate on the same configuration.
f_own, psd_own, _ = welch_psd(x, seg_len=1024, overlap=0.5)
rel = np.max(np.abs(psd_lib - psd_own) / psd_lib.max())
print(f"max relative difference from from-scratch: {rel:.2e}")
scipy.signal.welch reproduces the from-scratch estimate in one call, with the window, overlap, and detrending all as named arguments. The relative-difference check confirms the hand-written routine and the library agree to numerical precision.scipy.welch peak at nu = 0.172
max relative difference from from-scratch: 3.7e-15
welch_psd implemented the textbook algorithm correctly and that the shortcut hides no different mathematics, only better engineering.The roughly twenty lines of welch_psd (segment loop, Hann taper, power normalization, one-sided folding) collapse to the single scipy.signal.welch call of Code 4.2.4, a better than tenfold reduction, and the library additionally handles odd segment lengths, boundary padding, the choice among a dozen window functions, and the scaling="density" versus "spectrum" distinction. Its siblings cover the rest of the section: scipy.signal.periodogram gives the raw unsmoothed estimate, scipy.signal.csd the cross-spectrum, and spectrum.pburg or statsmodels.regression.linear_model.yule_walker the parametric AR spectrum of subsection three. For long-horizon forecasting pipelines, the same Welch machinery is what the frequency-domain models of Chapter 14 call internally to find dominant periods. Use the from-scratch versions to understand the estimator; ship the library.
Spectral density estimation is having an unexpected second life inside neural time-series models. The frequency-domain forecasters that gained ground in 2024 to 2026, FEDformer and the FreTS / FITS line, operate directly on the discrete Fourier coefficients and implicitly learn which spectral bands carry predictive signal, so a clean spectral estimate of the input is what tells you which frequencies those models can exploit. TimesNet (2023 onward) uses an autocovariance-derived dominant-period detector, essentially a spectral-peak finder, to reshape a one-dimensional series into a two-dimensional tensor indexed by its strongest periods, and FITS achieves competitive long-horizon forecasts with a few thousand parameters by learning a complex-valued filter in the frequency domain, a learned cousin of the parametric AR spectrum of subsection three. On the diagnostic side, evaluations of temporal foundation models (Chronos, TimesFM, Moirai, MOMENT) increasingly report whether a model has reproduced the input spectrum or merely its level, and 2024 to 2025 work on multitaper and wavelet-based estimators sharpens confidence intervals for the non-stationary, regime-switching series these models are deployed on. The ninety-year-old Wiener-Khinchin identity now sets the input representation for the newest forecasters, a thread Chapter 14 picks up in full.
Explain in your own words why doubling the length of a series leaves each periodogram ordinate just as noisy as before, using the two-degrees-of-freedom argument of subsection one. Then state precisely what changes when you instead double the number of averaged Welch segments, and why that does reduce the variance. Conclude by contrasting the words "unbiased" and "consistent" as they apply to the raw periodogram, and explain why an estimator can be the first without being the second.
Using welch_psd from Code 4.2.1, simulate a signal containing two close tones at $\nu = 0.20$ and $\nu = 0.23$ in white noise. Sweep the segment length over $\{32, 64, 128, 256, 512\}$ and, for each, report whether the two peaks are resolved as separate maxima and how many segments were averaged. Identify the shortest segment that still resolves the pair, relate the threshold to the frequency-resolution rule $1/m > |\nu_1 - \nu_2|$ of subsection four, and plot the variance of the estimate against segment count to confirm the $1/(nb)$ scaling.
Take the AR(2) of the worked example and estimate its spectrum three ways on a short record of only $n = 256$ points: a single raw periodogram, a Welch estimate, and the from-scratch Yule-Walker AR(2) spectrum of Code 4.2.2. Overlay all three on the true spectrum (use true_ar2_spectrum) on a log axis, and report the maximum log-deviation from truth for each. Explain which method wins at this short length and why, connecting your answer to the bias of nonparametric smoothing on short records and the maximum-entropy property of the AR spectrum from the fun note in subsection three.
Add a slow linear trend and a nonzero mean to a stationary series, then estimate its spectrum with and without detrending. Show how the undetrended estimate develops a spurious low-frequency ramp that can hide a genuine low-frequency peak, and quantify how much of the total estimated power the artifact absorbs. Then discuss the harder case: how would you distinguish a real low-frequency spectral peak (genuine slow oscillation) from the leakage of a trend or unit root, and how does this connect to the differencing and stationarity tests of Chapter 5? There is no single correct answer; argue from the spectrum's behavior as you change the detrending order.