holt_winters 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +76 -0
- data/ext/holtWinters.java +244 -0
- data/ext/holt_winters.c +148 -0
- data/ext/holt_winters_R_language.c +93 -0
- data/holt_winters.gemspec +22 -0
- data/lib/holt_winters.rb +135 -0
- data/lib/holt_winters/version.rb +3 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NWE3MWEzMWQ1MDFiOTRkYmMzMDM5ZWMzMjZiYTZlODMyYjI5NDdlNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmU3OWMxNWIwNTI4OWFmZTc5MzllZTBlODU3YTI4NzMwNzA0NWI3MQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzRlNjA0MmE4MjEzZjI0ZDg1ZTUyYTk0OTEzNTllODJmYzBlODY2NTE1OTlk
|
10
|
+
ZTgxY2JmNjQ5MjYzMWIyMmJkZDg3ZWE1MzkwOWE1NGM0M2E2MmNmZWMxZTk0
|
11
|
+
MmM2NWRjOWZhNGQyOGFjYmYxNjMxYTM5NjM0YWRjMGI4ZGI3MmY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjE2MzYwNGQxZjI4MTMxNDk4NDg0NTExMjg2ZDVmZTYyNmEzOGVjZTRmODcz
|
14
|
+
YmQzZmZmYjJjNWI2NzA0NmM2NDFkYTFiZjMyNmM5OTg5YzQ3YzY3MDJjNGVm
|
15
|
+
YWE0N2IxZjNjODc4ZjQzNmY1ZGExYjg0MzFkNzYxNDM1NjE4ZDI=
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT-License)
|
2
|
+
|
3
|
+
Copyright (c) 2011 Brandon Keene
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Holt-Winters Triple Exponential Smoothing Algorithm
|
2
|
+
|
3
|
+
A Ruby port of Nishant Chandra's
|
4
|
+
[Java implementation](http://n-chandra.blogspot.com/2011/04/holt-winters-triple-exponential.html)
|
5
|
+
of the Holt-Winters smoothing algorithm.
|
6
|
+
|
7
|
+
![Algorithm](http://www.itl.nist.gov/div898/handbook/pmc/section4/eqns/ts26.gif)
|
8
|
+
|
9
|
+
The equations are intended to give more weight to recent observations and less weights to observations further in the past.
|
10
|
+
These weights are geometrically decreasing by a constant ratio.
|
11
|
+
|
12
|
+
# Usage
|
13
|
+
|
14
|
+
## forecast()
|
15
|
+
|
16
|
+
It calculates the initial values and returns the forecast for __m__ periods.
|
17
|
+
|
18
|
+
# y Time series array
|
19
|
+
# alpha Level smoothing coefficient
|
20
|
+
# beta Trend smoothing coefficient (increasing beta tightens fit)
|
21
|
+
# gamma Seasonal smoothing coefficient
|
22
|
+
# period A complete season's data consists of L periods. And we need
|
23
|
+
# to estimate the trend factor from one period to the next. To
|
24
|
+
# accomplish this, it is advisable to use two complete seasons;
|
25
|
+
# that is, 2L periods.
|
26
|
+
# m Extrapolated future data points
|
27
|
+
# - 4 quarterly
|
28
|
+
# - 7 weekly
|
29
|
+
# - 12 monthly
|
30
|
+
def forecast(y, alpha, beta, gamma, period, m)
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
## Example
|
36
|
+
|
37
|
+
This will generate a several variations of beta for a simple line:
|
38
|
+
|
39
|
+
require 'holt_winters'
|
40
|
+
|
41
|
+
x = (0..128).to_a
|
42
|
+
puts x.join(',')
|
43
|
+
puts HoltWinters.forecast(x, 0.5, 0, 0, 12, 2).join(',')
|
44
|
+
puts HoltWinters.forecast(x, 0.5, 0.25, 0, 12, 2).join(',')
|
45
|
+
puts HoltWinters.forecast(x, 0.5, 0.5, 0, 12, 2).join(',')
|
46
|
+
puts HoltWinters.forecast(x, 0.5, 0.75, 0, 12, 2).join(',')
|
47
|
+
puts HoltWinters.forecast(x, 0.5, 1.0, 0, 12, 2).join(',')
|
48
|
+
|
49
|
+
Try plotting the different lines to see how beta affects the forecast:
|
50
|
+
|
51
|
+
![Chart](http://www.itl.nist.gov/div898/handbook/pmc/section4/gifs/tseries6.gif)
|
52
|
+
|
53
|
+
# License
|
54
|
+
|
55
|
+
(The MIT-License)
|
56
|
+
|
57
|
+
Copyright (c) 2011 Brandon Keene
|
58
|
+
|
59
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
60
|
+
a copy of this software and associated documentation files (the
|
61
|
+
"Software"), to deal in the Software without restriction, including
|
62
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
63
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
64
|
+
permit persons to whom the Software is furnished to do so, subject to
|
65
|
+
the following conditions:
|
66
|
+
|
67
|
+
The above copyright notice and this permission notice shall be
|
68
|
+
included in all copies or substantial portions of the Software.
|
69
|
+
|
70
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
71
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
72
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
73
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
74
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
75
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
76
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,244 @@
|
|
1
|
+
package com.fr.tsa;
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Copyright 2011 Nishant Chandra
|
5
|
+
*
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
* you may not use this file except in compliance with the License.
|
8
|
+
* You may obtain a copy of the License at
|
9
|
+
*
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
*
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
* See the License for the specific language governing permissions and
|
16
|
+
* limitations under the License.
|
17
|
+
*/
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Given a time series, say a complete monthly data for 12 months, the Holt-Winters smoothing and forecasting
|
21
|
+
* technique is built on the following formulae (multiplicative version):
|
22
|
+
*
|
23
|
+
* St[i] = alpha * y[i] / It[i - period] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1])
|
24
|
+
* Bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * Bt[i - 1]
|
25
|
+
* It[i] = beta * y[i] / St[i] + (1.0 - beta) * It[i - period]
|
26
|
+
* Ft[i + m] = (St[i] + (m * Bt[i])) * It[i - period + m]
|
27
|
+
*
|
28
|
+
* Note: Many authors suggest calculating initial values of St, Bt and It in a variety of ways, but
|
29
|
+
* some of them are incorrect e.g. determination of It parameter using regression. I have used
|
30
|
+
* the NIST recommended methods.
|
31
|
+
*
|
32
|
+
* For more details, see:
|
33
|
+
* http://adorio-research.org/wordpress/?p=1230
|
34
|
+
* http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
|
35
|
+
*
|
36
|
+
* @author Nishant Chandra
|
37
|
+
*
|
38
|
+
*/
|
39
|
+
public class HoltWintersTripleExponentialImpl {
|
40
|
+
|
41
|
+
/**
|
42
|
+
* This method is the entry point. It calculates the initial values and returns the forecast
|
43
|
+
* for the m periods.
|
44
|
+
*
|
45
|
+
* @param y - Time series data.
|
46
|
+
* @param alpha - Exponential smoothing coefficients for level, trend, seasonal components.
|
47
|
+
* @param beta - Exponential smoothing coefficients for level, trend, seasonal components.
|
48
|
+
* @param gamma - Exponential smoothing coefficients for level, trend, seasonal components.
|
49
|
+
* @param perdiod - A complete season's data consists of L periods. And we need to estimate
|
50
|
+
* the trend factor from one period to the next. To accomplish this, it is advisable to use
|
51
|
+
* two complete seasons; that is, 2L periods.
|
52
|
+
* @param m - Extrapolated future data points.
|
53
|
+
* @param debug - Print debug values. Useful for testing.
|
54
|
+
*
|
55
|
+
* 4 quarterly
|
56
|
+
* 7 weekly.
|
57
|
+
* 12 monthly
|
58
|
+
*/
|
59
|
+
public static double[] forecast(int[] y, double alpha, double beta,
|
60
|
+
double gamma, int period, int m, boolean debug) {
|
61
|
+
|
62
|
+
if (y == null) {
|
63
|
+
return null;
|
64
|
+
}
|
65
|
+
|
66
|
+
int seasons = y.length / period;
|
67
|
+
double a0 = calculateInitialLevel(y, period);
|
68
|
+
double b0 = calculateInitialTrend(y, period);
|
69
|
+
double[] initialSeasonalIndices = calculateSeasonalIndices(y, period, seasons);
|
70
|
+
|
71
|
+
if (debug) {
|
72
|
+
System.out.println(String.format(
|
73
|
+
"Total observations: %d, Seasons %d, Periods %d", y.length,
|
74
|
+
seasons, period));
|
75
|
+
System.out.println("Initial level value a0: " + a0);
|
76
|
+
System.out.println("Initial trend value b0: " + b0);
|
77
|
+
printArray("Seasonal Indices: ", initialSeasonalIndices);
|
78
|
+
}
|
79
|
+
|
80
|
+
double[] forecast = calculateHoltWinters(y, a0, b0, alpha, beta, gamma,
|
81
|
+
initialSeasonalIndices, period, m, debug);
|
82
|
+
|
83
|
+
if (debug) {
|
84
|
+
printArray("Forecast", forecast);
|
85
|
+
}
|
86
|
+
|
87
|
+
return forecast;
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* This method realizes the Holt-Winters equations.
|
92
|
+
*
|
93
|
+
* @param y
|
94
|
+
* @param a0
|
95
|
+
* @param b0
|
96
|
+
* @param alpha
|
97
|
+
* @param beta
|
98
|
+
* @param gamma
|
99
|
+
* @param initialSeasonalIndices
|
100
|
+
* @param period
|
101
|
+
* @param m
|
102
|
+
* @param debug
|
103
|
+
* @return - Forecast for m periods.
|
104
|
+
*/
|
105
|
+
private static double[] calculateHoltWinters(int[] y, double a0, double b0, double alpha,
|
106
|
+
double beta, double gamma, double[] initialSeasonalIndices, int period, int m, boolean debug) {
|
107
|
+
|
108
|
+
double[] St = new double[y.length];
|
109
|
+
double[] Bt = new double[y.length];
|
110
|
+
double[] It = new double[y.length];
|
111
|
+
double[] Ft = new double[y.length + m];
|
112
|
+
|
113
|
+
//Initialize base values
|
114
|
+
St[1] = a0;
|
115
|
+
Bt[1] = b0;
|
116
|
+
|
117
|
+
for (int i = 0; i < period; i++) {
|
118
|
+
It[i] = initialSeasonalIndices[i];
|
119
|
+
}
|
120
|
+
|
121
|
+
Ft[m] = (St[0] + (m * Bt[0])) * It[0];//This is actually 0 since Bt[0] = 0
|
122
|
+
Ft[m + 1] = (St[1] + (m * Bt[1])) * It[1];//Forecast starts from period + 2
|
123
|
+
|
124
|
+
//Start calculations
|
125
|
+
for (int i = 2; i < y.length; i++) {
|
126
|
+
|
127
|
+
//Calculate overall smoothing
|
128
|
+
if((i - period) >= 0) {
|
129
|
+
St[i] = alpha * y[i] / It[i - period] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]);
|
130
|
+
} else {
|
131
|
+
St[i] = alpha * y[i] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1]);
|
132
|
+
}
|
133
|
+
|
134
|
+
//Calculate trend smoothing
|
135
|
+
Bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * Bt[i - 1];
|
136
|
+
|
137
|
+
//Calculate seasonal smoothing
|
138
|
+
if((i - period) >= 0) {
|
139
|
+
It[i] = beta * y[i] / St[i] + (1.0 - beta) * It[i - period];
|
140
|
+
}
|
141
|
+
|
142
|
+
//Calculate forecast
|
143
|
+
if( ((i + m) >= period) ){
|
144
|
+
Ft[i + m] = (St[i] + (m * Bt[i])) * It[i - period + m];
|
145
|
+
}
|
146
|
+
|
147
|
+
if(debug){
|
148
|
+
System.out.println(String.format(
|
149
|
+
"i = %d, y = %d, S = %f, Bt = %f, It = %f, F = %f", i,
|
150
|
+
y[i], St[i], Bt[i], It[i], Ft[i]));
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
return Ft;
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* See: http://robjhyndman.com/researchtips/hw-initialization/
|
159
|
+
* 1st period's average can be taken. But y[0] works better.
|
160
|
+
*
|
161
|
+
* @return - Initial Level value i.e. St[1]
|
162
|
+
*/
|
163
|
+
private static double calculateInitialLevel(int[] y, int period) {
|
164
|
+
|
165
|
+
/**
|
166
|
+
double sum = 0;
|
167
|
+
|
168
|
+
for (int i = 0; i < period; i++) {
|
169
|
+
sum += y[i];
|
170
|
+
}
|
171
|
+
|
172
|
+
return sum / period;
|
173
|
+
**/
|
174
|
+
return y[0];
|
175
|
+
}
|
176
|
+
|
177
|
+
/**
|
178
|
+
* See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
|
179
|
+
*
|
180
|
+
* @return - Initial trend - Bt[1]
|
181
|
+
*/
|
182
|
+
private static double calculateInitialTrend(int[] y, int period){
|
183
|
+
|
184
|
+
double sum = 0;
|
185
|
+
|
186
|
+
for (int i = 0; i < period; i++) {
|
187
|
+
sum += (y[period + i] - y[i]);
|
188
|
+
}
|
189
|
+
|
190
|
+
return sum / (period * period);
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
|
195
|
+
*
|
196
|
+
* @return - Seasonal Indices.
|
197
|
+
*/
|
198
|
+
private static double[] calculateSeasonalIndices(int[] y, int period, int seasons){
|
199
|
+
|
200
|
+
double[] seasonalAverage = new double[seasons];
|
201
|
+
double[] seasonalIndices = new double[period];
|
202
|
+
|
203
|
+
double[] averagedObservations = new double[y.length];
|
204
|
+
|
205
|
+
for (int i = 0; i < seasons; i++) {
|
206
|
+
for (int j = 0; j < period; j++) {
|
207
|
+
seasonalAverage[i] += y[(i * period) + j];
|
208
|
+
}
|
209
|
+
seasonalAverage[i] /= period;
|
210
|
+
}
|
211
|
+
|
212
|
+
for (int i = 0; i < seasons; i++) {
|
213
|
+
for (int j = 0; j < period; j++) {
|
214
|
+
averagedObservations[(i * period) + j] = y[(i * period) + j] / seasonalAverage[i];
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
for (int i = 0; i < period; i++) {
|
219
|
+
for (int j = 0; j < seasons; j++) {
|
220
|
+
seasonalIndices[i] += averagedObservations[(j * period) + i];
|
221
|
+
}
|
222
|
+
seasonalIndices[i] /= seasons;
|
223
|
+
}
|
224
|
+
|
225
|
+
return seasonalIndices;
|
226
|
+
}
|
227
|
+
|
228
|
+
/**
|
229
|
+
* Utility method to pring array values.
|
230
|
+
*
|
231
|
+
* @param description
|
232
|
+
* @param data
|
233
|
+
*/
|
234
|
+
private static void printArray(String description, double[] data){
|
235
|
+
|
236
|
+
System.out.println(String.format("******************* %s *********************", description));
|
237
|
+
|
238
|
+
for (int i = 0; i < data.length; i++) {
|
239
|
+
System.out.println(data[i]);
|
240
|
+
}
|
241
|
+
|
242
|
+
System.out.println(String.format("*****************************************************************", description));
|
243
|
+
}
|
244
|
+
}
|
data/ext/holt_winters.c
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
#include <string.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Based on th R implementation
|
7
|
+
*
|
8
|
+
* a: level component
|
9
|
+
* b: trend component
|
10
|
+
* s: seasonal component
|
11
|
+
*
|
12
|
+
* Additive:
|
13
|
+
*
|
14
|
+
* Yhat[t+h] = a[t] + h * b[t] + s[t + 1 + (h - 1) mod p],
|
15
|
+
* a[t] = α (Y[t] - s[t-p]) + (1-α) (a[t-1] + b[t-1])
|
16
|
+
* b[t] = β (a[t] - a[t-1]) + (1-β) b[t-1]
|
17
|
+
* s[t] = γ (Y[t] - a[t]) + (1-γ) s[t-p]
|
18
|
+
*
|
19
|
+
* Multiplicative:
|
20
|
+
*
|
21
|
+
* Yhat[t+h] = (a[t] + h * b[t]) * s[t + 1 + (h - 1) mod p],
|
22
|
+
* a[t] = α (Y[t] / s[t-p]) + (1-α) (a[t-1] + b[t-1])
|
23
|
+
* b[t] = β (a[t] - a[t-1]) + (1-β) b[t-1]
|
24
|
+
* s[t] = γ (Y[t] / a[t]) + (1-γ) s[t-p]
|
25
|
+
*/
|
26
|
+
void HoltWinters (
|
27
|
+
double *x,
|
28
|
+
int *xl, // Time t + h
|
29
|
+
double *alpha, // alpha parameter of Holt-Winters Filter.
|
30
|
+
double *beta, // beta parameter of Holt-Winters Filter. If set to 0, the function will do exponential smoothing.
|
31
|
+
double *gamma, // gamma parameter used for the seasonal component. If set to 0, an non-seasonal model is fitted.
|
32
|
+
int *start_time, // Time t
|
33
|
+
int *seasonal,
|
34
|
+
int *period,
|
35
|
+
double *a, // Start value for level (a[0]).
|
36
|
+
double *b, // Start value for trend (b[0]).
|
37
|
+
double *s, // Vector of start values for the seasonal component (s_1[0] ... s_p[0])
|
38
|
+
|
39
|
+
/* return values */
|
40
|
+
double *SSE, // The final sum of squared errors achieved in optimizing
|
41
|
+
double *level, // Estimated values for the level component (size xl - t + 1)
|
42
|
+
double *trend, // Estimated values for the trend component (size xl - t + 1)
|
43
|
+
double *season // Estimated values for the seasonal component (size xl - t + 1)
|
44
|
+
)
|
45
|
+
|
46
|
+
{
|
47
|
+
double res = 0, xhat = 0, stmp = 0;
|
48
|
+
int i, i0, s0;
|
49
|
+
|
50
|
+
/* copy start values to the beginning of the vectors */
|
51
|
+
level[0] = *a;
|
52
|
+
if (*beta > 0) trend[0] = *b;
|
53
|
+
if (*gamma > 0) memcpy(season, s, *period * sizeof(double));
|
54
|
+
|
55
|
+
for (i = *start_time - 1; i < *xl; i++) {
|
56
|
+
/* indices for period i */
|
57
|
+
i0 = i - *start_time + 2;
|
58
|
+
s0 = i0 + *period - 1;
|
59
|
+
|
60
|
+
/* forecast *for* period i */
|
61
|
+
xhat = level[i0 - 1] + (*beta > 0 ? trend[i0 - 1] : 0);
|
62
|
+
stmp = *gamma > 0 ? season[s0 - *period] : (*seasonal != 1);
|
63
|
+
if (*seasonal == 1)
|
64
|
+
xhat += stmp;
|
65
|
+
else
|
66
|
+
xhat *= stmp;
|
67
|
+
|
68
|
+
/* Sum of Squared Errors */
|
69
|
+
res = x[i] - xhat;
|
70
|
+
*SSE += res * res;
|
71
|
+
|
72
|
+
/* estimate of level *in* period i */
|
73
|
+
if (*seasonal == 1)
|
74
|
+
level[i0] = *alpha * (x[i] - stmp)
|
75
|
+
+ (1 - *alpha) * (level[i0 - 1] + trend[i0 - 1]);
|
76
|
+
else
|
77
|
+
level[i0] = *alpha * (x[i] / stmp)
|
78
|
+
+ (1 - *alpha) * (level[i0 - 1] + trend[i0 - 1]);
|
79
|
+
|
80
|
+
/* estimate of trend *in* period i */
|
81
|
+
if (*beta > 0)
|
82
|
+
trend[i0] = *beta * (level[i0] - level[i0 - 1])
|
83
|
+
+ (1 - *beta) * trend[i0 - 1];
|
84
|
+
|
85
|
+
/* estimate of seasonal component *in* period i */
|
86
|
+
if (*gamma > 0) {
|
87
|
+
if (*seasonal == 1)
|
88
|
+
season[s0] = *gamma * (x[i] - level[i0])
|
89
|
+
+ (1 - *gamma) * stmp;
|
90
|
+
else
|
91
|
+
season[s0] = *gamma * (x[i] / level[i0])
|
92
|
+
+ (1 - *gamma) * stmp;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
int main() {
|
98
|
+
// US population in millions
|
99
|
+
double series[] = {3.93, 5.31, 7.24, 9.64, 12.90, 17.10, 23.20, 31.40, 39.80, 50.20, 62.90, 76.00, 92.00, 105.70, 122.80, 131.70, 151.30, 179.30, 203.20};
|
100
|
+
|
101
|
+
int forecast = 19;
|
102
|
+
double alpha = 0.9999208;
|
103
|
+
double beta = 0;
|
104
|
+
double gamma = 0;
|
105
|
+
int start_time = 2;
|
106
|
+
int seasonal = 0;
|
107
|
+
int period = 0;
|
108
|
+
double a0 = series[0];
|
109
|
+
double b0 = 0;
|
110
|
+
double s[] = {};
|
111
|
+
|
112
|
+
double errors;
|
113
|
+
int nb_computations = forecast - start_time - 1;
|
114
|
+
double *estimated_level = malloc(nb_computations * sizeof(double));
|
115
|
+
double *estimated_trend = malloc(nb_computations * sizeof(double));
|
116
|
+
double *estimated_season = malloc(nb_computations * sizeof(double));
|
117
|
+
|
118
|
+
HoltWinters(
|
119
|
+
series,
|
120
|
+
&forecast,
|
121
|
+
&alpha,
|
122
|
+
&beta,
|
123
|
+
&gamma,
|
124
|
+
&start_time,
|
125
|
+
&seasonal,
|
126
|
+
&period,
|
127
|
+
&a0,
|
128
|
+
&b0,
|
129
|
+
s,
|
130
|
+
&errors,
|
131
|
+
estimated_level,
|
132
|
+
estimated_trend,
|
133
|
+
estimated_season
|
134
|
+
);
|
135
|
+
|
136
|
+
int i = 0;
|
137
|
+
int first_year = 1800;
|
138
|
+
printf("Estimated:\n");
|
139
|
+
for (i = 0; i < nb_computations; i++) {
|
140
|
+
printf("\tyear = %d, level: %f, trend: %f\n", first_year + i * 10, estimated_level[i], estimated_trend[i]);
|
141
|
+
}
|
142
|
+
|
143
|
+
free(estimated_level);
|
144
|
+
free(estimated_trend);
|
145
|
+
free(estimated_season);
|
146
|
+
|
147
|
+
return 0;
|
148
|
+
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
/* R : A Computer Language for Statistical Data Analysis
|
2
|
+
*
|
3
|
+
* Copyright (C) 2003-7 The R Development Core Team
|
4
|
+
*
|
5
|
+
* This program is free software; you can redistribute it and/or modify
|
6
|
+
* it under the terms of the GNU General Public License as published by
|
7
|
+
* the Free Software Foundation; either version 2 of the License, or
|
8
|
+
* (at your option) any later version.
|
9
|
+
*
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
* GNU General Public License for more details.
|
14
|
+
*
|
15
|
+
* You should have received a copy of the GNU General Public License
|
16
|
+
* along with this program; if not, a copy is available at
|
17
|
+
* http://www.r-project.org/Licenses/.
|
18
|
+
*/
|
19
|
+
|
20
|
+
#include <R.h>
|
21
|
+
#include "ts.h"
|
22
|
+
#include <stdlib.h>
|
23
|
+
|
24
|
+
void HoltWinters (
|
25
|
+
double *x,
|
26
|
+
int *xl,
|
27
|
+
double *alpha,
|
28
|
+
double *beta,
|
29
|
+
double *gamma,
|
30
|
+
int *start_time,
|
31
|
+
int *seasonal,
|
32
|
+
int *period,
|
33
|
+
double *a,
|
34
|
+
double *b,
|
35
|
+
double *s,
|
36
|
+
|
37
|
+
/* return values */
|
38
|
+
double *SSE,
|
39
|
+
double *level,
|
40
|
+
double *trend,
|
41
|
+
double *season
|
42
|
+
)
|
43
|
+
|
44
|
+
{
|
45
|
+
double res = 0, xhat = 0, stmp = 0;
|
46
|
+
int i, i0, s0;
|
47
|
+
|
48
|
+
/* copy start values to the beginning of the vectors */
|
49
|
+
level[0] = *a;
|
50
|
+
if (*beta > 0) trend[0] = *b;
|
51
|
+
if (*gamma > 0) memcpy(season, s, *period * sizeof(double));
|
52
|
+
|
53
|
+
for (i = *start_time - 1; i < *xl; i++) {
|
54
|
+
/* indices for period i */
|
55
|
+
i0 = i - *start_time + 2;
|
56
|
+
s0 = i0 + *period - 1;
|
57
|
+
|
58
|
+
/* forecast *for* period i */
|
59
|
+
xhat = level[i0 - 1] + (*beta > 0 ? trend[i0 - 1] : 0);
|
60
|
+
stmp = *gamma > 0 ? season[s0 - *period] : (*seasonal != 1);
|
61
|
+
if (*seasonal == 1)
|
62
|
+
xhat += stmp;
|
63
|
+
else
|
64
|
+
xhat *= stmp;
|
65
|
+
|
66
|
+
/* Sum of Squared Errors */
|
67
|
+
res = x[i] - xhat;
|
68
|
+
*SSE += res * res;
|
69
|
+
|
70
|
+
/* estimate of level *in* period i */
|
71
|
+
if (*seasonal == 1)
|
72
|
+
level[i0] = *alpha * (x[i] - stmp)
|
73
|
+
+ (1 - *alpha) * (level[i0 - 1] + trend[i0 - 1]);
|
74
|
+
else
|
75
|
+
level[i0] = *alpha * (x[i] / stmp)
|
76
|
+
+ (1 - *alpha) * (level[i0 - 1] + trend[i0 - 1]);
|
77
|
+
|
78
|
+
/* estimate of trend *in* period i */
|
79
|
+
if (*beta > 0)
|
80
|
+
trend[i0] = *beta * (level[i0] - level[i0 - 1])
|
81
|
+
+ (1 - *beta) * trend[i0 - 1];
|
82
|
+
|
83
|
+
/* estimate of seasonal component *in* period i */
|
84
|
+
if (*gamma > 0) {
|
85
|
+
if (*seasonal == 1)
|
86
|
+
season[s0] = *gamma * (x[i] - level[i0])
|
87
|
+
+ (1 - *gamma) * stmp;
|
88
|
+
else
|
89
|
+
season[s0] = *gamma * (x[i] / level[i0])
|
90
|
+
+ (1 - *gamma) * stmp;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "holt_winters/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "holt_winters"
|
7
|
+
s.version = HoltWinters::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Brandon Keene"]
|
10
|
+
s.email = ["bkeene@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Holt-Winters Triple Exponential Smoothing}
|
13
|
+
|
14
|
+
s.rubyforge_project = "holt_winters"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec", "~> 2.6.0"
|
22
|
+
end
|
data/lib/holt_winters.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# Given a time series, say a complete monthly data for 12 months, the Holt-Winters smoothing and forecasting
|
2
|
+
# technique is built on the following formulae (multiplicative version):
|
3
|
+
#
|
4
|
+
# St[i] = alpha * y[i] / It[i - period] + (1.0 - alpha) * (St[i - 1] + Bt[i - 1])
|
5
|
+
# Bt[i] = gamma * (St[i] - St[i - 1]) + (1 - gamma) * Bt[i - 1]
|
6
|
+
# It[i] = beta * y[i] / St[i] + (1.0 - beta) * It[i - period]
|
7
|
+
# Ft[i + m] = (St[i] + (m * Bt[i])) * It[i - period + m]
|
8
|
+
#
|
9
|
+
# Note: Many authors suggest calculating initial values of St, Bt and It in a variety of ways, but
|
10
|
+
# some of them are incorrect e.g. determination of It parameter using regression. I have used
|
11
|
+
# the NIST recommended methods.
|
12
|
+
#
|
13
|
+
# For more details, see:
|
14
|
+
# http://adorio-research.org/wordpress/?p=1230
|
15
|
+
# http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
|
16
|
+
#
|
17
|
+
module HoltWinters
|
18
|
+
class << self
|
19
|
+
# Calculate initial values and return the forecast for m periods.
|
20
|
+
#
|
21
|
+
# y Time series array
|
22
|
+
# alpha Level smoothing coefficient
|
23
|
+
# beta Trend smoothing coefficient (increasing beta tightens fit)
|
24
|
+
# gamma Seasonal smoothing coefficient
|
25
|
+
# period A complete season's data consists of L periods. And we need
|
26
|
+
# to estimate the trend factor from one period to the next. To
|
27
|
+
# accomplish this, it is advisable to use two complete seasons;
|
28
|
+
# that is, 2L periods.
|
29
|
+
# m Extrapolated future data points
|
30
|
+
# - 4 quarterly
|
31
|
+
# - 7 weekly
|
32
|
+
# - 12 monthly
|
33
|
+
#
|
34
|
+
def forecast(y, alpha, beta, gamma, period, m)
|
35
|
+
return nil if y.empty?
|
36
|
+
|
37
|
+
seasons = y.size / period
|
38
|
+
a0 = initial_level(y, period)
|
39
|
+
b0 = initial_trend(y, period)
|
40
|
+
|
41
|
+
seasonal = seasonal_indicies(y, period, seasons)
|
42
|
+
|
43
|
+
holt_winters(y, a0, b0, alpha, beta, gamma, seasonal, period, m);
|
44
|
+
end
|
45
|
+
|
46
|
+
def holt_winters(y, a0, b0, alpha, beta, gamma, seasonal, period, m)
|
47
|
+
st = Array.new(y.length, 0.0)
|
48
|
+
bt = Array.new(y.length, 0.0)
|
49
|
+
it = Array.new(y.length, 0.0)
|
50
|
+
ft = Array.new(y.length + m, 0.0)
|
51
|
+
|
52
|
+
st[1] = a0
|
53
|
+
bt[1] = b0
|
54
|
+
|
55
|
+
(0..period - 1).each do |i|
|
56
|
+
it[i] = seasonal[i]
|
57
|
+
end
|
58
|
+
|
59
|
+
ft[m] = (st[0] + (m * bt[0])) * it[0] # This is actually 0 since bt[0] = 0
|
60
|
+
ft[m + 1] = (st[1] + (m * bt[1])) * it[1] # Forecast starts from period + 2
|
61
|
+
|
62
|
+
(2..(y.size - 1)).each do |i|
|
63
|
+
# Calculate overall smoothing
|
64
|
+
if (i - period) >= 0
|
65
|
+
st[i] = alpha * y[i] / it[i - period] + (1.0 - alpha) * (st[i - 1] + bt[i - 1])
|
66
|
+
else
|
67
|
+
st[i] = alpha * y[i] + (1.0 - alpha) * (st[i - 1] + bt[i - 1])
|
68
|
+
end
|
69
|
+
|
70
|
+
# Calculate trend smoothing
|
71
|
+
bt[i] = gamma * (st[i] - st[i - 1]) + (1 - gamma) * bt[i - 1]
|
72
|
+
|
73
|
+
# Calculate seasonal smoothing
|
74
|
+
if (i - period) >= 0
|
75
|
+
it[i] = beta * y[i] / st[i] + (1.0 - beta) * it[i - period]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Calculate forecast
|
79
|
+
if (i + m) >= period
|
80
|
+
ft[i + m] = (st[i] + (m * bt[i])) * it[i - period + m]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
ft
|
85
|
+
end
|
86
|
+
|
87
|
+
# See: http://robjhyndman.com/researchtips/hw-initialization/
|
88
|
+
# 1st period's average can be taken. But y[0] works better.
|
89
|
+
def initial_level(y, period)
|
90
|
+
y.first
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
# See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
|
95
|
+
def initial_trend(y, period)
|
96
|
+
sum = 0
|
97
|
+
|
98
|
+
(0..period - 1).each do |i|
|
99
|
+
sum += (y[period + i] - y[i])
|
100
|
+
end
|
101
|
+
|
102
|
+
sum / (period * period)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# See: http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm
|
107
|
+
def seasonal_indicies(y, period, seasons)
|
108
|
+
seasonal_average = Array.new(seasons, 0.0)
|
109
|
+
seasonal_indices = Array.new(period, 0.0)
|
110
|
+
averaged_observations = Array.new(y.size, 0.0)
|
111
|
+
|
112
|
+
(0..seasons - 1).each do |i|
|
113
|
+
(0..period - 1).each do |j|
|
114
|
+
seasonal_average[i] += y[(i * period) + j]
|
115
|
+
end
|
116
|
+
seasonal_average[i] /= period
|
117
|
+
end
|
118
|
+
|
119
|
+
(0..seasons - 1).each do |i|
|
120
|
+
(0..period - 1).each do |j|
|
121
|
+
averaged_observations[(i * period) + j] = y[(i * period) + j] / seasonal_average[i]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
(0..period - 1).each do |i|
|
126
|
+
(0..seasons - 1).each do |j|
|
127
|
+
seasonal_indices[i] += averaged_observations[(j * period) + i]
|
128
|
+
end
|
129
|
+
seasonal_indices[i] /= seasons
|
130
|
+
end
|
131
|
+
|
132
|
+
seasonal_indices
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: holt_winters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brandon Keene
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.6.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.6.0
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- bkeene@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- Gemfile
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- ext/holtWinters.java
|
38
|
+
- ext/holt_winters.c
|
39
|
+
- ext/holt_winters_R_language.c
|
40
|
+
- holt_winters.gemspec
|
41
|
+
- lib/holt_winters.rb
|
42
|
+
- lib/holt_winters/version.rb
|
43
|
+
homepage: ''
|
44
|
+
licenses: []
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project: holt_winters
|
62
|
+
rubygems_version: 2.0.7
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Holt-Winters Triple Exponential Smoothing
|
66
|
+
test_files: []
|