/** 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;
  }
  /**
   * Used to initialize the time based model. This method must be called before any other method in
   * the class. Since the time based model does not derive any equation for forecasting, this method
   * uses the input DataSet to calculate forecast values for all values of the independent time
   * variable within the initial data set.
   *
   * @param dataSet a data set of observations that can be used to initialize the forecasting
   *     parameters of the forecasting model.
   */
  public void init(DataSet dataSet) {
    initTimeVariable(dataSet);
    String timeVariable = getTimeVariable();

    if (dataSet.getPeriodsPerYear() <= 1)
      throw new IllegalArgumentException(
          "Data set passed to init in the triple exponential smoothing model does not contain seasonal data. Don't forget to call setPeriodsPerYear on the data set to set this.");

    periodsPerYear = dataSet.getPeriodsPerYear();

    // Calculate initial values for base and trend
    initBaseAndTrendValues(dataSet);

    // Initialize seasonal indices using data for all complete years
    initSeasonalIndices(dataSet);

    Iterator it = dataSet.iterator();
    maxObservedTime = Double.NEGATIVE_INFINITY;
    while (it.hasNext()) {
      DataPoint dp = (DataPoint) it.next();
      if (dp.getIndependentValue(timeVariable) > maxObservedTime)
        maxObservedTime = dp.getIndependentValue(timeVariable);
    }

    super.init(dataSet);
  }
  /**
   * 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;
  }
  /**
   * 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);
  }
Beispiel #5
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 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);
      }
    }
  }
  /**
   * 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;
  }
  /**
   * 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);
    }
  }
  /**
   * Tests the correct output of a DataSet to a TimeSeries by outputting it, then iterating through
   * TimeSeries object checking the correct values were stored/output.
   */
  public void testOutput()
      throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
          IllegalAccessException, InvocationTargetException, InstantiationException {
    // Constants used to determine size of test
    int NUMBER_OF_TIME_PERIODS = 10;
    String TIME_VARIABLE = "t";

    // Set up array for expected results
    double expectedValue[] = new double[NUMBER_OF_TIME_PERIODS];

    // We'll set up periods starting from today
    RegularTimePeriod period = new Day();

    // Create a test DataSet for output
    //  - note that only one independent variable (the time variable)
    //    will be output. This is expected.
    DataSet dataSet = new DataSet();
    dataSet.setTimeVariable(TIME_VARIABLE);
    for (int d = 0; d < NUMBER_OF_TIME_PERIODS; d++) {
      double value = (double) d;
      DataPoint obs = new Observation(value);
      obs.setIndependentValue(TIME_VARIABLE, period.getMiddleMillisecond());
      dataSet.add(obs);

      period = period.next();
      expectedValue[d] = value;
    }

    assertEquals(
        "Checking only one independent variable exists in dataSet",
        1,
        dataSet.getIndependentVariables().length);

    assertEquals(
        "Checking dataSet has correct number of entries", NUMBER_OF_TIME_PERIODS, dataSet.size());

    // Create TimeSeriesOutputter and use it to output dataSet
    TimeSeries timeSeries = new TimeSeries("test");
    TimeSeriesOutputter outputter = new TimeSeriesOutputter(timeSeries, period.getClass());
    outputter.output(dataSet);

    assertEquals(
        "Checking number of items in time series",
        NUMBER_OF_TIME_PERIODS,
        timeSeries.getItemCount());

    // Reset period to start checking from today onwards
    period = new Day();
    for (int d = 0; d < NUMBER_OF_TIME_PERIODS; d++) {
      TimeSeriesDataItem dataItem = timeSeries.getDataItem(d);

      period = dataItem.getPeriod();
      assertNotNull("Checking time period", period);

      long timeValue = period.getMiddleMillisecond();
      assertTrue(
          "Checking time periods match",
          (double) timeValue >= period.getFirstMillisecond()
              && (double) timeValue <= period.getLastMillisecond());

      assertEquals(
          "Checking values for period " + dataItem.getPeriod() + " match",
          expectedValue[d],
          dataItem.getValue().doubleValue(),
          TOLERANCE);

      period = period.next();
    }
  }