//
//  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;


import java.util.ArrayList;
import java.util.Iterator;
import net.sourceforge.openforecast.models.MovingAverageModel;
import net.sourceforge.openforecast.models.MultipleLinearRegressionModel;
import net.sourceforge.openforecast.models.PolynomialRegressionModel;
import net.sourceforge.openforecast.models.RegressionModel;
import net.sourceforge.openforecast.models.SimpleExponentialSmoothingModel;
import net.sourceforge.openforecast.models.DoubleExponentialSmoothingModel;
import net.sourceforge.openforecast.models.TripleExponentialSmoothingModel;



/**
 * The Forecaster class is a factory class that obtains the best
 * ForecastingModel for the given data set. The interpretation of the "best"
 * forecasting model can be user selected (bias, MAD, MAPE, MSE, SAE or a
 * blend of these), or left up to the Forecaster. If the interpretation is left
 * up to the Forecaster class then it will evaluate a combination of these other
 * measures and comes up with somewhat of a concensus opinion as the to best
 * model. For more details on the different options available, see
 * {@link net.sourceforge.openforecast.EvaluationCriteria}.
 * @author Steven R. Gould
 */
public class Forecaster
{
    /**
     * Make constructor private to prevent this class from being instantiated
     * directly.
     */
    private Forecaster()
    {
    }
    
    /**
     * Obtains the best forecasting model for the given DataSet. There is
     * some intelligence built into this method to help it determine which
     * forecasting model is best suited to the data. In particular, it will
     * try applying various forecasting models, using different combinations
     * of independent variables and select the one with the least Sum of
     * Absolute Errors (SAE); i.e. the most accurate one based on historical
     * data.
     * @param dataSet a set of observations on which the given model should be
     *        based.
     * @return the best ForecastingModel for the given data set.
     */
    public static ForecastingModel getBestForecast( DataSet dataSet )
    {
        return getBestForecast( dataSet, EvaluationCriteria.BLEND );
    }
    
