脚本专家 发表于 2026-10-3 10:00:00

Python SciPy时间序列分析:滤波去趋势与周期检测实战

时间序列分析处理的是按时间排列的数据:判断趋势、周期性、噪声强弱,或者两个序列之间的延迟关系。SciPy 在信号处理和统计检验方面提供了不少现成工具,配合 NumPy 与 Matplotlib,可以覆盖日常分析的大部分需求。下面直接用一组模拟的全年每日温度数据,依次演示移动平均、低通滤波、去趋势、频谱分析、自相关和互相关的代码实现与参数含义。

1. 构造带季节性和趋势的温度数据

原文用 NumPy 模拟 365 天温度,由三部分组成:周期为 365 天的正弦波表示季节性,0.05 * days 表示缓慢线性上升趋势,再叠加正态随机噪声。代码先固定随机种子,保证结果可复现。


import numpy as np
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
np.random.seed(0)

days = np.arange(365)
seasonal = 10 * np.sin(2 * np.pi * days / 365)
trend = 0.05 * days
noise = np.random.normal(0, 2, 365)
temperature_data = 20 + seasonal + trend + noise

plt.plot(days, temperature_data)
plt.xlabel('天数')
plt.ylabel('温度')
plt.title('模拟的每日温度数据')
plt.show()


绘制后是一条上下波动且整体略微上升的曲线,噪声让线条带有毛刺。这组 temperature_data 就是后续所有分析的输入。

2. 用移动平均压制高频噪声

原始数据中的高频噪声会干扰趋势判断。移动平均的思路是取一个窗口,例如 7 天,计算窗口内均值,再向后滑动。NumPy 的 convolve 可以简洁实现。


def moving_average(data, window_size):
    return np.convolve(data, np.ones(window_size) / window_size, mode='valid')

smoothed = moving_average(temperature_data, 7)
plt.plot(days, temperature_data, label='原始数据', alpha=0.5)
plt.plot(days, smoothed, label='7天移动平均', linewidth=2)
plt.legend()
plt.show()


窗口取 7 天,是因为温度数据通常以周为周期存在波动,7 天平均能抹掉大部分随机噪声,同时保留季节性的轮廓。平滑后的曲线更干净,上升趋势和正弦波动都更清晰。需要留意,mode='valid' 会使输出长度变为 len(data)-window_size+1,因此画图时横轴使用了 days。

3. Butterworth 低通滤波:更灵活地控制截止频率

移动平均本质上是一个简单低通滤波器,但如果想更精确地控制截止频率,可以改用 SciPy 的 signal.butter 和 filtfilt。Butterworth 滤波器会衰减高于指定频率的成分,filtfilt 做双向滤波,避免相位偏移。


from scipy.signal import butter, filtfilt

def lowpass_filter(data, cutoff, fs, order=4):
    nyquist = 0.5 * fs
    normal_cutoff = cutoff / nyquist
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return filtfilt(b, a, data)

filtered = lowpass_filter(temperature_data, cutoff=0.1, fs=1)
plt.plot(days, temperature_data, alpha=0.5, label='原始数据')
plt.plot(days, filtered, label='低通滤波后', linewidth=2)
plt.legend()
plt.show()


这里采样频率 fs=1,表示每天一个采样点;截止频率 cutoff=0.1,表示保留周期大于 10 天的成分,把更快的波动滤掉。结果和移动平均类似,但边缘处理更平滑,没有移动平均两端缺数据的问题。

4. 去趋势:分离长期上升与季节性波动

温度数据里包含线性上升趋势。如果只想观察季节性的正弦波动,可以用 scipy.signal.detrend 把线性趋势减掉。


from scipy.signal import detrend

detrended = detrend(temperature_data)
plt.plot(days, detrended)
plt.xlabel('天数')
plt.ylabel('去趋势后的温度')
plt.title('去趋势结果')
plt.show()


去趋势后的曲线围绕 0 上下波动,正弦形状更明显。要注意,detrend 默认只去线性趋势;如果原始数据中的趋势不是线性的,效果可能不够。这里的数据恰好是线性趋势,所以结果比较干净。

从分析流程看,做频谱分析、自相关或寻找周期性之前,通常先去趋势。否则线性趋势会在低频段产生很强的能量,掩盖真正的周期信号;去趋势也能让数据均值趋于稳定,满足平稳性要求。换句话说,detrend 没有改变数据的形状,只是把数据整体平移并拉平,让它围绕 0 波动,相当于减掉了 20 + 0.05 * days 这条背景直线。因此去趋势结果图的 Y 轴围绕 0 左右波动,而初始模拟数据图围绕 30 左右波动。

5. 频谱分析:用 periodogram 找主要周期

想知道数据里有哪些周期成分,频谱分析很直接。scipy.signal.periodogram 可以计算功率谱密度,横轴是频率,纵轴是功率,频率的倒数就是周期。


from scipy.signal import periodogram

frequencies, power = periodogram(temperature_data, fs=1)
plt.semilogy(frequencies, power)
plt.xlabel('频率(周期/天)')
plt.ylabel('功率')
plt.title('周期图')
plt.show()


图上会在频率 1/365 ≈ 0.00274 附近出现明显峰值,对应 365 天的年周期。使用对数纵轴,是因为不同频率处的功率差异可能很大。如果还看到其他小峰值,可能是噪声或谐波,但主峰位置直接告诉你数据里最强的周期。

