SSA时间序列分解

奇异谱分析(SSA)根据观测到的时间序列构造轨迹矩阵,并对轨迹矩阵进行分解重构,从而提取出代表原时间序列不同成分的信号,如长期趋势信号、周期信号、噪声信号等,从而进一步对分解得到的信号进行分析。

算法流程

Step1:根据原始时间序列构建轨迹矩阵 X X X

Step2:对矩阵X进行奇异值分解: X = ∑ i = 1 r σ i U i V i T X=\sum_{i=1}^{r} \sigma_i U_i V_i^T X=i=1rσiUiViT

Step3:按奇异值生成 r r r个子矩阵: X i = σ i U i V i T X_i = \sigma_i U_i V_{i}^T Xi=σiUiViT

Step4:根据某一分组原则将子矩阵 X i X_i Xi分为 m m m个组

Step5:对子矩阵 X i X_i Xi进行对角均值化处理得到子序列 F i ~ \widetilde{F_i} Fi

Step6:对 m m m个组中的子序列相加得到分组子序列。

矩阵分解

时间序列嵌入形成轨迹矩阵

输入:原始时间序列 y y y,窗口长度 L ( 2 ≤ L ≤ N / 2 ) L (2 \le L \le N/2) L(2LN/2) N = l e n ( y ) N=len(y) N=len(y)
X = [ y 1 y 2 y 3 y 4 … y N − L + 1 y 2 y 3 y 4 y 5 … y N − L + 2 y 3 y 4 y 5 y 6 … y N − L + 3 ⋮ ⋮ ⋮ ⋮ ⋱ ⋮ y L y L + 1 y L + 2 y L + 3 … y N ] (1) \mathbf{X} = \begin{bmatrix}\tag{1} y_1 & y_2 & y_3 & y_4 &\ldots & y_{N-L+1} \\ y_2 & y_3 & y_4 & y_5 &\ldots & y_{N-L+2} \\ y_3 & y_4 & y_5 & y_6 &\ldots & y_{N-L+3} \\ \vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\ y_{L} & y_{L+1} & y_{L+2} & y_{L+3} & \ldots & y_{N} \\ \end{bmatrix} X=y1y2y3yLy2y3y4yL+1y3y4y5yL+2y4y5y6yL+3yNL+1yNL+2yNL+3yN(1)
显然矩阵 X X X是一个汉克尔矩阵(每一条副对角线的元素都相等),矩阵的行数为窗口长度 L L L,矩阵的列数为 N − L + 1 N-L+1 NL+1

奇异值分解(SVD)

X = U Σ V T (2) X = U \Sigma V^T \tag{2} X=UΣVT(2)

其中:

  • U U U L × L L \times L L×L酉矩阵

  • Σ \Sigma Σ L × K L \times K L×K对角阵

  • V V V K × K K \times K K×K酉矩阵

奇异值分解将轨迹矩阵 X X X分解为酉矩阵 U U U,对角阵 Σ \Sigma Σ和酉矩阵 V V V的线性组合。这意味着:
X = ∑ i = 1 r σ i U i V i T = ∑ i = 1 r X i (3) \begin{aligned}\tag{3} \mathbf{X} & = \sum_{i=1}^{r}\sigma_i U_i V_i^{\text{T}} \\ & = \sum_{i=1}^{r}\mathbf{X}_i \end{aligned} X=i=1rσiUiViT=i=1rXi(3)

r r r表示矩阵 X X X的非零特征根数也即矩阵的秩。

序列重构

特征分组

不妨设根据某一分组原则将子矩阵分为了trend、periodic、noise 3组,对应地矩阵 X X X分解得到的子序列将被组合为3部分。
X ~ (trend) = ∑ t ∈ t r e n d X ~ t    ⟹    F ~ (trend) = ∑ t ∈ t r e n d F ~ t X ~ (periodic) = ∑ p ∈ p e r i o d i c X ~ p    ⟹    F ~ (periodic) = ∑ p ∈ p e r i o d i c F ~ p X ~ (noise) = ∑ n ∈ n o i s e X ~ n    ⟹    F ~ (noise) = s u m n ∈ n o i s e F ~ n (4) \begin{aligned}\tag{4} \tilde{\mathbf{X}}^{\text{(trend)}} & = \sum_{t \in trend}\tilde{\mathbf{X}}_t & \implies & \tilde{F}^{\text{(trend)}} = \sum_{t \in trend} \tilde{F}_t \\ \tilde{\mathbf{X}}^{\text{(periodic)}} & = \sum_{p \in periodic}\tilde{\mathbf{X}}_p & \implies & \tilde{F}^{\text{(periodic)}} = \sum_{p \in periodic} \tilde{F}_p\\ \tilde{\mathbf{X}}^{\text{(noise)}} & = \sum_{n \in noise}\tilde{\mathbf{X}}_n & \implies & \tilde{F}^{\text{(noise)}} = sum_{n \in noise} \tilde{F}_n \end{aligned} X~(trend)X~(periodic)X~(noise)=ttrendX~t=pperiodicX~p=nnoiseX~nF~(trend)=ttrendF~tF~(periodic)=pperiodicF~pF~(noise)=sumnnoiseF~n(4)

