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

BestFit::calculateGoodnessOfFit()   C

Complexity

Conditions 11
Paths 160

Size

Total Lines 51
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 11.044

Importance

Changes 0
Metric Value
eloc 38
dl 0
loc 51
ccs 26
cts 28
cp 0.9286
rs 6.8166
c 0
b 0
f 0
cc 11
nc 160
nop 8
crap 11.044

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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