6. 自相关:观察当前值与过去值的关系

自相关衡量时间序列和它自己延迟若干天后的相关性。如果数据有季节性,自相关会在滞后 365 天附近出现高点。下面用 scipy.stats.pearsonr 计算滞后 1 到 30 天的相关系数。


from scipy.stats import pearsonr

lags = range(1, 31)
autocorrs = , temperature_data) for lag in lags]
plt.stem(lags, autocorrs)
plt.xlabel('滞后(天)')
plt.ylabel('自相关')
plt.title('30天内的自相关')
plt.show()


由于数据包含正弦季节性,自相关会呈现波浪形,滞后越接近 365 的整数倍,相关性越高。这里只画了 30 天,能看到相关性逐渐下降然后可能回升,这反映了短期的周期结构。

7. 互相关:分析两个序列之间的延迟关系

有时关心两个序列是否相关,以及一个序列是否领先另一个序列。互相关就是处理这类问题的工具。原文生成了一组模拟湿度数据,让它与温度存在 5 天延迟关系,然后计算不同偏移下的相关系数。


humidity_data = 70 + 0.5 * temperature_data[:-5] + np.random.normal(0, 1, 360)
shifts = range(-10, 11)
cross_corrs = , np.roll(humidity_data, shift)) for shift in shifts]
plt.stem(shifts, cross_corrs)
plt.xlabel('偏移量')
plt.ylabel('互相关')
plt.title('温度与湿度的互相关')
plt.show()


结果会在偏移 +5 或 -5 附近出现峰值,具体方向取决于 np.roll 的定义。这告诉我们湿度对温度有大约 5 天的滞后响应。在实际分析中,互相关能帮助判断因果方向,或者找到两个序列的最佳对齐方式。

8. 小结与参数选择建议

整个流程可以概括为:构造数据 → 移动平均 → 低通滤波 → 去趋势 → 频谱分析 → 自相关 → 互相关。移动平均和低通滤波适合平滑与去噪;去趋势用于分离长期趋势和周期成分;频谱和自相关用于揭示周期;互相关用于两个序列的延迟分析。这些技术也是更高级建模,例如 ARIMA、傅里叶拟合的基础。实际使用时,可以在自己的数据上替换 temperature_data,并调整窗口大小、截止频率、滤波器阶数、滞后范围和偏移范围,观察结果如何变化。

热心网友7 发表于 2026-10-3 19:00:00

Re: Python SciPy时间序列分析:滤波去趋势与周期检测实战

这篇整理得很清楚,适合跟着一步步跑。用全年温度这种直觉强的数据做例子,把季节项、线性趋势和噪声分开构造,后面每个方法的作用就很容易对应上。移动平均那段把窗口取 7 的理由和 valid 模式导致横轴偏移都点到了,这种细节对新手很友好。Butterworth 部分把采样频率、截止频率、Nyquist 和 filtfilt 避免相位偏移讲明白,比单纯给代码更有用。去趋势的必要性也解释得好:如果不去掉线性趋势,低频能量会掩盖真正的周期信号,这个在实际分析里很容易踩坑。期待后面自相关和互相关的内容,尤其是怎么根据相关峰判断滞后天数。整体这条分析链路很完整,很有参考价值。

热心网友3 发表于 2026-10-3 19:10:00

Re: Python SciPy时间序列分析:滤波去趋势与周期检测实战

楼主这篇整理得很系统,模拟数据用 20 加季节性、线性趋势和噪声叠加的思路很直观。移动平均部分把 mode='valid' 后长度变化和 days 对齐讲清楚了,这个细节对画图挺关键。Butterworth 用 fs=1、cutoff=0.1 来解释保留周期大于 10 天的成分,也很容易理解。去趋势那段提醒 detrend 默认只去线性趋势,以及频谱分析前先去趋势以免低频能量掩盖周期,很实用。期待后续 periodogram 峰值换算周期以及自相关、互相关找延迟的内容,尤其两个序列延迟关系那部分。感谢分享。

热心网友3 发表于 2026-10-3 19:20:00

Re: Python SciPy时间序列分析:滤波去趋势与周期检测实战

这篇实战整理得很顺,从模拟温度数据到移动平均、Butterworth 低通、去趋势和 periodogram,每一步都把参数含义和实际效果连起来了,对刚上手时间序列分析的人很友好。 几个细节我觉得特别实用:移动平均用 valid 后输出会变短,画图时横轴必须跟着对齐到 days,不然很容易看起来错位;filtfilt 做双向滤波避免相位偏移,也很适合温度这种平滑信号;去趋势放在频谱分析之前这点很关键,线性趋势如果不去掉,低频能量确实容易把年度周期压住,periodogram 上的主峰就没那么干净了。 另外,Butterworth 那里 cutoff 和 fs 的关系值得多提醒一句,截止频率不能超过 fs/2,否则归一化 cutoff 会出问题。periodogram 用 semilogy 看功率谱也更容易分辨峰值。整体流程很完整,期待后面自相关和互相关部分,尤其如果能把滞后天数和峰值位置怎么对应讲清楚,就更方便直接套用了。
页: [1]
查看完整版本: Python SciPy时间序列分析:滤波去趋势与周期检测实战