Menu

Signal ProcessingLive Code Editor
59 researchers ran this analysis this month

Peak Detection in Python with scipy.signal.find_peaks

Technique overview

Detect peaks in experimental data using scipy.signal.find_peaks. Covers prominence, height, threshold parameters with visualizations and peak area integration.

Peak detection is a building block for nearly every analytical pipeline that processes spectroscopic or chromatographic data. Whether you are identifying Raman bands in a mineral spectrum, counting action potentials in an electrophysiology recording, or locating retention-time peaks in a GC-MS chromatogram, scipy.signal.find_peaks is the go-to function. The challenge is not calling find_peaks itself - it is tuning the prominence, height, distance, and width parameters so that you capture the true peaks without false positives from noise or false negatives from broad features. This page demonstrates the parameter-tuning workflow with visual feedback and extends the analysis to peak area integration.

Key points

  • Detect peaks in experimental data using scipy.signal.find_peaks. Covers prominence, height, threshold parameters with visualizations and peak area integration.
  • Peak detection is a building block for nearly every analytical pipeline that processes spectroscopic or chromatographic data.
  • Whether you are identifying Raman bands in a mineral spectrum, counting action potentials in an electrophysiology recording, or locating retention-time peaks in a GC-MS chromatogram, scipy.signal.find_peaks is the go-to function.
  • The challenge is not calling find_peaks itself - it is tuning the prominence, height, distance, and width parameters so that you capture the true peaks without false positives from noise or false negatives from broad features.
scipynumpymatplotlib

Example Visualization

Review the example first, then use the live editor below to run and customize the full workflow.

Mathematical Foundation

Peak detection is a building block for nearly every analytical pipeline that processes spectroscopic or chromatographic data.

xᵢ > xᵢ₋₁ and xᵢ > xᵢ₊₁

Equation

A peak at index i satisfies: x[i] > x[i-1] AND x[i] > x[i+1], subject to height, prominence, distance, and width constraints.

Parameter breakdown

heightMinimum peak height (absolute threshold)
prominenceMinimum vertical distance between the peak and its surrounding valleys (relative threshold)
distanceMinimum horizontal distance between neighbouring peaks (in sample indices)
widthMinimum peak width at half prominence
thresholdMinimum difference between a peak and its immediate neighbours

When to use this technique

Use find_peaks when your signal has clearly defined local maxima. Always start with prominence-based detection (robust to baseline drift) before fine-tuning with height and distance. For noisy data, smooth first with Savitzky-Golay, then detect peaks.

Apply This Technique Now

Run this analysis workflow with AI in seconds. Use the prepared technique prompt or bring your own dataset.

View example prompt
Example AI Prompt

"Detect all peaks in my spectroscopy data, annotate them with height and width markers, and calculate the integrated area under each peak"

How to apply this technique in 30 seconds

1

Upload Data

Upload your CSV or Excel file in Analyze and keep your column names as-is.

2

Generate

Run the example prompt and let AI generate this technique automatically.

3

Refine and Export

Adjust code or prompt, then export publication-ready figures.

Implementation Code

The core data processing logic. Copy this block and replace the sample data with your measurements.

import numpy as np
from scipy.signal import find_peaks, peak_prominences, peak_widths

# --- Simulated chromatogram with 4 peaks ---
np.random.seed(42)
x = np.linspace(0, 20, 1000)  # minutes
signal = (0.6 * np.exp(-((x - 4.2) / 0.3) ** 2)
        + 1.0 * np.exp(-((x - 8.5) / 0.5) ** 2)
        + 0.3 * np.exp(-((x - 11.0) / 0.2) ** 2)
        + 0.8 * np.exp(-((x - 15.3) / 0.6) ** 2))
signal += np.random.normal(0, 0.02, size=x.shape)

# --- Detect peaks ---
peaks, properties = find_peaks(signal, height=0.1, prominence=0.05,
                                distance=20, width=3)

prominences = peak_prominences(signal, peaks)[0]
widths_result = peak_widths(signal, peaks, rel_height=0.5)

print(f"Found {len(peaks)} peaks:")
for i, pk in enumerate(peaks):
    print(f"  Peak {i+1}: time={x[pk]:.2f} min, height={signal[pk]:.3f}, "
          f"prominence={prominences[i]:.3f}, width={widths_result[0][i]:.1f} pts")

Visualization Code

Complete matplotlib code for a publication-ready figure. Copy, paste into your notebook, and adjust labels to match your data.

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks, peak_widths

np.random.seed(42)
x = np.linspace(0, 20, 1000)
signal = (0.6 * np.exp(-((x - 4.2) / 0.3) ** 2)
        + 1.0 * np.exp(-((x - 8.5) / 0.5) ** 2)
        + 0.3 * np.exp(-((x - 11.0) / 0.2) ** 2)
        + 0.8 * np.exp(-((x - 15.3) / 0.6) ** 2))
signal += np.random.normal(0, 0.02, size=x.shape)

peaks, props = find_peaks(signal, height=0.1, prominence=0.05,
                           distance=20, width=3)
widths_half = peak_widths(signal, peaks, rel_height=0.5)

fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(x, signal, color='#888', lw=0.8, label='Signal')
ax.plot(x[peaks], signal[peaks], 'v', color='#9240ff', markersize=10,
        label='Detected peaks')