    /**
     * Obtains the best forecasting model for the given DataSet. To determine
     * which model is best the specified EvaluationCriteria is used - this
     * includes options to use bias, mean absolute deviation (MAD), mean
     * absolute percentage error (MAPE), mean squared error (MSE) and more. For
     * a complete list refer to the final static members defined in the
     * EvaluationCriteria class.
     * @param dataSet a set of observations on which the given model should be
     *        based.
     * @param evalMethod specifies how to determine the "best" model; using
     *        which EvaluationCriteria.
     * @return the best ForecastingModel for the given data set.
     * @see EvaluationCriteria
     * @since 0.5
     */
    public static ForecastingModel getBestForecast( DataSet dataSet, EvaluationCriteria evalMethod )
    {
        String independentVariable[] = dataSet.getIndependentVariables();
        ForecastingModel bestModel = null;
        
        // Try single variable models
        for ( int i=0; i availableVariables
            = new ArrayList(independentVariable.length);
        for ( int i=0; i bestVariables = new ArrayList(independentVariable.length);
        
        // While some variables still available to consider
        while ( availableVariables.size() > 0 )
            {
                int count = bestVariables.size();
                String workingList[] = new String[count+1];
                if ( count > 0 )
                    for ( int i=0; i it = availableVariables.iterator();
                while ( it.hasNext() )
                    {
                        // Get current variable
                        String currentVar = it.next();
                        
                        // Add variable to list to use for regression
                        workingList[count] = currentVar;
                        
                        // Do multiple variable linear regression
                        ForecastingModel model
                            = new MultipleLinearRegressionModel( workingList );
                        model.init( dataSet );
                        
                        //  If best so far, then save best variable
                        if ( betterThan( model, bestModel, evalMethod ) )
                            {
                                bestModel = model;
                                bestAvailVariable = currentVar;
                            }
                        
                        // Remove the current variable from the working list
                        workingList[count] = null;
                    }
                
                // If no better model could be found (by adding another
                //     variable), then we're done
                if ( bestAvailVariable == null )
                    break;
                
                // Remove best variable from list of available vars
                int bestVarIndex = availableVariables.indexOf( bestAvailVariable );
                availableVariables.remove( bestVarIndex );
                
                // Add best variable to list of vars. to use
                bestVariables.add( count, bestAvailVariable );
                
                count++;
            }
        
        
        // Try time-series models
        if ( dataSet.getTimeVariable() != null )
            {
                // Try moving average model
                ForecastingModel model = new MovingAverageModel();
                model.init( dataSet );
                if ( betterThan( model, bestModel, evalMethod ) )
                    bestModel = model;
                
                // Try moving average model using periods per year if avail.
                if ( dataSet.getPeriodsPerYear() > 0 )
                    {
                        model = new MovingAverageModel( dataSet.getPeriodsPerYear() );
                        model.init( dataSet );
                        if ( betterThan( model, bestModel, evalMethod ) )
                            bestModel = model;
                    }
                
                // TODO: Vary the period and try other MA models
                // TODO: Consider appropriate use of time period in this
                
                // Try the best fit simple exponential smoothing model
                model = SimpleExponentialSmoothingModel.getBestFitModel(dataSet);
                if ( betterThan( model, bestModel, evalMethod ) )
                    bestModel = model;
                
                // Try the best fit double exponential smoothing model
                model = DoubleExponentialSmoothingModel.getBestFitModel(dataSet);
                if ( betterThan( model, bestModel, evalMethod ) )
                    bestModel = model;
                
                // Try the best fit triple exponential smoothing model
                model = TripleExponentialSmoothingModel.getBestFitModel(dataSet);
                if ( betterThan( model, bestModel, evalMethod ) )
                    bestModel = model;
                
                
            }
        
        return bestModel;
    }
    
    /**
     * A helper method to determine, based on the existing evaluation criteria,
     * whether one model is "better than" a second model. This is done using
     * the evaluation criteria exposed by each model, as defined in the
     * ForecastingModel interface.
     *
     * 

Generally, model2 should be the model that you expect to be worse. It * can also be null if no model2 has been selected. model1 * cannot be null. If model2 is null, then * betterThan will return true on the assumption that some model, any * model, is better than no model. * *

The determination of which model is "best" is definitely subjective * when the two models are close. The approach implemented here is to * consider all current evaluation criteria (which admittedly are not * independent of each other), and if more of the criteria are in favor of * one model, then betterThan will return true. * *

It is expected that this implementation may change over time, so do * not depend on the approach described here. Rather just consider that * this method will implement a reasonable comparison of two models. * @param model1 the first model to compare. * @param model2 the second model to compare. If model1 is determined to * be "better than" model2, then true is returned. model2 can be * null representing the absence of a model. * @param evalMethod specifies how to determine the "best" model; using * which EvaluationCriteria. * @return true if model1 is "better than" model2; otherwise false. */ private static boolean betterThan( ForecastingModel model1, ForecastingModel model2, EvaluationCriteria evalMethod ) { // Special case. Any model is better than no model! if ( model2 == null ) return true; double tolerance = 0.00000001; // Use evaluation method as requested by user if ( evalMethod == EvaluationCriteria.BIAS ) return ( model1.getBias() <= model2.getBias() ); else if ( evalMethod == EvaluationCriteria.MAD ) return ( model1.getMAD() <= model2.getMAD() ); else if ( evalMethod == EvaluationCriteria.MAPE ) return ( model1.getMAPE() <= model2.getMAPE() ); else if ( evalMethod == EvaluationCriteria.MSE ) return ( model1.getMSE() <= model2.getMSE() ); else if ( evalMethod == EvaluationCriteria.SAE ) return ( model1.getSAE() <= model2.getSAE() ); else if ( evalMethod == EvaluationCriteria.AIC ) return ( model1.getAIC() <= model2.getAIC() ); // Default evaluation method is a combination int score = 0; if ( model1.getAIC()-model2.getAIC() <= tolerance ) score++; else if ( model1.getAIC()-model2.getAIC() >= tolerance ) score--; if ( model1.getBias()-model2.getBias() <= tolerance ) score++; else if ( model1.getBias()-model2.getBias() >= tolerance ) score--; if ( model1.getMAD()-model2.getMAD() <= tolerance ) score++; else if ( model1.getMAD()-model2.getMAD() >= tolerance ) score--; if ( model1.getMAPE()-model2.getMAPE() <= tolerance ) score++; else if ( model1.getMAPE()-model2.getMAPE() >= tolerance ) score--; if ( model1.getMSE()-model2.getMSE() <= tolerance ) score++; else if ( model1.getMSE()-model2.getMSE() >= tolerance ) score--; if ( model1.getSAE()-model2.getSAE() <= tolerance ) score++; else if ( model1.getSAE()-model2.getSAE() >= tolerance ) score--; if ( score == 0 ) { // At this point, we're still unsure which one is best // so we'll take another approach double diff = model1.getAIC() - model2.getAIC() + model1.getBias() - model2.getBias() + model1.getMAD() - model2.getMAD() + model1.getMAPE() - model2.getMAPE() + model1.getMSE() - model2.getMSE() + model1.getSAE() - model2.getSAE(); return ( diff < 0 ); } return ( score > 0 ); } } // Local Variables: // tab-width: 4 // End:

Ads help maintain this website.