FAQ > 金融建模 > 应用案例 > 指标或功能实现

Q:如何修改数组中某列数据的奇异值(异常值)    

  • A:在处理数组数据时可能会遇到一些异常值比如:nan,nil,inf,处理方法参考:FAQ:Q:空值、INF、NAN的判断及替换方法
    或者一些与整体数据偏离较大的奇异值,判定模型参考:FAQ:AbnormalData
    本文主要介绍如何判断数组指定列中的奇异值及不同修改奇异值使其平滑的方法。
    实现范例
    范例一:找出数组指定列中的奇异值并修改为模型处理后的值,以中位数法为例
    中位数法:上边界=中位数+5.2*medianof(abs(yi-中位数)),下边界=中位数-5.2*medianof(abs(yi-中位数)),其中medianof表示求序列中位数。
    超出上边界的数改为上边界,低于下边界的数改为下边界。
      col:='SH000001';
      setsysparam(pn_stock(),col);
      setsysparam(pn_date(),20240930T);
      setsysparam(pn_cycle(),cy_day());
      days:=100;
      //SH000001在20240930最近100天的行情数据中设置三个奇异值
      t:=Nday(days,'日期',datetostr(sp_time()),col,close());
      t[29,col]:=1500;
      t[59,col]:=4500;
      t[79,col]:=6000;
      //找到数组指定列奇异值,以中位数法为例
      AbnormalData(t[:,col],"median",abn);
      //奇异值替换为模型处理后的值
      t[abn[:,0],col]:=abn[:,2];

      //删除奇异值所在的行
      //t:=select * from t where not (thisrowindex in abn[:,0]) end;
      return t;

    结果:
    平滑前:


    平滑后:


    范例二:找出数组指定列中的奇异值并修改为自定义的值,以中位数法为例
    中位数法:上边界=中位数+5.2*medianof(abs(yi-中位数)),下边界=中位数-5.2*medianof(abs(yi-中位数)),其中medianof表示求序列中位数。
      col:='SH000001';
      setsysparam(pn_stock(),col);
      setsysparam(pn_date(),20240930T);
      setsysparam(pn_cycle(),cy_day());
      days:=100;
      //SH000001在20240930最近100天的行情数据中设置三个奇异值
      t:=Nday(days,'日期',datetostr(sp_time()),col,close());
      t[29,col]:=1500;
      t[59,col]:=4500;
      t[79,col]:=6000;
      //找到数组指定列奇异值,以中位数法为例
      AbnormalData(t[:,col],"median",abn);
      len:=mrows(t);
      N:=10;
      //修改奇异值为自定义值,比如最近10条记录的中位数
      for i in abn do
      begin
        row:=abn[i,0];
        //获取指定下标最近N条记录的上下标
        getDistinceIndex(row,len,N,bi,ei);
        if row>bi and row<ei then
          t[row,col]:=Median(t[bi:row-1,col] union t[row+1:ei,col]);
        else
          t[row,col]:=Median(t[bi:ei,col]);
      end

      return t;

    结果:
    平滑前:


    平滑后:


    中间模型:getDistinceIndex
    附件:getDistinceIndex.fun
    function getDistinceIndex(row,len,N,bi,ei);
    begin
    {
      说明:获取指定下标row在数组中最近N个值的上下标,从左边开始取,不够再取右边
      参数:
        row: int 指定下标
        len: Int 数组长度
        N: int 最近的N个值,不包括下标为row的值
        bi: int 距离最近的开始下标
        ei: int 距离最近的截止下标
      返回:数组,距离最近指定下标最近N个值的开始下标及截止下标
    }
      if row<0 or N>len-1 or N<=0 or row>len-1 then
      begin
        bi:=0;
        ei:=len-1;
      end
      else
      begin
        bi:=row?(row-N>=0?row-N:0):1;
        ei:=bi or row=N?row+(N-(row-bi+1)):N;
      end
      return array(bi,ei);
    end


    范例三:设置局部范围找出数组中指定列的异常值并修改为自定义的值,以3倍标准差法为例
      如果数组的数据特别大,会出现局部范围较为异常的值在整体上并不属于异常值,因此本范例主要介绍如何在局部范围内找出异常值。
    注意:
     1.为避免一段时间数据表现平稳的部分偏离不大数据判定为异常值,数据范围不能太小;
     2.行情中可能会出现短时间价格波动特别大的情况,此时本方法对异常值的判断可能会失真,请谨慎使用。

    3倍标准差法:上边界=μ+3σ,下边界=μ-3σ,其中u表示均值,σ表示标准差。
      col:='SH000001';
      setsysparam(pn_stock(),col);
      setsysparam(pn_date(),20240830T);
      setsysparam(pn_cycle(),cy_day());
      days:=100;
      //样例数据:SH000001在20240830最近100天的行情数据中设置四个奇异值
      t:=Nday(days,'日期',datetostr(sp_time()),col,close());
      t[0,col]:=2700;
      t[29,col]:=3500;
      t[59,col]:=3300;
      t[89,col]:=3200;
      N:=30;//局部范围:最近30条记录
      len:=mrows(t);
      r:=array();
      for i:=0 to len-1 do
      begin
        //获取指定下标最近30条记录的上下标(不含边界)
        getDistinceIndex(i,len,N,bi,ei);
        if i=0 then bi:=0;
        if i>ei then ei:=i;
        //找出局部范围奇异值,以3倍标准差法为例
        tt:=t[bi:ei];
        AbnormalData(tt[:,col],"zvalue",abn);
        //判断最新的值是不是异常值,对历史记录不调整
        abn:=select * from abn where [1]=t[i,col] end;
        if not istable(abn) then continue;

        //异常值替换为自定义的值,最近10条记录的均值
        len1:=mrows(tt);
        N1:=10;
        for j in abn do
        begin
          row:=abn[j,0];
          //获取指定下标最近10条记录的上下标
          getDistinceIndex(row,len1,N1,b,e);
          //奇异值替换为最近10条记录的均值
          if row>b and row<e then
            abn[j,2]:=Mean(tt[b:row-1,col] union tt[row+1:e,col]);
          else
            abn[j,2]:=mean(tt[b:e,col]);
        end
        abn[0,0]:=i;
        t[abn[:,0],col]:=abn[:,2];
        r&=abn;
      end
      //return r; //查看调整过的异常值
      return t;

    结果:
    平滑前:


    平滑后: