//
// OpenForecast - open source, general-purpose forecasting package.
// Copyright (C) 2002-2011 Steven R. Gould
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
package net.sourceforge.openforecast.models;
import java.util.Iterator;
import net.sourceforge.openforecast.DataPoint;
import net.sourceforge.openforecast.DataSet;
import net.sourceforge.openforecast.Observation;
/**
* A time based forecasting model is the base class that implements much of
* the common code for models based on a time series. In particular, it was
* designed to support the needs of the Weighted Moving Average, as well as
* the Single, Double and Triple Exponential Smoothing models.
*
* These models have an advantage over other forecasting models in that
* they smooth out peaks and troughs (or valleys) in a set of observations.
* However, they also have several disadvantages. In particular these models
* do not produce an actual equation. Therefore, they often are not all that
* useful as medium-long range forecasting tools. They can only reliably be
* used to forecast a few periods into the future.
* @author Steven R. Gould
* @since 0.4
*/
public abstract class AbstractTimeBasedModel extends AbstractForecastingModel
{
/**
* The name of the independent (time) variable used in this model.
*/
private String timeVariable = null;
/**
* Initialized to the time difference (in whatever units time is reported
* in) between two consecutive data points. You could also think of this
* as the "delta time" between data points.
*/
private double timeDiff = 0.0;
/**
* Stores the minimum number of prior periods of data required to produce a
* forecast. Since this varies depending on the details of the model, any
* subclass must call setMinimumNumberOfPeriods - usually from the
* constructor - before init is invoked to provide the correct information.
*/
private int minPeriods = 0;
/**
* The observed values are stored for future reference. In this model,
* unlike most others, we store all observed values. This is because these
* models don't derive any formula from the data, so the values may be
* needed later in order to derive future forecasts.
*/
private DataSet observedValues;
/**
* The forecast values are stored to save recalculation. In this model,
* unlike most others, we store all forecast values. This is because these
* models don't derive any formula from the data.
*/
private DataSet forecastValues;
/**
* The minimum value of the independent variable supported by this
* forecasting model. This is dependent on the data set used to
* initialize the model.
* @see #maxIndependentValue
*/
private double minTimeValue;
/**
* The maximum value of the independent variable supported by this
* forecasting model. This is dependent on the data set used to
* initialize the model.
* @see #minTimeValue
*/
private double maxTimeValue;
/**
* Constructs a new time based forecasting model. For a valid model to be
* constructed, you should call init and pass in a data set containing a
* series of data points. The data set should also have the time variable
* initialized to the independent time variable name.
*/
public AbstractTimeBasedModel()
{
}
/**
* Constructs a new time based forecasting model, using the named variable
* as the independent (time) variable.
* @param timeVariable the name of the independent variable to use as the
* time variable in this model.
* @deprecated As of 0.4, replaced by {@link #AbstractTimeBasedModel}.
*/
public AbstractTimeBasedModel( String timeVariable )
{
this.timeVariable = timeVariable;
}
/**
* Returns the current number of periods used in this model. This is also
* the minimum number of periods required in order to produce a valid
* forecast. Since this varies depending on the details of the model, any
* subclass must override this to provide the correct information.
* @return the minimum number of periods used in this model.
*/
protected abstract int getNumberOfPeriods();
/**
* 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 );
if ( dataSet == null || dataSet.size() == 0 )
throw new IllegalArgumentException("Data set cannot be empty in call to init.");
int minPeriods = getNumberOfPeriods();
if ( dataSet.size() < minPeriods )
throw new IllegalArgumentException("Data set too small. Need "
+minPeriods
+" data points, but only "
+dataSet.size()
+" passed to init.");
observedValues = new DataSet( dataSet );
observedValues.sort( timeVariable );
// Check that intervals between data points are consistent
// i.e. check for complete data set
Iterator it = observedValues.iterator();
DataPoint dp = it.next(); // first data point
double lastValue = dp.getIndependentValue(timeVariable);
dp = it.next(); // second data point
double currentValue = dp.getIndependentValue(timeVariable);
// Create data set in which to save new forecast values
forecastValues = new DataSet();
// Determine "standard"/expected time difference between observations
timeDiff = currentValue - lastValue;
// Min. time value is first observation time
minTimeValue = lastValue;
while ( it.hasNext() )
{
lastValue = currentValue;
// Get next data point
dp = it.next();
currentValue = dp.getIndependentValue(timeVariable);
double diff = currentValue - lastValue;
if ( Math.abs(timeDiff - diff) > TOLERANCE )
throw new IllegalArgumentException( "Inconsistent intervals found in time series, using variable '"+timeVariable+"'" );
try
{
initForecastValue( currentValue );
}
catch (IllegalArgumentException ex)
{
// We can ignore these during initialization
}
}
// Create test data set for determining accuracy indicators
// - same as input data set, but without the first n data points
DataSet testDataSet = new DataSet( observedValues );
int count = 0;
while ( count++ < minPeriods )
testDataSet.remove( (testDataSet.iterator()).next() );
// Calculate accuracy
calculateAccuracyIndicators( testDataSet );
}
/**
* Initializes the time variable from the given data set. If the data set
* does not have a time variable explicitly defined, then provided there
* is only one independent variable defined for the data set that is used
* as the time variable. If more than one independent variable is defined
* for the data set, then it is not possible to take an educated guess at
* which one is the time variable. In this case, an
* IllegalArgumentException will be thrown.
* @param dataSet the data set to use to initialize the time variable.
* @throws IllegalArgumentException If more than one independent variable
* is defined for the data set and no time variable has been specified. To
* correct this, be sure to explicitly specify the time variable in the
* data set passed to {@link #init}.
*/
protected void initTimeVariable( DataSet dataSet )
throws IllegalArgumentException
{
if ( timeVariable == null )
{
// Time variable not set, so look at independent variables
timeVariable = dataSet.getTimeVariable();
if ( timeVariable == null )
{
String[] independentVars
= dataSet.getIndependentVariables();
if ( independentVars.length != 1 )
throw new IllegalArgumentException("Unable to determine the independent time variable for the data set passed to init for "+toString()+". Please use DataSet.setTimeVariable before invoking model.init.");
timeVariable = independentVars[0];
}
}
}
/**
* A helper method that calculates a complete set of forecast values
* derived from the given DataSet. These are calculated in advance due
* to the way in which forecast values are so dependent on the original
* data set. The resulting forecast values are stored in the private
* DataSet forecastValues. Additionally, this method initializes the
* private member variables minTimeValue and maxTimeValue.
* @param dataSet the set of data points on which to base the forecast.
* @return a data set containing all "possible" forecast DataPoints
* that can reasonably be supported by this forecasting model. "Possible"
* forecast DataPoints generally are those which are even partially
* based on an observed value, since forecasting purely off of forecast
* values tends to be unreliable at best.
*/
private double initForecastValue( double timeValue )
throws IllegalArgumentException
{
// Temporary store for current forecast value
double forecast = forecast(timeValue);
// Create new forecast data point
DataPoint dpForecast = new Observation( forecast );
dpForecast.setIndependentValue( timeVariable, timeValue );
// Add new data point to forecast set
forecastValues.add( dpForecast );
// Update maximum time value, if necessary
if ( timeValue > maxTimeValue )
maxTimeValue = timeValue;
return forecast;
}
/**
* Using the current model parameters (initialized in init), apply the
* forecast model to the given data point. The data point must have a valid
* value for the independent variable. Upon return, the value of the
* dependent variable will be updated with the forecast value computed for
* that data point.
* @param dataPoint the data point for which a forecast value (for the
* dependent variable) is required.
* @return the same data point passed in but with the dependent value
* updated to contain the new forecast value.
* @throws ModelNotInitializedException if forecast is called before the
* model has been initialized with a call to init.
* @throws IllegalArgumentException if the forecast period specified by
* the dataPoint is invalid with respect to the historical data
* provided.
*/
public double forecast( DataPoint dataPoint )
throws IllegalArgumentException
{
if ( !initialized )
throw new ModelNotInitializedException();
// Get value of independent variable (the time variable)
double t = dataPoint.getIndependentValue( timeVariable );
return getForecastValue( t );
}
/**
* Returns the forecast value of the dependent variable for the given
* value of the independent time variable. Subclasses must implement
* this method in such a manner consistent with the forecasting model
* they implement. Subclasses can make use of the getForecastValue and
* getObservedValue methods to obtain "earlier" forecasts and
* observations respectively.
* @param timeValue the value of the time variable for which a forecast
* value is required.
* @return the forecast value of the dependent variable for the given
* time.
* @throws IllegalArgumentException if there is insufficient historical
* data - observations passed to init - to generate a forecast for the
* given time value.
*/
protected abstract double forecast( double timeValue )
throws IllegalArgumentException;
/**
* Returns the forecast value for the dependent variable for the given
* value of the independent time variable. This method is only intended
* for use by models that base future forecasts, in part, on past
* forecasts.
* @param timeValue the value of the independent time variable for which
* the forecast value is required. This value must be greater than the
* minimum time value defined by the observations passed into the init
* method.
* @return the forecast value of the dependent variable for the given
* value of the independent time variable.
* @throws IllegalArgumentException if the given value of the time
* variable was not a valid value for forecasts.
*/
protected double getForecastValue( double timeValue )
throws IllegalArgumentException
{
if ( timeValue>=minTimeValue-TOLERANCE
&& timeValue<=maxTimeValue+TOLERANCE )
{
// Find required forecast value in set of
// pre-computed forecasts
Iterator it = forecastValues.iterator();
while ( it.hasNext() )
{
DataPoint dp = it.next();
double currentTime
= dp.getIndependentValue( timeVariable );
// If required data point found,
// return pre-computed forecast
if ( Math.abs(currentTime-timeValue) < TOLERANCE )
return dp.getDependentValue();
}
}
try
{
return initForecastValue( timeValue );
}
catch ( IllegalArgumentException idex )
{
throw new IllegalArgumentException(
"Time value (" + timeValue
+ ") invalid for Time Based forecasting model. Valid values are in the range "
+ minTimeValue + "-" + maxTimeValue
+ " in increments of " + timeDiff + "." );
}
}
/**
* Returns the observed value of the dependent variable for the given
* value of the independent time variable.
* @param timeValue the value of the independent time variable for which
* the observed value is required.
* @return the observed value of the dependent variable for the given
* value of the independent time variable.
* @throws IllegalArgumentException if the given value of the time
* variable was not found in the observations originally passed to init.
*/
protected double getObservedValue( double timeValue )
throws IllegalArgumentException
{
// Find required forecast value in set of
// pre-computed forecasts
Iterator it = observedValues.iterator();
while ( it.hasNext() )
{
DataPoint dp = it.next();
double currentTime
= dp.getIndependentValue( timeVariable );
// If required data point found,
// return pre-computed forecast
if ( Math.abs(currentTime-timeValue) < TOLERANCE )
return dp.getDependentValue();
}
throw new
IllegalArgumentException("No observation found for time value, "
+timeVariable+"="+timeValue);
}
/**
* Returns the name of the independent variable representing the time
* value used by this model.
* @return the name of the independent variable representing the time
* value.
*/
public String getTimeVariable()
{
return timeVariable;
}
/**
* Returns the minimum value of the independent time variable currently
* forecast by this model.
* @return the minimum value of the independent time variable.
*/
public double getMinimumTimeValue()
{
return minTimeValue;
}
/**
* Returns the maximum value of the independent time variable currently
* forecast by this model.
* @return the maximum value of the independent time variable.
*/
public double getMaximumTimeValue()
{
return maxTimeValue;
}
/**
* Returns the independent variable - or the time variable - used in this
* model.
* @return the independent variable in this model.
*/
public String getIndependentVariable()
{
return timeVariable;
}
/**
* Returns the current time interval between observations.
* @return the current time interval between observations.
*/
protected double getTimeInterval()
{
return timeDiff;
}
/**
* Returns a one or two word name of this type of forecasting model. Keep
* this short. A longer description should be implemented in the toString
* method.
* @return a string representation of the type of forecasting model
* implemented.
*/
public String getForecastType()
{
return "Time Based Model";
}
/**
* This should be overridden to provide a textual description of the
* current forecasting model including, where possible, any derived
* parameters used.
* @return a string representation of the current forecast model, and its
* parameters.
*/
public String toString()
{
return "time based model, spanning " + getNumberOfPeriods()
+ " periods and using a time variable of "
+ timeVariable+".";
}
}
// Local Variables:
// tab-width: 4
// End:
Ads help maintain this website.