通用时间序列的周期检测


53

这篇文章是另一篇有关时间序列异常检测通用方法的文章的延续。基本上,在这一点上,我感兴趣的是一种鲁棒的方式来发现受大量噪声影响的通用时间序列的周期性/季节性。从开发人员的角度来看,我想要一个简单的界面,例如:

unsigned int discover_period(vector<double> v);

其中v包含样本的数组在哪里,返回值是信号的周期。重点是,同样,我无法对所分析的信号做出任何假设。我已经尝试过基于信号自相关(检测相关图的峰值)的方法,但是它并不像我想要的那样健壮。


1
您是否尝试过xts :: periodicity?
法布里西奥

Answers:


49

如果您真的不知道周期是什么,最好的方法可能是找到对应于频谱密度最大值的频率。但是,低频频谱会受到趋势的影响,因此您需要先降低序列的趋势。以下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)
}

谢谢。同样,我将尽快尝试这种方法,并将最终结果写在这里。
gianluca

2
您的想法很好,但就我而言,它无法检测到真正简单(而不是那么嘈杂)的时间序列(例如dl.dropbox.com/u/540394/chart.png)的周期性。使用我的“经验”方法(基于自相关),我编写的简单算法返回了精确的周期1008(每10分钟采样一次,这意味着1008/24/6 = 7,因此是每周周期性)。我的主要问题是:1)收敛太慢(需要大量历史数据),并且我需要一种被动的在线方法;2)从内存使用的角度来看,它像地狱一样低效;3)一点也不健壮;
gianluca

谢谢。不幸的是,这仍然无法正常工作。对于先前评论的相同时间序列,它返回166,这仅是部分正确的(从我的角度来看,明显的每周时段更有趣)。并且使用一个非常嘈杂的时间序列(例如dl.dropbox.com/u/540394/chart2.png(TCP接收器窗口分析)),该函数返回10,而我期望为1(我看不到任何明显的周期性)。顺便说一句,我知道很难找到想要的东西,因为我要处理的信号太不同了。
gianluca 2010年

166是168的不错估计。如果您知道每周观察一次的数据是每小时一次,那么为什么要完全估计频率呢?
罗伯·海恩德曼

5
预报包中提供了改进版本,例如findfrequency
Rob Hyndman

10

如果您希望过程是平稳的-周期性/季节性不会随时间变化-那么像卡方图(参见例如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

此功能不会进行任何类型的健全性检查,以确保您已放入可识别的期间,也不能(可以)使用小数期间,如果您决定执行以下操作,也不会内置任何形式的多重比较控件看多个时期。但是除此之外,它应该相当健壮。


看起来很有趣,但我不明白的输出,它并没有告诉我在哪里的时段开始,大多数p值的1
赫尔曼Toothrot

3

您可能想要更清楚地定义自己想要的内容(如果不在这里,请自己定义)。如果您要寻找的是嘈杂数据中包含的统计上最重要的平稳期,则基本上可以采取两种方法:

1)计算鲁棒的自相关估计,并取最大系数
2)计算鲁棒的功率谱密度估计,并取谱的最大值

#2的问题在于,对于任何嘈杂的时间序列,您都会在低频下获得大量功率,因此很难区分。有一些技术可以解决此问题(即先变白,然后估计PSD),但是如果数据的真实周期足够长,则自动检测将变得很困难。

最好的选择是实现一个强大的自相关例程,例如Maronna,Martin和Yohai的“ 稳健统计-理论和方法”中的8.6、8.7章。在Google中搜索“健壮的durbin-levinson”也会产生一些结果。

如果您只是在寻找一个简单的答案,我不确定是否存在。时间序列中的周期检测可能很复杂,而要求执行魔术的自动化例程可能太多了。


感谢您提供宝贵的信息,我一定会看那本书。
gianluca 2010年

3

您可以使用DSP理论的希尔伯特变换来测量数据的瞬时频率。http://ta-lib.org/网站具有用于测量财务数据的主要周期的开源代码;相关功能称为HT_DCPERIOD;您也许可以使用此代码或使代码适合您的目的。


3

一种不同的方法可以是经验模式分解。该方法的发明者将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()

该方法有很好的理由被冠以“经验”商标,并且存在固有模式函数(各个附加成分)混淆的风险。另一方面,该方法非常直观,可能有助于快速直观地检查周期性。


0

参考上面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,这是一个月中的平均天数。有趣...

有什么想法或意见吗?


0

人们还可以使用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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.