Passed
Push — master ( 9e1378...d02904 )
by Mark
16:48 queued 06:34
created

Trends::COVAR()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 3
nop 2
dl 0
loc 12
rs 10
c 0
b 0
f 0
ccs 7
cts 7
cp 1
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
7
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
8
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
9
use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
10
11
class Trends
12
{
13
    use ArrayEnabled;
14
15 60
    private static function filterTrendValues(array &$array1, array &$array2): void
16
    {
17 60
        foreach ($array1 as $key => $value) {
18 58
            if ((is_bool($value)) || (is_string($value)) || ($value === null)) {
19 5
                unset($array1[$key], $array2[$key]);
20
            }
21
        }
22
    }
23
24
    /**
25
     * @param mixed $array1 should be array, but scalar is made into one
26
     * @param mixed $array2 should be array, but scalar is made into one
27
     */
28 60
    private static function checkTrendArrays(&$array1, &$array2): void
29
    {
30 60
        if (!is_array($array1)) {
31
            $array1 = [$array1];
32
        }
33 60
        if (!is_array($array2)) {
34
            $array2 = [$array2];
35
        }
36
37 60
        $array1 = Functions::flattenArray($array1);
38 60
        $array2 = Functions::flattenArray($array2);
39
40 60
        self::filterTrendValues($array1, $array2);
41 60
        self::filterTrendValues($array2, $array1);
42
43
        // Reset the array indexes
44 60
        $array1 = array_merge($array1);
45 60
        $array2 = array_merge($array2);
46
    }
47
48 60
    protected static function validateTrendArrays(array $yValues, array $xValues): void
49
    {
50 60
        $yValueCount = count($yValues);
51 60
        $xValueCount = count($xValues);
52
53 60
        if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) {
54 7
            throw new Exception(ExcelError::NA());
55 53
        } elseif ($yValueCount === 1) {
56 7
            throw new Exception(ExcelError::DIV0());
57
        }
58
    }
59
60
    /**
61
     * CORREL.
62
     *
63
     * Returns covariance, the average of the products of deviations for each data point pair.
64
     *
65
     * @param mixed $yValues array of mixed Data Series Y
66
     * @param null|mixed $xValues array of mixed Data Series X
67
     *
68
     * @return float|string
69
     */
70 7
    public static function CORREL($yValues, $xValues = null)
71
    {
72 7
        if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) {
73 1
            return ExcelError::VALUE();
74
        }
75
76
        try {
77 6
            self::checkTrendArrays($yValues, $xValues);
78 6
            self::validateTrendArrays($yValues, $xValues);
79 2
        } catch (Exception $e) {
80 2
            return $e->getMessage();
81
        }
82
83 4
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
84
85 4
        return $bestFitLinear->getCorrelation();
86
    }
87
88
    /**
89
     * COVAR.
90
     *
91
     * Returns covariance, the average of the products of deviations for each data point pair.
92
     *
93
     * @param mixed $yValues array of mixed Data Series Y
94
     * @param mixed $xValues array of mixed Data Series X
95
     *
96
     * @return float|string
97
     */
98 7
    public static function COVAR($yValues, $xValues)
99
    {
100
        try {
101 7
            self::checkTrendArrays($yValues, $xValues);
102 7
            self::validateTrendArrays($yValues, $xValues);
103 2
        } catch (Exception $e) {
104 2
            return $e->getMessage();
105
        }
106
107 5
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
108
109 5
        return $bestFitLinear->getCovariance();
110
    }
111
112
    /**
113
     * FORECAST.
114
     *
115
     * Calculates, or predicts, a future value by using existing values.
116
     * The predicted value is a y-value for a given x-value.
117
     *
118
     * @param mixed $xValue Float value of X for which we want to find Y
119
     *                      Or can be an array of values
120
     * @param mixed $yValues array of mixed Data Series Y
121
     * @param mixed $xValues of mixed Data Series X
122
     *
123
     * @return array|bool|float|string
124
     *         If an array of numbers is passed as an argument, then the returned result will also be an array
125
     *            with the same dimensions
126
     */
127 15
    public static function FORECAST($xValue, $yValues, $xValues)
128
    {
129 15
        if (is_array($xValue)) {
130 3
            return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $xValue, $yValues, $xValues);
131
        }
132
133
        try {
134 15
            $xValue = StatisticalValidations::validateFloat($xValue);
135 14
            self::checkTrendArrays($yValues, $xValues);
136 14
            self::validateTrendArrays($yValues, $xValues);
137 3
        } catch (Exception $e) {
138 3
            return $e->getMessage();
139
        }
