拟合曲线趋势
算法背景
很多仪表设备都能定时产生一些数据,称之为时间序列。
下图是一张时间序列的走势图,横轴是时间(dd hh:mm),纵轴是数值。
数据的波动很大,看局部会有点杂乱无章的感觉,但观察整张图又隐约可以看出曲线的走势(平稳——下降——平稳),能不能用数学方法把曲线的总体走势描述出来呢?这样将有利于分析设备仪表的工作状况。
答案是可以的。
下面就来介绍如何利用原始数据拟合出曲线的趋势线。
算法原理
我们把描述时间序列趋势的曲线称为主线。主线上相邻点之间的距离称为主线距离,同一时刻主线上的点与原值的距离称为原值距离。我们当然希望主线是平滑的,否则就和原值一样看起来杂乱无章了,所以所有的主线距离之和要尽量小,也就是说主线相邻点的值变化小,这样主线就会少一些剧变,更平滑一些。同时主线要能表征原值的特点,所以也要与原值接近,否则只考虑平滑时主线就成一条平直线了,这时候就要求所有的原值距离之和也要尽量小,这样主线偏离原值就更少。以这两者加权后的总和最小为目标,就可以拟合出一条主线,而调整权重则可以使主线更平滑或者更接近原值。
算法过程
时间序列用X表示:
X=[x1,x2,…,xn]
其中xi是序列X的第i个元素。
设主线为M:
M=[m1,m2,…,mn]
其中mi是序列M的第i个元素。
1. 计算主线距离
主线距离用MD表示
原值距离用VD表示。
2. 平衡两类距离的最小值
设置平衡系数k来平衡两类距离的权重,则总距离D
D=VD+k*MD
3. 优化mi使总距离D最短
对每个mi求偏导:
转换后的问题变为解多元线性方程问题。
系数矩阵用C表示
矩阵中未填写数值处都是0。
C*M=X
解上述方程就得到了主线M。
M=linefit(C,X)
其中linefit()是解线性方程的函数。
这里需要说明两点:
1. 平衡系数调节两类距离的权重,k越大,主线距离MD占比越大,主线越平滑,k越小,原值距离VD占比越大,主线越接近原值。当k=0时,主线就是原值。当k=∞时,主线是一个常数,常数值等于原值的平均值。
2. 系数矩阵C是一个n*n方阵,因此它的计算受到了这个方阵大小的限制(n很大时,计算机内存很可能存不下这个n*n方阵)。仔细观察系数矩阵C,可以将其转换一个n*3的矩阵,利用矩阵变换规则同样可以求得主线M,这样就可以求n很大时的主线。
写出代码
A | B | C | D | E | |
1 | =seq | /输入参数:时间序列 X | |||
2 | =k | /平衡系数:k | |||
3 | =func(A6,A1,A2) | /拟合主线 | |||
4 | return A3 | ||||
5 | /拟合主线 2,参数:时间序列 X,参数 k | ||||
6 | func | ||||
7 | 1e15 | ||||
8 | =if(B6==0,A6,if(B6>=B7,((avg=round(A6.avg(),6),A6.(avg))),func(A11,A6,B6))) | ||||
9 | return B8 | /k=0时,主线=原值。k=∞时,主线=常数=原值的平均值。 | |||
10 | /拟合主线(相邻两数之差的平方和 + 原值与该数的差的平方和最小),参数:序列,参数 k | ||||
11 | func | ||||
12 | =transpose(A11) | /x | |||
13 | =B12.len() | ||||
14 | =B13.(if(#==1,[B11+1,-B11,0],if(#==B13,[0,-B11,B11+1],[-B11,2*B11+1,-B11]))) | /系数矩阵 C | |||
15 | =func(A18,B14,B12) | /矩阵变换 | |||
16 | =func(A37,B15(1),B15(2)) | /解方程 | |||
17 | /矩阵变换,参数:系数矩阵 C,原值 X | ||||
18 | func | ||||
19 | for A18.len() | 1e-7 | |||
20 | if B19==1 | =A18(B19).select@1(abs(~)>C19) | /除数 | ||
21 | else if B19==A18.len() | ||||
22 | =A18(B19).m(2) | ||||
23 | =A18(B19)=A18(B19)++A18(B19-1).(~*(-D22)) | /更新 c=c+c[-1] | |||
24 | =B18(B19)=B18(B19)++B18(B19-1).(~*(-D22)) | /更新 x=x+c[-1] | |||
25 | =D20=D23.m(-1) | /除数 | |||
26 | else | =A18(B19-1).pselect@a(abs(~)>C19) | /错位 | ||
27 | =A18(B19).~ | ||||
28 | =A18(B19)(to(2))=A18(B19)(to(2))++A18(B19-1)(D26).(~*(-D27)) | /更新 c=c+c[-1] | |||
29 | =B18(B19)=B18(B19)++B18(B19-1).(~*(-D27)) | /更新 x=x+c[-1] | |||
30 | =D20=D28.m(-1) | /除数 | |||
31 | =A18(B19).(~/D20) | /c除以除数 | |||
32 | =A18(B19)=C31 | /更新 c | |||
33 | =B18(B19).(~/D20) | /x除以除数 | |||
34 | =B18(B19)=C33 | /更新 x | |||
35 | return [A18,B18] | ||||
36 | /解方程,参数:系数矩阵 C,X | ||||
37 | func | ||||
38 | =A37.(if(#==1,~.m(:2),~.m(2:))) | /第一行是前两个元素,其他是后两个元素 | |||
39 | =B38.rvs() | /c倒序 | |||
40 | =B37.rvs().conj() | /x倒序 | |||
41 | =B40.iterate@a(if(#==1,~~,~-(B39(#).m(-1)*~~)),B40.~) | /迭代更新 c | |||
42 | =B41.rvs().(round(~,6)) | /恢复原序 |
输入参数说明:
1. seq:时间序列X
2. k:平衡系数k,k越大,主线越平滑。
3. 代码是用矩阵变化的方式解方程的,因此n可以很大。
应用举例
时间序列X如下图:
图中横轴是时间(dd hh:mm),纵轴是序列中的值。
不同k的主线如下图:
M_10:k=10时的主线。
M_100:k=100时的主线。
M_1000:k=1000时的主线。
k越大时,主线越平滑。