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