140
141 12
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
142
143 12
        return $bestFitLinear->getValueOfYForX($xValue);
144
    }
145
146
    /**
147
     * GROWTH.
148
     *
149
     * Returns values along a predicted exponential Trend
150
     *
151
     * @param mixed[] $yValues Data Series Y
152
     * @param mixed[] $xValues Data Series X
153
     * @param mixed[] $newValues Values of X for which we want to find Y
154
     * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
155
     *
156
     * @return float[]
157
     */
158 3
    public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true)
159
    {
160 3
        $yValues = Functions::flattenArray($yValues);
161 3
        $xValues = Functions::flattenArray($xValues);
162 3
        $newValues = Functions::flattenArray($newValues);
163 3
        $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
164
165 3
        $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
166 3
        if (empty($newValues)) {
167 2
            $newValues = $bestFitExponential->getXValues();
168
        }
169
170 3
        $returnArray = [];
171 3
        foreach ($newValues as $xValue) {
172 3
            $returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)];
173
        }
174
175 3
        return $returnArray; //* @phpstan-ignore-line
176
    }
177
178
    /**
179
     * INTERCEPT.
180
     *
181
     * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values.
182
     *
183
     * @param mixed[] $yValues Data Series Y
184
     * @param mixed[] $xValues Data Series X
185
     *
186
     * @return float|string
187
     */
188 9
    public static function INTERCEPT($yValues, $xValues)
189
    {
190
        try {
191 9
            self::checkTrendArrays($yValues, $xValues);
192 9
            self::validateTrendArrays($yValues, $xValues);
193 2
        } catch (Exception $e) {
194 2
            return $e->getMessage();
195
        }
196
197 7
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
198
199 7
        return $bestFitLinear->getIntersect();
200
    }
201
202
    /**
203
     * LINEST.
204
     *
205
     * Calculates the statistics for a line by using the "least squares" method to calculate a straight line
206
     *     that best fits your data, and then returns an array that describes the line.
207
     *
208
     * @param mixed[] $yValues Data Series Y
209
     * @param null|mixed[] $xValues Data Series X
210
     * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
211
     * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics
212
     *
213
     * @return array|int|string The result, or a string containing an error
214
     */
215 9
    public static function LINEST($yValues, $xValues = null, $const = true, $stats = false)
216
    {
217 9
        $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
218 9
        $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
219 9
        if ($xValues === null) {
220
            $xValues = $yValues;
221
        }
222
223
        try {
224 9
            self::checkTrendArrays($yValues, $xValues);
225 9
            self::validateTrendArrays($yValues, $xValues);
226
        } catch (Exception $e) {
227
            return $e->getMessage();
228
        }
229
230 9
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
231
232 9
        if ($stats === true) {
233 3
            return [
234 3
                [
235 3
                    $bestFitLinear->getSlope(),
236 3
                    $bestFitLinear->getIntersect(),
237 3
                ],
238 3
                [
239 3
                    $bestFitLinear->getSlopeSE(),
240 3
                    ($const === false) ? ExcelError::NA() : $bestFitLinear->getIntersectSE(),
241 3
                ],
242 3
                [
243 3
                    $bestFitLinear->getGoodnessOfFit(),
244 3
                    $bestFitLinear->getStdevOfResiduals(),
245 3
                ],
246 3
                [
247 3
                    $bestFitLinear->getF(),
248 3
                    $bestFitLinear->getDFResiduals(),
249 3
                ],
250 3
                [
251 3
                    $bestFitLinear->getSSRegression(),
252 3
                    $bestFitLinear->getSSResiduals(),
253 3
                ],
254 3
            ];
255
        }
256
257 6
        return [
258 6
            $bestFitLinear->getSlope(),
259 6
            $bestFitLinear->getIntersect(),
260 6
        ];
261
    }
262
263
    /**
264
     * LOGEST.
265
     *
266
     * Calculates an exponential curve that best fits the X and Y data series,
267
     *        and then returns an array that describes the line.
268
     *
269
     * @param mixed[] $yValues Data Series Y
270
     * @param null|mixed[] $xValues Data Series X
271
     * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
272
     * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics
273
     *
274
     * @return array|int|string The result, or a string containing an error
275
     */
276 7
    public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false)
