- 数据可视化
- 数据分析
【免费下载链接】plotly.py
The interactive graphing library for Python :sparkles:
本文以 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.Figure与go.Scatter精确控制每条轨迹的样式;numpy用于生成测试数据(linspace、sin、随机噪声);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_length | 53 | 滑动窗口大小,必须是奇数,且大于polyorder;窗口越大平滑越强,但边界截断区间越大 |
polyorder | 3 | 窗口内拟合多项式的阶数;阶数越高越贴合原始形状,保留细节越多,但抗噪能力下降 |
其余可选参数(如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逐段解读:
- 三角权重核:
np.arange(degree + 1)生成[0, 1, ..., degree],np.arange(degree)[::-1]生成[degree-1, ..., 0],拼接后得到长度2*degree+1的对称三角序列,窗口中心权重最大、两端为 0; - 滑动加权平均:对每个位置截取与核等长的数据段,逐元素相乘后除以核的总权重(
np.sum(triangle)),即得到该点的加权均值; - 边界处理:滑动起始索引为
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_objects的go.Scatter轨迹,其定义位于 plotly/graph_objs/_scatter.py(class Scatter(_BaseTraceType))。与平滑可视化直接相关的两个属性在源码中有明确说明:
mode:The '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'即可;marker:The 'marker' property is an instance of Marker(见 plotly/graph_objs/_scatter.py),其size、color、symbol均可在 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-Golay | scipy.signal.savgol_filter | window_length(奇数,> 阶数)、polyorder | 保形性好,适合保留峰谷特征的光谱/信号数据 |
| 三角移动平均 | 自实现smoothTriangle | degree(三角核半宽) | 实现直观、更平滑,但会削平波峰,适合趋势提取 |
工程落地建议:先在笔记本里用np.random.seed固定种子复现本文示例,再替换为真实数据集,通过fig.show()反复对比原始、含噪与平滑轨迹,同时用window_length/polyorder/degree三组参数做网格微调;若数据量很大(数万点以上),savgol_filter的向量化实现相比纯 Python 循环的smoothTriangle有更优的性能表现。无论选择哪种算法,都要回到本文开头的原则:在报告中如实说明平滑参数与取舍,避免平滑结果被误读为原始测量。
- 数据可视化
- 数据分析
【免费下载链接】plotly.py
The interactive graphing library for Python :sparkles:
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考