Failed Conditions
Push — master ( 67fec4...924347 )
by Adrien
07:48
created

BestFit::calculateGoodnessOfFit()   C

Complexity

Conditions 11
Paths 160

Size

Total Lines 50
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 11.0295

Importance

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

How to fix   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
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
    protected $goodnessOfFit = 1;
57
58
    protected $stdevOfResiduals = 0;
59
60
    protected $covariance = 0;
61
62
    protected $correlation = 0;
63
64
    protected $SSRegression = 0;
65
66
    protected $SSResiduals = 0;
67
68
    protected $DFResiduals = 0;
69
70
    protected $f = 0;
71
72
    protected $slope = 0;
73
74
    protected $slopeSE = 0;
75
76
    protected $intersect = 0;
77
78
    protected $intersectSE = 0;
79
80
    protected $xOffset = 0;
81
82
    protected $yOffset = 0;
83
84
    public function getError()
85
    {
86
        return $this->error;
87
    }
88
89
    public function getBestFitType()
90
    {
91
        return $this->bestFitType;
92
    }
93
94
    /**
95
     * Return the Y-Value for a specified value of X.
96
     *
97
     * @param float $xValue X-Value
98
     *
99
     * @return bool Y-Value
100
     */
101
    public function getValueOfYForX($xValue)
0 ignored issues
show
Unused Code introduced by
The parameter $xValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

101
    public function getValueOfYForX(/** @scrutinizer ignore-unused */ $xValue)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
102
    {
103
        return false;
104
    }
105
106
    /**
107
     * Return the X-Value for a specified value of Y.
108
     *
109
     * @param float $yValue Y-Value
110
     *
111
     * @return bool X-Value
112
     */
113
    public function getValueOfXForY($yValue)
0 ignored issues
show
Unused Code introduced by
The parameter $yValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

113
    public function getValueOfXForY(/** @scrutinizer ignore-unused */ $yValue)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
114
    {
115
        return false;
116
    }
117
118
    /**
119
     * Return the original set of X-Values.
120
     *
121
     * @return float[] X-Values
122
     */
123 2
    public function getXValues()
124
    {
125 2
        return $this->xValues;
126
    }
127
128
    /**
129
     * Return the Equation of the best-fit line.
130
     *
131
     * @param int $dp Number of places of decimal precision to display
132
     *
133
     * @return bool
134
     */
135
    public function getEquation($dp = 0)
136
    {
137
        return false;
138
    }
139
140
    /**
141
     * Return the Slope of the line.
142
     *
143
     * @param int $dp Number of places of decimal precision to display
144
     *
145
     * @return float
146
     */
147 37
    public function getSlope($dp = 0)
148
    {
149 37
        if ($dp != 0) {
150 2
            return round($this->slope, $dp);
151
        }
152
153 37
        return $this->slope;
154
    }
155
156
    /**
157
     * Return the standard error of the Slope.
158
     *
159
     * @param int $dp Number of places of decimal precision to display
160
     *
161
     * @return float
162
     */
163 3
    public function getSlopeSE($dp = 0)
164
    {
165 3
        if ($dp != 0) {
166
            return round($this->slopeSE, $dp);
167
        }
168
169 3
        return $this->slopeSE;
170
    }
171
172
    /**
173
     * Return the Value of X where it intersects Y = 0.
174
     *
175
     * @param int $dp Number of places of decimal precision to display
176
     *
177
     * @return float
178
     */
179 36
    public function getIntersect($dp = 0)
180
    {
181 36
        if ($dp != 0) {
182 2
            return round($this->intersect, $dp);
183
        }
184
185 36
        return $this->intersect;
186
    }
187
188
    /**
189
     * Return the standard error of the Intersect.
190
     *
191
     * @param int $dp Number of places of decimal precision to display
192
     *
193
     * @return float
194
     */
195 2
    public function getIntersectSE($dp = 0)
