用 Python 与 Plotly 实现数据平滑:Savitzky-Golay 滤波与三角移动平均完整实战
2026/9/21 19:28:40 网站建设 项目流程
  • 数据可视化
  • 数据分析

【免费下载链接】plotly.py

The interactive graphing library for Python :sparkles:

项目地址:https://gitcode.com/gh_mirrors/pl/plotly.py
点击查看免费下载

本文以 plotly.py 官方文档 doc/python/smoothing.md 为核心,结合仓库中plotly.graph_objects的 Scatter 轨迹源码,系统讲解如何在 Python 中对含噪数据做平滑处理:从 Savitzky-Golay 滤波(SciPy 内置)到自实现三角移动平均(TMA),并完整演示如何用go.Scatter将原始信号、含噪信号与平滑结果叠加在同一张图中。读完本文,你将掌握两类主流平滑算法的原理与参数调优方法,并能直接用 Plotly 可视化验证平滑效果。

为什么需要对数据做平滑处理

平滑(Smoothing)是一种消除数据集中噪声的技术。真实世界采集到的数据(传感器读数、实验测量值、金融时间序列等)几乎都叠加了不同程度的随机扰动,平滑算法的共同目的就是"磨平棱角"(roughing out the edges),让数据呈现出其背后潜在的结构与趋势。

但平滑并非无代价。只有当数据中几乎没有小尺度结构时,平滑才是有意义的——如果数据本身蕴含高频细节(尖峰、快速振荡、短时突变),平滑会把这些真实特征一并抹掉,从而歪曲数据原本的含义。从科学严谨性出发,使用平滑算法时必须至少能解释清楚:

  • 为什么要对这份数据做平滑;
  • 选择了哪种算法、哪些参数;
  • 平滑后保留了什么、丢失了什么。

本文将演示的两种方法——Savitzky-Golay 滤波与三角移动平均——恰好代表了两种不同的平滑哲学:前者用低阶多项式拟合滑动窗口以尽量保留数据形状,后者用加权平均压低噪声。

环境准备:导入依赖

本文所有示例基于 Python 3 + Plotly,并依赖 NumPy、Pandas 与 SciPy 生态(对应文档参见 doc/python/getting-started.md):

import plotly.graph_objects as go import numpy as np import pandas as pd import scipy from scipy import signal

其中:

  • plotly.graph_objects提供底层、声明式的绘图接口,通过go.Figurego.Scatter精确控制每条轨迹的样式;
  • numpy用于生成测试数据(linspacesin、随机噪声);
  • pandas是 Plotly 生态常用的数据容器,也便于后续替换为真实数据集;
  • scipy.signal提供本文的核心滤波函数savgol_filter

准备测试数据:正弦信号 + 均匀噪声

为了让平滑效果可对比,先构造一组"干净正弦 + 均匀分布噪声"的合成数据,并固定随机种子以保证结果可复现:

np.random.seed(1) x = np.linspace(0, 10, 100) y = np.sin(x) noise = 2 * np.random.random(len(x)) - 1 # 均匀分布于 [-1, 1] y_noise = y + noise

这里共 100 个采样点,噪声幅度与信号幅度相当(±1 对比正弦振幅 1),属于"高噪声"场景,正好能直观检验两种平滑算法的去噪能力。

方法一:Savitzky-Golay 滤波

原理:滑动窗口内的低阶多项式拟合

Savitzky-Golay 滤波(参见 SciPy 的scipy.signal.savgol_filter实现)的核心思想是:对数据中相邻的每一个窗口,用低阶多项式做最小二乘拟合,然后以拟合多项式在窗口中心点的值作为平滑输出。与简单平均相比,它在平滑噪声的同时能较好地保留信号的峰值、宽度等形状特征,尤其适合光谱、信号处理类数据。

从代码结构上看,示例中signal.savgol_filter(y_noise, 53, 3)的两个核心参数是:

