本文将解释为什么Savitzky-Golay滤波器能够比移动平均线更好地平滑时间序列数据,并附带Python代码示例。
时间序列平滑的本质
想象你正在分析传感器数据或股票价格数据,原始数据由于噪声的存在,其起伏波动很大,就像过山车一样。平滑的目的就是抑制这些波动噪声,从而发现潜在的真实数据趋势信号。
加载时间序列数据
我们首先需要导入必要的Python库。
代码语言:javascript复制import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
from scipy.signal import savgol_filter
import plotly.express as px
from statsforecast import StatsForecast
train = pd.read_csv('https://auto-arima-results.s3.amazonaws.com/M4-Hourly.csv')
test = pd.read_csv('https://auto-arima-results.s3.amazonaws.com/M4-Hourly-test.csv').rename(columns={'y': 'y_test'})
uid = np.array(['H386'])
df_train = train.query('unique_id in @uid')
df_test = test.query('unique_id in @uid')
StatsForecast.plot(df_train, df_test, plot_random = False, engine='plotly')
该时间序列取自 M4 竞赛数据集,我选择这个是因为它具有重复性(季节性)但不平滑的行为
平滑时间序列
平滑窗口大小的重要性
在平滑时间序列数据时,"窗口大小"是一个非常重要的参数,它决定了在任意给定点附近,我们考虑多大范围的数据来进行平滑。这就好比相机镜头的光圈大小 - 光圈越大,捕获的图像画面就越多,从而影响最终图像的清晰度和细节程度。
对于移动平均线来说,窗口大小定义了计算某个平滑点时,需要平均多少个相邻数据点。而对于Savitzky-Golay滤波器,除了能平均数据点外,它还可以将多项式拟合到窗口内的数据,从而在平滑和保留数据细节之间取得平衡。如果你对 Savitzky-Golay 滤波器的详细信息感兴趣,可以阅读: https://inst.eecs.berkeley.edu/~ee123/sp16/docs/SGFilter.pdf。代码如下:
因此,窗口大小对于平滑的效果有着重大影响。选择一个合适的窗口大小是使用这些平滑技术的关键所在。
代码如下:
代码语言:javascript复制computed_features = [] # 我稍后需要此列表来绘制 window_size 在 [ 10 , 25 ] 中的平for window_size in [10, 25]:
df_train.loc[:,f'moving_average_{window_size}'] = df_train['y'].rolling(window=window_size, , center=True).mean()
df_train.loc[:,f'savgol_filter_{window_size}'] = savgol_filter(df_train['y'], window_size, 2)
computed_features.append(f'moving_average_{window_size}')
computed_features.append(f'savgol_filter_{window_size}')
我们计算了原始时间序列的总共 4 个平滑版本,使用窗口大小 10 和 25。
窗口大小为 10
现在让我们看看第一个窗口大小与原始时间序列的结果对比。
代码语言:javascript复制fig = px.line(df_train[df_train.ds>500], x='ds', y=['y'] computed_features[:2], title='Different moving average estimators',
labels={'Value': 'y', 'Date': 'Date'},
line_shape='linear')
# Improve layout
fig.update_layout(
xaxis_title='Date',
yaxis_title='Sensor Value',
hovermode='x'
)
fig.show()
使用 Savitzky-Golay 滤波器和移动平均线(窗口大小为 10)的原始和平滑时间序列
移动平均线的缺陷
移动平均线虽然简单,但它存在一些明显的缺陷。首先,它对数据变化的反应相对滞后。当数据趋势发生改变时,移动平均线往往无法及时跟上。
另外,移动平均线在计算时,对窗口内所有数据点的重视程度是完全一样的,忽视了它们之间的细微差别和相关性。
相比之下,Savitzky-Golay滤波器利用最小二乘法,将低阶多项式拟合到局部相邻数据点上,从而能更好地保留原始数据的形状和特征,包括峰值和谷值等重要细节。这些细节在使用移动平均线时可能会被过度平滑掉。
如下图所示,随着窗口大小的增加,Savitzky-Golay滤波器能通过预测峰值的方式,更好地捕捉数据的变化趋势,而移动平均线则往往无法做到这一点。
因此,尽管移动平均线简单易用,但它反应迟钝且容易失去数据细节,这就是它的致命缺陷所在。
窗口大小为 25
正如我们在上图中看到的,Savitzky-Golay 滤波器仍然报告了峰值,这可能是我们想要消除它们的情况。所以让我们看看增加窗口大小是否能得到这个结果。
代码语言:javascript复制fig = px.line(df_train[df_train.ds>500], x='ds', y=['y'] computed_features[2:4], title='Different moving average estimators',
labels={'Value': 'y', 'Date': 'Date'},
line_shape='linear')
# Improve layout
fig.update_layout(
xaxis_title='Date',
yaxis_title='Sensor Value',
hovermode='x'
)
fig.show()
使用 Savitzky-Golay 滤波器和移动平均线(窗口大小为 25)的原始和平滑时间序列
在这里,Savitzky-Golay 滤波器非常出色地捕捉了时间序列的季节性,没有延迟,并消除了尖峰,而移动平均线将所有注意力集中在长期平均值上,丢失了信号中包含的许多信息。
结论
总体而言,当窗口大小适当调整时,Savitzky-Golay 滤波器倾向于保持更高的信号保真度,同时消除不必要的尖峰。无论如何,移动平均线仍然可以用于计算时间序列的平均值,即使通过扩大 Savitzky-Golay 滤波器的窗口大小可以获得相同的结果(并且可能具有更好的精度),但如果有兴趣捕捉过程围绕的底层平均值,则可以评估使用它。但对于大多数平滑用例,Savitzky-Golay 滤波器的表现要好得多。