├── README.md └── TripleExponentialImpl.java /README.md: -------------------------------------------------------------------------------- 1 | # TripleExponentialSmoothing 2 | 三阶指数平滑算法,对于带有趋势性及周期性的时间序列有不错的预测效果。 3 | -------------------------------------------------------------------------------- /TripleExponentialImpl.java: -------------------------------------------------------------------------------- 1 | package com.nust.predict.TripleExponentialSmoothing; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.nust.predict.utils.JCSVUtils; 7 | 8 | /** 9 | * Copyright 2011 Nishant Chandra 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | /** 25 | * Given a time series, say a complete monthly data for 12 months, the Holt-Winters smoothing and forecasting 26 | * technique is built on the following formulae (multiplicative version): 27 | * 28 | * St[i] = alpha * y[i] / It[i - period] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]) 29 | * Bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * Bt[i - 1] 30 | * It[i] = beta * y[i] / St[i] + (1.0 - beta) * It[i - period] 31 | * Ft[i + m] = (St[i] + (m * Bt[i])) * It[i - period + m] 32 | * 33 | * Note: Many authors suggest calculating initial values of St, Bt and It in a variety of ways, but 34 | * some of them are incorrect e.g. determination of It parameter using regression. I have used 35 | * the NIST recommended methods. 36 | * 37 | * For more details, see: 38 | * http://adorio-research.org/wordpress/?p=1230 39 | * http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm 40 | * 41 | * @author Nishant Chandra 42 | * 43 | */ 44 | public class TripleExponentialImpl { 45 | 46 | /** 47 | * This method is the entry point. It calculates the initial values and returns the forecast 48 | * for the m periods. 49 | * 50 | * @param y - Time series data. 51 | * @param alpha - Exponential smoothing coefficients for level, trend, seasonal components. 52 | * @param beta - Exponential smoothing coefficients for level, trend, seasonal components. 53 | * @param gamma - Exponential smoothing coefficients for level, trend, seasonal components. 54 | * @param perdiod - A complete season's data consists of L periods. And we need to estimate 55 | * the trend factor from one period to the next. To accomplish this, it is advisable to use 56 | * two complete seasons; that is, 2L periods. 57 | * @param m - Extrapolated future data points. 58 | * @param debug - Print debug values. Useful for testing. 59 | * 60 | * 4 quarterly 61 | * 7 weekly. 62 | * 12 monthly 63 | */ 64 | public static double[] forecast(double[] y, double alpha, double beta, 65 | double gamma, int period, int m, boolean debug) { 66 | 67 | if (y == null) { 68 | return null; 69 | } 70 | 71 | int seasons = y.length / period; 72 | double a0 = calculateInitialLevel(y, period); 73 | double b0 = calculateInitialTrend(y, period); 74 | double[] initialSeasonalIndices = calculateSeasonalIndices(y, period, seasons); 75 | 76 | if (debug) { 77 | System.out.println(String.format( 78 | "Total observations: %d, Seasons %d, Periods %d", y.length, 79 | seasons, period)); 80 | System.out.println("Initial level value a0: " + a0); 81 | System.out.println("Initial trend value b0: " + b0); 82 | printArray("Seasonal Indices: ", initialSeasonalIndices); 83 | } 84 | 85 | double[] forecast = calculateHoltWinters(y, a0, b0, alpha, beta, gamma, 86 | initialSeasonalIndices, period, m, debug); 87 | 88 | // if (debug) { 89 | // printArray("Forecast", forecast); 90 | // } 91 | 92 | return forecast; 93 | } 94 | 95 | /** 96 | * This method realizes the Holt-Winters equations. 97 | * 98 | * @param y 99 | * @param a0 100 | * @param b0 101 | * @param alpha 102 | * @param beta 103 | * @param gamma 104 | * @param initialSeasonalIndices 105 | * @param period 106 | * @param m 107 | * @param debug 108 | * @return - Forecast for m periods. 109 | */ 110 | private static double[] calculateHoltWinters(double[] y, double a0, double b0, double alpha, 111 | double beta, double gamma, double[] initialSeasonalIndices, int period, int m, boolean debug) { 112 | 113 | double[] St = new double[y.length]; 114 | double[] Bt = new double[y.length]; 115 | double[] It = new double[y.length]; 116 | double[] Ft = new double[y.length + m]; 117 | 118 | //Initialize base values 119 | St[1] = a0; 120 | Bt[1] = b0; 121 | 122 | for (int i = 0; i < period; i++) { 123 | It[i] = initialSeasonalIndices[i]; 124 | } 125 | 126 | Ft[m] = (St[0] + (m * Bt[0])) * It[0];//This is actually 0 since Bt[0] = 0 127 | Ft[m + 1] = (St[1] + (m * Bt[1])) * It[1];//Forecast starts from period + 2 128 | 129 | //Start calculations 130 | for (int i = 2; i < y.length; i++) { 131 | 132 | //Calculate overall smoothing 133 | if((i - period) >= 0) { 134 | St[i] = alpha * y[i] / It[i - period] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]); 135 | } else { 136 | St[i] = alpha * y[i] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]); 137 | } 138 | 139 | //Calculate trend smoothing 140 | Bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * Bt[i - 1]; 141 | 142 | //Calculate seasonal smoothing 143 | if((i - period) >= 0) { 144 | It[i] = beta * y[i] / St[i] + (1.0 - beta) * It[i - period]; 145 | } 146 | 147 | //Calculate forecast 148 | if( ((i + m) >= period) ){ 149 | Ft[i + m] = (St[i] + (m * Bt[i])) * It[i - period + m]; 150 | } 151 | 152 | if(debug){ 153 | System.out.println(String.format( 154 | "i = %d, y = %f, S = %f, Bt = %f, It = %f, F = %f", i, 155 | y[i], St[i], Bt[i], It[i], Ft[i])); 156 | } 157 | } 158 | 159 | return Ft; 160 | } 161 | 162 | /** 163 | * See: http://robjhyndman.com/researchtips/hw-initialization/ 164 | * 1st period's average can be taken. But y[0] works better. 165 | * 166 | * @return - Initial Level value i.e. St[1] 167 | */ 168 | private static double calculateInitialLevel(double[] y, int period) { 169 | 170 | /** 171 | double sum = 0; 172 | 173 | for (int i = 0; i < period; i++) { 174 | sum += y[i]; 175 | } 176 | 177 | return sum / period; 178 | **/ 179 | return y[0]; 180 | } 181 | 182 | /** 183 | * See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm 184 | * 185 | * @return - Initial trend - Bt[1] 186 | */ 187 | private static double calculateInitialTrend(double[] y, int period){ 188 | 189 | double sum = 0; 190 | 191 | for (int i = 0; i < period; i++) { 192 | sum += (y[period + i] - y[i]); 193 | } 194 | 195 | return sum / (period * period); 196 | } 197 | 198 | /** 199 | * See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm 200 | * 201 | * @return - Seasonal Indices. 202 | */ 203 | private static double[] calculateSeasonalIndices(double[] y, int period, int seasons){ 204 | 205 | double[] seasonalAverage = new double[seasons]; 206 | double[] seasonalIndices = new double[period]; 207 | 208 | double[] averagedObservations = new double[y.length]; 209 | 210 | for (int i = 0; i < seasons; i++) { 211 | for (int j = 0; j < period; j++) { 212 | seasonalAverage[i] += y[(i * period) + j]; 213 | } 214 | seasonalAverage[i] /= period; 215 | } 216 | 217 | for (int i = 0; i < seasons; i++) { 218 | for (int j = 0; j < period; j++) { 219 | averagedObservations[(i * period) + j] = y[(i * period) + j] / seasonalAverage[i]; 220 | } 221 | } 222 | 223 | for (int i = 0; i < period; i++) { 224 | for (int j = 0; j < seasons; j++) { 225 | seasonalIndices[i] += averagedObservations[(j * period) + i]; 226 | } 227 | seasonalIndices[i] /= seasons; 228 | } 229 | 230 | return seasonalIndices; 231 | } 232 | 233 | /** 234 | * Utility method to pring array values. 235 | * 236 | * @param description 237 | * @param data 238 | */ 239 | private static void printArray(String description, double[] data){ 240 | 241 | System.out.println(String.format("******************* %s *********************", description)); 242 | 243 | for (int i = 0; i < data.length; i++) { 244 | System.out.println(data[i]); 245 | } 246 | 247 | System.out.println(String.format("*****************************************************************", description)); 248 | } 249 | 250 | public static void main(String[] args){ 251 | List records = new ArrayList(); 252 | records = JCSVUtils.readeCsv("./data/tempreture.csv"); 253 | double[] real = new double[records.size()]; 254 | for(int i = 0; i < records.size(); i++){ 255 | real[i] = Double.valueOf(records.get(i)[0]); 256 | } 257 | double alpha = 0.1, beta = 0.45, gamma = 0; 258 | int period = 365, m = 365; 259 | boolean debug = false; 260 | double[] predict = forecast(real, alpha, beta, gamma, period, m, debug); 261 | System.out.println("-----------predict----------------------------------"); 262 | for(int i = real.length; i < predict.length; i++){ 263 | System.out.println(predict[i]); 264 | } 265 | 266 | } 267 | } 268 | --------------------------------------------------------------------------------