一种基于滑动平均的时间序列滤波方法(Java实现)

 1 滑动平均的认识

  移动平均法是用一组最近的实际数据值来预测未来一期或几期内公司产品的需求量、公司产能等的一种常用方法。移动平均法适用于即期预测。当产品需求既不快速增长也不快速下降,且不存在季节性因素时,移动平均法能有效地消除预测中的随机波动,是非常有用的。移动平均法根据预测时使用的各元素的权重不同

  移动平均法是一种简单平滑预测技术,它的基本思想是:根据时间序列资料、逐项推移,依次计算包含一定项数的序时平均值,以反映长期趋势的方法。因此,当时间序列的数值由于受周期变动和随机波动的影响,起伏较大,不易显示出事件的发展趋势时,使用移动平均法可以消除这些因素的影响,显示出事件的发展方向与趋势(即趋势线),然后依趋势线分析预测序列的长期趋势。

  移动平均法的种类

  移动平均法可以分为:简单移动平均和加权移动平均。

 (1)简单移动平均法

  简单移动平均的各元素的权重都相等。简单的移动平均的计算公式如下: Ft=(At-1+At-2+At-3+…+At-n)/n式中,

  ·Ft–对下一期的预测值;

  ·n–移动平均的时期个数;

  ·At-1–前期实际值;

  ·At-2,At-3和At-n分别表示前两期、前三期直至前n期的实际值。 
  

 (2)加权移动平均法

  加权移动平均给固定跨越期限内的每个变量值以不同的权重。其原理是:历史各期产品需求的数据信息对预测未来期内的需求量的作用是不一样的。除了以n为周期的周期性变化外,远离目标期的变量值的影响力相对较低,故应给予较低的权重。加权移动平均法的计算公式如下:

  Ft=w1At-1+w2At-2+w3At-3+…+wnAt-n式中,

  ·w1–第t-1期实际销售额的权重;

  ·w2–第t-2期实际销售额的权重;

  ·wn–第t-n期实际销售额的权

  ·n–预测的时期数;w1+ w2+…+ wn=1

  在运用加权平均法时,权重的选择是一个应该注意的问题。经验法和试算法是选择权重的最简单的方法。一般而言,最近期的数据最能预示未来的情况,因而权重应大些。例如,根据前一个月的利润和生产能力比起根据前几个月能更好的估测下个月的利润和生产能力。但是,如果数据是季节性的,则权重也应是季节性的。

  移动平均法的优缺点

  使用移动平均法进行预测能平滑掉需求的突然波动对预测结果的影响。但移动平均法运用时也存在着如下问题:

  1、加大移动平均法的期数(即加大n值)会使平滑波动效果更好,但会使预测值对数据实际变动更不敏感;

  2、移动平均值并不能总是很好地反映出趋势。由于是平均值,预测值总是停留在过去的水平上而无法预计会导致将来更高或更低的波动;

  3、移动平均法要由大量的过去数据的记录。

  移动平均法案例分析 
   
  简单移动平均法在房地产中的运用 
某类房地产2001年各月的价格如下表中第二列所示。由于各月的价格受某些不确定因素的影响,时高时低,变动较大。如果不予分析,不易显现其发展趋势。如果把每几个月的价格加起来计算其移动平均数,建立一个移动平均数时间序列,就可以从平滑的发展趋势中明显地看出其发展变动的方向和程度,进而可以预测未来的价格。

  在计算移动平均数时,每次应采用几个月来计算,需要根据时间序列的序数和变动周期来决定。如果序数多,变动周期长,则可以采用每6个月甚至每12个月来计算;反之,可以采用每2个月或每5个月来计算。对本例房地产2001年的价格,采用每5个月的实际值计算其移动平均数。计算方法是:把1~5月的价格加起来除以5得684元/平方米,把2~6月的价格加起来除以5得694元/平方米,把3~7月的价格加起来除以5得704元/平方米,依此类推,见表中第三列。再根据每5个月的移动平均数计算其逐月的上涨额,见表中第四列。 
  

这里写图片描述

假如需要预测该类房地产2002年1月的价格,则计算方法如下:由于最后一个移动平均数762与2002年1月相差3个月,所以预测该类房地产2002年1月的价格为:762 + 12 × 3 =798(元/平方米)

2 滑动平均Java实现

  • 剩余点个数=窗口长度-1=需要补数的个数
  • 如果不考虑剩余点则最终返回结果数组的长度=原始数组长度-窗口长度+1
import java.util.Arrays;

import static org.apache.commons.math.stat.StatUtils.mean;

