All Downloads are FREE. Search and download functionalities are using the official Maven repository.

weka.classifiers.timeseries.HoltWinters Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.1.27
Show newest version
/*
 *   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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy