Failed Conditions
Pull Request — master (#3743)
by Adrien
14:08
created

BestFit::scrutinizerLooseCompare()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
4
5
abstract class BestFit
6
{
7
    /**
8
     * Indicator flag for a calculation error.
9
     *
10
     * @var bool
11
     */
12
    protected $error = false;
13
14
    /**
15
     * Algorithm type to use for best-fit.
16
     *
17
     * @var string
18
     */
19
    protected $bestFitType = 'undetermined';
20
21
    /**
22
     * Number of entries in the sets of x- and y-value arrays.
23
     */
24
    protected int $valueCount;
25
26
    /**
27
     * X-value dataseries of values.
28
     *
29
     * @var float[]
30
     */
31
    protected $xValues = [];
32
33
    /**
34
     * Y-value dataseries of values.
35
     *
36
     * @var float[]
37
     */
38
    protected $yValues = [];
39
40
    /**
41
     * Flag indicating whether values should be adjusted to Y=0.
42
     *
43
     * @var bool
44
     */
45
    protected $adjustToZero = false;
46
47
    /**
48
     * Y-value series of best-fit values.
49
     *
50
     * @var float[]
51
     */
52
    protected $yBestFitValues = [];
53
54
    /** @var float */
55
    protected $goodnessOfFit = 1;
56
57
    /** @var float */
58
    protected $stdevOfResiduals = 0;
59
60
    /** @var float */
61
    protected $covariance = 0;
62
63
    /** @var float */
64
    protected $correlation = 0;
65
66
    /** @var float */
67
    protected $SSRegression = 0;
68
69
    /** @var float */
70
    protected $SSResiduals = 0;
71
72
    /** @var float */
73
    protected $DFResiduals = 0;
74
75
    /** @var float */
76
    protected $f = 0;
77
78
    /** @var float */
79
    protected $slope = 0;
80
81
    /** @var float */
82
    protected $slopeSE = 0;
83
84
    /** @var float */
85
    protected $intersect = 0;
86
87
    /** @var float */
88
    protected $intersectSE = 0;
89
90
    /** @var float */
91
    protected $xOffset = 0;
92
93
    /** @var float */
94
    protected $yOffset = 0;
95
96
    /** @return bool */
97
    public function getError()
98
    {
99
        return $this->error;
100
    }
101
102
    /** @return string */
103
    public function getBestFitType()
104
    {
105
        return $this->bestFitType;
106
    }
107
108
    /**
109
     * Return the Y-Value for a specified value of X.
110
     *
111
     * @param float $xValue X-Value
112
     *
113
     * @return float Y-Value
114
     */
115
    abstract public function getValueOfYForX($xValue);
116
117
    /**
118
     * Return the X-Value for a specified value of Y.
119
     *
120
     * @param float $yValue Y-Value
121
     *
122
     * @return float X-Value
123
     */
124
    abstract public function getValueOfXForY($yValue);
125
126
    /**
127
     * Return the original set of X-Values.
128
     *
129
     * @return float[] X-Values
130
     */
131 4
    public function getXValues()
132
    {
133 4
        return $this->xValues;
134
    }
135
136
    /**
137
     * Return the Equation of the best-fit line.
138
     *
139
     * @param int $dp Number of places of decimal precision to display
140
     *
141
     * @return string
142
     */
143
    abstract public function getEquation($dp = 0);
144
145
    /**
146
     * Return the Slope of the line.
147
     *
148
     * @param int $dp Number of places of decimal precision to display
149
     *
150
     * @return float
151
     */
152 41
    public function getSlope($dp = 0)
153
    {
154 41
        if ($dp != 0) {
155 2
            return round($this->slope, $dp);
156
        }
157
158 41
        return $this->slope;
159
    }
160
161
    /**
162
     * Return the standard error of the Slope.
163
     *
164
     * @param int $dp Number of places of decimal precision to display
165
     *
166
     * @return float
167
     */
168 3
    public function getSlopeSE($dp = 0)
169
    {
170 3
        if ($dp != 0) {
171
            return round($this->slopeSE, $dp);
172
        }
173
174 3
        return $this->slopeSE;
175
    }
176
177
    /**
178
     * Return the Value of X where it intersects Y = 0.
179
     *
180
     * @param int $dp Number of places of decimal precision to display
181
     *
182
     * @return float
183
     */
184 39
    public function getIntersect($dp = 0)
185
    {
186 39
        if ($dp != 0) {
187 2
            return round($this->intersect, $dp);
188
        }
189
190 39
        return $this->intersect;
191
    }
192
193
    /**
194
     * Return the standard error of the Intersect.
195
     *
196
     * @param int $dp Number of places of decimal precision to display
197
     *
198
     * @return float
199
     */
200 2
    public function getIntersectSE($dp = 0)
201
    {
202 2
        if ($dp != 0) {
203
            return round($this->intersectSE, $dp);
204
        }
205
206 2
        return $this->intersectSE;
207
    }
208
209
    /**
210
     * Return the goodness of fit for this regression.
211
     *
212
     * @param int $dp Number of places of decimal precision to return
213
     *
214
     * @return float
215
     */
216 10
    public function getGoodnessOfFit($dp = 0)
217
    {
218 10
        if ($dp != 0) {
219 3
            return round($this->goodnessOfFit, $dp);
220
        }
221
222 10
        return $this->goodnessOfFit;
223
    }
224
225
    /**
226
     * Return the goodness of fit for this regression.
227
     *
228
     * @param int $dp Number of places of decimal precision to return
229
     *
230
     * @return float
231
     */
232
    public function getGoodnessOfFitPercent($dp = 0)
233
    {
234
        if ($dp != 0) {
235
            return round($this->goodnessOfFit * 100, $dp);
236
        }
237
238
        return $this->goodnessOfFit * 100;
239
    }
240
241
    /**
242
     * Return the standard deviation of the residuals for this regression.
243
     *
244
     * @param int $dp Number of places of decimal precision to return
245
     *
246
     * @return float
247
     */
248 7
    public function getStdevOfResiduals($dp = 0)
249
    {
250 7
        if ($dp != 0) {
251
            return round($this->stdevOfResiduals, $dp);
252
        }
253
254 7
        return $this->stdevOfResiduals;
255
    }
256
257
    /**
258
     * @param int $dp Number of places of decimal precision to return
259
     *
260
     * @return float
261
     */
262 3
    public function getSSRegression($dp = 0)
263
    {
264 3
        if ($dp != 0) {
265
            return round($this->SSRegression, $dp);
266
        }
267
268 3
        return $this->SSRegression;
269
    }
270
271
    /**
272
     * @param int $dp Number of places of decimal precision to return
273
     *
274
     * @return float
275
     */
276 3
    public function getSSResiduals($dp = 0)
277
    {
278 3
        if ($dp != 0) {
279
            return round($this->SSResiduals, $dp);
280
        }
281
282 3
        return $this->SSResiduals;
283
    }
284
285
    /**
286
     * @param int $dp Number of places of decimal precision to return
287
     *
288
     * @return float
289
     */
290 3
    public function getDFResiduals($dp = 0)
291
    {
292 3
        if ($dp != 0) {
293
            return round($this->DFResiduals, $dp);
294
        }
295
296 3
        return $this->DFResiduals;
297
    }
298
299
    /**
300
     * @param int $dp Number of places of decimal precision to return
301
     *
302
     * @return float
303
     */
304 3
    public function getF($dp = 0)
305
    {
306 3
        if ($dp != 0) {
307
            return round($this->f, $dp);
308
        }
309
310 3
        return $this->f;
311
    }
312
313
    /**
314
     * @param int $dp Number of places of decimal precision to return
315
     *
316
     * @return float
317
     */
318 5
    public function getCovariance($dp = 0)
319
    {
320 5
        if ($dp != 0) {
321
            return round($this->covariance, $dp);
322
        }
323
324 5
        return $this->covariance;
325
    }
326
327
    /**
328
     * @param int $dp Number of places of decimal precision to return
329
     *
330
     * @return float
331
     */
332 4
    public function getCorrelation($dp = 0)
333
    {
334 4
        if ($dp != 0) {
335
            return round($this->correlation, $dp);
336
        }
337
338 4
        return $this->correlation;
339
    }
340
341
    /**
342
     * @return float[]
343
     */
344
    public function getYBestFitValues()
345
    {
346
        return $this->yBestFitValues;
347
    }
348
349
    /**
350
     * @param float $sumX
351
     * @param float $sumY
352 37
     * @param float $sumX2
353
     * @param float $sumY2
354 37
     * @param float $sumXY
355
     * @param float $meanX
356
     * @param float $meanY
357
     * @param bool|int $const
358
     */
359
    protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
360
    {
361
        $SSres = $SScov = $SStot = $SSsex = 0.0;
362
        foreach ($this->xValues as $xKey => $xValue) {
363
            $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
364
365
            $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
366
            if ($const === true) {
367 37
                $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
368
            } else {
369 37
                $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
370 37
            }
371 37
            $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
372
            if ($const === true) {
373 37
                $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
374 37
            } else {
375 32
                $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
376
            }
377 5
        }
378
379 37
        $this->SSResiduals = $SSres;
380 37
        $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
381 32
382
        if ($this->DFResiduals == 0.0) {
383 5
            $this->stdevOfResiduals = 0.0;
384
        } else {
385
            $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
386
        }
387 37
388 37
        if ($SStot == 0.0 || $SSres == $SStot) {
1 ignored issue
show
introduced by
The condition $SStot == 0.0 is always true.
Loading history...
389
            $this->goodnessOfFit = 1;
390 37
        } else {
391 1
            $this->goodnessOfFit = 1 - ($SSres / $SStot);
392
        }
393 36
394
        $this->SSRegression = $this->goodnessOfFit * $SStot;
395
        $this->covariance = $SScov / $this->valueCount;
396 37
        $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
397
        $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
398
        $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
399 37
        if ($this->SSResiduals != 0.0) {
400
            if ($this->DFResiduals == 0.0) {
401
                $this->f = 0.0;
402 37
            } else {
403 37
                $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
404 37
            }
405 37
        } else {
406 37
            if ($this->DFResiduals == 0.0) {
407 37
                $this->f = 0.0;
408 26
            } else {
409
                $this->f = $this->SSRegression / $this->DFResiduals;
410
            }
411 26
        }
412
    }
413
414 12
    /** @return float|int */
415 1
    private function sumSquares(array $values)
416
    {
417 11
        return array_sum(
418
            array_map(
419
                fn ($value): float|int => $value ** 2,
420
                $values
421
            )
422
        );
423 37
    }
424
425 37
    /**
426 37
     * @param float[] $yValues
427 37
     * @param float[] $xValues
428 37
     */
429 37
    protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
430 37
    {
431
        // calculate sums
432
        $sumValuesX = array_sum($xValues);
433
        $sumValuesY = array_sum($yValues);
434
        $meanValueX = $sumValuesX / $this->valueCount;
435
        $meanValueY = $sumValuesY / $this->valueCount;
436
        $sumSquaresX = $this->sumSquares($xValues);
437 37
        $sumSquaresY = $this->sumSquares($yValues);
438
        $mBase = $mDivisor = 0.0;
439
        $xy_sum = 0.0;
440 37
        for ($i = 0; $i < $this->valueCount; ++$i) {
441 37
            $xy_sum += $xValues[$i] * $yValues[$i];
442 37
443 37
            if ($const === true) {
444 37
                $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
445 37
                $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
446 37
            } else {
447 37
                $mBase += $xValues[$i] * $yValues[$i];
448 37
                $mDivisor += $xValues[$i] * $xValues[$i];
449 37
            }
450
        }
451 37
452 32
        // calculate slope
453 32
        $this->slope = $mBase / $mDivisor;
454
455 5
        // calculate intersect
456 5
        $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
457
458
        $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
459
    }
460
461 37
    /**
462
     * Define the regression.
463
     *
464 37
     * @param float[] $yValues The set of Y-values for this regression
465
     * @param float[] $xValues The set of X-values for this regression
466 37
     */
467
    public function __construct($yValues, $xValues = [])
468
    {
469
        //    Calculate number of points
470
        $yValueCount = count($yValues);
471
        $xValueCount = count($xValues);
472
473
        //    Define X Values if necessary
474
        if ($xValueCount === 0) {
475 37
            $xValues = range(1, $yValueCount);
476
        } elseif ($yValueCount !== $xValueCount) {
477
            //    Ensure both arrays of points are the same size
478 37
            $this->error = true;
479 37
        }
480
481
        $this->valueCount = $yValueCount;
482 37
        $this->xValues = $xValues;
483
        $this->yValues = $yValues;
484 37
    }
485
}
486