PolynomialBestFit.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
  3. use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;
  4. class PolynomialBestFit extends BestFit
  5. {
  6. /**
  7. * Algorithm type to use for best-fit
  8. * (Name of this Trend class).
  9. *
  10. * @var string
  11. */
  12. protected $bestFitType = 'polynomial';
  13. /**
  14. * Polynomial order.
  15. *
  16. * @var int
  17. */
  18. protected $order = 0;
  19. /**
  20. * Return the order of this polynomial.
  21. *
  22. * @return int
  23. */
  24. public function getOrder()
  25. {
  26. return $this->order;
  27. }
  28. /**
  29. * Return the Y-Value for a specified value of X.
  30. *
  31. * @param float $xValue X-Value
  32. *
  33. * @return float Y-Value
  34. */
  35. public function getValueOfYForX($xValue)
  36. {
  37. $retVal = $this->getIntersect();
  38. $slope = $this->getSlope();
  39. foreach ($slope as $key => $value) {
  40. if ($value != 0.0) {
  41. $retVal += $value * pow($xValue, $key + 1);
  42. }
  43. }
  44. return $retVal;
  45. }
  46. /**
  47. * Return the X-Value for a specified value of Y.
  48. *
  49. * @param float $yValue Y-Value
  50. *
  51. * @return float X-Value
  52. */
  53. public function getValueOfXForY($yValue)
  54. {
  55. return ($yValue - $this->getIntersect()) / $this->getSlope();
  56. }
  57. /**
  58. * Return the Equation of the best-fit line.
  59. *
  60. * @param int $dp Number of places of decimal precision to display
  61. *
  62. * @return string
  63. */
  64. public function getEquation($dp = 0)
  65. {
  66. $slope = $this->getSlope($dp);
  67. $intersect = $this->getIntersect($dp);
  68. $equation = 'Y = ' . $intersect;
  69. foreach ($slope as $key => $value) {
  70. if ($value != 0.0) {
  71. $equation .= ' + ' . $value . ' * X';
  72. if ($key > 0) {
  73. $equation .= '^' . ($key + 1);
  74. }
  75. }
  76. }
  77. return $equation;
  78. }
  79. /**
  80. * Return the Slope of the line.
  81. *
  82. * @param int $dp Number of places of decimal precision to display
  83. *
  84. * @return string
  85. */
  86. public function getSlope($dp = 0)
  87. {
  88. if ($dp != 0) {
  89. $coefficients = [];
  90. foreach ($this->slope as $coefficient) {
  91. $coefficients[] = round($coefficient, $dp);
  92. }
  93. return $coefficients;
  94. }
  95. return $this->slope;
  96. }
  97. public function getCoefficients($dp = 0)
  98. {
  99. return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
  100. }
  101. /**
  102. * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
  103. *
  104. * @param int $order Order of Polynomial for this regression
  105. * @param float[] $yValues The set of Y-values for this regression
  106. * @param float[] $xValues The set of X-values for this regression
  107. */
  108. private function polynomialRegression($order, $yValues, $xValues)
  109. {
  110. // calculate sums
  111. $x_sum = array_sum($xValues);
  112. $y_sum = array_sum($yValues);
  113. $xx_sum = $xy_sum = 0;
  114. for ($i = 0; $i < $this->valueCount; ++$i) {
  115. $xy_sum += $xValues[$i] * $yValues[$i];
  116. $xx_sum += $xValues[$i] * $xValues[$i];
  117. $yy_sum += $yValues[$i] * $yValues[$i];
  118. }
  119. /*
  120. * This routine uses logic from the PHP port of polyfit version 0.1
  121. * written by Michael Bommarito and Paul Meagher
  122. *
  123. * The function fits a polynomial function of order $order through
  124. * a series of x-y data points using least squares.
  125. *
  126. */
  127. for ($i = 0; $i < $this->valueCount; ++$i) {
  128. for ($j = 0; $j <= $order; ++$j) {
  129. $A[$i][$j] = pow($xValues[$i], $j);
  130. }
  131. }
  132. for ($i = 0; $i < $this->valueCount; ++$i) {
  133. $B[$i] = [$yValues[$i]];
  134. }
  135. $matrixA = new Matrix($A);
  136. $matrixB = new Matrix($B);
  137. $C = $matrixA->solve($matrixB);
  138. $coefficients = [];
  139. for ($i = 0; $i < $C->getRowDimension(); ++$i) {
  140. $r = $C->get($i, 0);
  141. if (abs($r) <= pow(10, -9)) {
  142. $r = 0;
  143. }
  144. $coefficients[] = $r;
  145. }
  146. $this->intersect = array_shift($coefficients);
  147. $this->slope = $coefficients;
  148. $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum);
  149. foreach ($this->xValues as $xKey => $xValue) {
  150. $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
  151. }
  152. }
  153. /**
  154. * Define the regression and calculate the goodness of fit for a set of X and Y data values.
  155. *
  156. * @param int $order Order of Polynomial for this regression
  157. * @param float[] $yValues The set of Y-values for this regression
  158. * @param float[] $xValues The set of X-values for this regression
  159. * @param bool $const
  160. */
  161. public function __construct($order, $yValues, $xValues = [], $const = true)
  162. {
  163. if (parent::__construct($yValues, $xValues) !== false) {
  164. if ($order < $this->valueCount) {
  165. $this->bestFitType .= '_' . $order;
  166. $this->order = $order;
  167. $this->polynomialRegression($order, $yValues, $xValues);
  168. if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
  169. $this->error = true;
  170. }
  171. } else {
  172. $this->error = true;
  173. }
  174. }
  175. }
  176. }