Xamarin Public Jenkins (auto-signing) 536cd135cc Imported Upstream version 5.4.0.167
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2017-08-21 15:34:15 +00:00

648 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//-------------------------------------------------------------
// <copyright company=Microsoft Corporation>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: TimeSeriesAndForecasting.cs
//
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting.Formulas
//
// Classes: TimeSeriesAndForecasting
//
// Purpose: This class is used for calculations of
// time series and forecasting
//
// Reviewed: GS - August 7, 2002
// AG - August 7, 2002
//
//===================================================================
using System;
using System.Globalization;
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting.Formulas
#else
namespace System.Web.UI.DataVisualization.Charting.Formulas
#endif
{
/// <summary>
/// This class is used for calculations of
/// time series and forecasting
/// </summary>
internal class TimeSeriesAndForecasting : IFormula
{
#region Enumeration
/// <summary>
/// AxisName of regression
/// </summary>
internal enum RegressionType
{
/// <summary>
/// Polynomial trend
/// </summary>
Polynomial,
/// <summary>
/// IsLogarithmic trend
/// </summary>
Logarithmic,
/// <summary>
/// Power trend
/// </summary>
Power,
/// <summary>
/// Exponential trend
/// </summary>
Exponential
}
#endregion
#region Properties
/// <summary>
/// Formula Module name
/// </summary>
virtual public string Name { get { return SR.FormulaNameTimeSeriesAndForecasting; } }
#endregion
#region Methods
/// <summary>
/// Public constructor.
/// </summary>
public TimeSeriesAndForecasting()
{
}
/// <summary>
/// The first method in the module, which converts a formula
/// name to the corresponding private method.
/// </summary>
/// <param name="formulaName">String which represent a formula name</param>
/// <param name="inputValues">Arrays of doubles - Input values</param>
/// <param name="outputValues">Arrays of doubles - Output values</param>
/// <param name="parameterList">Array of strings - Formula parameters</param>
/// <param name="extraParameterList">Array of strings - Extra Formula parameters from DataManipulator object</param>
/// <param name="outLabels">Array of strings - Used for Labels. Description for output results.</param>
virtual public void Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels )
{
string name;
name = formulaName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
// Not used for these formulas.
outLabels = null;
try
{
switch( name )
{
case "FORECASTING":
Forecasting( inputValues, out outputValues, parameterList );
break;
default:
outputValues = null;
break;
}
}
catch( IndexOutOfRangeException )
{
throw new InvalidOperationException( SR.ExceptionFormulaInvalidPeriod(name) );
}
catch( OverflowException )
{
throw new InvalidOperationException( SR.ExceptionFormulaNotEnoughDataPoints( name ) );
}
}
#endregion
#region Formulas
/// <summary>
/// Forecasting formula predicts future values of the time series variable.
/// Multiple regressions are used for this forecasting model. Any method
/// of fitting equations to data may be called regression. Such equations
/// are valuable for at least two purposes: making predictions and judging
/// the strength of relationships. Of the various methods of performing
/// regression, Last Square is the most widely used. This formula returns
/// two more series, which represents upper and lower bond of error. Error
/// is based on standard deviation and represents a linear combination of
/// approximation error and forecasting error.
/// ---------------------------------------------------------
/// Input:
/// - Y values.
/// Output:
/// - Forecasting
/// - upper bond error
/// - lower bond error
/// Parameters:
/// - Polynomial degree (Default: 2 - Linear regression )
/// - Forecasting period (Default: Half of the series length )
/// - Returns Approximation error (Default: true)
/// - Returns Forecasting error (Default: true)
/// </summary>
/// <param name="inputValues">Arrays of doubles - Input values</param>
/// <param name="outputValues">Arrays of doubles - Output values</param>
/// <param name="parameterList">Array of strings - Parameters</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
private void Forecasting(double[][] inputValues, out double[][] outputValues, string[] parameterList)
{
// Polynomial degree
int degree;
RegressionType regressionType = RegressionType.Polynomial;
if (String.Equals(parameterList[0],"Exponential", StringComparison.OrdinalIgnoreCase))
{
regressionType = RegressionType.Exponential;
degree = 2;
}
else if (String.Equals(parameterList[0],"Linear", StringComparison.OrdinalIgnoreCase))
{
regressionType = RegressionType.Polynomial;
degree = 2;
}
else if (String.Equals(parameterList[0],"IsLogarithmic", StringComparison.OrdinalIgnoreCase) ||
String.Equals(parameterList[0],"Logarithmic", StringComparison.OrdinalIgnoreCase))
{
regressionType = RegressionType.Logarithmic;
degree = 2;
}
else if (String.Equals(parameterList[0],"Power", StringComparison.OrdinalIgnoreCase))
{
regressionType = RegressionType.Power;
degree = 2;
}
else
{
if (parameterList.Length < 1 ||
!int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out degree))
{
degree = 2;
}
}
if (degree > 5 || degree < 1)
throw new InvalidOperationException(SR.ExceptionForecastingDegreeInvalid);
if (degree > inputValues[0].Length)
throw new InvalidOperationException(SR.ExceptionForecastingNotEnoughDataPoints(degree.ToString(System.Globalization.CultureInfo.InvariantCulture)));
// Forecasting period
int period;
if (parameterList.Length < 2 ||
!int.TryParse(parameterList[1], NumberStyles.Any, CultureInfo.InvariantCulture, out period))
{
period = inputValues[0].Length / 2;
}
// Approximation error
bool approximationError;
if (parameterList.Length < 3 ||
!bool.TryParse(parameterList[2], out approximationError))
{
approximationError = true;
}
// Forecasting error
bool forecastingError;
if (parameterList.Length < 4 ||
!bool.TryParse(parameterList[3], out forecastingError))
{
forecastingError = true;
}
double[][] tempOut;
// Find regresion
Regression(regressionType, inputValues, out tempOut, degree, period);
// If error disabled get out from procedure
if (!forecastingError && !approximationError)
{
outputValues = tempOut;
return;
}
double[][] inputErrorEst = new double[2][];
double[][] outputErrorEst;
inputErrorEst[0] = new double[inputValues[0].Length / 2];
inputErrorEst[1] = new double[inputValues[0].Length / 2];
for (int index = 0; index < inputValues[0].Length / 2; index++)
{
inputErrorEst[0][index] = inputValues[0][index];
inputErrorEst[1][index] = inputValues[1][index];
}
Regression(regressionType, inputErrorEst, out outputErrorEst, degree, inputValues[0].Length / 2);
// Find the average for forecasting error
double error = 0;
for (int index = inputValues[0].Length / 2; index < outputErrorEst[1].Length; index++)
{
error += (outputErrorEst[1][index] - inputValues[1][index]) * (outputErrorEst[1][index] - inputValues[1][index]);
}
error /= inputValues[0].Length - inputValues[0].Length / 2;
error = Math.Sqrt(error);
error /= (inputValues[0].Length / 4);
// Find the standard deviation
double dev = 0;
for (int index = 0; index < inputValues[0].Length; index++)
{
dev += (tempOut[1][index] - inputValues[1][index]) * (tempOut[1][index] - inputValues[1][index]);
}
dev /= inputValues[0].Length;
dev = Math.Sqrt(dev);
outputValues = new double[4][];
outputValues[0] = tempOut[0];
outputValues[1] = tempOut[1];
outputValues[2] = new double[tempOut[0].Length];
outputValues[3] = new double[tempOut[0].Length];
if (!approximationError)
dev = 0;
if (!forecastingError)
error = 0;
for (int index = 0; index < inputValues[0].Length; index++)
{
outputValues[2][index] = tempOut[1][index] + 2 * dev;
outputValues[3][index] = tempOut[1][index] - 2 * dev;
}
double sumError = 0;
for (int index = inputValues[0].Length; index < tempOut[0].Length; index++)
{
sumError += error;
outputValues[2][index] = tempOut[1][index] + sumError + 2 * dev;
outputValues[3][index] = tempOut[1][index] - sumError - 2 * dev;
}
}
/// <summary>
/// Any method of fitting equations to data may be called regression.
/// Such equations are valuable for at least two purposes: making
/// predictions and judging the strength of relationships. Of the
/// various methods of performing regression, Last Square is the
/// most widely used.
/// </summary>
/// <param name="regressionType">AxisName of regression Polynomial, exponential, etc.</param>
/// <param name="inputValues">Arrays of doubles - Input values</param>
/// <param name="outputValues">Arrays of doubles - Output values</param>
/// <param name="polynomialDegree">Polynomial degree (Default: 2 - Linear regression )</param>
/// <param name="forecastingPeriod">Forecasting period (Default: Half of the series length )</param>
private void Regression( RegressionType regressionType, double [][] inputValues, out double [][] outputValues, int polynomialDegree, int forecastingPeriod )
{
if( regressionType == RegressionType.Exponential )
{
double [] oldYValues = new double[ inputValues[1].Length ];
for( int index = 0; index < inputValues[1].Length; index++ )
{
oldYValues[ index ] = inputValues[1][index];
if( inputValues[1][index] <= 0 )
{
throw new InvalidOperationException(SR.ExceptionForecastingExponentialRegressionHasZeroYValues);
}
inputValues[1][index] = Math.Log( inputValues[1][index] );
}
PolynomialRegression( regressionType, inputValues, out outputValues, 2, forecastingPeriod, 0 );
inputValues[1] = oldYValues;
}
else if( regressionType == RegressionType.Logarithmic )
{
double interval;
double first = inputValues[0][0];
// Find Interval for X values
interval = Math.Abs( inputValues[0][0] - inputValues[0][inputValues[0].Length - 1] ) / ( inputValues[0].Length - 1 );
if( interval <= 0 )
interval = 1;
for( int index = 0; index < inputValues[0].Length; index++ )
{
inputValues[0][index] = Math.Log( inputValues[0][index] );
}
PolynomialRegression( regressionType, inputValues, out outputValues, 2, forecastingPeriod, interval );
// Create Y values based on approximation.
for( int i = 0; i < outputValues[0].Length; i++ )
{
// Set X value
outputValues[0][i] = first + i * interval;
}
}
else if( regressionType == RegressionType.Power )
{
double [] oldYValues = new double[ inputValues[1].Length ];
double interval;
double first = inputValues[0][0];
for( int index = 0; index < inputValues[1].Length; index++ )
{
oldYValues[ index ] = inputValues[1][index];
if( inputValues[1][index] <= 0 )
{
throw new InvalidOperationException(SR.ExceptionForecastingPowerRegressionHasZeroYValues);
}
}
// Find Interval for X values
interval = Math.Abs( inputValues[0][0] - inputValues[0][inputValues[0].Length - 1] ) / ( inputValues[0].Length - 1 );
if( interval <= 0 )
interval = 1;
PolynomialRegression( regressionType, inputValues, out outputValues, 2, forecastingPeriod, interval );
inputValues[1] = oldYValues;
// Create Y values based on approximation.
for( int i = 0; i < outputValues[0].Length; i++ )
{
// Set X value
outputValues[0][i] = first + i * interval;
}
}
else
{
PolynomialRegression( regressionType, inputValues, out outputValues, polynomialDegree, forecastingPeriod, 0 );
}
}
/// <summary>
/// Any method of fitting equations to data may be called regression.
/// Such equations are valuable for at least two purposes: making
/// predictions and judging the strength of relationships. Of the
/// various methods of performing regression, Last Square is the
/// most widely used.
/// </summary>
/// <param name="regressionType">AxisName of regression Polynomial, exponential, etc.</param>
/// <param name="inputValues">Arrays of doubles - Input values</param>
/// <param name="outputValues">Arrays of doubles - Output values</param>
/// <param name="polynomialDegree">Polynomial degree (Default: 2 - Linear regression )</param>
/// <param name="forecastingPeriod">Forecasting period (Default: Half of the series length )</param>
/// <param name="logInterval">Interval for logarithmic scale</param>
private void PolynomialRegression( RegressionType regressionType, double [][] inputValues, out double [][] outputValues, int polynomialDegree, int forecastingPeriod, double logInterval )
{
double [] coefficients = new double [polynomialDegree];
int size = inputValues[0].Length;
double minimumX = double.MaxValue;
double interval = 1.0;
// Find Interval for X values
interval = Math.Abs( inputValues[0][0] - inputValues[0][inputValues[0].Length - 1] ) / ( inputValues[0].Length - 1 );
if( interval <= 0 )
interval = 1;
if( regressionType != RegressionType.Logarithmic )
{
// Avoid Rounding error because of big X values.
// Find Minimum X value
for( int xIndex = 0; xIndex < inputValues[0].Length; xIndex++ )
{
if( minimumX > inputValues[0][xIndex] )
minimumX = inputValues[0][xIndex];
}
// Change X values.
for( int xIndex = 0; xIndex < inputValues[0].Length; xIndex++ )
{
inputValues[0][xIndex] -= minimumX - 1;
}
}
if( regressionType == RegressionType.Power )
{
for( int index = 0; index < inputValues[0].Length; index++ )
{
inputValues[0][index] = Math.Log( inputValues[0][index] );
inputValues[1][index] = Math.Log( inputValues[1][index] );
}
}
double [][] mainDeterminant = new double [polynomialDegree][];
for(int arrayIndex = 0; arrayIndex < polynomialDegree; arrayIndex++)
{
mainDeterminant[arrayIndex] = new double [polynomialDegree];
}
// Main determinant
for( int k = 0; k < polynomialDegree; k++ )
{
for( int i = 0; i < polynomialDegree; i++ )
{
mainDeterminant[i][k] = 0;
for( int j = 0; j < inputValues[0].Length; j++ )
{
mainDeterminant[i][k] += (double)Math.Pow( inputValues[0][j], (i+k) );
}
}
}
double mainValue = Determinant(mainDeterminant);
// Coefficient determinant
for( int i = 0; i < polynomialDegree; i++ )
{
double [][] coeffDeterminant = CopyDeterminant(mainDeterminant);
for( int k = 0; k < polynomialDegree; k++ )
{
coeffDeterminant[i][k] = 0;
for( int j = 0; j < inputValues[0].Length; j++ )
{
coeffDeterminant[i][k] += (double)inputValues[1][j] * (double)Math.Pow( inputValues[0][j], k );
}
}
coefficients[i] = Determinant(coeffDeterminant) / mainValue;
}
// Create output arrays for approximation and forecasting
outputValues = new double[2][];
outputValues[0] = new double[size + forecastingPeriod];
outputValues[1] = new double[size + forecastingPeriod];
if( regressionType == RegressionType.Polynomial )
{
// Create Y values based on approximation.
for( int i = 0; i < size + forecastingPeriod; i++ )
{
// Set X value
outputValues[0][i] = inputValues[0][0] + i * interval;
outputValues[1][i] = 0;
for( int j = 0; j < polynomialDegree; j++ )
{
outputValues[1][i]+= (double)coefficients[j]*Math.Pow(outputValues[0][i],j);
}
}
}
else if( regressionType == RegressionType.Exponential )
{
// Create Y values based on approximation.
for( int i = 0; i < size + forecastingPeriod; i++ )
{
// Set X value
outputValues[0][i] = inputValues[0][0] + i * interval;
outputValues[1][i]= Math.Exp( coefficients[0] ) * Math.Exp( coefficients[1] * outputValues[0][i] );
}
}
else if( regressionType == RegressionType.Logarithmic )
{
// Create Y values based on approximation.
for( int i = 0; i < size + forecastingPeriod; i++ )
{
// Set X value
outputValues[0][i] = Math.Exp( inputValues[0][0] ) + i * logInterval;
outputValues[1][i]= coefficients[1] * Math.Log( outputValues[0][i] ) + coefficients[0];
}
}
else if( regressionType == RegressionType.Power )
{
// Create Y values based on approximation.
for( int i = 0; i < size + forecastingPeriod; i++ )
{
// Set X value
outputValues[0][i] = Math.Exp( inputValues[0][0] ) + i * logInterval;
outputValues[1][i]= Math.Exp( coefficients[0] ) * Math.Pow( outputValues[0][i], coefficients[1] );
}
}
if( regressionType != RegressionType.Logarithmic )
{
// Return X values.
for( int xIndex = 0; xIndex < size + forecastingPeriod; xIndex++ )
{
outputValues[0][xIndex] += minimumX - 1;
}
}
}
/// <summary>
/// This method recalculates determinant. This method is used for
/// recursive calls for sub determinants too.
/// </summary>
/// <param name="inputDeterminant">Input determinant</param>
/// <returns>Result of determinant</returns>
private double Determinant( double [][] inputDeterminant )
{
double sum = 0;
double sign = 1.0;
// Determinant is 2X2 - calculate value
if( inputDeterminant.Length == 2 )
{
return inputDeterminant[0][0]*inputDeterminant[1][1] - inputDeterminant[0][1]*inputDeterminant[1][0];
}
// Determinant is biger than 2X2. Go to recursive
// call of Determinant method
for( int column = 0; column < inputDeterminant.GetLength(0); column++ )
{
// Make sub determinant
double [][] newDeterminant = MakeSubDeterminant( inputDeterminant, column );
sum += sign * Determinant( newDeterminant ) * inputDeterminant[column][0];
sign *= -1.0;
}
return sum;
}
/// <summary>
/// This method will create a new determinant, which is
/// smaller by one rank (dimension). Specified column
/// and zero rows will be skipped.
/// </summary>
/// <param name="inputDeterminant">Input determinant</param>
/// <param name="columnPos">Position of column, which has to be skipped</param>
/// <returns>New determinant</returns>
private double [][] MakeSubDeterminant( double [][] inputDeterminant, int columnPos )
{
// Get Determinant Size
int size = inputDeterminant.GetLength(0);
// Prepare sub Determinant
double [][] newDeterminant = new double [size - 1][];
for(int arrayIndex = 0; arrayIndex < size - 1; arrayIndex++)
{
newDeterminant[arrayIndex] = new double [size - 1];
}
int newColumn = 0;
// Copy columns
for( int column = 0; column < size; column++ )
{
// Skeep this column
if( column == columnPos )
continue;
// Copy rows
for( int row = 1; row < size; row++ )
{
newDeterminant[newColumn][row-1] = inputDeterminant[column][row];
}
// Go to new column for new determinant
newColumn++;
}
// Return new determinant
return newDeterminant;
}
/// <summary>
/// This method will copy determinant
/// </summary>
/// <param name="inputDeterminant">Input determinant</param>
/// <returns>New determinant</returns>
private double [][] CopyDeterminant( double [][] inputDeterminant )
{
// Get Determinant Size
int size = inputDeterminant.GetLength(0);
// Prepare sub Determinant
double [][] newDeterminant = new double [size][];
for(int arrayIndex = 0; arrayIndex < size; arrayIndex++)
{
newDeterminant[arrayIndex] = new double [size];
}
// Copy columns
for( int column = 0; column < size; column++ )
{
// Copy rows
for( int row = 0; row < size; row++ )
{
newDeterminant[column][row] = inputDeterminant[column][row];
}
}
// Return new determinant
return newDeterminant;
}
#endregion
}
}