時間序列資料的定義,讀取與指數平滑(Java)

心拍數#0822發表於2020-11-12

  應上頭的要求,需要實現以下指數平滑進行資源排程負載的預測,那就是用我最喜歡的Java做一下吧。

  引用《計量經濟學導論》的一句話:時間序列資料區別於橫截面資料的一個明顯特點是,時間序列資料集是按照時間順序排列的。

  顯然,橫截面資料被視為隨機的結果,也就是說在總體中隨機抽取樣本。時間序列資料和橫截面資料區別較為微妙,雖然它也滿足隨機性,但是這個序列標有時間腳標,依照時間有序,而不可以讓時間隨機排列導致錯亂,我們不能讓時間逆轉重新開始這個過程。對於這樣的序列我們稱之為隨機過程,或者時間序列過程。

  對於時間序列,經常研究的一個問題就是預測,而指數平滑法是非常常見也常用的方法之一。這裡對於二次指數平滑進行Java的實現(一次指數平滑包含在二次指數平滑之內)。其原理參照: https://cloud.tencent.com/developer/article/1058557 。這裡就不再贅述。

 

資料也是參照我國1981年至1983年度平板玻璃月產量資料,以下檔案儲存為data2.txt

我國1981年至1983年度平板玻璃月產量資料
1,240.3
2,222.8
3,243.1
4,222.2
5,222.6
6,218.7
7,234.5
8,248.6
9,261
10,275.3
11,269.4
12,291.2
13,301.9
14,285.5
15,286.6
16,260.5
17,298.5
18,291.8
19,267.3
20,277.9
21,303.5
22,313.3
23,327.6
24,338.3
25,340.37
26,318.51
27,336.85
28,326.64
29,342.9
30,337.53
31,320.09
32,332.17
33,344.01
34,335.79
35,350.67
36,367.37

對於以上資料,時間是int型別,而產量是double型別,為了便於讀取,對於以上資料定義行資料類

package timeSeries;

public class RowData {

    private int time;
    private double value;
    
    public RowData() {
        // TODO Auto-generated constructor stub
    }

    public RowData(int time, double value) {
        super();
        this.time = time;
        this.value = value;
    }

    public int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }

    public double getValue() {
        return value;
    }

    public void setValue(double value) {
        this.value = value;
    }

}

然後定義檔案讀取類,讀取所得資料為RowData陣列

package utilFile;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

import timeSeries.RowData;

public class FileOpts {

    public static ArrayList<RowData> loadTxt(String dataPath, boolean ishead) {
        File file = new File(dataPath);
        FileReader fr;
        ArrayList<RowData> datas = new ArrayList<RowData>();
        try {
            fr = new FileReader(file);
            BufferedReader br = new BufferedReader(fr);
            String line = "";
            String[] splitdata;
            if (ishead) {
                br.readLine();
            }
            while ((line = br.readLine()) != null) {
                splitdata = line.split(",");
                datas.add(new RowData(Integer.parseInt(splitdata[0]), Double.parseDouble(splitdata[1])));
            }
            br.close();
            fr.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return datas;
    }

}

然後定義時間序列分析類,其實就是一個函式

package timeSeries;

import java.util.ArrayList;

import java.util.Iterator;


public class ExponentialSmoothing2 {

    public static double[][] expSmoothOrder2(int[] time, double[] values, double alpha, int preNum) {
        int len = time.length;
        // 返回一個彙總表
        double[][] result = new double[len + preNum][7];
        // 第一列時間,第二列實際觀察值
        for (int i = 0; i < len; i++) {
            result[i][0] = time[i];
            result[i][1] = values[i];
        }
    
        result[0][2] = values[0];
        result[0][3] = result[0][2];
    
        // 第三列一次指數平滑值,第四列二次指數平滑值
        // S1, S2 2, 3
        for (int i = 1; i < len; i++) {
            result[i][2] = alpha*values[i] + (1-alpha)*result[i-1][2];
            result[i][3] = alpha*result[i][2] + (1-alpha)*result[i-1][3];
        }
    
        // 第五列a,第六列b
        // a, b 4, 5
        for (int i = 1; i < len; i++) {
            result[i][4] = 2*result[i][2] - result[i][3];
            result[i][5] = alpha/(1-alpha) * (result[i][2] - result[i][3]);
        }
        // 第七列預測值F
        // F 6
        for (int i = 1; i < len; i++) {
            result[i+preNum][6] = result[i][4] + result[i][5] * preNum;
        }
        return result;
    }
    
    public static void main(String[] args) {
        // 獲取資料
        ArrayList<RowData> data = utilFile.FileOpts.loadTxt("src/timeSeries/data2.txt", true);
        int len = data.size();
        int[] time = new int[len];
        double[] values = new double[len];
        Iterator<RowData> it = data.iterator();
        int index = 0;
        while (it.hasNext()) {
            RowData rowData = (RowData) it.next();
            time[index] = rowData.getTime();
            values[index] = rowData.getValue();
            index++;
        }
        // -------------------資料準備完畢---------------
        
//        System.out.println(Arrays.toString(time));
//        System.out.println(Arrays.toString(values));
        // ------------------二次指數平滑---------------------
        double[][] pre2= expSmoothOrder2(time, values, 0.5, 1);
        System.out.printf("%6s, %6s, %6s, %6s, %6s, %6s, %6s\n", "time", "y", "s1", "s2", "a", "b", "F");
        for (int i = 0; i < values.length; i++) {
            System.out.printf("%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f \n", pre2[i][0], pre2[i][1], pre2[i][2],
                    pre2[i][3], pre2[i][4], pre2[i][5], pre2[i][6]);
        }
//        System.out.printf("%6d, %6d, %6d, %6d, %6d, %6d, %6.2f \n", 37, 0, 0, 0, 0, 0, pre2[values.length][3]);
//        System.out.printf("%6d, %6d, %6d, %6d, %6d, %6d, %6.2f \n", 38, 0, 0, 0, 0, 0, pre2[35][1] + pre2[35][2] * 2);
        // 誤差分析
        double MSE = 0;
        double MAPE = 0;
        double temp;
//        System.out.println("pre2.length = "+pre2.length);
        for (int i = 2; i < pre2.length-1; i++) {
            MSE += (pre2[i][1]-pre2[i][6])*(pre2[i][1]-pre2[i][6])/(pre2.length-2);
            temp = (pre2[i][1]-pre2[i][6])/pre2[i][1];
            if (temp < 0) {
                MAPE -= temp/(pre2.length-2);
            }else {
                MAPE += temp/(pre2.length-2);
            }
//            System.out.printf("iter: %d, y = %6.2f, F = %6.2f, MSE = %6.2f, MAPE = %6.5f\n", i, pre2[i][1], pre2[i][6], MSE, MAPE);
        }
        System.out.printf("MSE = %6.2f, MAPE = %6.5f\n", MSE, MAPE);
        if (MAPE < 0.05) {
            System.out.println("百分誤差小於0.05,預測精度較高");
        }else {
            System.out.println("預測誤差超過了0.05");
        }
    }


}

執行結果:

 

  事實上還可以使用Java進行時間序列影像繪製,畢竟Java跑完python或matlab繪圖還挺麻煩,Java也是可以實現的,只不過為了方便需要寫一個龐大的API,日後閒下來再寫一篇部落格細說。

相關文章