PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Shared / Trend / PolynomialBestFit.php

PolynomialBestFit.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php

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