## Introduction

This code provides a basic set of functions which accept a comma-delimited string of time-series values, the number of periods into the future to extend a forecast, and a number of periods to include in a "holdout set" for additional testing (e.g. verifying forecasted values against observed occurrences without prior knowledge of the actuals).

## Background

Time-series forecasting methods use historical information only to produce estimates of future values. Time-series forecasting techniques assume the data's past pattern will continue in the future and include specific measures of error which can help users understand how accurate the forecast has been. Some techniques seek to identify underlying patterns such as trend or seasonal adjustments; others attempt to be self-correcting by including calculations about past periods' error in future forecasts.

The code included here addresses several of the most common time-series forecasting techniques, including naive/Bayes, simple moving average, weighted moving average, exponential smoothing, and adaptive rate smoothing.

In the naive/Bayes approach, the current period's value is used as the forecast for the upcoming period.

In a simple moving average, the prior n-number of values are averaged together with equal weight to produce a value for the upcoming period.

In a weighted moving average, a percentage of weight is applied to n-number of prior values by multiplying the weight by the value and summing the results to produce a value for the upcoming period. By applying different weights (which sum to 1.0), past periods can be given different emphasis. If the user wishes to place more weight upon earlier periods, they might use weights of 0.5, 0.3, and 0.2. If the user wishes to place more weight on more recent periods, they might reverse the order of those parameters.

Exponential smoothing is a version of the weighted moving average which gives recent values more weight than earlier values. However, unlike the weighted moving average, it requires only three inputs: the prior period's forecast, the current period's value, and a smoothing factor (alpha), with a value between 0 and 1.0.

Adaptive rate smoothing modifies the exponential smoothing technique by modifying the alpha smoothing parameter for each period's forecast by the inclusion of a Tracking Signal. This is a technique shown to respond much more quickly to step changes while retaining the ability to filter out random noise. For a full discussion of this technique, see Trigg and Leach, 1967 (ref below).

## Using the Code

The code provided consists of a class which contains the analysis functions themselves and a demonstration form, which takes comma-separated values and displays them in a grid with an associated forecast and measures of instance-specific error. Five buttons provide samples for how the function libraries are to be called and pass parameters which, though reasonable, should be modified to suit the user's purpose. In addition to the tested set and error, the grid displays forecasted values for a number of periods into the future. Finally, several specific measures of error are calculated and displayed in labels. Users of these functions should have a solid grasp of what the individual error measures indicate in order to properly interpret both these functions and the results.

Some terminology I have used in the functions which may not be immediately obvious to the reader:

- Extension - An integer number of periods in the future into which the function should attempt to produce forecasts. The further into the future (higher the number) we attempt to forecast, the higher the probability of error.
- Holdout Set - An integer number of periods to withhold from the testable set of observed values (from the end). The functions calculate forecasts for these values without looking at the observed values until after the forecast is generated. In this way, forecasts for a number of periods may be verified against observed values without the inconvenience of having to wait for future periods to occur.

Generally, these functions can be called by passing in a decimal array of time-series data (examples provided), an integer extension, and an integer holdout. Other parameters are documented in the code.

ForecastTable dt = TimeSeries.simpleMovingAverage(new decimal[3] {1,2,3}, 5, 3, 0);
grdResults.DataSource = dt;

The `simpleMovingAverage `

result (like the other functions in this class) is calculated according to a well-known formula, which is included in the comments.

public static ForecastTable simpleMovingAverage(decimal[] values,
int Extension, int Periods, int Holdout)
{
ForecastTable dt = new ForecastTable();
for (Int32 i = 0; i < values.Length + Extension; i++)
{
DataRow row = dt.NewRow();
dt.Rows.Add(row);
row.BeginEdit();
row["Instance"] = i;
if (i < values.Length)
{
row["Value"] = values[i];
}
row["Holdout"] = (i > (values.Length - Holdout)) && (i < values.Length);
if (i == 0)
{
row["Forecast"] = values[i];
}
else if (i <= values.Length - Holdout)
{
decimal avg = 0;
DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() +
" AND Instance < " + i.ToString(), "Instance");
foreach (DataRow priorRow in rows)
{
avg += (Decimal)priorRow["Value"];
}
avg /= rows.Length;
row["Forecast"] = avg;
}
else
{
decimal avg = 0;
DataRow[] rows = dt.Select("Instance>=" + (i - Periods).ToString() +
" AND Instance < " + i.ToString(), "Instance");
foreach (DataRow priorRow in rows)
{
if ((Int32)priorRow["Instance"] < values.Length)
{
avg += (Decimal)priorRow["Value"];
}
else
{
avg += (Decimal)priorRow["Forecast"];
}
}
avg /= rows.Length;
row["Forecast"] = avg;
}
row.EndEdit();
}
dt.AcceptChanges();
return dt;
}

