/** 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); }
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; }