对角平均(化矩阵为序列)

y ~ k = { 1 k ∑ m = 1 k x m , k − m + 1   1 ≤ k < L ∗ 1 L ∗ ∑ m = 1 L ∗ x m , k − m + 1   L ∗ ≤ k ≤ K ∗ 1 N − k + 1 ∑ m = k − K ∗ + 1 N − K ∗ + 1 x m , k − m + 1   K ∗ < k ≤ N (5) \widetilde{y}_{k} = \left\{ \begin{array}{lr}\tag{5} \frac{1}{k}\sum_{m=1}^{k} x_{m, k-m+1} & \ 1 \le k < L^* \\ \frac{1}{L^*}\sum_{m=1}^{L^*} x_{m, k-m+1} & \ L^* \le k \le K^* \\ \frac{1}{N-k+1}\sum_{m=k-K^*+1}^{N-K^*+1} x_{m, k-m+1} & \ K^* < k \le N \\ \end{array} \right. y k=k1m=1kxm,km+1L1m=1Lxm,km+1Nk+11m=kK+1NK+1xm,km+1 1k<L LkK K<kN(5)

其中, L ∗ = m i n ( L , N − L + 1 ) , K ∗ = m a x ( L , N − L + 1 ) L^* = min(L,N-L+1),K^*=max(L,N-L+1) L=min(L,NL+1),K=max(L,NL+1)

实例

数据来源于:Kaggle原油价格预测

'''数据处理'''
import pandas as pd
import numpy as np
'''数据可视化'''
import matplotlib.pyplot as plt
plt.rcParams['figure.dpi'] = 800           #调整分辨率
plt.rcParams['font.sans-serif']=['SimHei'] #显示中文
plt.rcParams['axes.unicode_minus']=False   #正常显示负号

###加载数据集###
file = r'D:/机器学习/crude-oil-price.csv'
oil_info = pd.read_csv(file,index_col='date')
price = oil_info['price']


# 算法封装
class SSA(object):
    
    __supported_types = (pd.Series,np.ndarray,list) #限制时间序列的输入类型
    
    def __init__(self,tseries,L):
        '''
        Args:
            tseries:原始时间序列
            L:窗口长度
        '''
        
        if not isinstance(tseries, self.__supported_types):
            raise TypeError("请确保时间序列的数据类型为Pandas Series,NumpPy array 或者list")
        else:
            self.orig_TS = pd.Series(tseries)
            
        self.N = len(tseries)        #原始时间序列长度
        if not 2 <= L <= self.N / 2:
            raise ValueError("窗口长度必须介于[2,N/2]")
        self.L = L                   #窗口长度,轨迹矩阵的行数
        self.K = self.N - self.L + 1 #轨迹矩阵的列数
        self.X = np.array([self.orig_TS.values[i:L+i] for i in range(0,self.K)]).T
        
        #奇异值分解
        self.U,self.Sigma,VH = np.linalg.svd(self.X)
        self.r = np.linalg.matrix_rank(self.X)    #矩阵的秩等于非零特征值的数量
        #每一个非零特征值都对应一个子矩阵,子矩阵对角平均化后得到原始时间序列的一个子序列
        self.TS_comps = np.zeros((self.N,self.r)) 
        
        #对角平均还原
        for i in range(self.r):
            X_elem = self.Sigma[i] * np.outer(self.U[:,i], VH[i,:]) 
            X_rev = X_elem[::-1]
            self.TS_comps[:,i] = [X_rev.diagonal(j).mean() for j in range(-X_rev.shape[0]+1, X_rev.shape[1])]
            
    def comps_to_df(self):
        '''将子序列数组转换成DataFrame类型
        '''
        cols = ["F{}".format(i) for i in range(self.r)]
        return pd.DataFrame(data=self.TS_comps,columns=cols,index=self.orig_TS.index)
    
    def reconsruct(self,indices):
        '''重构,可以是部分重构(相当于子序列的分组合并),也可以是全部合并(重构为原序列)
        Args:
            indices   重构所选择的子序列
        '''
        if isinstance(indices,int):
            indices = [indices]
        
        ts_vals = self.TS_comps[:,indices].sum(axis=1)
        return pd.Series(ts_vals,index=self.orig_TS.index)
    
    def vis(self):
        '''可视化子序列
        '''
        fig,axs = plt.subplots(self.r,sharex='all')
        for i in range(self.r):
            axs[i].plot(self.reconsruct(i),lw=1)
            

price_SSA = SSA(price,7)
comps_df = price_SSA.comps_to_df()

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值