时序数据常用趋势检测方法

背景

在最近的项目中,需要自动检测某段时间内的某个指标是上升了还是下降了,因此需要研究下常用的时序数据趋势检测方法。

方法一 斜率法

原理

斜率法的原理就是使用最小二乘等方法对时序数据进行拟合,然后根据拟合成的直线的斜率k判断序列的数据走势,当k>0时,则代表趋势上升;当k<0时,则代表趋势下降。

代码

import numpy as np
def trendline(data):
    order=1
    index=[i for i in range(1,len(data)+1)]
    coeffs = np.polyfit(index, list(data), order)
    slope = coeffs[-2]
    return float(slope)
b_array = [5,4,3,3,2,1]
a = trendline(b_array)
print(a)

优缺点

优点是方法简单;缺点是要求趋势是线性的,当数去波动较大时无法准确拟合。

方法二 Cox-Stuart检验

原理

直接考虑数据的变化趋势,若数据有上升趋势,那么排在后面的数据的值要比排在前面的数据的值显著的大,反之,若数据有下降趋势,那么排在后面的数据的值要比排在前面的数据的值显著的小,利用前后两个时期不同数据的差值正负来判断数据总的变化趋势。

算法步骤

取xi和xi+c组成一对(xi,xi+c)。这里如果n为偶数,则c=n/2,如果n是奇数,则c=(n+1)/2。当n为偶数时,共有n’=c对,而n是奇数时,共有 n’=c-1对。

用每一对的两元素差Di=xi−xi+c的符号来衡量增减。令S+为正的Di的数目,S−为负的Di的数目。显然当正号太对时有下降趋势,反之有增长趋势。在没有趋势的零假设下他们因服从二项分布b(n’,0.5)。

用p(+)表示取到正数的概率,用p(-)表示取到负数的概率,这样我们就得到符号检验方法来检验序列是否存在趋势性。

代码

# 统计正的Di出现次数为14次,负的Di出现次数为2次,接下来判断是否存在显著的上升趋势
# 做出原假设H0:该数据不存在上升趋势;
# 做出替代假设H1:该数据存在上升趋势。
# 运行CS趋势检验方法,可以发现,使用二项式离散分布检验得到p-value值为0.004181,小于0.05,因此有理由认为原假设H0不成立,从而支持假设H1:该数据存在上升趋势

import numpy as np
import pandas as pd
import scipy.stats as stats

import matplotlib.pyplot as plt

a_array = [206,223,235,264,229,217,188,204,182,230,223,227,242,238,207,208,
           216,233,233,274,234,227,221,214,226,228,235,237,243,240,231,210]





def cox_staut(list_c):
    lst=list_c.copy() ## 副本
    n0 = len(lst) ##列表长度
    
    ## 判断奇偶性
    if n0%2 == 1:
        del lst[int((n0-1)/2)] #奇数删除中间数值
        
    ## 配对前后数据
    c=int(len(lst)/2)
    ## 统计正Di和负Di出现次数
    pos=neg=0
    for i in range(c):
        diff=lst[i+c]-lst[i]
        if diff>0:
            pos+=1
        elif diff<0:
            neg+=1
        else:
            continue
    
    n1=pos+neg
    k=min(pos,neg)
    ## 使用二项式离散分布检验趋势是否显著
    p=2*stats.binom.cdf(k,n1,0.5)
    print('fall:%i  rise:%i  p-value:%f'%(neg, pos, p))
    # p<0.05,零假设不成立
    if pos > neg and p < 0.05:
        return 'increasing'
    elif neg > pos and p < 0.05:
        return 'decreasing'
    else:
        return 'no trend'


plt.plot(range(1, len(a_array) + 1), a_array)
cox_staut(a_array)

b_array = [1,2,3,3,5,10]
cox_staut(b_array)



优缺点

优点是不依赖趋势结构,可以快速判断趋势是否存在;缺点是未考虑数据的时序性,仅从符号检验来判断。

方法三 Mann-Kendall检验

原理

Mann-Kendall检验不需要样本遵循一定的分布,也不受少数异常值的干扰。在Mann-Kendall检验中,原假设H0为时间序列数据(X1,…,Xn),是n个独立的、随机变量同分布的样本;备择假设H1 是双边检验,对于所有的k,j≤n,且k≠j,Xk和Xj的分布是不相同的。若原假设是不可接受的,即在α置信水平上,时间序列数据存在明显的上升或下降趋势。对于统计量Z,大于0时是上升趋势;小于0时是下降趋势。

