spectral-analysis
Frequency-domain analysis — FFT, power spectral density (Welch/periodogram), spectrograms, wavelet transforms, and coherence. Use for any signal with periodic, quasi-periodic, or transient frequency content in physics data.
- License 许可
- MIT
- 最近更新
- 2026-10-28
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- spectral-analysis
- tags
- [ "FFT", "Spectral Analysis", "PSD", "Wavelets", "Signal Processing", "Frequency", "Physics" ]
- author
- Synthetic Sciences
- license
- MIT
- version
- 1.0.0
- category
- physics
- description
- Frequency-domain analysis — FFT, power spectral density (Welch/periodogram), spectrograms, wavelet transforms, and coherence. Use for any signal with periodic, quasi-periodic, or transient frequency content in physics data.
- dependencies
- [ "scipy>=1.11.0", "numpy>=1.24.0", "matplotlib>=3.7.0" ]
03 SKILL.md 正文
Spectral Analysis
Overview
Extract frequency content from physics signals using FFT, power spectral density estimation, spectrograms, and wavelet transforms. Covers stationary and non-stationary signals.
When to Use
- Finding oscillation frequencies in time-series data
- Computing power spectra of turbulence, vibrations, or wave data
- Detecting transient features (spectrograms, wavelets)
- Filtering signals (low-pass, band-pass, notch)
- Cross-spectral analysis and coherence between signals
Core Workflows
1. FFT (Fast Fourier Transform)
import numpy as np
import matplotlib.pyplot as plt
# Signal: sum of two sinusoids + noise
dt = 0.001 # sampling interval [s]
fs = 1 / dt # sampling frequency [Hz]
t = np.arange(0, 1, dt)
signal = 1.5 * np.sin(2*np.pi*50*t) + 0.8 * np.sin(2*np.pi*120*t)
signal += 0.5 * np.random.randn(len(t))
# Compute FFT
N = len(t)
fft_vals = np.fft.rfft(signal)
freqs = np.fft.rfftfreq(N, d=dt)
amplitude = 2.0 / N * np.abs(fft_vals) # single-sided amplitude
phase = np.angle(fft_vals)
fig, axes = plt.subplots(2, 1, figsize=(10, 7))
axes[0].plot(t[:200], signal[:200], 'b-', linewidth=0.8)
axes[0].set_xlabel('Time [s]')
axes[0].set_ylabel('Amplitude')
axes[0].set_title('Time Domain Signal')
axes[0].grid(True, alpha=0.3)
axes[1].plot(freqs, amplitude, 'r-', linewidth=0.8)
axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Amplitude')
axes[1].set_title('FFT Amplitude Spectrum')
axes[1].set_xlim(0, 200)
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('fft_analysis.png', dpi=150, bbox_inches='tight')
2. Power Spectral Density (Welch Method)
from scipy.signal import welch, periodogram
# Welch PSD (better noise averaging than raw FFT)
freqs_w, psd_w = welch(signal, fs=fs, nperseg=256, noverlap=128)
# Periodogram (raw, no averaging)
freqs_p, psd_p = periodogram(signal, fs=fs)
fig, ax = plt.subplots(figsize=(10, 5))
ax.semilogy(freqs_p, psd_p, 'gray', alpha=0.3, label='Periodogram')
ax.semilogy(freqs_w, psd_w, 'r-', linewidth=2, label='Welch PSD')
ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('PSD [V²/Hz]')
ax.set_title('Power Spectral Density')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 200)
plt.savefig('psd.png', dpi=150, bbox_inches='tight')
Welch parameters:
nperseg: Segment length (frequency resolution = fs/nperseg)noverlap: Overlap between segments (typically 50% = nperseg//2)window: Window function ('hann' default, 'blackman' for better sidelobe suppression)- Trade-off: longer segments → better frequency resolution, worse noise averaging
3. Spectrogram (Time-Frequency)
from scipy.signal import spectrogram
# Chirp signal (frequency sweeps from 10 to 200 Hz)
t_chirp = np.linspace(0, 2, 8000)
chirp = np.sin(2*np.pi * (10*t_chirp + 47.5*t_chirp**2))
f_spec, t_spec, Sxx = spectrogram(chirp, fs=4000, nperseg=256, noverlap=200)
fig, ax = plt.subplots(figsize=(10, 5))
pcm = ax.pcolormesh(t_spec, f_spec, 10*np.log10(Sxx + 1e-20),
shading='gouraud', cmap='inferno')
ax.set_ylabel('Frequency [Hz]')
ax.set_xlabel('Time [s]')
ax.set_title('Spectrogram')
plt.colorbar(pcm, ax=ax, label='PSD [dB/Hz]')
plt.savefig('spectrogram.png', dpi=150, bbox_inches='tight')
4. Wavelet Transform (Continuous)
from scipy.signal import cwt, morlet2
# Continuous wavelet transform with Morlet wavelet
widths = np.geomspace(1, 128, num=100) # scale parameters
cwtmatr = cwt(signal, morlet2, widths, w=6)
# Convert scales to frequencies: f = w * fs / (2*pi*scale)
frequencies = 6 * fs / (2 * np.pi * widths)
fig, ax = plt.subplots(figsize=(10, 5))
ax.pcolormesh(t, frequencies, np.abs(cwtmatr),
shading='gouraud', cmap='viridis')
ax.set_ylabel('Frequency [Hz]')
ax.set_xlabel('Time [s]')
ax.set_title('Continuous Wavelet Transform (Morlet)')
ax.set_ylim(0, 200)
plt.colorbar(ax.collections[0], ax=ax, label='|CWT|')
plt.savefig('cwt.png', dpi=150, bbox_inches='tight')
5. Cross-Spectral Analysis and Coherence
from scipy.signal import coherence, csd
# Two related signals
signal2 = 1.2 * np.sin(2*np.pi*50*t + 0.3) + 0.6 * np.random.randn(len(t))
# Coherence (how correlated are the two signals at each frequency)
f_coh, Cxy = coherence(signal, signal2, fs=fs, nperseg=256)
# Cross-spectral density
f_csd, Pxy = csd(signal, signal2, fs=fs, nperseg=256)
fig, axes = plt.subplots(2, 1, figsize=(10, 7))
axes[0].plot(f_coh, Cxy, 'b-', linewidth=1.5)
axes[0].set_ylabel('Coherence')
axes[0].set_title('Coherence between signals')
axes[0].set_xlim(0, 200)
axes[0].grid(True, alpha=0.3)
axes[1].semilogy(f_csd, np.abs(Pxy), 'r-', linewidth=1.5)
axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('|CSD| [V²/Hz]')
axes[1].set_title('Cross-Spectral Density')
axes[1].set_xlim(0, 200)
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('coherence.png', dpi=150, bbox_inches='tight')
6. Filtering
from scipy.signal import butter, sosfilt, sosfiltfilt
def bandpass_filter(data, lowcut, highcut, fs, order=4):
"""Apply zero-phase Butterworth bandpass filter."""
nyq = 0.5 * fs
sos = butter(order, [lowcut/nyq, highcut/nyq], btype='band', output='sos')
return sosfiltfilt(sos, data)
# Example: extract 45-55 Hz component
filtered = bandpass_filter(signal, 45, 55, fs, order=4)
Frequency Resolution vs Averaging Trade-off
| Parameter | Effect on Resolution | Effect on Noise |
|---|---|---|
Longer nperseg |
Better frequency resolution | More noise (fewer averages) |
Shorter nperseg |
Worse frequency resolution | Less noise (more averages) |
More noverlap |
Same resolution | Slightly less noise |
| Windowing (Hann) | Wider main lobe | Better sidelobe suppression |
Rule of thumb: Frequency resolution Δf = fs / nperseg
Common Pitfalls
| Pitfall | Fix |
|---|---|
| Aliasing (fs too low) | Nyquist: fs ≥ 2 × f_max |
| Spectral leakage | Apply window function (Hann, Blackman) |
| Zero-padding confusion | Zero-padding interpolates FFT, doesn't improve resolution |
| Wrong units on PSD | Check: V²/Hz for continuous, V² for discrete |
| DC component dominates | Subtract mean before FFT |
| Non-uniform sampling | Use Lomb-Scargle periodogram (scipy.signal.lombscargle) |
04 同仓库更多 Skills
statistical-power
Sample size calculation, power analysis, and effect size conversion for scientific studies. Use BEFORE data collection (a priori) to determine required N, or after (post-hoc) to compute achieved power. Covers parametric tests (t-test, ANOVA, chi-square, proportions, correlation, regression), simulation-based power for complex/clustered/nested designs, and power curves. Use this skill when someone asks 'how many samples do I need', 'what is my power', 'what effect size can I detect', or any variant. Pairs with /experimental-design (choose design first, then compute power) and /statistical-analysis (analyze data after collection).
statistical-analysis
Guided statistical analysis with test selection and reporting. Use when you need help choosing appropriate tests for your data, assumption checking, power analysis, and APA-formatted results. Best for academic research reporting, test selection guidance. For implementing specific models programmatically use statsmodels.
scientific-writing
Core skill for the deep research and writing tool. Write scientific manuscripts in full paragraphs (never bullet points). Use two-stage process with (1) section outlines with key points using research-lookup then (2) convert to flowing prose. IMRAD structure, citations (APA/AMA/Vancouver), figures/tables, reporting guidelines (CONSORT/STROBE/PRISMA), for research papers and journal submissions.
scientific-visualization
Meta-skill for publication-ready figures. Use when creating journal submission figures requiring multi-panel layouts, significance annotations, error bars, colorblind-safe palettes, and specific journal formatting (Nature, Science, Cell). Orchestrates matplotlib/seaborn/plotly with publication styles. For quick exploration use seaborn or plotly directly.
research-code
Debug, profile, review, and restructure Python research and analysis code. Use when analysis scripts crash, produce wrong results, run too slowly, or need a correctness review. Covers: reading tracebacks and diagnosing root causes, hypothesis-driven debugging, reviewing code for common statistical/numerical errors, profiling data pipelines, improving reproducibility (seeds, environments, determinism), and structuring analysis scripts for sharing. Distinct from /exploratory-data-analysis (which understands data) and /statistical-analysis (which chooses and runs tests) — this skill focuses on the code itself.
markdown-mermaid-writing
Comprehensive markdown and Mermaid diagram writing skill. Use when creating any scientific document, report, analysis, or visualization. Establishes text-based diagrams as the default documentation standard with full style guides (markdown + mermaid), 24 diagram type references, and 9 document templates.