277
    {
278 7
        $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
279 7
        $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
280 7
        if ($xValues === null) {
281
            $xValues = $yValues;
282
        }
283
284
        try {
285 7
            self::checkTrendArrays($yValues, $xValues);
286 7
            self::validateTrendArrays($yValues, $xValues);
287
        } catch (Exception $e) {
288
            return $e->getMessage();
289
        }
290
291 7
        foreach ($yValues as $value) {
292 7
            if ($value < 0.0) {
293
                return ExcelError::NAN();
294
            }
295
        }
296
297 7
        $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
298
299 7
        if ($stats === true) {
300
            return [
301
                [
302
                    $bestFitExponential->getSlope(),
303
                    $bestFitExponential->getIntersect(),
304
                ],
305
                [
306
                    $bestFitExponential->getSlopeSE(),
307
                    ($const === false) ? ExcelError::NA() : $bestFitExponential->getIntersectSE(),
308
                ],
309
                [
310
                    $bestFitExponential->getGoodnessOfFit(),
311
                    $bestFitExponential->getStdevOfResiduals(),
312
                ],
313
                [
314
                    $bestFitExponential->getF(),
315
                    $bestFitExponential->getDFResiduals(),
316
                ],
317
                [
318
                    $bestFitExponential->getSSRegression(),
319
                    $bestFitExponential->getSSResiduals(),
320
                ],
321
            ];
322
        }
323
324 7
        return [
325 7
            $bestFitExponential->getSlope(),
326 7
            $bestFitExponential->getIntersect(),
327 7
        ];
328
    }
329
330
    /**
331
     * RSQ.
332
     *
333
     * Returns the square of the Pearson product moment correlation coefficient through data points
334
     *     in known_y's and known_x's.
335
     *
336
     * @param mixed[] $yValues Data Series Y
337
     * @param mixed[] $xValues Data Series X
338
     *
339
     * @return float|string The result, or a string containing an error
340
     */
341 6
    public static function RSQ($yValues, $xValues)
342
    {
343
        try {
344 6
            self::checkTrendArrays($yValues, $xValues);
345 6
            self::validateTrendArrays($yValues, $xValues);
346 2
        } catch (Exception $e) {
347 2
            return $e->getMessage();
348
        }
349
350 4
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
351
352 4
        return $bestFitLinear->getGoodnessOfFit();
353
    }
354
355
    /**
356
     * SLOPE.
357
     *
358
     * Returns the slope of the linear regression line through data points in known_y's and known_x's.
359
     *
360
     * @param mixed[] $yValues Data Series Y
361
     * @param mixed[] $xValues Data Series X
362
     *
363
     * @return float|string The result, or a string containing an error
364
     */
365 10
    public static function SLOPE($yValues, $xValues)
366
    {
367
        try {
368 10
            self::checkTrendArrays($yValues, $xValues);
369 10
            self::validateTrendArrays($yValues, $xValues);
370 2
        } catch (Exception $e) {
371 2
            return $e->getMessage();
372
        }
373
374 8
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
375
376 8
        return $bestFitLinear->getSlope();
377
    }
378
379
    /**
380
     * STEYX.
381
     *
382
     * Returns the standard error of the predicted y-value for each x in the regression.
383
     *
384
     * @param mixed[] $yValues Data Series Y
385
     * @param mixed[] $xValues Data Series X
386
     *
387
     * @return float|string
388
     */
389 6
    public static function STEYX($yValues, $xValues)
390
    {
391
        try {
392 6
            self::checkTrendArrays($yValues, $xValues);
393 6
            self::validateTrendArrays($yValues, $xValues);
394 2
        } catch (Exception $e) {
395 2
            return $e->getMessage();
396
        }
397
398 4
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
399
400 4
        return $bestFitLinear->getStdevOfResiduals();
401
    }
402
403
    /**
404
     * TREND.
405
     *
406
     * Returns values along a linear Trend
407
     *
408
     * @param mixed[] $yValues Data Series Y
409
     * @param mixed[] $xValues Data Series X
410
     * @param mixed[] $newValues Values of X for which we want to find Y
411
     * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
412
     *
413
     * @return float[]
414
     */
415 3
    public static function TREND($yValues, $xValues = [], $newValues = [], $const = true)
416
    {
417 3
        $yValues = Functions::flattenArray($yValues);
418 3
        $xValues = Functions::flattenArray($xValues);
419 3
        $newValues = Functions::flattenArray($newValues);
420 3
        $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
421
422 3
        $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
423 3
        if (empty($newValues)) {
424 2
            $newValues = $bestFitLinear->getXValues();
425
        }
426
427 3
        $returnArray = [];
428 3
        foreach ($newValues as $xValue) {
429 3
            $returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)];
430
        }
431
432 3
        return $returnArray; //* @phpstan-ignore-line
433
    }
434
}
435