123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- //
- // Purpose: This class is used for calculations of
- // time series and forecasting
- //
- using System;
- using System.Globalization;
- namespace FastReport.DataVisualization.Charting.Formulas
- {
- /// <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
- }
- }
|