| package stats |
| |
| import ( |
| "fmt" |
| "math" |
| ) |
| |
| // dist represents the probability distribution of the sign rank test. |
| type dist struct { |
| n int |
| pmf []float64 |
| } |
| |
| // newWilcoxonDistribution is based on scipy: https://github.com/scipy/scipy/blob/main/scipy/stats/_hypotests.py#L600 |
| func newWilcoxonDistribution(n int) (*dist, error) { |
| if n <= 0 { |
| return nil, fmt.Errorf("input n (%v) should be a non zero integer", n) |
| } |
| |
| size := n*(n+1)/2 + 1 |
| c := make([]float64, size) |
| c[0] = 1.0 |
| cSize := 1 |
| for k := 1; k <= n; k++ { |
| prevC := c |
| c = make([]float64, size) |
| m := cSize |
| curSize := k*(k+1)/2 + 1 |
| for i := 0; i < m; i++ { |
| c[i] = prevC[i] * 0.5 |
| } |
| j := 0 |
| for i := curSize - m; i < curSize; i++ { |
| c[i] += prevC[j] * 0.5 |
| j++ |
| } |
| cSize = curSize |
| } |
| return &dist{ |
| n: n, |
| pmf: c, |
| }, nil |
| } |
| |
| // pSignRank is the distribution function for the Wilcoxon Signed Rank statistic obtained from a sample with size n. |
| func (dist dist) pSignRank(x float64, lowerTail bool) float64 { |
| x = math.Round(x + 1e-7) |
| if x < 0 { |
| if lowerTail { |
| return 0 |
| } |
| return 1 |
| } |
| |
| if x >= float64(len(dist.pmf)-1) { |
| if lowerTail { |
| return 1 |
| } |
| return 0 |
| } |
| |
| p := 0.0 |
| for i := 0; i <= int(x); i++ { |
| p += dist.pmf[i] |
| } |
| |
| if lowerTail { |
| return p |
| } |
| return 1 - p |
| } |
| |
| // qSignRank is the quantile function for the Wilcoxon Signed Rank statistic obtained from a sample with size n. |
| func (dist dist) qSignRank(x float64, lowerTail bool) (float64, error) { |
| if x < 0 || x > 1 { |
| return 0, fmt.Errorf("input probability x (%v) should between 0 and 1", x) |
| } |
| if lowerTail { |
| if x == 0 { |
| return 0, nil |
| } |
| if x == 1 { |
| return float64(len(dist.pmf) - 1), nil |
| } |
| } else { |
| if x == 1 { |
| return 0, nil |
| } |
| if x == 0 { |
| return float64(len(dist.pmf) - 1), nil |
| } |
| } |
| |
| if !lowerTail { |
| x = 1 - x |
| } |
| |
| p := 0.0 |
| q := 0 |
| if x <= 0.5 { |
| for true { |
| if q >= len(dist.pmf) { |
| return 0, fmt.Errorf("q (%v) should be less than length of pmf (%v)", q, len(dist.pmf)) |
| } |
| p += dist.pmf[q] |
| if p >= x { |
| break |
| } |
| q++ |
| } |
| } else { |
| x = 1 - x |
| for true { |
| p += dist.pmf[q] |
| if p > x { |
| q = len(dist.pmf) - 1 - q |
| break |
| } |
| q++ |
| } |
| } |
| |
| return float64(q), nil |
| } |