如何自动检测时间序列的信号周期性,尤其是日度周期性?
问题背景
我手头有一个包含约1000条不同时间序列的数据集,其中部分序列存在明显周期性,其余序列则没有。我需要自动判断给定时间序列是否存在明确的周期性,从而确定在应用异常检测方法之前,是否需要先对其做季节性分解。
现有一条具备日度周期性的信号,采样间隔为15分钟,信号示例如下:
已尝试方案
方案1:kats库自带检测函数
代码实现如下:
from kats.consts import TimeSeriesData from kats.detectors.seasonality import FFTDetector, ACFDetector def detect_seasonality(df,feature,time_col,detector_type): df_kpi = df[[feature]].reset_index().rename(columns={feature:'value'}) ts = TimeSeriesData(df_kpi,time_col_name=time_col) if detector_type == 'fft': detector = FFTDetector(ts) elif detector_type == 'acf': detector = ACFDetector(ts) else: raise Exception("Detector types are fft or acf") detection = detector.detector() seasonality_presence = detection['seasonality_presence'] return seasonality_presence
检测结果:fft和acf检测器返回的周期性存在性结果均为False。
方案2:自主实现FFT检测
代码实现如下:
import numpy as np import scipy.signal from matplotlib import pyplot as plt L = np.array(df[kpi_of_interest].values) L -= np.mean(L) # Window signal L *= scipy.signal.windows.hann(len(L)) fft = np.fft.rfft(L, norm="ortho") plt.figure() plt.plot(abs(fft))
对应的频谱图如下:
检测结果:无法明确识别出预期的日度周期性。
待解答疑问
目前每条时间序列仅约10天的数据,请问还有哪些更合适的方法可用于自动检测日度周期性?检测前需要完成哪些必要的预处理步骤?检测失败是否可能是由数据量不足导致的?
检测失败原因判断
10天的15分钟采样数据量本身足以检测日度周期,检测失败的核心原因不是数据量不足,大概率是现有方法没有针对日度周期做参数适配,以及预处理步骤有缺漏。
必要预处理步骤
在做周期性检测前,必须完成以下操作:
- 缺失值填补:15分钟采样的序列如果存在缺失值,会直接破坏周期特征,优先用同时间段的历史均值填补,不要用线性插值或前向填充,避免掩盖周期特征
- 趋势剔除:如果序列存在明显的上升/下降趋势,会大幅干扰周期性检测的结果,建议先用STL分解提取残差序列,或者用一阶差分法消除趋势
- 异常值过滤:单值突增/突降的异常点会拉高频谱噪声,可先用3σ原则过滤掉偏离均值3倍标准差之外的异常点,再做检测
适配短序列的日度周期检测方法
1. 固定滞后项ACF显著性检验
不需要用通用的ACF检测器,直接针对日度周期的滞后项做检验即可:15分钟采样的日度周期对应滞后长度是24*4=96,直接计算序列滞后96位的自相关系数,做显著性检验:
from statsmodels.tsa.stattools import acf # 序列长度N,滞后96位的自相关系数及置信区间 acf_vals, conf_int = acf(preprocessed_series, nlags=96, alpha=0.05) # 判断滞后96位的自相关系数是否显著不为0 lag96_acf = acf_vals[96] is_significant = (conf_int[96][0] > 0) or (conf_int[96][1] < 0)
如果滞后96位的自相关系数显著不为0,直接判定存在日度周期即可,准确率远高于通用周期检测工具。
2. 周期箱线方差检验
把10天的序列按每日的96个时间点分组,同一时间点的10个值归为一组,计算组间方差和组内方差的比值:如果比值明显大于1,说明同时间点的取值相似度远高于不同时间点,存在日度周期。
import pandas as pd import numpy as np # 给每个点打时间点标签(0-95,对应一天内的第n个15分钟) df['time_of_day'] = df['timestamp'].dt.hour * 4 + df['timestamp'].dt.minute // 15 # 计算组间方差和组内方差 between_var = df.groupby('time_of_day')['value'].mean().var() within_var = df.groupby('time_of_day')['value'].var().mean() f_score = between_var / within_var # 阈值可根据业务调整,一般f_score>2即可判定存在日度周期
3. 傅里叶变换功率占比检验
你之前的FFT实现只做了可视化没有做量化判断,可直接计算对应日度周期的频率点功率占总功率的比例:
15分钟采样的采样频率是 1/(15*60) Hz,日度周期的频率是 1/(24*3600) Hz,找到频谱中最接近该频率的点,计算该点的功率占所有频率点总功率的比例,比例超过5%即可判定存在明显日度周期。
内容的提问来源于stack exchange,提问作者kspr

