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