Each of the forecasting functions (`naive()`

, `simpleMovingAverage()`

, `weightedMovingAverage()`

, `exponentialSmoothing()`

, and `adaptiveRateSmoothing()`

) work in a similar manner to initialize early rows with default values--since prior data is not yet available--then calculates a forecast for each value in the testable set. Finally, holdouts and extension values are calculated.

Error analysis is accomplished through the implementation of several measures: `MeanSignedError()`

, `MeanAbsoluteError()`

, `MeanPercentError()`

, `MeanAbsolutePercentError()`

, `TrackingSignal()`

, `MeanSquaredError()`

, `CumulativeSignedError()`

, and `CumulativeAbsoluteError()`

.

One of the most useful measures of forecast error is the `MeanAbsolutePercentError `

(MAPE). This value is calculated by summing the absolute value of the percent error for each period's forecast and dividing by the number of periods tested.

public static decimal MeanAbsolutePercentError
(ForecastTable dt, bool Holdout, int IgnoreInitial)
{
string Filter = "AbsolutePercentError Is Not Null AND Instance > "
+ IgnoreInitial.ToString();
if (Holdout)
Filter += " AND Holdout=True";
if (dt.Select(Filter).Length == 0)
return 1;
return (Decimal)dt.Compute("AVG(AbsolutePercentError)", Filter);
}

## References

- Krajewski and Ritzman, Operations Management Processes and Value Chains 7
^{th} edition (2004), Pearson Prentice Hall, Upper Saddle River, NJ, pp 535-581
- Trigg and Leach, "Exponential Smoothing with an Adaptive Response Rate", Operational Research, Vol. 18, No. 1, (Mar. 1967), pp 53-59

## History

This code has not been thoroughly tested and may contain bugs. It is intended to be instructive about forecasting techniques and should not be relied upon for actual forecasts used in decision-making. It has not been optimized for performance or efficiency and the code as it is written is not particularly elegant. My goal was to make it as easy to read and understand as possible, so that others can create their own functions which implement the concepts of time-series forecasting. That said, I do welcome constructive feedback if you see a bug or some glaring omission, or perhaps you feel I could have explained something more clearly.

I learned my first programming language--Apple Basic--in 1989. Over the years, I've done projects in C/C++, Pascal, FoxPro, 4D, AS/400, dBase, perl/CGI, Access, MSSQL, VB5-6/COM+, Classic ASP, uncounted legions of Windows, Mac, and *nix scripting languages and now C# ASP.NET/MVC/Razor/jQuery.

I started getting paid to do this stuff in 1993 as the admin for a 100 node network and began writing one-off apps for the company in my spare time. By 2002, I was developing and managing enterprise software projects full time.

I sat through so many Microsoft classes that they should offer me an honorary MCSE, MCSD, and a bunch of other letters, plus name something on campus after me. I took an undergraduate degree in Management & Business Information Systems, earned an MBA, and I hold a PMP credential, though trying to bring projects to successful completion (as opposed to tracking processes into perpetuity) using the PMBOK is like trying to get to a nice restaurant in a big city by reading a book about its architecture. But, I digress...

I've worked in the building materials industry supporting wholesale trading since 1993. I maintain an unhealthy level of interest in exchange-based and cash forward trading, derivatives, simulation, forecasting, project management, and other quantitative analysis topics (e.g. queue theory, optimal inventory policy, etc.) Most recently, I finished a Systems Science certificate in Computer Modeling & Simulation.