weka.classifiers.timeseries.HoltWinters Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of timeseriesForecasting Show documentation
Show all versions of timeseriesForecasting Show documentation
Provides a time series forecasting environment for Weka. Includes a wrapper for Weka regression schemes that automates the process of creating lagged variables and date-derived periodic variables and provides the ability to do closed-loop forecasting. New evaluation routines are provided by a special evaluation module and graphing of predictions/forecasts are provided via the JFreeChart library. Includes both command-line and GUI user interfaces. Sample time series data can be found in ${WEKA_HOME}/packages/timeseriesForecasting/sample-data.
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* HoltWinters.java
* Copyright (C) 2010-2016 University of Waikato, Hamilton, New Zealand
*/
package weka.classifiers.timeseries;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import weka.classifiers.AbstractClassifier;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.Utils;
/**
* Class implementing the Holt-Winters triple exponential smoothing method for
* time series forecasting. Designed to be used in the Weka forecasting
* environment. Although lagged variables are not used by this method, for
* evaluation purposes within the framework the user is required to set a lag
* length that is at least 3 times that of the seasonal cycle length. This is
* because priming data (which is set by the framework to have as many instances
* as the lag length) is used to train the Holt-Winters forecaster. Two seasonal
* cycles worth of data are used to set the initial parameters of the method
* (primarily the seasonal components) and the remaining training data is used
* to smooth over.
*
* @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
* @version $Revision: $
*/
public class HoltWinters extends AbstractClassifier implements
PrimingDataLearner, OptionHandler, Serializable {
/**
* For serialization
*/
private static final long serialVersionUID = -4478856847945888899L;
/** Value smoothing parameter */
protected double m_alpha = 0.2;
/** Trend smoothing parameter */
protected double m_beta = 0.2;
/** Seasonal smoothing parameter */
protected double m_gamma = 0.2;
/** The length of the seasonal cycle */
protected int m_seasonCycleLength = 12; // monthly data (1 year)
protected boolean m_includeSeason = true;
protected boolean m_includeTrend = true;
/** The seasonal correction factors */
protected double[] m_c;
/** Used for setting initial estimates */
protected double[] m_twoSeasonsOfY;
/** % m_seasonCycleLength gives the position in the seasonal cycle */
protected int m_counter;
/**
* Holds the number of training points that were used to estimate the starting
* parameters of the model (i.e. initial smoothed value, trend estimate and
* seasonal correction factors)
*/
// protected int m_numTrainingPoints;
/** Used in computing the smoothed value */
protected double m_sPrev;
/** Used in computing the smoothed trend */
protected double m_bPrev;
/** Smoothed value estimate */
protected double m_s;
/** Trend */
protected double m_b;
/**
* Description of this forecaster.
*
* @return a description of this forecaster
*/
public String globalInfo() {
return "Class implementing the Holt-Winters triple exponential smoothing method. "
+ "Use the max lag length option in the time series forecasting "
+ "environment to control how many of the most recent training "
+ "data instances are used for estimating the initial parameters "
+ "and smoothing. If not specified manually, the max lag will be set "
+ "to 3 * seasonal cycle length";
}
@Override
public void reset() {
m_sPrev = Utils.missingValue();
m_bPrev = Utils.missingValue();
m_s = Utils.missingValue();
m_b = Utils.missingValue();
m_c = new double[m_seasonCycleLength];
m_twoSeasonsOfY = new double[2 * m_seasonCycleLength];
m_counter = 0;
// m_numTrainingPoints = 0;
}
/**
* Return the minimum number of training/priming data points required before a
* forecast can be made
*
* @return the minimum number of training/priming data points required
*/
@Override
public int getMinRequiredTrainingPoints() {
// two full seasons for setting the seasonal correction factors and
// one season for smoothing over.
return 3 * m_seasonCycleLength;
}
/**
* Update the smoothed estimates of value and trend
*
* @param value the point to update with
*/
protected void update(double value) {
// value
double seasonAdjust = m_includeSeason ? m_c[m_counter % m_seasonCycleLength]
: 1.0;
double trendAdjust = m_includeTrend ? m_bPrev : 0.0;
m_s = (m_alpha * value / seasonAdjust)
+ ((1 - m_alpha) * (m_sPrev + trendAdjust));
// trend
if (m_includeTrend) {
m_b = (m_beta * (m_s - m_sPrev)) + ((1.0 - m_beta) * m_bPrev);
}
// season
if (m_includeSeason) {
m_c[m_counter % m_seasonCycleLength] = (m_gamma * value / m_s)
+ ((1.0 - m_gamma) * m_c[m_counter % m_seasonCycleLength]);
}
m_counter++; // increment to the next time step
// System.out.println("" + (m_counter + 0) + " " + (m_s + m_b)
// * m_c[(m_counter + 0) % m_seasonCycleLength]);
m_sPrev = m_s;
m_bPrev = m_b;
}
/**
* Initializes the value, trend and seasonal correction factors estimations
* once we have seen enough data for the seasonal correction factors
*/
protected void initialize() {
double[] obsFirstYear = new double[m_seasonCycleLength];
System.arraycopy(m_twoSeasonsOfY, 0, obsFirstYear, 0, m_seasonCycleLength);
double sum = Utils.sum(obsFirstYear);
m_sPrev = sum / m_seasonCycleLength;
double[] obsSecondYear = new double[m_seasonCycleLength];
System.arraycopy(m_twoSeasonsOfY, m_seasonCycleLength, obsSecondYear, 0,
m_seasonCycleLength);
sum = Utils.sum(obsSecondYear);
double avgSecondYear = sum / m_seasonCycleLength;
m_bPrev = (m_sPrev - avgSecondYear) / m_seasonCycleLength;
// initialize the seasonal correction factors
for (int i = 1; i <= m_seasonCycleLength; i++) {
m_c[i - 1] = (m_twoSeasonsOfY[i - 1] - (i - 1) * m_bPrev / 2) / m_sPrev;
}
}
/**
* Update the smoothed estimates using the supplied value
*
* @param primingOrPredictedTargetValue the value to update with
*/
@Override
public void updateForecaster(double primingOrPredictedTargetValue) {
if (m_counter < 2 * m_seasonCycleLength) {
if (!Utils.isMissingValue(primingOrPredictedTargetValue)) {
m_twoSeasonsOfY[m_counter++] = primingOrPredictedTargetValue;
}
return;
}
if (m_counter == 2 * m_seasonCycleLength) {
// initialize
initialize();
}
if (m_counter >= 2 * m_seasonCycleLength) {
if (!Utils.isMissingValue(primingOrPredictedTargetValue)) {
// update
update(primingOrPredictedTargetValue);
// m_counter++; // increment to the next time step
}
}
}
/**
* Generates a one-step ahead forecast. Clients should follow this with a call
* to updateForecaster() and pass in the forecasted value if they want to
* generate further projected values (i.e. beyond one-step ahead).
*
* @throws Exception if forecast can't be produced
* @return a one-step ahead forecast
*/
public double forecast() throws Exception {
if (m_counter < m_seasonCycleLength * 2) {
throw new Exception(
"Haven't seen enough training data to make a forecast yet");
}
// we use counter % season here because the last training update has
// already incremented counter to the next time step
double result = (m_s + (m_includeTrend ? m_b : 0.0))
* (m_includeSeason ? m_c[m_counter % m_seasonCycleLength] : 1.0);
return result;
}
@Override
public void buildClassifier(Instances data) throws Exception {
reset();
for (int i = 0; i < data.numInstances(); i++) {
updateForecaster(data.instance(i).classValue());
}
}
@Override
public double classifyInstance(Instance inst) throws Exception {
return forecast();
}
@Override
public Enumeration