/** Returns the seasonal index for the given time period. */
  private double getSeasonalIndex(double time) throws IllegalArgumentException {
    // TODO: Optimize this search by having data set sorted by time

    // Handle initial conditions for seasonal index
    if (time < getMinimumTimeValue() + (NUMBER_OF_YEARS - 1) * periodsPerYear - TOLERANCE)
      return getSeasonalIndex(time + periodsPerYear * getTimeInterval());

    // Search for previously calculated - and saved - seasonal index
    String timeVariable = getTimeVariable();
    Iterator it = seasonalIndex.iterator();
    while (it.hasNext()) {
      DataPoint dp = (DataPoint) it.next();
      double dpTimeValue = dp.getIndependentValue(timeVariable);
      if (Math.abs(time - dpTimeValue) < TOLERANCE) return dp.getDependentValue();
    }

    // Saved seasonal index not found, so calculate it
    //  (and save it for future reference)
    double previousYear = time - getTimeInterval() * periodsPerYear;
    double index =
        gamma * (getObservedValue(time) / getForecastValue(time))
            + (1 - gamma) * getSeasonalIndex(previousYear);

    DataPoint dp = new Observation(index);
    dp.setIndependentValue(timeVariable, time);
    seasonalIndex.add(dp);

    return index;
  }
  /**
   * Calculates and returns the trend for the given time period. Except for the initial periods -
   * where forecasts are not available - the trend is calculated using forecast values, and not
   * observed values. See the class documentation for details on the formulation used.
   *
   * @param time the time value for which the trend is required.
   * @return the trend of the data at the given period of time.
   * @param IllegalArgumentException if the trend cannot be determined for the given time period.
   */
  private double getTrend(double time) throws IllegalArgumentException {
    // TODO: Optimize this search by having data set sorted by time

    // Search for previously calculated - and saved - trend value
    String timeVariable = getTimeVariable();
    Iterator it = trendValues.iterator();
    while (it.hasNext()) {
      DataPoint dp = (DataPoint) it.next();
      double dpTimeValue = dp.getIndependentValue(timeVariable);
      if (Math.abs(time - dpTimeValue) < TOLERANCE) return dp.getDependentValue();
    }

    if (time < getMinimumTimeValue() + TOLERANCE)
      throw new IllegalArgumentException(
          "Attempt to forecast for an invalid time - before the observations began ("
              + getMinimumTimeValue()
              + ").");

    // Saved trend not found, so calculate it
    //  (and save it for future reference)
    double previousTime = time - getTimeInterval();
    double trend =
        beta * (getBase(time) - getBase(previousTime)) + (1 - beta) * getTrend(previousTime);

    DataPoint dp = new Observation(trend);
    dp.setIndependentValue(timeVariable, time);
    trendValues.add(dp);

    return trend;
  }
  /**
   * Calculates (and caches) the initial seasonal indices for the given DataSet. Note that there are
   * a variety of ways to estimate initial values for the seasonal indices. The approach here
   * averages seasonal indices calculated for the first two complete "years" - or cycles - in the
   * DataSet.
   *
   * @param dataSet the set of data points - observations - to use to initialize the seasonal
   *     indices.
   */
  private void initSeasonalIndices(DataSet dataSet) {
    String timeVariable = getTimeVariable();

    double yearlyAverage[] = new double[NUMBER_OF_YEARS];
    Iterator it = dataSet.iterator();
    for (int year = 0; year < NUMBER_OF_YEARS; year++) {
      double sum = 0.0;
      for (int p = 0; p < periodsPerYear; p++) {
        DataPoint dp = (DataPoint) it.next();
        sum += dp.getDependentValue();
      }

      yearlyAverage[year] = sum / (double) periodsPerYear;
    }

    it = dataSet.iterator();
    double index[] = new double[periodsPerYear];
    for (int year = 0; year < NUMBER_OF_YEARS; year++) {
      double sum = 0.0;
      for (int p = 0; p < periodsPerYear; p++) {
        DataPoint dp = (DataPoint) it.next();
        index[p] += (dp.getDependentValue() / yearlyAverage[year]) / NUMBER_OF_YEARS;
      }
    }

    it = dataSet.iterator();

    // Skip over first n-1 years
    for (int year = 0; year < NUMBER_OF_YEARS - 1; year++)
      for (int p = 0; p < periodsPerYear; p++) it.next();

    for (int p = 0; p < periodsPerYear; p++) {
      DataPoint dp = (DataPoint) it.next();
      double time = dp.getIndependentValue(timeVariable);

      Observation obs = new Observation(index[p]);
      obs.setIndependentValue(timeVariable, time);

      seasonalIndex.add(obs);
    }
  }
  /**
   * Calculates (and caches) the initial base and trend values for the given DataSet. Note that
   * there are a variety of ways to estimate initial values for both the base and the trend. The
   * approach here averages the trend calculated from the first two complete "years" - or cycles -
   * of data in the DataSet.
   *
   * @param dataSet the set of data points - observations - to use to initialize the base and trend
   *     values.
   */
  private void initBaseAndTrendValues(DataSet dataSet) {
    String timeVariable = getTimeVariable();
    double trend = 0.0;
    Iterator it = dataSet.iterator();
    for (int p = 0; p < periodsPerYear; p++) {
      DataPoint dp = (DataPoint) it.next();
      trend -= dp.getDependentValue();
    }

    double year2Average = 0.0;
    for (int p = 0; p < periodsPerYear; p++) {
      DataPoint dp = (DataPoint) it.next();
      trend += dp.getDependentValue();

      year2Average += dp.getDependentValue();
    }
    trend /= periodsPerYear;
    trend /= periodsPerYear;
    year2Average /= periodsPerYear;

    it = dataSet.iterator();
    for (int p = 0; p < periodsPerYear * NUMBER_OF_YEARS; p++) {
      DataPoint obs = (DataPoint) it.next();
      double time = obs.getIndependentValue(timeVariable);

      DataPoint dp = new Observation(trend);
      dp.setIndependentValue(timeVariable, time);
      trendValues.add(dp);

      // Initialize base values for second year only
      if (p >= periodsPerYear) {
        // This formula gets a little convoluted partly due to
        // the fact that p is zero-based, and partly because
        // of the generalized nature of the formula
        dp.setDependentValue(
            year2Average + (p + 1 - periodsPerYear - (periodsPerYear + 1) / 2.0) * trend);
        // dp.setIndependentValue( timeVariable, time );
        baseValues.add(dp);
      }
    }
  }
  /**
   * Since this version of triple exponential smoothing uses the current observation to calculate a
   * smoothed value, we must override the calculation of the accuracy indicators.
   *
   * @param dataSet the DataSet to use to evaluate this model, and to calculate the accuracy
   *     indicators against.
   */
  protected void calculateAccuracyIndicators(DataSet dataSet) {
    // WARNING: THIS STILL NEEDS TO BE VALIDATED

    // Note that the model has been initialized
    initialized = true;

    // Reset various helper summations
    double sumErr = 0.0;
    double sumAbsErr = 0.0;
    double sumAbsPercentErr = 0.0;
    double sumErrSquared = 0.0;

    String timeVariable = getTimeVariable();
    double timeDiff = getTimeInterval();

    // Calculate the Sum of the Absolute Errors
    Iterator it = dataSet.iterator();
    while (it.hasNext()) {
      // Get next data point
      DataPoint dp = (DataPoint) it.next();
      double x = dp.getDependentValue();
      double time = dp.getIndependentValue(timeVariable);
      double previousTime = time - timeDiff;

      // Get next forecast value, using one-period-ahead forecast
      double forecastValue = getForecastValue(previousTime) + getTrend(previousTime);

      // Calculate error in forecast, and update sums appropriately
      double error = forecastValue - x;
      sumErr += error;
      sumAbsErr += Math.abs(error);
      sumAbsPercentErr += Math.abs(error / x);
      sumErrSquared += error * error;
    }

    // Initialize the accuracy indicators
    int n = dataSet.size();

    accuracyIndicators.setBias(sumErr / n);
    accuracyIndicators.setMAD(sumAbsErr / n);
    accuracyIndicators.setMAPE(sumAbsPercentErr / n);
    accuracyIndicators.setMSE(sumErrSquared / n);
    accuracyIndicators.setSAE(sumAbsErr);
  }
