From 7eb9e1e47278577013e9b813ca1edb28d7e53aef Mon Sep 17 00:00:00 2001 From: Chris Dzombak Date: Sun, 12 Jan 2025 13:55:07 -0500 Subject: [PATCH] implement circular mean & stddev directly --- go.mod | 2 -- libwx.go | 8 +++----- stat.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 stat.go diff --git a/go.mod b/go.mod index 80db251..1bed671 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,9 @@ go 1.22 toolchain go1.23.4 -replace gonum.org/v1/gonum => github.com/cdzombak/gonum v0.0.0-20250111220929-90b1f7503766 require ( github.com/stretchr/testify v1.9.0 - gonum.org/v1/gonum v0.0.0-00010101000000-000000000000 ) require ( diff --git a/libwx.go b/libwx.go index 5087f1a..1321394 100644 --- a/libwx.go +++ b/libwx.go @@ -8,8 +8,6 @@ package libwx import ( "errors" "math" - - "gonum.org/v1/gonum/stat" ) var ErrInputRange = errors.New("one or more input values are outside the calculation's supported range") @@ -284,7 +282,7 @@ func HeatIndexWarningC(heatIndex TempC) HeatIndexWarning { // AvgDirectionDeg calculates the circular mean of the given set of angles (in degrees). // This is useful to find e.g. the average wind direction. func AvgDirectionDeg(degrees []Degree) Degree { - return radToDeg(stat.CircularMean(degToRadSlice(clampedDegSlice(degrees)), nil)) + return radToDeg(circularMean(degToRadSlice(clampedDegSlice(degrees)), nil)) } // WeightedAvgDirectionDeg calculates the weighted circular mean of the given set of angles (in degrees). @@ -293,11 +291,11 @@ func WeightedAvgDirectionDeg(degrees []Degree, weights []float64) (Degree, error if len(degrees) != len(weights) { return 0.0, ErrMismatchedInputLength } - return radToDeg(stat.CircularMean(degToRadSlice(clampedDegSlice(degrees)), weights)), nil + return radToDeg(circularMean(degToRadSlice(clampedDegSlice(degrees)), weights)), nil } // StdDevDeg calculates the circular standard deviation of the given set of angles (in degrees). // This is useful to find e.g. the variability of wind direction. func StdDevDeg(degrees []Degree) Degree { - return radToDeg(stat.CircularStdDev(degToRadSlice(clampedDegSlice(degrees)))) + return radToDeg(circularStdDev(degToRadSlice(clampedDegSlice(degrees)))) } diff --git a/stat.go b/stat.go new file mode 100644 index 0000000..3f4d295 --- /dev/null +++ b/stat.go @@ -0,0 +1,35 @@ +package libwx + +import "math" + +// borrowed from gonum: +func circularMean(x, weights []float64) float64 { + if weights != nil && len(x) != len(weights) { + panic("stat: slice length mismatch") + } + + var aX, aY float64 + if weights != nil { + for i, v := range x { + aX += weights[i] * math.Cos(v) + aY += weights[i] * math.Sin(v) + } + } else { + for _, v := range x { + aX += math.Cos(v) + aY += math.Sin(v) + } + } + + return math.Atan2(aY, aX) +} + +// from my (open) gonum PR which doesn't (yet) support weights: +func circularStdDev(x []float64) float64 { + var aX, aY float64 + for _, v := range x { + aX += math.Cos(v) + aY += math.Sin(v) + } + return math.Sqrt(-2 * math.Log(math.Sqrt(aY*aY+aX*aX)/float64(len(x)))) +}