gonum.org/v1/gonum@v0.14.0/stat/statmat_test.go (about) 1 // Copyright ©2014 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package stat 6 7 import ( 8 "math" 9 "testing" 10 11 "golang.org/x/exp/rand" 12 13 "gonum.org/v1/gonum/floats" 14 "gonum.org/v1/gonum/mat" 15 ) 16 17 func TestCovarianceMatrix(t *testing.T) { 18 const tol = 1e-15 19 20 // An alternative way to test this is to call the Variance and 21 // Covariance functions and ensure that the results are identical. 22 for i, test := range []struct { 23 data *mat.Dense 24 weights []float64 25 ans *mat.Dense 26 }{ 27 { 28 data: mat.NewDense(5, 2, []float64{ 29 -2, -4, 30 -1, 2, 31 0, 0, 32 1, -2, 33 2, 4, 34 }), 35 weights: nil, 36 ans: mat.NewDense(2, 2, []float64{ 37 2.5, 3, 38 3, 10, 39 }), 40 }, { 41 data: mat.NewDense(3, 2, []float64{ 42 1, 1, 43 2, 4, 44 3, 9, 45 }), 46 weights: []float64{ 47 1, 48 1.5, 49 1, 50 }, 51 ans: mat.NewDense(2, 2, []float64{ 52 0.8, 3.2, 53 3.2, 13.142857142857146, 54 }), 55 }, 56 } { 57 // Make a copy of the data to check that it isn't changing. 58 r := test.data.RawMatrix() 59 d := make([]float64, len(r.Data)) 60 copy(d, r.Data) 61 62 w := make([]float64, len(test.weights)) 63 if test.weights != nil { 64 copy(w, test.weights) 65 } 66 67 var cov mat.SymDense 68 CovarianceMatrix(&cov, test.data, test.weights) 69 if !mat.EqualApprox(&cov, test.ans, tol) { 70 t.Errorf("%d: expected cov %v, found %v", i, test.ans, &cov) 71 } 72 if !floats.Equal(d, r.Data) { 73 t.Errorf("%d: data was modified during execution", i) 74 } 75 if !floats.Equal(w, test.weights) { 76 t.Errorf("%d: weights was modified during execution", i) 77 } 78 79 // compare with call to Covariance 80 _, cols := cov.Dims() 81 for ci := 0; ci < cols; ci++ { 82 for cj := 0; cj < cols; cj++ { 83 x := mat.Col(nil, ci, test.data) 84 y := mat.Col(nil, cj, test.data) 85 wc := Covariance(x, y, test.weights) 86 if math.Abs(wc-cov.At(ci, cj)) > 1e-14 { 87 t.Errorf("CovMat does not match at (%v, %v). Want %v, got %v.", ci, cj, wc, cov.At(ci, cj)) 88 } 89 } 90 } 91 } 92 93 if !panics(func() { CovarianceMatrix(nil, mat.NewDense(5, 2, nil), []float64{}) }) { 94 t.Errorf("CovarianceMatrix did not panic with weight size mismatch") 95 } 96 if !panics(func() { CovarianceMatrix(mat.NewSymDense(1, nil), mat.NewDense(5, 2, nil), nil) }) { 97 t.Errorf("CovarianceMatrix did not panic with preallocation size mismatch") 98 } 99 if !panics(func() { CovarianceMatrix(nil, mat.NewDense(2, 2, []float64{1, 2, 3, 4}), []float64{1, -1}) }) { 100 t.Errorf("CovarianceMatrix did not panic with negative weights") 101 } 102 if panics(func() { 103 dst := mat.NewSymDense(4, nil) 104 dst.Reset() 105 CovarianceMatrix(dst, mat.NewDense(2, 2, []float64{1, 2, 3, 4}), nil) 106 }) { 107 t.Errorf("CovarianceMatrix panics with reset destination") 108 } 109 } 110 111 func TestCorrelationMatrix(t *testing.T) { 112 for i, test := range []struct { 113 data *mat.Dense 114 weights []float64 115 ans *mat.Dense 116 }{ 117 { 118 data: mat.NewDense(3, 3, []float64{ 119 1, 2, 3, 120 3, 4, 5, 121 5, 6, 7, 122 }), 123 weights: nil, 124 ans: mat.NewDense(3, 3, []float64{ 125 1, 1, 1, 126 1, 1, 1, 127 1, 1, 1, 128 }), 129 }, 130 { 131 data: mat.NewDense(5, 2, []float64{ 132 -2, -4, 133 -1, 2, 134 0, 0, 135 1, -2, 136 2, 4, 137 }), 138 weights: nil, 139 ans: mat.NewDense(2, 2, []float64{ 140 1, 0.6, 141 0.6, 1, 142 }), 143 }, { 144 data: mat.NewDense(3, 2, []float64{ 145 1, 1, 146 2, 4, 147 3, 9, 148 }), 149 weights: []float64{ 150 1, 151 1.5, 152 1, 153 }, 154 ans: mat.NewDense(2, 2, []float64{ 155 1, 0.9868703275903379, 156 0.9868703275903379, 1, 157 }), 158 }, 159 } { 160 // Make a copy of the data to check that it isn't changing. 161 r := test.data.RawMatrix() 162 d := make([]float64, len(r.Data)) 163 copy(d, r.Data) 164 165 w := make([]float64, len(test.weights)) 166 if test.weights != nil { 167 copy(w, test.weights) 168 } 169 170 var corr mat.SymDense 171 CorrelationMatrix(&corr, test.data, test.weights) 172 if !mat.Equal(&corr, test.ans) { 173 t.Errorf("%d: expected corr %v, found %v", i, test.ans, &corr) 174 } 175 if !floats.Equal(d, r.Data) { 176 t.Errorf("%d: data was modified during execution", i) 177 } 178 if !floats.Equal(w, test.weights) { 179 t.Errorf("%d: weights was modified during execution", i) 180 } 181 182 // compare with call to Covariance 183 _, cols := corr.Dims() 184 for ci := 0; ci < cols; ci++ { 185 for cj := 0; cj < cols; cj++ { 186 x := mat.Col(nil, ci, test.data) 187 y := mat.Col(nil, cj, test.data) 188 wc := Correlation(x, y, test.weights) 189 if math.Abs(wc-corr.At(ci, cj)) > 1e-14 { 190 t.Errorf("CorrMat does not match at (%v, %v). Want %v, got %v.", ci, cj, wc, corr.At(ci, cj)) 191 } 192 } 193 } 194 195 } 196 if !panics(func() { CorrelationMatrix(nil, mat.NewDense(5, 2, nil), []float64{}) }) { 197 t.Errorf("CorrelationMatrix did not panic with weight size mismatch") 198 } 199 if !panics(func() { CorrelationMatrix(mat.NewSymDense(1, nil), mat.NewDense(5, 2, nil), nil) }) { 200 t.Errorf("CorrelationMatrix did not panic with preallocation size mismatch") 201 } 202 if !panics(func() { CorrelationMatrix(nil, mat.NewDense(2, 2, []float64{1, 2, 3, 4}), []float64{1, -1}) }) { 203 t.Errorf("CorrelationMatrix did not panic with negative weights") 204 } 205 } 206 207 func TestCorrCov(t *testing.T) { 208 // test both Cov2Corr and Cov2Corr 209 for i, test := range []struct { 210 data *mat.Dense 211 weights []float64 212 }{ 213 { 214 data: mat.NewDense(3, 3, []float64{ 215 1, 2, 3, 216 3, 4, 5, 217 5, 6, 7, 218 }), 219 weights: nil, 220 }, 221 { 222 data: mat.NewDense(5, 2, []float64{ 223 -2, -4, 224 -1, 2, 225 0, 0, 226 1, -2, 227 2, 4, 228 }), 229 weights: nil, 230 }, { 231 data: mat.NewDense(3, 2, []float64{ 232 1, 1, 233 2, 4, 234 3, 9, 235 }), 236 weights: []float64{ 237 1, 238 1.5, 239 1, 240 }, 241 }, 242 } { 243 var corr, cov mat.SymDense 244 CorrelationMatrix(&corr, test.data, test.weights) 245 CovarianceMatrix(&cov, test.data, test.weights) 246 247 r := cov.SymmetricDim() 248 249 // Get the diagonal elements from cov to determine the sigmas. 250 sigmas := make([]float64, r) 251 for i := range sigmas { 252 sigmas[i] = math.Sqrt(cov.At(i, i)) 253 } 254 255 covFromCorr := mat.NewSymDense(corr.SymmetricDim(), nil) 256 covFromCorr.CopySym(&corr) 257 corrToCov(covFromCorr, sigmas) 258 259 corrFromCov := mat.NewSymDense(cov.SymmetricDim(), nil) 260 corrFromCov.CopySym(&cov) 261 covToCorr(corrFromCov) 262 263 if !mat.EqualApprox(&corr, corrFromCov, 1e-14) { 264 t.Errorf("%d: corrToCov did not match direct Correlation calculation. Want: %v, got: %v. ", i, corr, corrFromCov) 265 } 266 if !mat.EqualApprox(&cov, covFromCorr, 1e-14) { 267 t.Errorf("%d: covToCorr did not match direct Covariance calculation. Want: %v, got: %v. ", i, cov, covFromCorr) 268 } 269 270 if !panics(func() { corrToCov(mat.NewSymDense(2, nil), []float64{}) }) { 271 t.Errorf("CorrelationMatrix did not panic with sigma size mismatch") 272 } 273 } 274 } 275 276 func TestMahalanobis(t *testing.T) { 277 // Comparison with scipy. 278 for cas, test := range []struct { 279 x, y *mat.VecDense 280 Sigma *mat.SymDense 281 ans float64 282 }{ 283 { 284 x: mat.NewVecDense(3, []float64{1, 2, 3}), 285 y: mat.NewVecDense(3, []float64{0.8, 1.1, -1}), 286 Sigma: mat.NewSymDense(3, 287 []float64{ 288 0.8, 0.3, 0.1, 289 0.3, 0.7, -0.1, 290 0.1, -0.1, 7}), 291 ans: 1.9251757377680914, 292 }, 293 } { 294 var chol mat.Cholesky 295 ok := chol.Factorize(test.Sigma) 296 if !ok { 297 panic("bad test") 298 } 299 ans := Mahalanobis(test.x, test.y, &chol) 300 if math.Abs(ans-test.ans) > 1e-14 { 301 t.Errorf("Cas %d: got %v, want %v", cas, ans, test.ans) 302 } 303 } 304 } 305 306 // benchmarks 307 308 func randMat(r, c int) mat.Matrix { 309 x := make([]float64, r*c) 310 for i := range x { 311 x[i] = rand.Float64() 312 } 313 return mat.NewDense(r, c, x) 314 } 315 316 func benchmarkCovarianceMatrix(b *testing.B, m mat.Matrix) { 317 var res mat.SymDense 318 b.ResetTimer() 319 for i := 0; i < b.N; i++ { 320 res.Reset() 321 CovarianceMatrix(&res, m, nil) 322 } 323 } 324 func benchmarkCovarianceMatrixWeighted(b *testing.B, m mat.Matrix) { 325 r, _ := m.Dims() 326 wts := make([]float64, r) 327 for i := range wts { 328 wts[i] = 0.5 329 } 330 var res mat.SymDense 331 b.ResetTimer() 332 for i := 0; i < b.N; i++ { 333 res.Reset() 334 CovarianceMatrix(&res, m, wts) 335 } 336 } 337 func benchmarkCovarianceMatrixInPlace(b *testing.B, m mat.Matrix) { 338 _, c := m.Dims() 339 res := mat.NewSymDense(c, nil) 340 b.ResetTimer() 341 for i := 0; i < b.N; i++ { 342 CovarianceMatrix(res, m, nil) 343 } 344 } 345 346 func BenchmarkCovarianceMatrixSmallxSmall(b *testing.B) { 347 // 10 * 10 elements 348 x := randMat(small, small) 349 benchmarkCovarianceMatrix(b, x) 350 } 351 func BenchmarkCovarianceMatrixSmallxMedium(b *testing.B) { 352 // 10 * 1000 elements 353 x := randMat(small, medium) 354 benchmarkCovarianceMatrix(b, x) 355 } 356 357 func BenchmarkCovarianceMatrixMediumxSmall(b *testing.B) { 358 // 1000 * 10 elements 359 x := randMat(medium, small) 360 benchmarkCovarianceMatrix(b, x) 361 } 362 func BenchmarkCovarianceMatrixMediumxMedium(b *testing.B) { 363 // 1000 * 1000 elements 364 x := randMat(medium, medium) 365 benchmarkCovarianceMatrix(b, x) 366 } 367 368 func BenchmarkCovarianceMatrixLargexSmall(b *testing.B) { 369 // 1e5 * 10 elements 370 x := randMat(large, small) 371 benchmarkCovarianceMatrix(b, x) 372 } 373 374 func BenchmarkCovarianceMatrixHugexSmall(b *testing.B) { 375 // 1e7 * 10 elements 376 x := randMat(huge, small) 377 benchmarkCovarianceMatrix(b, x) 378 } 379 380 func BenchmarkCovarianceMatrixSmallxSmallWeighted(b *testing.B) { 381 // 10 * 10 elements 382 x := randMat(small, small) 383 benchmarkCovarianceMatrixWeighted(b, x) 384 } 385 func BenchmarkCovarianceMatrixSmallxMediumWeighted(b *testing.B) { 386 // 10 * 1000 elements 387 x := randMat(small, medium) 388 benchmarkCovarianceMatrixWeighted(b, x) 389 } 390 391 func BenchmarkCovarianceMatrixMediumxSmallWeighted(b *testing.B) { 392 // 1000 * 10 elements 393 x := randMat(medium, small) 394 benchmarkCovarianceMatrixWeighted(b, x) 395 } 396 func BenchmarkCovarianceMatrixMediumxMediumWeighted(b *testing.B) { 397 // 1000 * 1000 elements 398 x := randMat(medium, medium) 399 benchmarkCovarianceMatrixWeighted(b, x) 400 } 401 402 func BenchmarkCovarianceMatrixLargexSmallWeighted(b *testing.B) { 403 // 1e5 * 10 elements 404 x := randMat(large, small) 405 benchmarkCovarianceMatrixWeighted(b, x) 406 } 407 408 func BenchmarkCovarianceMatrixHugexSmallWeighted(b *testing.B) { 409 // 1e7 * 10 elements 410 x := randMat(huge, small) 411 benchmarkCovarianceMatrixWeighted(b, x) 412 } 413 414 func BenchmarkCovarianceMatrixSmallxSmallInPlace(b *testing.B) { 415 // 10 * 10 elements 416 x := randMat(small, small) 417 benchmarkCovarianceMatrixInPlace(b, x) 418 } 419 func BenchmarkCovarianceMatrixSmallxMediumInPlace(b *testing.B) { 420 // 10 * 1000 elements 421 x := randMat(small, medium) 422 benchmarkCovarianceMatrixInPlace(b, x) 423 } 424 425 func BenchmarkCovarianceMatrixMediumxSmallInPlace(b *testing.B) { 426 // 1000 * 10 elements 427 x := randMat(medium, small) 428 benchmarkCovarianceMatrixInPlace(b, x) 429 } 430 func BenchmarkCovarianceMatrixMediumxMediumInPlace(b *testing.B) { 431 // 1000 * 1000 elements 432 x := randMat(medium, medium) 433 benchmarkCovarianceMatrixInPlace(b, x) 434 } 435 436 func BenchmarkCovarianceMatrixLargexSmallInPlace(b *testing.B) { 437 // 1e5 * 10 elements 438 x := randMat(large, small) 439 benchmarkCovarianceMatrixInPlace(b, x) 440 } 441 442 func BenchmarkCovarianceMatrixHugexSmallInPlace(b *testing.B) { 443 // 1e7 * 10 elements 444 x := randMat(huge, small) 445 benchmarkCovarianceMatrixInPlace(b, x) 446 } 447 448 func BenchmarkCovToCorr(b *testing.B) { 449 // generate a 10x10 covariance matrix 450 m := randMat(small, small) 451 var cov mat.SymDense 452 CovarianceMatrix(&cov, m, nil) 453 cc := mat.NewSymDense(cov.SymmetricDim(), nil) 454 b.ResetTimer() 455 for i := 0; i < b.N; i++ { 456 b.StopTimer() 457 cc.CopySym(&cov) 458 b.StartTimer() 459 covToCorr(cc) 460 } 461 } 462 463 func BenchmarkCorrToCov(b *testing.B) { 464 // generate a 10x10 correlation matrix 465 m := randMat(small, small) 466 var corr mat.SymDense 467 CorrelationMatrix(&corr, m, nil) 468 cc := mat.NewSymDense(corr.SymmetricDim(), nil) 469 sigma := make([]float64, small) 470 for i := range sigma { 471 sigma[i] = 2 472 } 473 b.ResetTimer() 474 for i := 0; i < b.N; i++ { 475 b.StopTimer() 476 cc.CopySym(&corr) 477 b.StartTimer() 478 corrToCov(cc, sigma) 479 } 480 }