196
    {
197 2
        if ($dp != 0) {
198
            return round($this->intersectSE, $dp);
199
        }
200
201 2
        return $this->intersectSE;
202
    }
203
204
    /**
205
     * Return the goodness of fit for this regression.
206
     *
207
     * @param int $dp Number of places of decimal precision to return
208
     *
209
     * @return float
210
     */
211 8
    public function getGoodnessOfFit($dp = 0)
212
    {
213 8
        if ($dp != 0) {
214 3
            return round($this->goodnessOfFit, $dp);
215
        }
216
217 8
        return $this->goodnessOfFit;
218
    }
219
220
    /**
221
     * Return the goodness of fit for this regression.
222
     *
223
     * @param int $dp Number of places of decimal precision to return
224
     *
225
     * @return float
226
     */
227
    public function getGoodnessOfFitPercent($dp = 0)
228
    {
229
        if ($dp != 0) {
230
            return round($this->goodnessOfFit * 100, $dp);
231
        }
232
233
        return $this->goodnessOfFit * 100;
234
    }
235
236
    /**
237
     * Return the standard deviation of the residuals for this regression.
238
     *
239
     * @param int $dp Number of places of decimal precision to return
240
     *
241
     * @return float
242
     */
243 5
    public function getStdevOfResiduals($dp = 0)
244
    {
245 5
        if ($dp != 0) {
246
            return round($this->stdevOfResiduals, $dp);
247
        }
248
249 5
        return $this->stdevOfResiduals;
250
    }
251
252
    /**
253
     * @param int $dp Number of places of decimal precision to return
254
     *
255
     * @return float
256
     */
257 3
    public function getSSRegression($dp = 0)
258
    {
259 3
        if ($dp != 0) {
260
            return round($this->SSRegression, $dp);
261
        }
262
263 3
        return $this->SSRegression;
264
    }
265
266
    /**
267
     * @param int $dp Number of places of decimal precision to return
268
     *
269
     * @return float
270
     */
271 3
    public function getSSResiduals($dp = 0)
272
    {
273 3
        if ($dp != 0) {
274
            return round($this->SSResiduals, $dp);
275
        }
276
277 3
        return $this->SSResiduals;
278
    }
279
280
    /**
281
     * @param int $dp Number of places of decimal precision to return
282
     *
283
     * @return float
284
     */
285 3
    public function getDFResiduals($dp = 0)
286
    {
287 3
        if ($dp != 0) {
288
            return round($this->DFResiduals, $dp);
289
        }
290
291 3
        return $this->DFResiduals;
292
    }
293
294
    /**
295
     * @param int $dp Number of places of decimal precision to return
296
     *
297
     * @return float
298
     */
299 3
    public function getF($dp = 0)
300
    {
301 3
        if ($dp != 0) {
302
            return round($this->f, $dp);
303
        }
304
305 3
        return $this->f;
306
    }
307
308
    /**
309
     * @param int $dp Number of places of decimal precision to return
310
     *
311
     * @return float
312
     */
313 3
    public function getCovariance($dp = 0)
314
    {
315 3
        if ($dp != 0) {
316
            return round($this->covariance, $dp);
317
        }
318
319 3
        return $this->covariance;
320
    }
321
322
    /**
323
     * @param int $dp Number of places of decimal precision to return
324
     *
325
     * @return float
326
     */
327 2
    public function getCorrelation($dp = 0)
328
    {
329 2
        if ($dp != 0) {
330
            return round($this->correlation, $dp);
331
        }
332
333 2
        return $this->correlation;
334
    }
335
336
    /**
337
     * @return float[]
338
     */
339
    public function getYBestFitValues()
340
    {
341
        return $this->yBestFitValues;
342
    }
343
344 39
    protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
