//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @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 { /// /// This class is used for calculations of /// time series and forecasting /// internal class TimeSeriesAndForecasting : IFormula { #region Enumeration /// /// AxisName of regression /// internal enum RegressionType { /// /// Polynomial trend /// Polynomial, /// /// IsLogarithmic trend /// Logarithmic, /// /// Power trend /// Power, /// /// Exponential trend /// Exponential } #endregion #region Properties /// /// Formula Module name /// virtual public string Name { get { return SR.FormulaNameTimeSeriesAndForecasting; } } #endregion #region Methods /// /// Public constructor. /// public TimeSeriesAndForecasting() { } /// /// The first method in the module, which converts a formula /// name to the corresponding private method. /// /// String which represent a formula name /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Array of strings - Formula parameters /// Array of strings - Extra Formula parameters from DataManipulator object /// Array of strings - Used for Labels. Description for output results. 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 /// /// 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) /// /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Array of strings - Parameters [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; } } /// /// 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. /// /// AxisName of regression Polynomial, exponential, etc. /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Polynomial degree (Default: 2 - Linear regression ) /// Forecasting period (Default: Half of the series length ) 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 ); } } /// /// 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. /// /// AxisName of regression Polynomial, exponential, etc. /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Polynomial degree (Default: 2 - Linear regression ) /// Forecasting period (Default: Half of the series length ) /// Interval for logarithmic scale 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; } } } /// /// This method recalculates determinant. This method is used for /// recursive calls for sub determinants too. /// /// Input determinant /// Result of determinant 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; } /// /// This method will create a new determinant, which is /// smaller by one rank (dimension). Specified column /// and zero rows will be skipped. /// /// Input determinant /// Position of column, which has to be skipped /// New determinant 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; } /// /// This method will copy determinant /// /// Input determinant /// New determinant 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 } }