• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

markrogoyski / math-php / 18770613404

24 Oct 2025 01:36AM UTC coverage: 99.66% (-0.02%) from 99.682%
18770613404

push

github

markrogoyski
Add unit tests.

8213 of 8241 relevant lines covered (99.66%)

230.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.51
/src/Statistics/Correlation.php
1
<?php
2

3
namespace MathPHP\Statistics;
4

5
use MathPHP\Exception;
6
use MathPHP\Functions\Map;
7
use MathPHP\LinearAlgebra\Eigenvalue;
8
use MathPHP\LinearAlgebra\Eigenvector;
9
use MathPHP\LinearAlgebra\NumericMatrix;
10
use MathPHP\LinearAlgebra\MatrixFactory;
11
use MathPHP\Probability\Distribution\Continuous\ChiSquared;
12
use MathPHP\Probability\Distribution\Continuous\StandardNormal;
13
use MathPHP\Trigonometry;
14

15
/**
16
 * Statistical correlation
17
 *  - covariance
18
 *  - correlation coefficient (r)
19
 *  - coefficient of determination (R²)
20
 *  - Kendall's tau (τ)
21
 *  - Spearman's rho (ρ)
22
 *  - confidence ellipse
23
 */
24
class Correlation
25
{
26
    private const X = 0;
27
    private const Y = 1;
28

29
    /**
30
     * Covariance
31
     * Convenience method to access population and sample covariance.
32
     *
33
     * A measure of how much two random variables change together.
34
     * Average product of their deviations from their respective means.
35
     * The population covariance is defined in terms of the sample means x, y
36
     * https://en.wikipedia.org/wiki/Covariance
37
     *
38
     * @param array<float> $X values for random variable X
39
     * @param array<float> $Y values for random variable Y
40
     * @param bool $population Optional flag for population or sample covariance
41
     *
42
     * @return float
43
     *
44
     * @throws Exception\BadDataException
45
     */
46
    public static function covariance(array $X, array $Y, bool $population = false): float
47
    {
48
        return $population
32✔
49
            ? self::populationCovariance($X, $Y)
3✔
50
            : self::sampleCovariance($X, $Y);
32✔
51
    }
52

53
    /**
54
     * Population Covariance
55
     * A measure of how much two random variables change together.
56
     * Average product of their deviations from their respective means.
57
     * The population covariance is defined in terms of the population means μx, μy
58
     * https://en.wikipedia.org/wiki/Covariance
59
     *
60
     * cov(X, Y) = σxy = E[⟮X - μx⟯⟮Y - μy⟯]
61
     *
62
     *                   ∑⟮xᵢ - μₓ⟯⟮yᵢ - μy⟯
63
     * cov(X, Y) = σxy = -----------------
64
     *                           N
65
     *
66
     * @param array<float> $X values for random variable X
67
     * @param array<float> $Y values for random variable Y
68
     *
69
     * @return float
70
     *
71
     * @throws Exception\BadDataException if X and Y do not have the same number of elements
72
     */
73
    public static function populationCovariance(array $X, array $Y): float
74
    {
75
        if (\count($X) !== \count($Y)) {
46✔
76
            throw new Exception\BadDataException('X and Y must have the same number of elements.');
2✔
77
        }
78
        $μₓ = Average::mean($X);
44✔
79
        $μy = Average::mean($Y);
44✔
80

81
        $∑⟮xᵢ − μₓ⟯⟮yᵢ − μy⟯ = \array_sum(\array_map(
44✔
82
            function ($xᵢ, $yᵢ) use ($μₓ, $μy) {
83
                return ( $xᵢ - $μₓ ) * ( $yᵢ - $μy );
44✔
84
            },
44✔
85
            $X,
44✔
86
            $Y
44✔
87
        ));
88
        $N = \count($X);
44✔
89

90
        return $∑⟮xᵢ − μₓ⟯⟮yᵢ − μy⟯ / $N;
44✔
91
    }
92

93
    /**
94
     * Sample covariance
95
     * A measure of how much two random variables change together.
96
     * Average product of their deviations from their respective means.
97
     * The population covariance is defined in terms of the sample means x, y
98
     * https://en.wikipedia.org/wiki/Covariance
99
     *
100
     * cov(X, Y) = Sxy = E[⟮X - x⟯⟮Y - y⟯]
101
     *
102
     *                   ∑⟮xᵢ - x⟯⟮yᵢ - y⟯
103
     * cov(X, Y) = Sxy = ---------------
104
     *                         n - 1
105
     *
106
     * @param array<float> $X values for random variable X
107
     * @param array<float> $Y values for random variable Y
108
     *
109
     * @return float
110
     *
111
     * @throws Exception\BadDataException if X and Y do not have the same number of elements
112
     */
113
    public static function sampleCovariance(array $X, array $Y): float
114
    {
115
        if (\count($X) !== \count($Y)) {
57✔
116
            throw new Exception\BadDataException('X and Y must have the same number of elements.');
2✔
117
        }
118

119
        $n = \count($X);
55✔
120
        if ($n < 2) {
55✔
121
            throw new Exception\OutOfBoundsException('Sample covariance requires at least 2 data points. n = ' . $n);
1✔
122
        }
123

124
        $x = Average::mean($X);
54✔
125
        $y = Average::mean($Y);
54✔
126

127
        $∑⟮xᵢ − x⟯⟮yᵢ − y⟯ = \array_sum(\array_map(
54✔
128
            function ($xᵢ, $yᵢ) use ($x, $y) {
129
                return ( $xᵢ - $x ) * ( $yᵢ - $y );
54✔
130
            },
54✔
131
            $X,
54✔
132
            $Y
54✔
133
        ));
134

135
        return $∑⟮xᵢ − x⟯⟮yᵢ − y⟯ / ($n - 1);
54✔
136
    }
137

138
    /**
139
     * Weighted covariance
140
     * A measure of how much two random variables change together with weights.
141
     * https://en.wikipedia.org/wiki/Pearson_correlation_coefficient#Weighted_correlation_coefficient
142
     *
143
     *                       ∑wᵢ⟮xᵢ - μₓ⟯⟮yᵢ - μy⟯
144
     * cov(X, Y, w) = sxyw = --------------------
145
     *                              ∑wᵢ
146
     *
147
     * @param array<float> $X values for random variable X
148
     * @param array<float> $Y values for random variable Y
149
     * @param array<float> $w values for weights
150
     *
151
     * @return float
152
     *
153
     * @throws Exception\BadDataException if X and Y do not have the same number of elements
154
     */
155
    public static function weightedCovariance(array $X, array $Y, array $w): float
156
    {
157
        if (\count($X) !== \count($Y) || \count($X) !== \count($w)) {
13✔
158
            throw new Exception\BadDataException('X, Y and w must have the same number of elements.');
3✔
159
        }
160

161
        $μₓ = Average::weightedMean($X, $w);
10✔
162
        $μy = Average::weightedMean($Y, $w);
10✔
163

164
        $∑wᵢ⟮xᵢ − μₓ⟯⟮yᵢ − μy⟯ = \array_sum(\array_map(
10✔
165
            function ($xᵢ, $yᵢ, $wᵢ) use ($μₓ, $μy) {
166
                return $wᵢ * ( $xᵢ - $μₓ ) * ( $yᵢ - $μy );
10✔
167
            },
10✔
168
            $X,
10✔
169
            $Y,
10✔
170
            $w
10✔
171
        ));
172

173
        $∑wᵢ = \array_sum($w);
10✔
174

175
        return $∑wᵢ⟮xᵢ − μₓ⟯⟮yᵢ − μy⟯ / $∑wᵢ;
10✔
176
    }
177

178
    /**
179
     * r - correlation coefficient
180
     * Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r)
181
     *
182
     * Convenience method for population and sample correlationCoefficient
183
     *
184
     * @param array<float> $X values for random variable X
185
     * @param array<float> $Y values for random variable Y
186
     * @param bool $population Optional flag for population or sample covariance
187
     *
188
     * @return float
189
     *
190
     * @throws Exception\BadDataException
191
     * @throws Exception\OutOfBoundsException
192
     */
193
    public static function r(array $X, array $Y, bool $population = false): float
194
    {
195
        return $population
39✔
196
            ? self::populationCorrelationCoefficient($X, $Y)
18✔
197
            : self::sampleCorrelationCoefficient($X, $Y);
39✔
198
    }
199

200
    /**
201
     * Population correlation coefficient
202
     * Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r)
203
     *
204
     * A normalized measure of the linear correlation between two variables X and Y,
205
     * giving a value between +1 and −1 inclusive, where 1 is total positive correlation,
206
     * 0 is no correlation, and −1 is total negative correlation.
207
     * It is widely used in the sciences as a measure of the degree of linear dependence
208
     * between two variables.
209
     * https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient
210
     *
211
     * The correlation coefficient of two variables in a data sample is their covariance
212
     * divided by the product of their individual standard deviations.
213
     *
214
     *        cov(X,Y)
215
     * ρxy = ----------
216
     *         σx σy
217
     *
218
     *  conv(X,Y) is the population covariance
219
     *  σx is the population standard deviation of X
220
     *  σy is the population standard deviation of Y
221
     *
222
     * @param array<float> $X values for random variable X
223
     * @param array<float> $Y values for random variable Y
224
     *
225
     * @return float
226
     *
227
     * @throws Exception\BadDataException
228
     * @throws Exception\OutOfBoundsException
229
     */
230
    public static function populationCorrelationCoefficient(array $X, array $Y): float
231
    {
232
        if (\count($X) !== \count($Y)) {
39✔
233
            throw new Exception\BadDataException('X and Y must have the same number of elements.');
1✔
234
        }
235

236
        $cov⟮X,Y⟯ = self::populationCovariance($X, $Y);
38✔
237
        $σx      = Descriptive::standardDeviation($X, true);
38✔
238
        $σy      = Descriptive::standardDeviation($Y, true);
38✔
239

240
        if ($σx == 0 || $σy == 0) {
38✔
241
            throw new Exception\BadDataException(
2✔
242
                'Correlation coefficient is undefined when one or both variables have zero variance. ' .
243
                'σx = ' . $σx . ', σy = ' . $σy
2✔
244
            );
245
        }
246

247
        return $cov⟮X,Y⟯ / ( $σx * $σy );
36✔
248
    }
249

250
    /**
251
     * Sample correlation coefficient
252
     * Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r)
253
     *
254
     * A normalized measure of the linear correlation between two variables X and Y,
255
     * giving a value between +1 and −1 inclusive, where 1 is total positive correlation,
256
     * 0 is no correlation, and −1 is total negative correlation.
257
     * It is widely used in the sciences as a measure of the degree of linear dependence
258
     * between two variables.
259
     * https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient
260
     *
261
     * The correlation coefficient of two variables in a data sample is their covariance
262
     * divided by the product of their individual standard deviations.
263
     *
264
     *          Sxy
265
     * rxy = ----------
266
     *         sx sy
267
     *
268
     *  Sxy is the sample covariance
269
     *  σx is the sample standard deviation of X
270
     *  σy is the sample standard deviation of Y
271
     *
272
     * @param array<float> $X values for random variable X
273
     * @param array<float> $Y values for random variable Y
274
     *
275
     * @return float
276
     *
277
     * @throws Exception\BadDataException
278
     * @throws Exception\OutOfBoundsException
279
     */
280
    public static function sampleCorrelationCoefficient(array $X, array $Y): float
281
    {
282
        if (\count($X) !== \count($Y)) {
25✔
283
            throw new Exception\BadDataException('X and Y must have the same number of elements.');
1✔
284
        }
285

286
        if (\count($X) < 2) {
24✔
287
            throw new Exception\OutOfBoundsException('Sample correlation coefficient requires at least 2 data points. n = ' . \count($X));
1✔
288
        }
289

290
        $Sxy = self::sampleCovariance($X, $Y);
23✔
291
        $sx  = Descriptive::standardDeviation($X, Descriptive::SAMPLE);
23✔
292
        $sy  = Descriptive::standardDeviation($Y, Descriptive::SAMPLE);
23✔
293

294
        if ($sx == 0 || $sy == 0) {
23✔
295
            throw new Exception\BadDataException(
×
296
                'Correlation coefficient is undefined when one or both variables have zero variance. ' .
297
                'sx = ' . $sx . ', sy = ' . $sy
×
298
            );
299
        }
300

301
        return $Sxy / ( $sx * $sy );
23✔
302
    }
303

304
    /**
305
     * R² - coefficient of determination
306
     * Convenience wrapper for coefficientOfDetermination
307
     *
308
     * @param array<float> $X values for random variable X
309
     * @param array<float> $Y values for random variable Y
310
     * @param bool $population
311
     *
312
     * @return float
313
     *
314
     * @throws Exception\BadDataException
315
     * @throws Exception\OutOfBoundsException
316
     */
317
    public static function r2(array $X, array $Y, bool $population = false): float
318
    {
319
        return \pow(self::r($X, $Y, $population), 2);
10✔
320
    }
321

322
    /**
323
     * R² - coefficient of determination
324
     *
325
     * Indicates the proportion of the variance in the dependent variable
326
     * that is predictable from the independent variable.
327
     * Range of 0 - 1. Close to 1 means the regression line is a good fit
328
     * https://en.wikipedia.org/wiki/Coefficient_of_determination
329
     *
330
     * @param array<float> $X values for random variable X
331
     * @param array<float> $Y values for random variable Y
332
     * @param bool $population
333
     *
334
     * @return float
335
     *
336
     * @throws Exception\BadDataException
337
     * @throws Exception\OutOfBoundsException
338
     */
339
    public static function coefficientOfDetermination(array $X, array $Y, bool $population = false): float
340
    {
341
        return \pow(self::r($X, $Y, $population), 2);
9✔
342
    }
343

344
    /**
345
     * Weighted correlation coefficient
346
     * Pearson product-moment correlation coefficient (PPMCC or PCC or Pearson's r) width weighted values
347
     *
348
     * A normalized measure of the linear correlation between two variables X and Y,
349
     * giving a value between +1 and −1 inclusive, where 1 is total positive correlation,
350
     * 0 is no correlation, and −1 is total negative correlation.
351
     * It is widely used in the sciences as a measure of the degree of linear dependence between two variables.
352
     * https://en.wikipedia.org/wiki/Pearson_correlation_coefficient#Weighted_correlation_coefficient
353
     *
354
     * The weighted correlation coefficient of two variables in a data sample is their covariance
355
     * divided by the product of their individual standard deviations.
356
     *
357
     *          cov(X,Y,w)
358
     * ρxyw = -------------
359
     *          √(sxw syw)
360
     *
361
     *  conv(X,Y, w) is the weighted covariance
362
     *  sxw is the weighted variance of X
363
     *  syw is the weighted variance of Y
364
     *
365
     * @param array<float> $X values for random variable X
366
     * @param array<float> $Y values for random variable Y
367
     * @param array<float> $w values for weights
368
     *
369
     * @return float
370
     *
371
     * @throws Exception\BadDataException
372
     */
373
    public static function weightedCorrelationCoefficient(array $X, array $Y, array $w): float
374
    {
375
        $cov⟮X,Y,w⟯ = self::weightedCovariance($X, $Y, $w);
5✔
376
        $sxw         = Descriptive::weightedSampleVariance($X, $w, true);
5✔
377
        $syw         = Descriptive::weightedSampleVariance($Y, $w, true);
5✔
378

379
        return $cov⟮X,Y,w⟯ / \sqrt($sxw * $syw);
5✔
380
    }
381

382
    /**
383
     * τ - Kendall rank correlation coefficient (Kendall's tau)
384
     *
385
     * A statistic used to measure the ordinal association between two
386
     * measured quantities. It is a measure of rank correlation:
387
     * the similarity of the orderings of the data when ranked by each
388
     * of the quantities.
389
     * https://en.wikipedia.org/wiki/Kendall_rank_correlation_coefficient
390
     * https://onlinecourses.science.psu.edu/stat509/node/158
391
     *
392
     * tau-a (no rank ties):
393
     *
394
     *        nc - nd
395
     *   τ = ----------
396
     *       n(n - 1)/2
397
     *
398
     *   Where
399
     *     nc: number of concordant pairs
400
     *     nd: number of discordant pairs
401
     *
402
     * tau-b (rank ties exist):
403
     *
404
     *                 nc - nd
405
     *   τ = -----------------------------
406
     *       √(nc + nd + X₀)(nc + nd + Y₀)
407
     *
408
     *   Where
409
     *     X₀: number of pairs tied only on the X variable
410
     *     Y₀: number of pairs tied only on the Y variable
411
     *
412
     * @param array<mixed> $X values for random variable X
413
     * @param array<mixed> $Y values for random variable Y
414
     *
415
     * @todo Implement with algorithm faster than O(n²)
416
     *
417
     * @return float
418
     *
419
     * @throws Exception\BadDataException if both random variables do not have the same number of elements
420
     */
421
    public static function kendallsTau(array $X, array $Y): float
422
    {
423
        if (\count($X) !== \count($Y)) {
28✔
424
            throw new Exception\BadDataException('Both random variables must have the same number of elements');
2✔
425
        }
426

427
        $n = \count($X);
26✔
428

429
        // Match X and Y pairs and sort by X rank
430
        $xy = \array_map(
26✔
431
            function ($x, $y) {
432
                return [$x, $y];
26✔
433
            },
26✔
434
            $X,
26✔
435
            $Y
26✔
436
        );
437
        \usort($xy, function ($a, $b) {
438
            return $a[0] <=> $b[0];
26✔
439
        });
26✔
440

441
        // Initialize counters
442
        $nc      = 0;  // concordant pairs
26✔
443
        $nd      = 0;  // discordant pairs
26✔
444
        $ties_x  = 0;  // ties xᵢ = xⱼ
26✔
445
        $ties_y  = 0;  // ties yᵢ = yⱼ
26✔
446
        $ties_xy = 0;  // ties xᵢ = xⱼ and yᵢ = yⱼ
26✔
447

448
        // Tally concordant, discordant, and tied pairs
449
        for ($i = 0; $i < $n; $i++) {
26✔
450
            for ($j = $i + 1; $j < $n; $j++) {
26✔
451
                // xᵢ = xⱼ and yᵢ = yⱼ -- neither concordant or discordant
452
                if ($xy[$i][self::X] == $xy[$j][self::X] && $xy[$i][self::Y] == $xy[$j][self::Y]) {
26✔
453
                    $ties_xy++;
6✔
454
                // xᵢ = xⱼ -- neither concordant or discordant
455
                } elseif ($xy[$i][self::X] == $xy[$j][self::X]) {
26✔
456
                    $ties_x++;
10✔
457
                // yᵢ = yⱼ -- neither concordant or discordant
458
                } elseif ($xy[$i][self::Y] == $xy[$j][self::Y]) {
26✔
459
                    $ties_y++;
11✔
460
                // xᵢ < xⱼ and yᵢ < yⱼ -- concordant
461
                } elseif ($xy[$i][self::X] < $xy[$j][self::X] && $xy[$i][self::Y] < $xy[$j][self::Y]) {
26✔
462
                    $nc++;
24✔
463
                // xᵢ > xⱼ and yᵢ < yⱼ or  xᵢ < xⱼ and yᵢ > yⱼ -- discordant
464
                } else {
465
                    $nd++;
16✔
466
                }
467
            }
468
        }
469

470
        // Numerator: (number of concordant pairs) - (number of discordant pairs)
471
        $⟮nc − nd⟯ = $nc - $nd;
26✔
472

473
        /* tau-a (no rank ties):
474
         *
475
         *        nc - nd
476
         *   τ = ----------
477
         *       n(n - 1)/2
478
         */
479
        if ($ties_x == 0 && $ties_y == 0) {
26✔
480
            return $⟮nc − nd⟯ / (($n * ($n - 1)) / 2);
15✔
481
        }
482

483
        /* tau-b (rank ties exist):
484
         *
485
         *                 nc - nd
486
         *   τ = -----------------------------
487
         *       √(nc + nd + X₀)(nc + nd + Y₀)
488
         */
489
        return $⟮nc − nd⟯ / \sqrt(($nc + $nd + $ties_x) * ($nc + $nd + $ties_y));
11✔
490
    }
491

492
    /**
493
     * ρ - Spearman's rank correlation coefficient (Spearman's rho)
494
     *
495
     * https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient
496
     *
497
     *     cov(rgᵪ, rgᵧ)
498
     * ρ = ------------
499
     *        σᵣᵪσᵣᵧ
500
     *
501
     *   Where
502
     *    cov(rgᵪ, rgᵧ): covariance of the rank variables
503
     *    σᵣᵪ and σᵣᵧ:   standard deviations of the rank variables
504
     *
505
     * @param array<int|float> $X values for random variable X
506
     * @param array<int|float> $Y values for random variable Y
507
     *
508
     * @return float
509
     *
510
     * @throws Exception\BadDataException if both random variables do not have the same number of elements
511
     * @throws Exception\OutOfBoundsException if one of the random variables is empty
512
     */
513
    public static function spearmansRho(array $X, array $Y): float
514
    {
515
        if (\count($X) !== \count($Y)) {
28✔
516
            throw new Exception\BadDataException('Both random variables for spearmansRho must have the same number of elements');
2✔
517
        }
518

519
        $rgᵪ         = Distribution::fractionalRanking($X);
26✔
520
        $rgᵧ         = Distribution::fractionalRanking($Y);
26✔
521
        $cov⟮rgᵪ、rgᵧ⟯ = Correlation::covariance($rgᵪ, $rgᵧ);
26✔
522
        $σᵣᵪ         = Descriptive::sd($rgᵪ);
26✔
523
        $σᵣᵧ         = Descriptive::sd($rgᵧ);
26✔
524

525
        return $cov⟮rgᵪ、rgᵧ⟯  / ($σᵣᵪ * $σᵣᵧ);
26✔
526
    }
527

528
    /**
529
     * Descriptive correlation report about two random variables
530
     *
531
     * @param  array<float> $X values for random variable X
532
     * @param  array<float> $Y values for random variable Y
533
     * @param  bool $population Optional flag if all samples of a population are present
534
     *
535
     * @return array{
536
     *     cov: float,
537
     *     r:   float,
538
     *     r2:  float,
539
     *     tau: float,
540
     *     rho: float,
541
     * }
542
     *
543
     * @throws Exception\BadDataException
544
     * @throws Exception\OutOfBoundsException
545
     */
546
    public static function describe(array $X, array $Y, bool $population = false): array
547
    {
548
        return [
549
            'cov' => self::covariance($X, $Y, $population),
1✔
550
            'r'   => self::r($X, $Y, $population),
1✔
551
            'r2'  => self::r2($X, $Y, $population),
1✔
552
            'tau' => self::kendallsTau($X, $Y),
1✔
553
            'rho' => self::spearmansRho($X, $Y),
1✔
554
        ];
555
    }
556

557
    /**
558
     * Confidence ellipse (error ellipse)
559
     * Given the data in $X and $Y, create an ellipse
560
     * surrounding the data at $z standard deviations.
561
     *
562
     * The function will return $num_points pairs of X,Y data
563
     * http://stackoverflow.com/questions/3417028/ellipse-around-the-data-in-matlab
564
     *
565
     * @param array<float> $X an array of independent data
566
     * @param array<float> $Y an array of dependent data
567
     * @param float $z the number of standard deviations to encompass
568
     * @param int $num_points the number of points to include around the ellipse. The actual array
569
     *                          will be one larger because the first point and last will be repeated
570
     *                          to ease display.
571
     *
572
     * @return array<array<float>> paired x and y points on an ellipse aligned with the data provided
573
     *
574
     * @throws Exception\BadDataException
575
     * @throws Exception\BadParameterException
576
     * @throws Exception\IncorrectTypeException
577
     * @throws Exception\MatrixException
578
     * @throws Exception\OutOfBoundsException
579
     */
580
    public static function confidenceEllipse(array $X, array $Y, float $z, int $num_points = 11): array
581
    {
582
        $standardNormal = new StandardNormal();
1✔
583
        $p  = 2 * $standardNormal->cdf($z) - 1;
1✔
584
        $chiSquared = new ChiSquared(2);
1✔
585
        $χ² = $chiSquared->inverse($p);
1✔
586

587
        $data_array[] = $X;
1✔
588
        $data_array[] = $Y;
1✔
589
        $data_matrix  = new NumericMatrix($data_array);
1✔
590

591
        $covariance_matrix = $data_matrix->covarianceMatrix();
1✔
592

593
        // Scale the data by the confidence interval
594
        $cov         = $covariance_matrix->scalarMultiply($χ²);
1✔
595
        $eigenvalues = Eigenvalue::closedFormPolynomialRootMethod($cov);
1✔
596

597
        // Sort the eigenvalues from highest to lowest
598
        \rsort($eigenvalues);
1✔
599
        $V = Eigenvector::eigenvectors($cov, $eigenvalues);
1✔
600

601
        // Make ia diagonal matrix of the eigenvalues
602
        $D = MatrixFactory::diagonal($eigenvalues);
1✔
603
        $D = $D->map('\sqrt');
1✔
604
        $transformation_matrix = $V->multiply($D);
1✔
605

606
        $x_bar = Average::mean($X);
1✔
607
        $y_bar = Average::mean($Y);
1✔
608
        $translation_matrix = new NumericMatrix([[$x_bar],[$y_bar]]);
1✔
609

610
        // We add a row to allow the transformation matrix to also translate the ellipse to a different location
611
        $transformation_matrix = $transformation_matrix->augment($translation_matrix);
1✔
612

613
        $unit_circle = new NumericMatrix(Trigonometry::unitCircle($num_points));
1✔
614

615
        // We add a column of ones to allow us to translate the ellipse
616
        $unit_circle_with_ones = $unit_circle->augment(MatrixFactory::one($num_points, 1));
1✔
617

618
        // The unit circle is rotated, stretched, and translated to the appropriate ellipse by the translation matrix.
619
        $ellipse = $transformation_matrix->multiply($unit_circle_with_ones->transpose())->transpose();
1✔
620

621
        return $ellipse->getMatrix();
1✔
622
    }
623
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc