commit 1b75ddb4a0f09d5cf3a9b1a34105658719c186d0
parent 8463439870ce528d18a9890541345d667cd3bee9
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Wed, 14 Jun 2023 11:24:13 -0700
fix standard deviation
Diffstat:
2 files changed, 110 insertions(+), 28 deletions(-)
diff --git a/pkg/web/handlers/interceptors/chess.go b/pkg/web/handlers/interceptors/chess.go
@@ -910,42 +910,59 @@ func AnalyseGame(pgn string, t int64) (out AnalyseResult, err error) {
}, nil
}
-func standardDeviation(num []WinPercent) float64 {
- nb := len(num)
- var sum, mean, sd float64
- for i := 0; i < nb; i++ {
- sum += float64(num[i])
+func mean(arr []float64) float64 {
+ var sum float64
+ for _, n := range arr {
+ sum += n
}
- mean = sum / float64(nb)
- for j := 0; j < nb; j++ {
- sd += math.Pow(float64(num[j])-mean, 2)
+ return sum / float64(len(arr))
+}
+
+func standardDeviation(arr []float64) float64 {
+ nb := float64(len(arr))
+ m := mean(arr)
+ var acc float64
+ for _, n := range arr {
+ acc += (n - m) * (n - m)
}
- sd = math.Sqrt(sd) / float64(nb)
- return sd
+ return math.Sqrt(acc / nb)
}
// https://www.scribbr.com/statistics/standard-deviation/
-//// using population variance
-//def standardDeviation(a: Iterable[Double]): Option[Double] =
-//mean(a) map { mean =>
-//Math.sqrt:
-//a.foldLeft(0d) { (sum, x) =>
-//sum + Math.pow(x - mean, 2)
-//} / a.size
-//}
+/**
+ def mean[T](a: Iterable[T])(using n: Numeric[T]): Option[Double] =
+ a.nonEmpty option (n.toDouble(a.sum) / a.size)
+
+ def standardDeviation(a: Iterable[Double]): Option[Double] =
+ mean(a) map { mean =>
+ Math.sqrt:
+ a.foldLeft(0d) { (sum, x) =>
+ sum + Math.pow(x - mean, 2)
+ } / a.size
+ }
+*/
type WinPercent float64
type Cp int
-func fromCentiPawns(cp Cp) WinPercent {
- return 50 + 50*winningChances(cp)
+func (c Cp) ceiled() Cp {
+ if c > 1000 {
+ return 1000
+ } else if c < -1000 {
+ return -1000
+ }
+ return c
}
-func winningChances(cp Cp) WinPercent {
+func fromCentiPawns(cp Cp) float64 {
+ return 50 + 50*winningChances(cp.ceiled())
+}
+
+func winningChances(cp Cp) float64 {
const MULTIPLIER = -0.00368208 // https://github.com/lichess-org/lila/pull/11148
res := 2/(1+math.Exp(MULTIPLIER*float64(cp))) - 1
- out := WinPercent(math.Max(math.Min(res, 1), -1))
+ out := math.Max(math.Min(res, 1), -1)
return out
}
@@ -959,7 +976,7 @@ func fromWinPercents(before, after float64) (accuracy float64) {
return math.Min(math.Max(raw, 0), 100)
}
-func calcWindows(allWinPercents []WinPercent, windowSize int) (out [][]WinPercent) {
+func calcWindows(allWinPercents []float64, windowSize int) (out [][]float64) {
start := allWinPercents[:windowSize]
tmp := windowSize
if tmp > len(allWinPercents) {
@@ -970,7 +987,7 @@ func calcWindows(allWinPercents []WinPercent, windowSize int) (out [][]WinPercen
}
for i := 0; i < len(allWinPercents)-(windowSize-1); i++ {
- curr := make([]WinPercent, 0)
+ curr := make([]float64, 0)
for j := 0; j < windowSize; j++ {
curr = append(curr, allWinPercents[i+j])
}
@@ -979,14 +996,14 @@ func calcWindows(allWinPercents []WinPercent, windowSize int) (out [][]WinPercen
return
}
-func calcWeights(windows [][]WinPercent) (out []float64) {
+func calcWeights(windows [][]float64) (out []float64) {
for _, w := range windows {
out = append(out, math.Min(math.Max(standardDeviation(w), 0.5), 12))
}
return
}
-func calcWeightedAccuracies(allWinPercents []WinPercent, weights []float64) (float64, float64) {
+func calcWeightedAccuracies(allWinPercents []float64, weights []float64) (float64, float64) {
sw := calcWindows(allWinPercents, 2)
whites := make([][2]float64, 0)
blacks := make([][2]float64, 0)
@@ -1025,6 +1042,19 @@ func harmonicMean(arr [][2]float64) float64 {
return float64(len(vs)) / sm
}
+/**
+ def harmonicMean(a: Iterable[Double]): Option[Double] =
+ a.nonEmpty option {
+ a.size / a.foldLeft(0d) { (acc, v) => acc + 1 / Math.max(1, v) }
+ }
+
+ def weightedMean(a: Iterable[(Double, Double)]): Option[Double] =
+ a.nonEmpty so {
+ a.foldLeft(0d -> 0d) { case ((av, aw), (v, w)) => (av + v * w, aw + w) } match
+ case (v, w) => w != 0 option v / w
+ }
+*/
+
func weightedMean(a [][2]float64) float64 {
vs := make([]float64, 0)
ws := make([]float64, 0)
@@ -1040,7 +1070,7 @@ func weightedMean(a [][2]float64) float64 {
continue
}
sumWeight += ws[i]
- avg += float64(v) * ws[i]
+ avg += v * ws[i]
}
avg /= sumWeight
return avg
@@ -1048,7 +1078,7 @@ func weightedMean(a [][2]float64) float64 {
func gameAccuracy(cps []int) (float64, float64) {
cps = append([]int{15}, cps...)
- var allWinPercents []WinPercent
+ var allWinPercents []float64
for _, cp := range cps {
allWinPercents = append(allWinPercents, fromCentiPawns(Cp(cp)))
}
diff --git a/pkg/web/handlers/interceptors/chess_test.go b/pkg/web/handlers/interceptors/chess_test.go
@@ -82,3 +82,55 @@ func Test_gameAccuracy(t *testing.T) {
assert.True(t, isCloseTo(w, 20, 8))
assert.True(t, isCloseTo(b, 20, 8))
}
+
+func Test_gameAccuracy1(t *testing.T) {
+ type args struct {
+ cps []int
+ }
+ tests := []struct {
+ name string
+ args args
+ want float64
+ want1 float64
+ }{
+ {
+ name: "test1",
+ args: args{
+ cps: []int{48, 38, 36, 36, 25, 61, 42, 131, 82, 135, 109, 186, 152, 145, 134, 280, 287, 264, 278, 271, 271, 306, 246, 335, 311, 500, 87, 276, 284, 398, 196, 514, 520, 641, -144, -74, -156, -107, -94, 60, 47, 146, -33, 98, -483, 31, -174, 385, -152, 467, 515, 846, 874, 884, 874, 938, 885, 889, 935, 946, 831, 998, 1018, 1026, 1031, 1142, 935, 1062, 1096, 1123, 1112, 1123},
+ },
+ want: 61.61609601278025,
+ want1: 59.024965435590794,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, got1 := gameAccuracy(tt.args.cps)
+ assert.Equalf(t, tt.want, got, "gameAccuracy(%v)", tt.args.cps)
+ assert.Equalf(t, tt.want1, got1, "gameAccuracy(%v)", tt.args.cps)
+ })
+ }
+}
+
+func Test_standardDeviation(t *testing.T) {
+ type args struct {
+ num []float64
+ }
+ tests := []struct {
+ name string
+ args args
+ want float64
+ }{
+ {
+ name: "",
+ args: args{
+ num: []float64{91.37426170872673, 37.04656899332343, 43.22998523826144, 36.02211364871788, 40.27589492090241, 41.43247132264411, 55.50076484124536},
+ },
+ want: 18.186779319663053,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equalf(t, tt.want, standardDeviation(tt.args.num), "standardDeviation(%v)", tt.args.num)
+ })
+ }
+}