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

BestFit::sumSquares()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 1
rs 10
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