Failed Conditions
Push — master ( 07c60b...7328e1 )
by Adrien
12:29
created

BestFit::calculateGoodnessOfFit()   C

Complexity

Conditions 11
Paths 160

Size

Total Lines 51
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 11.0295

Importance

Changes 0
Metric Value
eloc 38
c 0
b 0
f 0
dl 0
loc 51
ccs 30
cts 32
cp 0.9375
rs 6.8166
cc 11
nc 160
nop 8
crap 11.0295

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
     * @param float $sumX2
353
     * @param float $sumY2
354
     * @param float $sumXY
355
     * @param float $meanX
356
     * @param float $meanY
357
     * @param bool|int $const
358
     */
359 37
    protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
360
    {
361 37
        $SSres = $SScov = $SStot = $SSsex = 0.0;
362 37
        foreach ($this->xValues as $xKey => $xValue) {
363 37
            $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
364
365 37
            $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
366 37
            if ($const === true) {
367 32
                $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
368
            } else {
369 5
                $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
370
            }
371 37
            $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
372 37
            if ($const === true) {
373 32
                $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
374
            } else {
375 5
                $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
376
            }
377
        }
378
379 37
        $this->SSResiduals = $SSres;
380 37
        $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
381
382 37
        if ($this->DFResiduals == 0.0) {
383 1
            $this->stdevOfResiduals = 0.0;
384
        } else {
385 36
            $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
386
        }
387
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
        } else {
391 37
            $this->goodnessOfFit = 1 - ($SSres / $SStot);
392
        }
393
394 37
        $this->SSRegression = $this->goodnessOfFit * $SStot;
395 37
        $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 37
        $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
398 37
        $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
399 37
        if ($this->SSResiduals != 0.0) {
400 26
            if ($this->DFResiduals == 0.0) {
401
                $this->f = 0.0;
402
            } else {
403 26
                $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
404
            }
405
        } else {
406 12
            if ($this->DFResiduals == 0.0) {
407 1
                $this->f = 0.0;
408
            } else {
409 11
                $this->f = $this->SSRegression / $this->DFResiduals;
410
            }
411
        }
412
    }
413
414
    /** @return float|int */
415 37
    private function sumSquares(array $values)
416
    {
417 37
        return array_sum(
418 37
            array_map(
419 37
                fn ($value): float|int => $value ** 2,
420 37
                $values
421 37
            )
422 37
        );
423
    }
424
425
    /**
426
     * @param float[] $yValues
427
     * @param float[] $xValues
428
     */
429 37
    protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
430
    {
431
        // calculate sums
432 37
        $sumValuesX = array_sum($xValues);
433 37
        $sumValuesY = array_sum($yValues);
434 37
        $meanValueX = $sumValuesX / $this->valueCount;
435 37
        $meanValueY = $sumValuesY / $this->valueCount;
436 37
        $sumSquaresX = $this->sumSquares($xValues);
437 37
        $sumSquaresY = $this->sumSquares($yValues);
438 37
        $mBase = $mDivisor = 0.0;
439 37
        $xy_sum = 0.0;
440 37
        for ($i = 0; $i < $this->valueCount; ++$i) {
441 37
            $xy_sum += $xValues[$i] * $yValues[$i];
442
443 37
            if ($const === true) {
444 32
                $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
445 32
                $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
446
            } else {
447 5
                $mBase += $xValues[$i] * $yValues[$i];
448 5
                $mDivisor += $xValues[$i] * $xValues[$i];
449
            }
450
        }
451
452
        // calculate slope
453 37
        $this->slope = $mBase / $mDivisor;
454
455
        // calculate intersect
456 37
        $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
457
458 37
        $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
459
    }
460
461
    /**
462
     * Define the regression.
463
     *
464
     * @param float[] $yValues The set of Y-values for this regression
465
     * @param float[] $xValues The set of X-values for this regression
466
     */
467 37
    public function __construct($yValues, $xValues = [])
468
    {
469
        //    Calculate number of points
470 37
        $yValueCount = count($yValues);
471 37
        $xValueCount = count($xValues);
472
473
        //    Define X Values if necessary
474 37
        if ($xValueCount === 0) {
475
            $xValues = range(1, $yValueCount);
476 37
        } elseif ($yValueCount !== $xValueCount) {
477
            //    Ensure both arrays of points are the same size
478
            $this->error = true;
479
        }
480
481 37
        $this->valueCount = $yValueCount;
482 37
        $this->xValues = $xValues;
483 37
        $this->yValues = $yValues;
484
    }
485
}
486