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