Skip to content

Commit

Permalink
New descriptive statistics function for numerical slices
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolaasuni-vonage committed Sep 5, 2023
1 parent 09da71b commit 1b5064b
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 54 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.79.1
1.80.0
8 changes: 4 additions & 4 deletions examples/service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
replace github.com/Vonage/gosrvlib => ../..

require (
github.com/Vonage/gosrvlib v1.79.1
github.com/Vonage/gosrvlib v1.80.0
github.com/golang/mock v1.6.0
github.com/jstemmer/go-junit-report v0.9.1
github.com/prometheus/client_golang v1.16.0
Expand Down Expand Up @@ -39,7 +39,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.5 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/consul/api v1.24.0 // indirect
Expand Down Expand Up @@ -90,8 +90,8 @@ require (
golang.org/x/net v0.14.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
Expand Down
41 changes: 6 additions & 35 deletions examples/service/go.sum

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/go-playground/validator/v10 v10.15.3
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
github.com/jmoiron/sqlx v1.3.5
github.com/jstemmer/go-junit-report v0.9.1
github.com/julienschmidt/httprouter v1.3.0
Expand All @@ -28,7 +29,7 @@ require (
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.25.0
golang.org/x/crypto v0.12.0
golang.org/x/text v0.12.0
golang.org/x/text v0.13.0
)

require (
Expand Down Expand Up @@ -66,8 +67,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.5 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/consul/api v1.24.0 // indirect
Expand Down Expand Up @@ -116,7 +116,7 @@ require (
golang.org/x/net v0.14.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
Expand Down
16 changes: 6 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
Expand Down Expand Up @@ -403,7 +402,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA=
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
Expand Down Expand Up @@ -543,8 +541,8 @@ github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0 h1:zHs+jv3LO743/zFGcB
github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg=
github.com/google/s2a-go v0.1.5/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down Expand Up @@ -901,7 +899,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
Expand Down Expand Up @@ -1075,8 +1072,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand All @@ -1093,8 +1090,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -1255,7 +1252,6 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
Expand Down
54 changes: 54 additions & 0 deletions pkg/sliceutil/example_stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package sliceutil_test

import (
"fmt"
"log"

"github.com/Vonage/gosrvlib/pkg/sliceutil"
)

func ExampleStats() {
data := []int{53, 83, 13, 79, 13, 37, 83, 29, 37, 13, 83, 83}

ds, err := sliceutil.Stats(data)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Count: %d\n", ds.Count)
fmt.Printf("Entropy: %.3f\n", ds.Entropy)
fmt.Printf("ExKurtosis: %.3f\n", ds.ExKurtosis)
fmt.Printf("Max: %d\n", ds.Max)
fmt.Printf("MaxID: %d\n", ds.MaxID)
fmt.Printf("Mean: %.3f\n", ds.Mean)
fmt.Printf("MeanDev: %.3f\n", ds.MeanDev)
fmt.Printf("Median: %.3f\n", ds.Median)
fmt.Printf("Min: %d\n", ds.Min)
fmt.Printf("MinID: %d\n", ds.MinID)
fmt.Printf("Mode: %d\n", ds.Mode)
fmt.Printf("ModeFreq: %d\n", ds.ModeFreq)
fmt.Printf("Range: %d\n", ds.Range)
fmt.Printf("Skewness: %.3f\n", ds.Skewness)
fmt.Printf("StdDev: %.3f\n", ds.StdDev)
fmt.Printf("Sum: %d\n", ds.Sum)
fmt.Printf("Variance: %.3f\n", ds.Variance)

// Output:
// Count: 12
// Entropy: -2277.134
// ExKurtosis: -1.910
// Max: 83
// MaxID: 1
// Mean: 50.500
// MeanDev: 0.000
// Median: 45.000
// Min: 13
// MinID: 2
// Mode: 83
// ModeFreq: 4
// Range: 70
// Skewness: -0.049
// StdDev: 30.285
// Sum: 606
// Variance: 917.182
}
179 changes: 179 additions & 0 deletions pkg/sliceutil/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package sliceutil

import (
"fmt"
"math"
"slices"

"github.com/Vonage/gosrvlib/pkg/typeutil"
)

// DescStats contains descriptive statistics items for a data set.
type DescStats[V typeutil.Number] struct {
// Count is the total number of items in the data set.
Count int `json:"count"`

// Entropy computes the Shannon entropy of a distribution.
Entropy float64 `json:"entropy"`

// ExKurtosis is the population excess kurtosis of the data set.
// The kurtosis is defined by the 4th moment of the mean divided by the squared variance.
// The excess kurtosis subtracts 3.0 so that the excess kurtosis of the normal distribution is zero.
ExKurtosis float64 `json:"exkurtosis"`

// Max is the maximum value of the data.
Max V `json:"max"`

// MaxID is the index (key) of the Max malue in a data set.
MaxID int `json:"maxid"`

// Mean or Average is a central tendency of the data.
Mean float64 `json:"mean"`

// MeanDev is the Mean Deviation or Mean Absolute Deviation.
// It is an average of absolute differences between each value in the data, and the average of all values.
MeanDev float64 `json:"meandev"`

// Median is the value that divides the data into 2 equal parts.
// When the data is sorted, the number of terms on the left and right side of median is the same.
Median float64 `json:"median"`

// Min is the minimal value of the data.
Min V `json:"min"`

// MinID is the index (key) of the Min malue in a data set.
MinID int `json:"minid"`

// Mode is the term appearing maximum time in data set.
// It is the term that has the highest frequency.
Mode V `json:"mode"`

// ModeFreq is the frequency of the Mode value.
ModeFreq int `json:"modefreq"`

// Range is the difference between the highest (Max) and lowest (Min) value.
Range V `json:"range"`

// Skewness is a measure of the asymmetry of the probability distribution of a real-valued random variable about its mean.
// Provides the adjusted Fisher-Pearson standardized moment coefficient.
Skewness float64 `json:"skewness"`

// StdDev is the Standard deviation of the data.
// It measures the average distance between each quantity and mean.
StdDev float64 `json:"stddev"`

// Sum of all the values in the data.
Sum V `json:"sum"`

// Variance is a square of average distance between each quantity and Mean.
Variance float64 `json:"variance"`
}

// Stats returns descriptive statistics parameters to summarize the input data set.
//
//nolint:gocognit,gocyclo
func Stats[S ~[]V, V typeutil.Number](s S) (*DescStats[V], error) {
n := len(s)

if n < 1 {
return nil, fmt.Errorf("input slice is empty")
}

ord := slices.Clone(s)
slices.Sort(ord)

ds := &DescStats[V]{
Count: len(s),
Max: s[0],
Median: float64(s[0]),
Min: s[0],
Mode: s[0],
ModeFreq: 1,
Sum: s[0],
Mean: float64(s[0]),
}

if n == 1 {
return ds, nil
}

nf := float64(n)
freq := 1

for i := 1; i < n; i++ {
v := s[i]
vf := float64(s[i])

ds.Sum += v

if v < ds.Min {
ds.Min = v
ds.MinID = i
}

if v > ds.Max {
ds.Max = v
ds.MaxID = i
}

if v != 0 {
ds.Entropy -= vf * math.Log(vf)
}

if ord[i] == ord[i-1] {
freq++
} else {
if freq > ds.ModeFreq {
ds.Mode = ord[i]
ds.ModeFreq = freq
}
freq = 1
}
}

if freq > ds.ModeFreq {
ds.Mode = ord[n-1]
ds.ModeFreq = freq
}

ds.Range = ds.Max - ds.Min
ds.Mean = float64(ds.Sum) / nf

midpos := n / 2
if n%2 != 0 {
ds.Median = float64(ord[midpos])
} else {
ds.Median = (float64(ord[midpos-1]) + float64(ord[midpos])) / 2
}

for i := 0; i < n; i++ {
d := float64(ord[i]) - ds.Mean
ds.MeanDev += d
ds.Variance += d * d
}

ds.MeanDev /= nf
ds.Variance /= (nf - 1)
ds.StdDev = math.Sqrt(ds.Variance)

if n < 3 {
return ds, nil
}

for i := 0; i < n; i++ {
d := (float64(ord[i]) - ds.Mean) / ds.StdDev
d3 := d * d * d
ds.Skewness += d3
ds.ExKurtosis += d3 * d
}

ds.Skewness *= (nf / ((nf - 1) * (nf - 2))) // adjusted Fisher-Pearson standardized moment coefficient

if n < 4 {
ds.ExKurtosis = 0
} else {
ds.ExKurtosis = (ds.ExKurtosis * (((nf + 1) / (nf - 1)) * (nf / (nf - 2)) * (1 / (nf - 3)))) - (3 * ((nf - 1) / (nf - 2)) * ((nf - 1) / (nf - 3)))
}

return ds, nil
}
Loading

0 comments on commit 1b5064b

Please sign in to comment.