Example #6
0
 public TreeMap<Integer, Double> getForecast(int start, int end) {
   TreeMap<Integer, Double> forecast = new TreeMap<Integer, Double>();
   fcModel.init(this.demandData.getDemandDataSet(reviewPeriod));
   DataSet fcSet = new DataSet();
   for (int i = start; i <= end; i++) {
     DataPoint dp = new Observation(0.0);
     dp.setIndependentValue("Tick", i);
     fcSet.add(dp);
   }
   fcModel.forecast(fcSet);
   Iterator it = fcSet.iterator();
   while (it.hasNext()) {
     DataPoint dp = (DataPoint) it.next();
     forecast.put((int) dp.getIndependentValue("Tick"), dp.getDependentValue());
   }
   // System.out.println("DemandData: " + this.demandData.getDataMap());
   // System.out.println("Forecast: " + forecast);
   return forecast;
 }
  /**
   * Calculates and returns the base value for the given time period. Except for the first "year" -
   * where base values are not available - the base is calculated using a smoothed value of the
   * previous base. See the class documentation for details on the formulation used.
   *
   * @param time the time value for which the trend is required.
   * @return the estimated base value at the given period of time.
   * @param IllegalArgumentException if the base cannot be determined for the given time period.
   */
  private double getBase(double time) throws IllegalArgumentException {
    // TODO: Optimize this search by having data set sorted by time

    // Search for previously calculated - and saved - base value
    String timeVariable = getTimeVariable();
    Iterator it = baseValues.iterator();
    while (it.hasNext()) {
      DataPoint dp = (DataPoint) it.next();
      double dpTimeValue = dp.getIndependentValue(timeVariable);
      if (Math.abs(time - dpTimeValue) < TOLERANCE) return dp.getDependentValue();
    }

    if (time < getMinimumTimeValue() + periodsPerYear * getTimeInterval() + TOLERANCE)
      throw new IllegalArgumentException(
          "Attempt to forecast for an invalid time "
              + time
              + " - before sufficient observations were made ("
              + getMinimumTimeValue()
              + periodsPerYear * getTimeInterval()
              + ").");

    // Saved base value not found, so calculate it
    //  (and save it for future reference)
    double previousTime = time - getTimeInterval();
    double previousYear = time - periodsPerYear * getTimeInterval();

    double base =
        alpha * (getObservedValue(time) / getSeasonalIndex(previousYear))
            + (1 - alpha) * (getBase(previousTime) + getTrend(previousTime));

    DataPoint dp = new Observation(base);
    dp.setIndependentValue(timeVariable, time);
    baseValues.add(dp);

    return base;
  }