-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmahalanobis.go
137 lines (121 loc) · 3.46 KB
/
mahalanobis.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Naive implementation of the Mahalanobis distance using go.matrix
// (https://en.wikipedia.org/wiki/Mahalanobis_distance)
//
// This is me learning Go, it's probably broken, don't use it.
//
// Example:
//
// package main
//
// import (
// "fmt"
// "github.com/skelterjohn/go.matrix"
// "github.com/ant0ine/go.mahalanobis"
// )
//
// func main() {
//
// points := matrix.MakeDenseMatrix([]float64{
// 1, 4, 3, 4,
// 4, 2, 3, 4,
// }, 2, 4)
// fmt.Println("4 points:\n", points)
//
// target := matrix.MakeDenseMatrix([]float64{
// 3,
// 4,
// }, 2, 1)
// fmt.Println("the target point:\n", target)
//
// distance, err := mahalanobis.Distance(points, target)
// if err != nil {
// panic(err)
// }
// fmt.Println("Mahalanobis distance=", distance)
// }
package mahalanobis
import (
"errors"
"github.com/skelterjohn/go.matrix"
"math"
)
// Given a set a points, return the mean vector.
// points.Rows() = dimensions. points.Cols() = number of points.
func MeanVector(points *matrix.DenseMatrix) *matrix.DenseMatrix {
mean := matrix.Zeros(points.Rows(), 1)
for i := 0; i < points.Rows(); i++ {
sum := 0.0
for j := 0; j < points.Cols(); j++ {
sum += points.Get(i, j)
}
mean.Set(i, 0, sum/float64(points.Cols()))
}
return mean
}
func sample_covariance_matrix(points, mean *matrix.DenseMatrix) *matrix.DenseMatrix {
dim := points.Rows()
cov := matrix.Zeros(dim, dim)
for i := 0; i < dim; i++ {
for j := 0; j < dim; j++ {
if i > j {
// symetric matrix
continue
}
// TODO in go routines ?
sum := 0.0
for k := 0; k < points.Cols(); k++ {
sum += (points.Get(i, k) - mean.Get(i, 0)) * (points.Get(j, k) - mean.Get(j, 0))
}
// this is the sample covariance, divide by (N - 1)
covariance := sum / (float64(points.Cols() - 1))
cov.Set(i, j, covariance)
// symetric matrix
cov.Set(j, i, covariance)
}
}
return cov
}
// Return the covariance matrix for this set of points (sample covariance is used)
// points.Rows() = dimensions. points.Cols() = number of points.
func CovarianceMatrix(points *matrix.DenseMatrix) *matrix.DenseMatrix {
mean := MeanVector(points)
return sample_covariance_matrix(points, mean)
}
// Return the square of the Mahalanobis distance
// points.Rows() = dimensions. points.Cols() = number of points.
// target.Cols = 1
func DistanceSquare(points, target *matrix.DenseMatrix) (float64, error) {
// TODO support multiple points for target, and return a matrix of distances
if target.Rows() != points.Rows() {
err := errors.New("target does not have the same dimension than points")
return -1, err
}
mean := MeanVector(points)
delta := target.Copy()
delta.SubtractDense(mean)
cov := sample_covariance_matrix(points, mean)
inv, err := cov.Inverse()
if err != nil {
return -1, err
}
product1, err := inv.TimesDense(delta)
if err != nil {
return -1, err
}
delta_t := delta.Transpose()
product2, err := delta_t.TimesDense(product1)
if err != nil {
return -1, err
}
return product2.Get(0, 0), nil
}
// Return the Mahalanobis distance
// points.Rows() = dimensions. points.Cols() = number of points.
// target.Cols = 1
func Distance(points, target *matrix.DenseMatrix) (float64, error) {
square, err := DistanceSquare(points, target)
if err != nil {
return -1, err
}
return math.Sqrt(square), nil
}