如何计算生物分析仪多峰数据的每个峰下面积
嘿,这个需求完全可以实现!我结合你现有的代码,给你拆解几个实用的方案,直接就能改到你的脚本里~
核心思路
要计算峰下面积,关键是确定每个峰的左右边界(也就是峰从哪里开始上升,到哪里回到基线),然后对这个区间的x、y值做积分。你的x轴是等间隔的(0.05秒),刚好适合用Python里的积分工具快速计算。
步骤1:确定峰的边界
首先,你已经用peakutils.indexes拿到了峰的索引,接下来要给每个峰划分对应的区间。这里分两种常用场景:
场景1:峰之间分离清晰(无重叠)
直接用相邻峰的中点作为边界,简单高效:
n_peaks = len(indices) peak_bounds = [] for i in range(n_peaks): # 左边界:第一个峰从数据起始开始,其他取当前峰与前一个峰的中点 left_idx = 0 if i == 0 else (indices[i-1] + indices[i]) // 2 # 右边界:最后一个峰到数据末尾,其他取当前峰与后一个峰的中点 right_idx = len(y)-1 if i == n_peaks-1 else (indices[i] + indices[i+1]) // 2 peak_bounds.append( (left_idx, right_idx) )
场景2:峰有重叠或基线有波动
这种情况建议找相邻峰之间的**谷底(最小值点)**作为边界,更精准:
n_peaks = len(indices) peak_bounds = [] for i in range(n_peaks): if i == 0: left_idx = 0 # 第一个峰左边界从数据开头开始 else: # 找前一个峰到当前峰之间的最小值索引 between_range = range(indices[i-1], indices[i]) min_pos = indices[i-1] + np.argmin(y[between_range]) left_idx = min_pos if i == n_peaks-1: right_idx = len(y)-1 # 最后一个峰右边界到数据末尾 else: # 找当前峰到后一个峰之间的最小值索引 between_range = range(indices[i], indices[i+1]) min_pos = indices[i] + np.argmin(y[between_range]) right_idx = min_pos peak_bounds.append( (left_idx, right_idx) )
步骤2:计算峰下面积
Python里有两个常用的积分工具,都适合你的等间隔数据:
方法1:梯形积分(numpy.trapz)
简单快速,适合大多数场景:
import numpy as np peak_areas = [] for i, (left, right) in enumerate(peak_bounds): # 提取当前峰的x、y区间 x_segment = x[left:right+1] y_segment = y[left:right+1] # 计算面积(因为x等间隔,也可以简化成 np.trapz(y_segment)*0.05) area = np.trapz(y_segment, x_segment) peak_areas.append({ "peak_x": x[indices[i]], "peak_y": y[indices[i]], "area": area })
方法2:辛普森积分(scipy.integrate.simpson)
精度更高,适合需要更准确结果的场景:
from scipy.integrate import simpson peak_areas = [] for i, (left, right) in enumerate(peak_bounds): x_segment = x[left:right+1] y_segment = y[left:right+1] area = simpson(y_segment, x_segment) peak_areas.append({ "peak_x": x[indices[i]], "peak_y": y[indices[i]], "area": area })
额外优化:基线校正
如果你的数据有基线偏移(比如y值不是从0开始),建议先校正基线,计算峰的净面积:
# 假设前100个点是基线区域,计算基线均值 baseline = np.mean(y[:100]) y_corrected = y - baseline # 之后用y_corrected代替y计算面积即可
完整整合后的代码
把上面的逻辑放到你的create_plot函数里,最终版本大概是这样:
import numpy as np import peakutils from scipy.integrate import simpson def create_plot(sheet_name): sample = book.sheet_by_name(sheet_name) data = [[sample.cell_value(r, c) for r in range(sample.nrows)] for c in range(sample.ncols)] y = data[2][18:len(data[2]) - 2] x = np.arange(32, 138.05, 0.05) indices = peakutils.indexes(y, thres=0.35, min_dist=0.1) # 步骤1:确定峰的边界(这里用谷底法,适合重叠峰) n_peaks = len(indices) peak_bounds = [] for i in range(n_peaks): if i == 0: left_idx = 0 else: between_range = range(indices[i-1], indices[i]) min_pos = indices[i-1] + np.argmin(y[between_range]) left_idx = min_pos if i == n_peaks-1: right_idx = len(y)-1 else: between_range = range(indices[i], indices[i+1]) min_pos = indices[i] + np.argmin(y[between_range]) right_idx = min_pos peak_bounds.append( (left_idx, right_idx) ) # 步骤2:计算每个峰的面积(辛普森积分+基线校正) baseline = np.mean(y[:100]) # 按需调整基线区域 y_corrected = y - baseline peak_areas = [] for i, (left, right) in enumerate(peak_bounds): x_segment = x[left:right+1] y_segment = y_corrected[left:right+1] area = simpson(y_segment, x_segment) peak_areas.append({ "peak_index": indices[i], "peak_time": x[indices[i]], "peak_absorbance": y[indices[i]], "net_area": area }) return y, x, indices, peak_areas
注意事项
- 如果你的峰非常密集或者重叠严重,可以考虑用
scipy.signal.find_peaks替代peakutils.indexes,它有更多参数(比如prominence)来更精准地识别峰。 - 边界确定的逻辑可以根据你的实际数据调整,比如如果基线有漂移,可能需要用滑动窗口计算基线。
内容的提问来源于stack exchange,提问作者Harbus
相关产品推荐
相关产品推荐

