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