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