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