这篇文章是另一篇有关时间序列异常检测通用方法的文章的延续。基本上,在这一点上,我感兴趣的是一种鲁棒的方式来发现受大量噪声影响的通用时间序列的周期性/季节性。从开发人员的角度来看,我想要一个简单的界面,例如:
unsigned int discover_period(vector<double> v);
其中v
包含样本的数组在哪里,返回值是信号的周期。重点是,同样,我无法对所分析的信号做出任何假设。我已经尝试过基于信号自相关(检测相关图的峰值)的方法,但是它并不像我想要的那样健壮。
这篇文章是另一篇有关时间序列异常检测通用方法的文章的延续。基本上,在这一点上,我感兴趣的是一种鲁棒的方式来发现受大量噪声影响的通用时间序列的周期性/季节性。从开发人员的角度来看,我想要一个简单的界面,例如:
unsigned int discover_period(vector<double> v);
其中v
包含样本的数组在哪里,返回值是信号的周期。重点是,同样,我无法对所分析的信号做出任何假设。我已经尝试过基于信号自相关(检测相关图的峰值)的方法,但是它并不像我想要的那样健壮。
Answers:
如果您真的不知道周期是什么,最好的方法可能是找到对应于频谱密度最大值的频率。但是,低频频谱会受到趋势的影响,因此您需要先降低序列的趋势。以下R函数适用于大多数系列。它远非完美,但我已经在几十个示例上对其进行了测试,而且看起来还可以。对于没有很强周期性的数据,它将返回1,否则将返回周期长度。
更新:功能的版本2。这要快得多,而且似乎更可靠。
find.freq <- function(x)
{
n <- length(x)
spec <- spec.ar(c(x),plot=FALSE)
if(max(spec$spec)>10) # Arbitrary threshold chosen by trial and error.
{
period <- round(1/spec$freq[which.max(spec$spec)])
if(period==Inf) # Find next local maximum
{
j <- which(diff(spec$spec)>0)
if(length(j)>0)
{
nextmax <- j[1] + which.max(spec$spec[j[1]:500])
period <- round(1/spec$freq[nextmax])
}
else
period <- 1
}
}
else
period <- 1
return(period)
}
findfrequency
如果您希望过程是平稳的-周期性/季节性不会随时间变化-那么像卡方图(参见例如Sokolove和Bushell,1978年)之类的东西可能是个不错的选择。它通常用于昼夜节律数据的分析,这些数据中可能包含大量噪声,但预计周期非常稳定。
这种方法没有对波形的形状做任何假设(除了在每个周期之间保持一致),但确实要求任何噪声的均值必须恒定且与信号不相关。
chisq.pd <- function(x, min.period, max.period, alpha) {
N <- length(x)
variances = NULL
periods = seq(min.period, max.period)
rowlist = NULL
for(lc in periods){
ncol = lc
nrow = floor(N/ncol)
rowlist = c(rowlist, nrow)
x.trunc = x[1:(ncol*nrow)]
x.reshape = t(array(x.trunc, c(ncol, nrow)))
variances = c(variances, var(colMeans(x.reshape)))
}
Qp = (rowlist * periods * variances) / var(x)
df = periods - 1
pvals = 1-pchisq(Qp, df)
pass.periods = periods[pvals<alpha]
pass.pvals = pvals[pvals<alpha]
#return(cbind(pass.periods, pass.pvals))
return(cbind(periods[pvals==min(pvals)], pvals[pvals==min(pvals)]))
}
x = cos( (2*pi/37) * (1:1000))+rnorm(1000)
chisq.pd(x, 2, 72, .05)
最后两行仅是一个示例,表明即使有很多附加噪声,它也可以识别纯三角函数的周期。
如所写,alpha
调用中的最后一个参数()是多余的,该函数仅返回它可以找到的“最佳”时间段;取消注释第一个return
语句,并注释掉第二个语句,使它返回该级别上所有重要期间的列表alpha
。
此功能不会进行任何类型的健全性检查,以确保您已放入可识别的期间,也不能(可以)使用小数期间,如果您决定执行以下操作,也不会内置任何形式的多重比较控件看多个时期。但是除此之外,它应该相当健壮。
您可能想要更清楚地定义自己想要的内容(如果不在这里,请自己定义)。如果您要寻找的是嘈杂数据中包含的统计上最重要的平稳期,则基本上可以采取两种方法:
1)计算鲁棒的自相关估计,并取最大系数
2)计算鲁棒的功率谱密度估计,并取谱的最大值
#2的问题在于,对于任何嘈杂的时间序列,您都会在低频下获得大量功率,因此很难区分。有一些技术可以解决此问题(即先变白,然后估计PSD),但是如果数据的真实周期足够长,则自动检测将变得很困难。
最好的选择是实现一个强大的自相关例程,例如Maronna,Martin和Yohai的“ 稳健统计-理论和方法”中的8.6、8.7章。在Google中搜索“健壮的durbin-levinson”也会产生一些结果。
如果您只是在寻找一个简单的答案,我不确定是否存在。时间序列中的周期检测可能很复杂,而要求执行魔术的自动化例程可能太多了。
您可以使用DSP理论的希尔伯特变换来测量数据的瞬时频率。http://ta-lib.org/网站具有用于测量财务数据的主要周期的开源代码;相关功能称为HT_DCPERIOD;您也许可以使用此代码或使代码适合您的目的。
一种不同的方法可以是经验模式分解。该方法的发明者将R包称为EMD:
require(EMD)
ndata <- 3000
tt2 <- seq(0, 9, length = ndata)
xt2 <- sin(pi * tt2) + sin(2* pi * tt2) + sin(6 * pi * tt2) + 0.5 * tt2
try <- emd(xt2, tt2, boundary = "wave")
### Ploting the IMF's
par(mfrow = c(try$nimf + 1, 1), mar=c(2,1,2,1))
rangeimf <- range(try$imf)
for(i in 1:try$nimf) {
plot(tt2, try$imf[,i], type="l", xlab="", ylab="", ylim=rangeimf, main=paste(i, "-th IMF", sep="")); abline(h=0)
}
plot(tt2, try$residue, xlab="", ylab="", main="residue", type="l", axes=FALSE); box()
该方法有很好的理由被冠以“经验”商标,并且存在固有模式函数(各个附加成分)混淆的风险。另一方面,该方法非常直观,可能有助于快速直观地检查周期性。
参考上面Rob Rob Hyndman的帖子https://stats.stackexchange.com/a/1214/70282
find.freq函数工作出色。在我使用的每日数据集上,它正确地将频率设为7。
当我仅在工作日尝试时,它提到的频率是23,这非常接近21.42857 = 29.6 * 5/7,这是一个月的平均工作天数。(或者相反,23 * 7/5是32。)
回顾我的日常数据,我尝试了将第一时间段平均化,然后找到下一个时间段的预感,等等。请参见下文:
find.freq.all =函数(x){ f = find.freq(x); freqs = c(f); 而(f> 1){ 开始= 1; #也尝试start = f; x = period.apply(x,seq(start,length(x),f),mean); f = find.freq(x); freqs = c(freqs,f); } if(length(freqs)== 1){return(freqs); } for(i in 2:length(freqs)){ freqs [i] = freqs [i] * freqs [i-1]; } freqs [1:(length(freqs)-1)]; } find.freq.all(dailyts)#使用每日数据
上面给出的(7,28)或(7,35)取决于seq以1还是f开头。(请参阅上面的评论。)
这意味着msts(...)的季节性周期应为(7,28)或(7,35)。
给定算法参数的敏感度,逻辑对初始条件敏感。28和35的平均值为31.5,接近一个月的平均长度。
我怀疑我重新发明了轮子,此算法的名称是什么?R中是否有更好的实现?
后来,我运行了上面的代码,尝试从1到7的所有开头,第二段时间分别获得35,35,28,28,28,28,28。平均得出30,这是一个月中的平均天数。有趣...
有什么想法或意见吗?
人们还可以使用Ljung-Box检验来找出哪个季节差异达到最佳平稳性。我当时正在研究另一个主题,实际上我将其用于相同的目的。尝试不同的时段(例如3到24)以获取每月数据。并通过Ljung-Box测试它们中的每一个并存储卡方结果。并选择卡方值最低的周期。
这是执行此操作的简单代码。
minval0 <- 5000 #assign a big number to be sure Chi values are smaller
minindex0 <- 0
periyot <- 0
for (i in 3:24) { #find optimum period by Qtests over original data
d0D1 <- diff(a, lag=i)
#store results
Qtest_d0D1[[i]] <- Box.test(d0D1, lag=20, type = "Ljung-Box")
#store Chi-Square statistics
sira0[i] <- Qtest_d0D1[[i]][1]
}
#turn list to a data frame, then matrix
datam0 <- data.frame(matrix(unlist(sira0), nrow=length(Qtest_d0D1)-2, byrow = T))
datamtrx0 <- as.matrix(datam0[])
#get min value's index
minindex0 <- which(datamtrx0 == min(datamtrx0), arr.ind = F)
periyot <- minindex0 + 2