/**
 * 滑动平均计算
 * Created by dandan.
 * 属性解释:
 * movWindowSize:移动平均计算的滑动窗口
 * movLeaveTemp:临时数组记录最后得不到均值处理的点
 * movResBuff:输出最终结果数组
 * movResBuffLen:输出最终结果数组长度
 * inputBuff:输入数据数组
 * winArray:滑动窗口所形成的数组
 * winArrayLen:滑动窗口所形成的数组长度
 * tempCal:原始数组+插值后的扩容数组
 */

public class movingAverage {

    private static final int WINDOWS = 5;
    private int movWindowSize = WINDOWS; //窗口大小

    public movingAverage() {

    }

    public movingAverage(int size) {

        movWindowSize = size;
    }

    // 均值滤波方法,输入一个inputBuff数组,返回一个movResBuff数组,两者下标不一样,所以定义不同的下标,inputBuff的下标为i,movResBuff的下标为movResBuffLen.
    // 同理,临时的winArray数组下表为winArrayLen
    public double[] movingAverageCal(double[] inputBuff) {
        int movResBuffLen = 0;
        int winArrayLen = 0;
        //定义返回结果数组
        double[] movResBuff = new double[inputBuff.length];
        //定义窗口数组
        double[] winArray = new double[movWindowSize];
        //求整体输入序列的平均值作为一个插值点
        double replace = mean(inputBuff);
        //对原始数组扩容,将插值点放进去.剩余点个数是窗口大小-1.需要补充值的个数等于剩余点的个数
        double[] tempCal = new double[inputBuff.length + movWindowSize-1];
        //拷贝原始数组到临时计算数组
        System.arraycopy(inputBuff, 0, tempCal, 0, inputBuff.length);
        //将平均值插入进去
        for (int m = inputBuff.length; m <tempCal.length ; m++) {
            tempCal[m]=replace;
        }
        //开始计算
        for (int i = 0; i < tempCal.length; i++) {
            if ((i + movWindowSize) > tempCal.length) {
                break; 
            } else { 
                for (int j = i; j < (movWindowSize + i); j++) {
                    winArray[winArrayLen] = tempCal[j];
                    winArrayLen++; 
                }
                movResBuff[movResBuffLen] = mean(winArray);
                movResBuffLen++;
                winArrayLen = 0; 
            } 
        }
        return movResBuff; 
    }

    public static void main(String[] args) {

       double[] inputBuff={670,680,690,680,700,720,730,740,740,760,780,790};

        movingAverage movingAverage = new movingAverage();

        double[] filter = movingAverage.movingAverageCal(inputBuff);

        System.out.println(filter.length);
        System.out.println(Arrays.toString(filter));
        System.out.println(mean(filter));

    }
}

3 UDF函数实现。

在上述返回值的基础上再次平均求得特征值

import org.apache.hadoop.hive.ql.exec.UDF;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.math.stat.StatUtils.mean;

public class movingAverageFeaCal extends UDF {

    public static void main(String[] args) {
        String num_all = "3.1002," +
                "3.0984," +
                "3.147," +
                "3.197," +
                "3.1002," +
                "3.1002," +
                "3.0854," +
                "3.0982," +
                "3.12," +
                "3.09," +
                "3.a091";

        movingAverageFeaCal movingAverageFeaCal = new movingAverageFeaCal();

        Double evaluate = movingAverageFeaCal.evaluate(num_all, 3);
        System.out.println(evaluate);
    }


    public Double evaluate(String num_all,int windowSize) {

        if (num_all == null || num_all.isEmpty()) {
            return null;//参数不全,不需要计算
        }
        String[] numArr = num_all.split(",");
        List<Double> list = new ArrayList();
        double num = 0;

        if (numArr.length > 0) {
            for (String aNumArr : numArr) {
//                boolean flag = Utils.isNumber(numArr[i]);
                if (aNumArr != null && !aNumArr.isEmpty() && !aNumArr.equals("null") && !aNumArr.equals("NULL") && Utils.isNumber(aNumArr)) {
                    num = Double.parseDouble(aNumArr);
                    list.add(num);
                }
            }
            // Double[] arr1 = list.toArray(new Double[list.size()]);
            double[] inputBuff= list.stream().mapToDouble(i -> i).toArray();
            movingAverage movingAverage = new movingAverage(windowSize);
            double[] feaArr = movingAverage.movingAverageCal(inputBuff);
            return mean(feaArr);

        } else {
            return null;
        }

    }


}

猜你喜欢

转载自blog.csdn.net/godlovedaniel/article/details/114635797