参数示例值含义
window_length53滑动窗口大小,必须是奇数,且大于polyorder;窗口越大平滑越强,但边界截断区间越大
polyorder3窗口内拟合多项式的阶数;阶数越高越贴合原始形状,保留细节越多,但抗噪能力下降

其余可选参数(如deriv指定求导阶数、delta采样间隔、mode边界处理方式)默认即可满足常规平滑需求。

完整代码与参数说明

import plotly.graph_objects as go import numpy as np import pandas as pd import scipy from scipy import signal np.random.seed(1) x = np.linspace(0, 10, 100) y = np.sin(x) noise = 2 * np.random.random(len(x)) - 1 # 均匀分布于 -1 和 1 y_noise = y + noise fig = go.Figure() fig.add_trace(go.Scatter( x=x, y=y, mode='markers', marker=dict(size=2, color='black'), name='Sine' )) fig.add_trace(go.Scatter( x=x, y=y_noise, mode='markers', marker=dict( size=6, color='royalblue', symbol='circle-open' ), name='Noisy Sine' )) fig.add_trace(go.Scatter( x=x, y=signal.savgol_filter(y_noise, 53, # 用于滤波的窗口大小 3), # 拟合多项式阶数 mode='markers', marker=dict( size=6, color='mediumpurple', symbol='triangle-up' ), name='Savitzky-Golay' )) fig.show()

运行后,图中三条轨迹分别对应:真实正弦(黑色小圆点)、加噪信号(蓝色空心圆)、平滑结果(紫色三角)。可以观察到 Savitzky-Golay 输出基本贴合正弦曲线,且没有像移动平均那样明显削平波峰波谷——这正是低阶多项式拟合的优势。

参数调优建议

  • 增大window_length:平滑更彻底,但会压扁波峰、拉伸波谷,失真加剧;
  • 增大polyorder:更忠实于原数据形状,但保留的噪声也更多;
  • 工程上常见组合是window_length约为数据点数的 5%~10%、polyorder取 2~4,再根据可视化结果微调。

方法二:三角移动平均(Triangular Moving Average)

原理:两级移动平均加权

移动平均的思路是:在数据中取一个窗口,计算窗口内点的平均值,然后窗口向后滑动一个点重复计算,得到一串新点即平滑结果。1D 情况下,对 $N$ 个数据点 $y_1, y_2, \dots, y_N$,取窗口大小 $n < N$,第 $i$ 个平滑点的**简单移动平均(SMA)**为:

$$ \begin{align*} SMA_i = \frac{y_i + \dots + y_{i+n}}{n} \end{align*} $$

而**三角移动平均(TMA)**是在 SMA 之上再做一次移动平均:

$$ \begin{align*} TMA_i = \frac{SMA_i + \dots + SMA_{i+n}}{n} \end{align*} $$

两次平均的效果是:越靠近当前点的数据获得越高的权重(权重分布呈三角形),因此 TMA 比 SMA 更平滑,同时更重视相邻点,减少拖尾。

自实现 smoothTriangle 函数

以下smoothTriangle是文档给出的 TMA 实现,核心是构造一个"先升后降"的三角权重核,然后对每个窗口做加权平均:

def smoothTriangle(data, degree): triangle = np.concatenate((np.arange(degree + 1), np.arange(degree)[::-1])) # 先升后降 smoothed = [] for i in range(degree, len(data) - degree * 2): point = data[i:i + len(triangle)] * triangle smoothed.append(np.sum(point) / np.sum(triangle)) # 处理边界 smoothed = [smoothed[0]] * int(degree + degree / 2) + smoothed while len(smoothed) < len(data): smoothed.append(smoothed[-1]) return smoothed

逐段解读:

  1. 三角权重核np.arange(degree + 1)生成[0, 1, ..., degree]np.arange(degree)[::-1]生成[degree-1, ..., 0],拼接后得到长度2*degree+1的对称三角序列,窗口中心权重最大、两端为 0;
  2. 滑动加权平均:对每个位置截取与核等长的数据段,逐元素相乘后除以核的总权重(np.sum(triangle)),即得到该点的加权均值;
  3. 边界处理:滑动起始索引为degree,结束索引为len(data) - degree * 2,因此前后各有一段数据无法生成有效平滑点;代码用首尾平滑值做常数延拓(前段复制smoothed[0],尾段用smoothed[-1]补齐),保证输出长度与输入一致——这也是把smoothed[0]重复degree + degree/2次的原因。

完整绘图代码

fig = go.Figure() fig.add_trace(go.Scatter( x=x, y=y, mode='markers', marker=dict( size=2, color='rgb(0, 0, 0)', ), name='Sine' )) fig.add_trace(go.Scatter( x=x, y=y_noise, mode='markers', marker=dict( size=6, color='#5E88FC', symbol='circle-open' ), name='Noisy Sine' )) fig.add_trace(go.Scatter( x=x, y=smoothTriangle(y_noise, 10), # degree 设为 10 mode='markers', marker=dict( size=6, color='#C190F0', symbol='triangle-up' ), name='Moving Triangle - Degree 10' )) fig.show()

这里degree=10决定了三角核的半宽(核总长 21),直接控制平滑强度:degree越大,参与平均的点越多、平滑越强,同时边界延拓区间也越长。与 Savitzky-Golay 相比,TMA 对波峰的削平效应更明显——这是加权平均类方法的固有特性,需要结合数据特征权衡。

源码佐证:go.Scatter 如何支撑上述可视化

本文所有示例均基于plotly.graph_objectsgo.Scatter轨迹,其定义位于 plotly/graph_objs/_scatter.py(class Scatter(_BaseTraceType))。与平滑可视化直接相关的两个属性在源码中有明确说明:

  • modeThe 'mode' property is a flaglist... Any combination of ['lines', 'markers', 'text'] joined with '+' characters OR exactly one of ['none'](见 plotly/graph_objs/_scatter.py)。示例统一使用mode='markers'以点图形式对比三条轨迹;当平滑结果希望展示为曲线时,改为mode='lines''lines+markers'即可;
  • markerThe 'marker' property is an instance of Marker(见 plotly/graph_objs/_scatter.py),其sizecolorsymbol均可在 plotly/graph_objs/scatter/_marker.py 中找到类型定义,其中symbol是枚举型(见 plotly/graph_objs/scatter/_marker.py),示例用到的'circle-open'(空心圆)与'triangle-up'(上三角)都是合法取值,且可通过symbol='circle-open'这类"名称后缀"快速切换实心/空心/加点样式。

换言之,对比图的三种视觉区分——颜色(color)、大小(size)、形状(symbol)——全部由 Marker 子对象驱动,无需额外引入其他图类型。若需要将平滑结果与原始信号做差值分析,还可借助同一go.Figure继续叠加轨迹,利用 Plotly 的图例(name参数)与 hover 信息直接检查残差。

小结与选型建议

方法实现来源核心参数特点
Savitzky-Golayscipy.signal.savgol_filterwindow_length(奇数,> 阶数)、polyorder保形性好,适合保留峰谷特征的光谱/信号数据
三角移动平均自实现smoothTriangledegree(三角核半宽)实现直观、更平滑,但会削平波峰,适合趋势提取

工程落地建议:先在笔记本里用np.random.seed固定种子复现本文示例,再替换为真实数据集,通过fig.show()反复对比原始、含噪与平滑轨迹,同时用window_length/polyorder/degree三组参数做网格微调;若数据量很大(数万点以上),savgol_filter的向量化实现相比纯 Python 循环的smoothTriangle有更优的性能表现。无论选择哪种算法,都要回到本文开头的原则:在报告中如实说明平滑参数与取舍,避免平滑结果被误读为原始测量

  • 数据可视化
  • 数据分析

【免费下载链接】plotly.py

The interactive graphing library for Python :sparkles:

项目地址:https://gitcode.com/gh_mirrors/pl/plotly.py
点击查看免费下载
上一篇:告别鼠标依赖:win-vind从命名到深度定制的全面解答
下一篇:PasteBar高级功能探索:网页内容抓取与自定义标签管理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询