# Annotate each peak
for i, pk in enumerate(peaks):
    ax.vlines(x[pk], ymin=0, ymax=signal[pk], color='#9240ff',
              linestyle='--', lw=0.8, alpha=0.5)
    ax.annotate(f'{x[pk]:.1f} min\nh={signal[pk]:.2f}',
                xy=(x[pk], signal[pk]), xytext=(8, 10),
                textcoords='offset points', fontsize=7,
                arrowprops=dict(arrowstyle='->', color='#9240ff', lw=0.8))

# Width bars
ax.hlines(widths_half[1], x[widths_half[2].astype(int)],
          x[widths_half[3].astype(int)], color='#9240ff', lw=1.5)

ax.set_xlabel('Retention Time (min)')
ax.set_ylabel('Detector Response (a.u.)')
ax.set_title('Peak Detection with scipy.signal.find_peaks', fontsize=12)
ax.legend(frameon=False)
ax.spines[['top', 'right']].set_visible(False)
plt.tight_layout()
plt.savefig('peak_detection.png', dpi=300, bbox_inches='tight')
plt.show()

Peak Area Integration (Trapezoidal Rule)

After detecting peaks, the next step in chromatography and spectroscopy is often to integrate the area under each peak. The trapezoidal rule (scipy.integrate.trapezoid) gives a quick, non-parametric area estimate. For overlapping peaks, deconvolution with Gaussian or Voigt models is preferred.

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks, peak_widths
from scipy.integrate import trapezoid

np.random.seed(42)
x = np.linspace(0, 20, 1000)
signal = (0.6 * np.exp(-((x - 4.2) / 0.3) ** 2)
        + 1.0 * np.exp(-((x - 8.5) / 0.5) ** 2)
        + 0.3 * np.exp(-((x - 11.0) / 0.2) ** 2)
        + 0.8 * np.exp(-((x - 15.3) / 0.6) ** 2))
signal += np.random.normal(0, 0.02, size=x.shape)

peaks, _ = find_peaks(signal, height=0.1, prominence=0.05, distance=20)
widths_result = peak_widths(signal, peaks, rel_height=0.95)

fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(x, signal, color='#888', lw=0.8)

colors = ['#9240ff', '#e67e22', '#27ae60', '#e74c3c']
for i, pk in enumerate(peaks):
    left = max(0, int(widths_result[2][i]))
    right = min(len(x) - 1, int(widths_result[3][i]))
    area = trapezoid(signal[left:right+1], x[left:right+1])
    ax.fill_between(x[left:right+1], signal[left:right+1],
                    alpha=0.3, color=colors[i % len(colors)])
    ax.annotate(f'Area = {area:.3f}', xy=(x[pk], signal[pk]),
                xytext=(0, 15), textcoords='offset points',
                ha='center', fontsize=8, color=colors[i % len(colors)])

ax.plot(x[peaks], signal[peaks], 'v', color='black', markersize=8)
ax.set_xlabel('Retention Time (min)')
ax.set_ylabel('Response')
ax.set_title('Peak Area Integration', fontsize=12)
ax.spines[['top', 'right']].set_visible(False)
plt.tight_layout()
plt.savefig('peak_areas.png', dpi=300, bbox_inches='tight')
plt.show()

Common Errors and How to Fix Them

Too many false peaks detected (noise spikes)

Why: Using height as the only constraint picks up every noise spike above the threshold.

Fix: Add a prominence requirement (prominence=0.05 or higher) and a minimum distance between peaks. Smooth the data with Savitzky-Golay before detection.

Real peaks are missed (false negatives)

Why: The prominence or height threshold is set too high, or the distance constraint is too large, merging nearby peaks into one.

Fix: Lower the prominence threshold incrementally and visually inspect results. Reduce the distance parameter for closely spaced peaks.

Noisy baseline creates spurious peaks at the edges

Why: Baseline drift or edge artifacts from smoothing create apparent peaks near the start or end of the trace.

Fix: Crop the first and last few percent of the signal, or subtract the baseline before peak detection using scipy.signal.detrend or asymmetric least-squares.

Negative peaks (troughs) are not detected

Why: find_peaks only searches for local maxima. Absorption peaks (dips) are missed by default.

Fix: Invert the signal: peaks_neg, _ = find_peaks(-signal, ...). Alternatively, pass the negated signal and flip the results back.

Parameter tuning feels arbitrary and irreproducible

Why: There is no universally correct set of parameters - they depend on signal-to-noise ratio, peak shapes, and instrument resolution.

Fix: Document the exact parameter values used. Create a parameter sweep plot showing detected peaks vs parameter values. Use a validation dataset with known peaks.

Frequently Asked Questions

Apply Peak Detection in Python with scipy.signal.find_peaks to Your Data

Upload your dataset and Plotivy generates the Python code, runs the analysis, and produces a publication-ready figure.

Generate Code for This Technique

Python Libraries

scipynumpymatplotlib

Quick Info

Domain
Signal Processing
Typical Audience
Spectroscopists, chromatographers, and electrophysiologists who need automated peak identification and quantification from instrument data

Related Chart Guides

Apply to your data

Upload a dataset and get Python code instantly

Get Started Free