算法步骤

将数据按采集时间列出:x1,x2,…,xn,即分别在时间1,2,…,n得到的数据。

确定所有n(n-1)/2个xj−xk差值的符号,其中j > k

令sgn(xj−xk)作为指示函数,依据xj−xk的正负号取值为1,0或-1

计算S=∑n−1k−1∑nj−k+1sgn(xj−xk)。即差值为正的数量减去差值为负的数量。如果S是一个正数,那么后一部分的观测值相比之前的观测值会趋向于变大;如果S是一个负数,那么后一部分的观测值相比之前的观测值会趋向于变小。

如果n≤10,依据Gilbert (1987, page 209, Section 16.4.1)中所描述,要在概率表 (Gilbert 1987, Table A18, page 272) 中查找S。如果此概率小于α(认为没有趋势时的截止概率),那就拒绝零假设,认为趋势存在。如果在概率表中找不到n(存在结数据——tied data values——会发生此情况),就用表中远离0的下一个值。比如S=12,如果概率表中没有S=12,那么就用S=13来处理也是一样的。如果n > 10,则依以下步骤6-10来判断有无趋势。这里遵循的是Gilbert (1987, page 211, Section 16.4.2)中的程序。

计算S的方差如下:VAR(S)=118[n(n−1)(2n+5)−∑gp−1tp(tp−1)(2tp+5)]。其中g是结组(tied groups)的数量,tp是第p组的观测值的数量。例如:在观测值的时间序列{23, 24, 29, 6, 29, 24, 24, 29, 23}中有g = 3个结组,相应地,对于结值(tiied value)23有t1=2、结值24有t2=3、结值29有t3=S3。当因为有相等值或未检测到而出现结时,VAR(S)可以通过Helsel (2005, p. 191)中的结修正方法来调整。

计算MK检验统计量Z_{MK}:

设想我们要测试零假设。H0(没有单调趋势)对比替代假设Ha(有单调增趋势),其1型错误率为α,0<α<0.50(注意α是MK检验错误地拒绝了零假设时可容忍的概率——即MK检验拒绝了零假设是错误地,但这个事情发生概率是α,我们可以容忍这个错误)。如果ZMK≥Z1−α,就拒绝零假设H0,接受替代假设Ha,其中Z1−α是标准正态分布的100(1−α)th百分位。

测试上面的H0与Ha(有单调递减趋势),其1型错误率为alpha,0<α<0.5,如果ZZMK≤–Z1−α,就拒绝零假设H0,接受替代假设Ha

测试上面的H0与Ha(有单调递增或递减趋势),其1型错误率为alpha,0<α<0.5,如果|ZMK|≥Z1−α2,就拒绝零假设H0,接受替代假设Ha,其中竖线代表绝对值。

代码

###Mann-Kendall检验

import scipy.stats as stats 
import math
from scipy.stats import norm, mstats
def mk_test(x, alpha=0.05):
    n = len(x)

    # calculate S
    s = 0
    for k in range(n-1):
        for j in range(k+1, n):
            s += np.sign(x[j] - x[k])

    # calculate the unique data
    unique_x, tp = np.unique(x, return_counts=True)
    g = len(unique_x)

    # calculate the var(s)
    if n == g:  # there is no tie
        var_s = (n*(n-1)*(2*n+5))/18
    else:  # there are some ties in data
        var_s = (n*(n-1)*(2*n+5) - np.sum(tp*(tp-1)*(2*tp+5)))/18

    if s > 0:
        z = (s - 1)/np.sqrt(var_s)
    elif s < 0:
        z = (s + 1)/np.sqrt(var_s)
    else: # s == 0:
        z = 0

    # calculate the p_value
    p = 2*(1-norm.cdf(abs(z)))  # two tail test
    h = abs(z) > norm.ppf(1-alpha/2)

    if (z < 0) and h:
        trend = 'decreasing'
    elif (z > 0) and h:
        trend = 'increasing'
    else:
        trend = 'no trend'

    return trend


b_array = [1,2,3,3,5,10]
plt.plot(range(1, len(b_array) + 1), b_array)
mk_test(b_array)
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值