345
    {
346 39
        $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
0 ignored issues
show
Unused Code introduced by
The assignment to $SScor is dead and can be removed.
Loading history...
347 39
        foreach ($this->xValues as $xKey => $xValue) {
348 39
            $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
349
350 39
            $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
351 39
            if ($const === true) {
352 34
                $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
353
            } else {
354 5
                $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
355
            }
356 39
            $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
357 39
            if ($const === true) {
358 34
                $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
359
            } else {
360 5
                $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
361
            }
362
        }
363
364 39
        $this->SSResiduals = $SSres;
365 39
        $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
366
367 39
        if ($this->DFResiduals == 0.0) {
368 1
            $this->stdevOfResiduals = 0.0;
369
        } else {
370 38
            $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
371
        }
372 39
        if (($SStot == 0.0) || ($SSres == $SStot)) {
1 ignored issue
show
introduced by
The condition $SStot == 0.0 is always true.
Loading history...
373
            $this->goodnessOfFit = 1;
374
        } else {
375 39
            $this->goodnessOfFit = 1 - ($SSres / $SStot);
376
        }
377
378 39
        $this->SSRegression = $this->goodnessOfFit * $SStot;
379 39
        $this->covariance = $SScov / $this->valueCount;
380 39
        $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
381 39
        $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
382 39
        $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
383 39
        if ($this->SSResiduals != 0.0) {
384 27
            if ($this->DFResiduals == 0.0) {
385
                $this->f = 0.0;
386
            } else {
387 27
                $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
388
            }
389
        } else {
390 12
            if ($this->DFResiduals == 0.0) {
391 1
                $this->f = 0.0;
392
            } else {
393 11
                $this->f = $this->SSRegression / $this->DFResiduals;
394
            }
395
        }
396 39
    }
397
398 39
    private function sumSquares(array $values)
399
    {
400 39
        return array_sum(
401 39
            array_map(
402
                function ($value) {
403 39
                    return $value ** 2;
404 39
                },
405
                $values
406
            )
407
        );
408
    }
409
410
    /**
411
     * @param float[] $yValues
412
     * @param float[] $xValues
413
     */
414 39
    protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
415
    {
416
        // calculate sums
417 39
        $sumValuesX = array_sum($xValues);
418 39
        $sumValuesY = array_sum($yValues);
419 39
        $meanValueX = $sumValuesX / $this->valueCount;
420 39
        $meanValueY = $sumValuesY / $this->valueCount;
421 39
        $sumSquaresX = $this->sumSquares($xValues);
422 39
        $sumSquaresY = $this->sumSquares($yValues);
423 39
        $mBase = $mDivisor = 0.0;
424 39
        $xy_sum = 0.0;
425 39
        for ($i = 0; $i < $this->valueCount; ++$i) {
426 39
            $xy_sum += $xValues[$i] * $yValues[$i];
427
428 39
            if ($const === true) {
429 34
                $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
430 34
                $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
431
            } else {
432 5
                $mBase += $xValues[$i] * $yValues[$i];
433 5
                $mDivisor += $xValues[$i] * $xValues[$i];
434
            }
435
        }
436
437
        // calculate slope
438 39
        $this->slope = $mBase / $mDivisor;
439
440
        // calculate intersect
441 39
        $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
442
443 39
        $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
444 39
    }
445
446
    /**
447
     * Define the regression.
448
     *
449
     * @param float[] $yValues The set of Y-values for this regression
450
     * @param float[] $xValues The set of X-values for this regression
451
     */
452 39
    public function __construct($yValues, $xValues = [])
453
    {
454
        //    Calculate number of points
455 39
        $yValueCount = count($yValues);
456 39
        $xValueCount = count($xValues);
457
458
        //    Define X Values if necessary
459 39
        if ($xValueCount === 0) {
460
            $xValues = range(1, $yValueCount);
461 39
        } elseif ($yValueCount !== $xValueCount) {
462
            //    Ensure both arrays of points are the same size
463
            $this->error = true;
464
        }
465
466 39
        $this->valueCount = $yValueCount;
467 39
        $this->xValues = $xValues;
468 39
        $this->yValues = $yValues;
469 39
    }
470
}
471