TimeSeriesAndForecasting.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. //
  5. // Purpose: This class is used for calculations of
  6. // time series and forecasting
  7. //
  8. using System;
  9. using System.Globalization;
  10. namespace FastReport.DataVisualization.Charting.Formulas
  11. {
  12. /// <summary>
  13. /// This class is used for calculations of
  14. /// time series and forecasting
  15. /// </summary>
  16. internal class TimeSeriesAndForecasting : IFormula
  17. {
  18. #region Enumeration
  19. /// <summary>
  20. /// AxisName of regression
  21. /// </summary>
  22. internal enum RegressionType
  23. {
  24. /// <summary>
  25. /// Polynomial trend
  26. /// </summary>
  27. Polynomial,
  28. /// <summary>
  29. /// IsLogarithmic trend
  30. /// </summary>
  31. Logarithmic,
  32. /// <summary>
  33. /// Power trend
  34. /// </summary>
  35. Power,
  36. /// <summary>
  37. /// Exponential trend
  38. /// </summary>
  39. Exponential
  40. }
  41. #endregion
  42. #region Properties
  43. /// <summary>
  44. /// Formula Module name
  45. /// </summary>
  46. virtual public string Name { get { return SR.FormulaNameTimeSeriesAndForecasting; } }
  47. #endregion
  48. #region Methods
  49. /// <summary>
  50. /// Public constructor.
  51. /// </summary>
  52. public TimeSeriesAndForecasting()
  53. {
  54. }
  55. /// <summary>
  56. /// The first method in the module, which converts a formula
  57. /// name to the corresponding private method.
  58. /// </summary>
  59. /// <param name="formulaName">String which represent a formula name</param>
  60. /// <param name="inputValues">Arrays of doubles - Input values</param>
  61. /// <param name="outputValues">Arrays of doubles - Output values</param>
  62. /// <param name="parameterList">Array of strings - Formula parameters</param>
  63. /// <param name="extraParameterList">Array of strings - Extra Formula parameters from DataManipulator object</param>
  64. /// <param name="outLabels">Array of strings - Used for Labels. Description for output results.</param>
  65. virtual public void Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels )
  66. {
  67. string name;
  68. name = formulaName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
  69. // Not used for these formulas.
  70. outLabels = null;
  71. try
  72. {
  73. switch( name )
  74. {
  75. case "FORECASTING":
  76. Forecasting( inputValues, out outputValues, parameterList );
  77. break;
  78. default:
  79. outputValues = null;
  80. break;
  81. }
  82. }
  83. catch( IndexOutOfRangeException )
  84. {
  85. throw new InvalidOperationException( SR.ExceptionFormulaInvalidPeriod(name) );
  86. }
  87. catch( OverflowException )
  88. {
  89. throw new InvalidOperationException( SR.ExceptionFormulaNotEnoughDataPoints( name ) );
  90. }
  91. }
  92. #endregion
  93. #region Formulas
  94. /// <summary>
  95. /// Forecasting formula predicts future values of the time series variable.
  96. /// Multiple regressions are used for this forecasting model. Any method
  97. /// of fitting equations to data may be called regression. Such equations
  98. /// are valuable for at least two purposes: making predictions and judging
  99. /// the strength of relationships. Of the various methods of performing
  100. /// regression, Last Square is the most widely used. This formula returns
  101. /// two more series, which represents upper and lower bond of error. Error
  102. /// is based on standard deviation and represents a linear combination of
  103. /// approximation error and forecasting error.
  104. /// ---------------------------------------------------------
  105. /// Input:
  106. /// - Y values.
  107. /// Output:
  108. /// - Forecasting
  109. /// - upper bond error
  110. /// - lower bond error
  111. /// Parameters:
  112. /// - Polynomial degree (Default: 2 - Linear regression )
  113. /// - Forecasting period (Default: Half of the series length )
  114. /// - Returns Approximation error (Default: true)
  115. /// - Returns Forecasting error (Default: true)
  116. /// </summary>
  117. /// <param name="inputValues">Arrays of doubles - Input values</param>
  118. /// <param name="outputValues">Arrays of doubles - Output values</param>
  119. /// <param name="parameterList">Array of strings - Parameters</param>
  120. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  121. private void Forecasting(double[][] inputValues, out double[][] outputValues, string[] parameterList)
  122. {
  123. // Polynomial degree
  124. int degree;
  125. RegressionType regressionType = RegressionType.Polynomial;
  126. if (String.Equals(parameterList[0],"Exponential", StringComparison.OrdinalIgnoreCase))
  127. {
  128. regressionType = RegressionType.Exponential;
  129. degree = 2;
  130. }
  131. else if (String.Equals(parameterList[0],"Linear", StringComparison.OrdinalIgnoreCase))
  132. {
  133. regressionType = RegressionType.Polynomial;
  134. degree = 2;
  135. }
  136. else if (String.Equals(parameterList[0],"IsLogarithmic", StringComparison.OrdinalIgnoreCase) ||
  137. String.Equals(parameterList[0],"Logarithmic", StringComparison.OrdinalIgnoreCase))
  138. {
  139. regressionType = RegressionType.Logarithmic;
  140. degree = 2;
  141. }
  142. else if (String.Equals(parameterList[0],"Power", StringComparison.OrdinalIgnoreCase))
  143. {
  144. regressionType = RegressionType.Power;
  145. degree = 2;
  146. }
  147. else
  148. {
  149. if (parameterList.Length < 1 ||
  150. !int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out degree))
  151. {
  152. degree = 2;
  153. }
  154. }
  155. if (degree > 5 || degree < 1)
  156. throw new InvalidOperationException(SR.ExceptionForecastingDegreeInvalid);
  157. if (degree > inputValues[0].Length)
  158. throw new InvalidOperationException(SR.ExceptionForecastingNotEnoughDataPoints(degree.ToString(System.Globalization.CultureInfo.InvariantCulture)));
  159. // Forecasting period
  160. int period;
  161. if (parameterList.Length < 2 ||
  162. !int.TryParse(parameterList[1], NumberStyles.Any, CultureInfo.InvariantCulture, out period))
  163. {
  164. period = inputValues[0].Length / 2;
  165. }
  166. // Approximation error
  167. bool approximationError;
  168. if (parameterList.Length < 3 ||
  169. !bool.TryParse(parameterList[2], out approximationError))
  170. {
  171. approximationError = true;
  172. }
  173. // Forecasting error
  174. bool forecastingError;
  175. if (parameterList.Length < 4 ||
  176. !bool.TryParse(parameterList[3], out forecastingError))
  177. {
  178. forecastingError = true;
  179. }
  180. double[][] tempOut;
  181. // Find regresion
  182. Regression(regressionType, inputValues, out tempOut, degree, period);
  183. // If error disabled get out from procedure
  184. if (!forecastingError && !approximationError)
  185. {
  186. outputValues = tempOut;
  187. return;
  188. }
  189. double[][] inputErrorEst = new double[2][];
  190. double[][] outputErrorEst;
  191. inputErrorEst[0] = new double[inputValues[0].Length / 2];
  192. inputErrorEst[1] = new double[inputValues[0].Length / 2];
  193. for (int index = 0; index < inputValues[0].Length / 2; index++)
  194. {
  195. inputErrorEst[0][index] = inputValues[0][index];
  196. inputErrorEst[1][index] = inputValues[1][index];
  197. }
  198. Regression(regressionType, inputErrorEst, out outputErrorEst, degree, inputValues[0].Length / 2);
  199. // Find the average for forecasting error
  200. double error = 0;
  201. for (int index = inputValues[0].Length / 2; index < outputErrorEst[1].Length; index++)
  202. {
  203. error += (outputErrorEst[1][index] - inputValues[1][index]) * (outputErrorEst[1][index] - inputValues[1][index]);
  204. }
  205. error /= inputValues[0].Length - inputValues[0].Length / 2;
  206. error = Math.Sqrt(error);
  207. error /= (inputValues[0].Length / 4);
  208. // Find the standard deviation
  209. double dev = 0;
  210. for (int index = 0; index < inputValues[0].Length; index++)
  211. {
  212. dev += (tempOut[1][index] - inputValues[1][index]) * (tempOut[1][index] - inputValues[1][index]);
  213. }
  214. dev /= inputValues[0].Length;
  215. dev = Math.Sqrt(dev);
  216. outputValues = new double[4][];
  217. outputValues[0] = tempOut[0];
  218. outputValues[1] = tempOut[1];
  219. outputValues[2] = new double[tempOut[0].Length];
  220. outputValues[3] = new double[tempOut[0].Length];
  221. if (!approximationError)
  222. dev = 0;
  223. if (!forecastingError)
  224. error = 0;
  225. for (int index = 0; index < inputValues[0].Length; index++)
  226. {
  227. outputValues[2][index] = tempOut[1][index] + 2 * dev;
  228. outputValues[3][index] = tempOut[1][index] - 2 * dev;
  229. }
  230. double sumError = 0;
  231. for (int index = inputValues[0].Length; index < tempOut[0].Length; index++)
  232. {
  233. sumError += error;
  234. outputValues[2][index] = tempOut[1][index] + sumError + 2 * dev;
  235. outputValues[3][index] = tempOut[1][index] - sumError - 2 * dev;
  236. }
  237. }
  238. /// <summary>
  239. /// Any method of fitting equations to data may be called regression.
  240. /// Such equations are valuable for at least two purposes: making
  241. /// predictions and judging the strength of relationships. Of the
  242. /// various methods of performing regression, Last Square is the
  243. /// most widely used.
  244. /// </summary>
  245. /// <param name="regressionType">AxisName of regression Polynomial, exponential, etc.</param>
  246. /// <param name="inputValues">Arrays of doubles - Input values</param>
  247. /// <param name="outputValues">Arrays of doubles - Output values</param>
  248. /// <param name="polynomialDegree">Polynomial degree (Default: 2 - Linear regression )</param>
  249. /// <param name="forecastingPeriod">Forecasting period (Default: Half of the series length )</param>
  250. private void Regression( RegressionType regressionType, double [][] inputValues, out double [][] outputValues, int polynomialDegree, int forecastingPeriod )
  251. {
  252. if( regressionType == RegressionType.Exponential )
  253. {
  254. double [] oldYValues = new double[ inputValues[1].Length ];
  255. for( int index = 0; index < inputValues[1].Length; index++ )
  256. {
  257. oldYValues[ index ] = inputValues[1][index];
  258. if( inputValues[1][index] <= 0 )
  259. {
  260. throw new InvalidOperationException(SR.ExceptionForecastingExponentialRegressionHasZeroYValues);
  261. }
  262. inputValues[1][index] = Math.Log( inputValues[1][index] );
  263. }
  264. PolynomialRegression( regressionType, inputValues, out outputValues, 2, forecastingPeriod, 0 );
  265. inputValues[1] = oldYValues;
  266. }
  267. else if( regressionType == RegressionType.Logarithmic )
  268. {
  269. double interval;
  270. double first = inputValues[0][0];
  271. // Find Interval for X values
  272. interval = Math.Abs( inputValues[0][0] - inputValues[0][inputValues[0].Length - 1] ) / ( inputValues[0].Length - 1 );
  273. if( interval <= 0 )
  274. interval = 1;
  275. for( int index = 0; index < inputValues[0].Length; index++ )
  276. {
  277. inputValues[0][index] = Math.Log( inputValues[0][index] );
  278. }
  279. PolynomialRegression( regressionType, inputValues, out outputValues, 2, forecastingPeriod, interval );
  280. // Create Y values based on approximation.
  281. for( int i = 0; i < outputValues[0].Length; i++ )
  282. {
  283. // Set X value
  284. outputValues[0][i] = first + i * interval;
  285. }
  286. }
  287. else if( regressionType == RegressionType.Power )
  288. {
  289. double [] oldYValues = new double[ inputValues[1].Length ];
  290. double interval;
  291. double first = inputValues[0][0];
  292. for( int index = 0; index < inputValues[1].Length; index++ )
  293. {
  294. oldYValues[ index ] = inputValues[1][index];
  295. if( inputValues[1][index] <= 0 )
  296. {
  297. throw new InvalidOperationException(SR.ExceptionForecastingPowerRegressionHasZeroYValues);
  298. }
  299. }
  300. // Find Interval for X values
  301. interval = Math.Abs( inputValues[0][0] - inputValues[0][inputValues[0].Length - 1] ) / ( inputValues[0].Length - 1 );
  302. if( interval <= 0 )
  303. interval = 1;
  304. PolynomialRegression( regressionType, inputValues, out outputValues, 2, forecastingPeriod, interval );
  305. inputValues[1] = oldYValues;
  306. // Create Y values based on approximation.
  307. for( int i = 0; i < outputValues[0].Length; i++ )
  308. {
  309. // Set X value
  310. outputValues[0][i] = first + i * interval;
  311. }
  312. }
  313. else
  314. {
  315. PolynomialRegression( regressionType, inputValues, out outputValues, polynomialDegree, forecastingPeriod, 0 );
  316. }
  317. }
  318. /// <summary>
  319. /// Any method of fitting equations to data may be called regression.
  320. /// Such equations are valuable for at least two purposes: making
  321. /// predictions and judging the strength of relationships. Of the
  322. /// various methods of performing regression, Last Square is the
  323. /// most widely used.
  324. /// </summary>
  325. /// <param name="regressionType">AxisName of regression Polynomial, exponential, etc.</param>
  326. /// <param name="inputValues">Arrays of doubles - Input values</param>
  327. /// <param name="outputValues">Arrays of doubles - Output values</param>
  328. /// <param name="polynomialDegree">Polynomial degree (Default: 2 - Linear regression )</param>
  329. /// <param name="forecastingPeriod">Forecasting period (Default: Half of the series length )</param>
  330. /// <param name="logInterval">Interval for logarithmic scale</param>
  331. private void PolynomialRegression( RegressionType regressionType, double [][] inputValues, out double [][] outputValues, int polynomialDegree, int forecastingPeriod, double logInterval )
  332. {
  333. double [] coefficients = new double [polynomialDegree];
  334. int size = inputValues[0].Length;
  335. double minimumX = double.MaxValue;
  336. double interval = 1.0;
  337. // Find Interval for X values
  338. interval = Math.Abs( inputValues[0][0] - inputValues[0][inputValues[0].Length - 1] ) / ( inputValues[0].Length - 1 );
  339. if( interval <= 0 )
  340. interval = 1;
  341. if( regressionType != RegressionType.Logarithmic )
  342. {
  343. // Avoid Rounding error because of big X values.
  344. // Find Minimum X value
  345. for( int xIndex = 0; xIndex < inputValues[0].Length; xIndex++ )
  346. {
  347. if( minimumX > inputValues[0][xIndex] )
  348. minimumX = inputValues[0][xIndex];
  349. }
  350. // Change X values.
  351. for( int xIndex = 0; xIndex < inputValues[0].Length; xIndex++ )
  352. {
  353. inputValues[0][xIndex] -= minimumX - 1;
  354. }
  355. }
  356. if( regressionType == RegressionType.Power )
  357. {
  358. for( int index = 0; index < inputValues[0].Length; index++ )
  359. {
  360. inputValues[0][index] = Math.Log( inputValues[0][index] );
  361. inputValues[1][index] = Math.Log( inputValues[1][index] );
  362. }
  363. }
  364. double [][] mainDeterminant = new double [polynomialDegree][];
  365. for(int arrayIndex = 0; arrayIndex < polynomialDegree; arrayIndex++)
  366. {
  367. mainDeterminant[arrayIndex] = new double [polynomialDegree];
  368. }
  369. // Main determinant
  370. for( int k = 0; k < polynomialDegree; k++ )
  371. {
  372. for( int i = 0; i < polynomialDegree; i++ )
  373. {
  374. mainDeterminant[i][k] = 0;
  375. for( int j = 0; j < inputValues[0].Length; j++ )
  376. {
  377. mainDeterminant[i][k] += (double)Math.Pow( inputValues[0][j], (i+k) );
  378. }
  379. }
  380. }
  381. double mainValue = Determinant(mainDeterminant);
  382. // Coefficient determinant
  383. for( int i = 0; i < polynomialDegree; i++ )
  384. {
  385. double [][] coeffDeterminant = CopyDeterminant(mainDeterminant);
  386. for( int k = 0; k < polynomialDegree; k++ )
  387. {
  388. coeffDeterminant[i][k] = 0;
  389. for( int j = 0; j < inputValues[0].Length; j++ )
  390. {
  391. coeffDeterminant[i][k] += (double)inputValues[1][j] * (double)Math.Pow( inputValues[0][j], k );
  392. }
  393. }
  394. coefficients[i] = Determinant(coeffDeterminant) / mainValue;
  395. }
  396. // Create output arrays for approximation and forecasting
  397. outputValues = new double[2][];
  398. outputValues[0] = new double[size + forecastingPeriod];
  399. outputValues[1] = new double[size + forecastingPeriod];
  400. if( regressionType == RegressionType.Polynomial )
  401. {
  402. // Create Y values based on approximation.
  403. for( int i = 0; i < size + forecastingPeriod; i++ )
  404. {
  405. // Set X value
  406. outputValues[0][i] = inputValues[0][0] + i * interval;
  407. outputValues[1][i] = 0;
  408. for( int j = 0; j < polynomialDegree; j++ )
  409. {
  410. outputValues[1][i]+= (double)coefficients[j]*Math.Pow(outputValues[0][i],j);
  411. }
  412. }
  413. }
  414. else if( regressionType == RegressionType.Exponential )
  415. {
  416. // Create Y values based on approximation.
  417. for( int i = 0; i < size + forecastingPeriod; i++ )
  418. {
  419. // Set X value
  420. outputValues[0][i] = inputValues[0][0] + i * interval;
  421. outputValues[1][i]= Math.Exp( coefficients[0] ) * Math.Exp( coefficients[1] * outputValues[0][i] );
  422. }
  423. }
  424. else if( regressionType == RegressionType.Logarithmic )
  425. {
  426. // Create Y values based on approximation.
  427. for( int i = 0; i < size + forecastingPeriod; i++ )
  428. {
  429. // Set X value
  430. outputValues[0][i] = Math.Exp( inputValues[0][0] ) + i * logInterval;
  431. outputValues[1][i]= coefficients[1] * Math.Log( outputValues[0][i] ) + coefficients[0];
  432. }
  433. }
  434. else if( regressionType == RegressionType.Power )
  435. {
  436. // Create Y values based on approximation.
  437. for( int i = 0; i < size + forecastingPeriod; i++ )
  438. {
  439. // Set X value
  440. outputValues[0][i] = Math.Exp( inputValues[0][0] ) + i * logInterval;
  441. outputValues[1][i]= Math.Exp( coefficients[0] ) * Math.Pow( outputValues[0][i], coefficients[1] );
  442. }
  443. }
  444. if( regressionType != RegressionType.Logarithmic )
  445. {
  446. // Return X values.
  447. for( int xIndex = 0; xIndex < size + forecastingPeriod; xIndex++ )
  448. {
  449. outputValues[0][xIndex] += minimumX - 1;
  450. }
  451. }
  452. }
  453. /// <summary>
  454. /// This method recalculates determinant. This method is used for
  455. /// recursive calls for sub determinants too.
  456. /// </summary>
  457. /// <param name="inputDeterminant">Input determinant</param>
  458. /// <returns>Result of determinant</returns>
  459. private double Determinant( double [][] inputDeterminant )
  460. {
  461. double sum = 0;
  462. double sign = 1.0;
  463. // Determinant is 2X2 - calculate value
  464. if( inputDeterminant.Length == 2 )
  465. {
  466. return inputDeterminant[0][0]*inputDeterminant[1][1] - inputDeterminant[0][1]*inputDeterminant[1][0];
  467. }
  468. // Determinant is biger than 2X2. Go to recursive
  469. // call of Determinant method
  470. for( int column = 0; column < inputDeterminant.GetLength(0); column++ )
  471. {
  472. // Make sub determinant
  473. double [][] newDeterminant = MakeSubDeterminant( inputDeterminant, column );
  474. sum += sign * Determinant( newDeterminant ) * inputDeterminant[column][0];
  475. sign *= -1.0;
  476. }
  477. return sum;
  478. }
  479. /// <summary>
  480. /// This method will create a new determinant, which is
  481. /// smaller by one rank (dimension). Specified column
  482. /// and zero rows will be skipped.
  483. /// </summary>
  484. /// <param name="inputDeterminant">Input determinant</param>
  485. /// <param name="columnPos">Position of column, which has to be skipped</param>
  486. /// <returns>New determinant</returns>
  487. private double [][] MakeSubDeterminant( double [][] inputDeterminant, int columnPos )
  488. {
  489. // Get Determinant Size
  490. int size = inputDeterminant.GetLength(0);
  491. // Prepare sub Determinant
  492. double [][] newDeterminant = new double [size - 1][];
  493. for(int arrayIndex = 0; arrayIndex < size - 1; arrayIndex++)
  494. {
  495. newDeterminant[arrayIndex] = new double [size - 1];
  496. }
  497. int newColumn = 0;
  498. // Copy columns
  499. for( int column = 0; column < size; column++ )
  500. {
  501. // Skeep this column
  502. if( column == columnPos )
  503. continue;
  504. // Copy rows
  505. for( int row = 1; row < size; row++ )
  506. {
  507. newDeterminant[newColumn][row-1] = inputDeterminant[column][row];
  508. }
  509. // Go to new column for new determinant
  510. newColumn++;
  511. }
  512. // Return new determinant
  513. return newDeterminant;
  514. }
  515. /// <summary>
  516. /// This method will copy determinant
  517. /// </summary>
  518. /// <param name="inputDeterminant">Input determinant</param>
  519. /// <returns>New determinant</returns>
  520. private double [][] CopyDeterminant( double [][] inputDeterminant )
  521. {
  522. // Get Determinant Size
  523. int size = inputDeterminant.GetLength(0);
  524. // Prepare sub Determinant
  525. double [][] newDeterminant = new double [size][];
  526. for(int arrayIndex = 0; arrayIndex < size; arrayIndex++)
  527. {
  528. newDeterminant[arrayIndex] = new double [size];
  529. }
  530. // Copy columns
  531. for( int column = 0; column < size; column++ )
  532. {
  533. // Copy rows
  534. for( int row = 0; row < size; row++ )
  535. {
  536. newDeterminant[column][row] = inputDeterminant[column][row];
  537. }
  538. }
  539. // Return new determinant
  540. return newDeterminant;
  541. }
  542. #endregion
  543. }
  544. }