github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/stat/distuv/distribution_test.go (about)

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package distuv
     6  
     7  import (
     8  	"math"
     9  	"testing"
    10  
    11  	"github.com/jingcheng-WU/gonum/floats"
    12  	"github.com/jingcheng-WU/gonum/floats/scalar"
    13  	"github.com/jingcheng-WU/gonum/integrate/quad"
    14  	"github.com/jingcheng-WU/gonum/stat"
    15  )
    16  
    17  type meaner interface {
    18  	Mean() float64
    19  }
    20  
    21  type quantiler interface {
    22  	Quantile(float64) float64
    23  }
    24  
    25  type medianer interface {
    26  	quantiler
    27  	Median() float64
    28  }
    29  
    30  type varStder interface {
    31  	StdDev() float64
    32  	Variance() float64
    33  }
    34  
    35  type entropyer interface {
    36  	LogProber
    37  	Entropy() float64
    38  }
    39  
    40  type exKurtosiser interface {
    41  	ExKurtosis() float64
    42  	Mean() float64
    43  }
    44  
    45  type skewnesser interface {
    46  	StdDev() float64
    47  	Mean() float64
    48  	Skewness() float64
    49  }
    50  
    51  type cumulanter interface {
    52  	Quantiler
    53  	CDF(x float64) float64
    54  	Survival(x float64) float64
    55  }
    56  
    57  func generateSamples(x []float64, r Rander) {
    58  	for i := range x {
    59  		x[i] = r.Rand()
    60  	}
    61  }
    62  
    63  type probLogprober interface {
    64  	Prob(x float64) float64
    65  	LogProb(x float64) float64
    66  }
    67  
    68  type cumulantProber interface {
    69  	cumulanter
    70  	probLogprober
    71  }
    72  
    73  func checkMean(t *testing.T, i int, x []float64, m meaner, tol float64) {
    74  	mean := stat.Mean(x, nil)
    75  	if !scalar.EqualWithinAbsOrRel(mean, m.Mean(), tol, tol) {
    76  		t.Errorf("Mean mismatch case %v: want: %v, got: %v", i, mean, m.Mean())
    77  	}
    78  }
    79  
    80  func checkMedian(t *testing.T, i int, x []float64, m medianer, tol float64) {
    81  	median := stat.Quantile(0.5, stat.Empirical, x, nil)
    82  	if !scalar.EqualWithinAbsOrRel(median, m.Median(), tol, tol) {
    83  		t.Errorf("Median mismatch case %v: want: %v, got: %v", i, median, m.Median())
    84  	}
    85  }
    86  
    87  func checkVarAndStd(t *testing.T, i int, x []float64, v varStder, tol float64) {
    88  	variance := stat.Variance(x, nil)
    89  	if !scalar.EqualWithinAbsOrRel(variance, v.Variance(), tol, tol) {
    90  		t.Errorf("Variance mismatch case %v: want: %v, got: %v", i, variance, v.Variance())
    91  	}
    92  	std := math.Sqrt(variance)
    93  	if !scalar.EqualWithinAbsOrRel(std, v.StdDev(), tol, tol) {
    94  		t.Errorf("StdDev mismatch case %v: want: %v, got: %v", i, std, v.StdDev())
    95  	}
    96  }
    97  
    98  func checkEntropy(t *testing.T, i int, x []float64, e entropyer, tol float64) {
    99  	tmp := make([]float64, len(x))
   100  	for i, v := range x {
   101  		tmp[i] = -e.LogProb(v)
   102  	}
   103  	entropy := stat.Mean(tmp, nil)
   104  	if !scalar.EqualWithinAbsOrRel(entropy, e.Entropy(), tol, tol) {
   105  		t.Errorf("Entropy mismatch case %v: want: %v, got: %v", i, entropy, e.Entropy())
   106  	}
   107  }
   108  
   109  func checkExKurtosis(t *testing.T, i int, x []float64, e exKurtosiser, tol float64) {
   110  	mean := e.Mean()
   111  	tmp := make([]float64, len(x))
   112  	for i, x := range x {
   113  		tmp[i] = math.Pow(x-mean, 4)
   114  	}
   115  	variance := stat.Variance(x, nil)
   116  	mu4 := stat.Mean(tmp, nil)
   117  	kurtosis := mu4/(variance*variance) - 3
   118  	if !scalar.EqualWithinAbsOrRel(kurtosis, e.ExKurtosis(), tol, tol) {
   119  		t.Errorf("ExKurtosis mismatch case %v: want: %v, got: %v", i, kurtosis, e.ExKurtosis())
   120  	}
   121  }
   122  
   123  func checkSkewness(t *testing.T, i int, x []float64, s skewnesser, tol float64) {
   124  	mean := s.Mean()
   125  	std := s.StdDev()
   126  	tmp := make([]float64, len(x))
   127  	for i, v := range x {
   128  		tmp[i] = math.Pow(v-mean, 3)
   129  	}
   130  	mu3 := stat.Mean(tmp, nil)
   131  	skewness := mu3 / math.Pow(std, 3)
   132  	if !scalar.EqualWithinAbsOrRel(skewness, s.Skewness(), tol, tol) {
   133  		t.Errorf("Skewness mismatch case %v: want: %v, got: %v", i, skewness, s.Skewness())
   134  	}
   135  }
   136  
   137  func checkQuantileCDFSurvival(t *testing.T, i int, xs []float64, c cumulanter, tol float64) {
   138  	// Quantile, CDF, and survival check.
   139  	for i, p := range []float64{0.1, 0.25, 0.5, 0.75, 0.9} {
   140  		x := c.Quantile(p)
   141  		cdf := c.CDF(x)
   142  		estCDF := stat.CDF(x, stat.Empirical, xs, nil)
   143  		if !scalar.EqualWithinAbsOrRel(cdf, estCDF, tol, tol) {
   144  			t.Errorf("CDF mismatch case %v: want: %v, got: %v", i, estCDF, cdf)
   145  		}
   146  		if !scalar.EqualWithinAbsOrRel(cdf, p, tol, tol) {
   147  			t.Errorf("Quantile/CDF mismatch case %v: want: %v, got: %v", i, p, cdf)
   148  		}
   149  		if math.Abs(1-cdf-c.Survival(x)) > 1e-14 {
   150  			t.Errorf("Survival/CDF mismatch case %v: want: %v, got: %v", i, 1-cdf, c.Survival(x))
   151  		}
   152  	}
   153  	if !panics(func() { c.Quantile(-0.0001) }) {
   154  		t.Errorf("Expected panic with negative argument to Quantile")
   155  	}
   156  	if !panics(func() { c.Quantile(1.0001) }) {
   157  		t.Errorf("Expected panic with Quantile argument above 1")
   158  	}
   159  }
   160  
   161  // checkProbContinuous checks that the PDF is consistent with LogPDF
   162  // and integrates to 1 from the lower to upper bound.
   163  func checkProbContinuous(t *testing.T, i int, x []float64, lower float64, upper float64, p probLogprober, tol float64) {
   164  	q := quad.Fixed(p.Prob, lower, upper, 1000000, nil, 0)
   165  	if math.Abs(q-1) > tol {
   166  		t.Errorf("Probability distribution doesn't integrate to 1. Case %v: Got %v", i, q)
   167  	}
   168  
   169  	// Check that PDF and LogPDF are consistent.
   170  	for i, v := range x {
   171  		if math.Abs(math.Log(p.Prob(v))-p.LogProb(v)) > 1e-14 {
   172  			t.Errorf("Prob and LogProb mismatch case %v at %v: want %v, got %v", i, v, math.Log(v), p.LogProb(v))
   173  			break
   174  		}
   175  	}
   176  }
   177  
   178  // checkProbQuantContinuous checks that the Prob, Rand, and Quantile are all consistent.
   179  // checkProbContinuous only checks that Prob is a valid distribution (integrates
   180  // to 1 and greater than 0). However, this is also true if the PDF of a different
   181  // distribution is used. This checks that PDF is also consistent with the
   182  // CDF implementation and the random samples.
   183  func checkProbQuantContinuous(t *testing.T, i int, xs []float64, c cumulantProber, tol float64) {
   184  	ps := make([]float64, 101)
   185  	floats.Span(ps, 0, 1)
   186  
   187  	var xp, x float64
   188  	for i, p := range ps {
   189  		x = c.Quantile(p)
   190  		if p == 0 {
   191  			xp = x
   192  			if floats.Min(xs) < x {
   193  				t.Errorf("Sample of x less than Quantile(0). Case %v.", i)
   194  				break
   195  			}
   196  			continue
   197  		}
   198  		if p == 1 {
   199  			if floats.Max(xs) > x {
   200  				t.Errorf("Sample of x greater than Quantile(1). Case %v.", i)
   201  				break
   202  			}
   203  		}
   204  
   205  		// The integral of the PDF between xp and x should be the difference in
   206  		// the quantiles.
   207  		q := quad.Fixed(c.Prob, xp, x, 1000, nil, 0)
   208  		if math.Abs(q-(p-ps[i-1])) > 1e-5 {
   209  			t.Errorf("Integral of PDF doesn't match quantile. Case %v. Want %v, got %v.", i, p-ps[i-1], q)
   210  			break
   211  		}
   212  
   213  		pEst := stat.CDF(x, stat.Empirical, xs, nil)
   214  		if math.Abs(pEst-p) > tol {
   215  			t.Errorf("Empirical CDF doesn't match quantile. Case %v.", i)
   216  		}
   217  		xp = x
   218  	}
   219  }
   220  
   221  type moder interface {
   222  	Mode() float64
   223  }
   224  
   225  func checkMode(t *testing.T, i int, xs []float64, m moder, dx float64, tol float64) {
   226  	rXs := make([]float64, len(xs))
   227  	for j, x := range xs {
   228  		rXs[j] = math.RoundToEven(x/dx) * dx
   229  	}
   230  	want, _ := stat.Mode(rXs, nil)
   231  	got := m.Mode()
   232  	if !scalar.EqualWithinAbs(want, got, tol) {
   233  		t.Errorf("Mode mismatch case %d: want %g, got %v", i, want, got)
   234  	}
   235  }
   236  
   237  // checkProbDiscrete confirms that PDF and Rand are consistent for discrete distributions.
   238  func checkProbDiscrete(t *testing.T, i int, xs []float64, p probLogprober, tol float64) {
   239  	// Make a map of all of the unique samples.
   240  	m := make(map[float64]int)
   241  	for _, v := range xs {
   242  		m[v]++
   243  	}
   244  	for x, count := range m {
   245  		prob := float64(count) / float64(len(xs))
   246  		if math.Abs(prob-p.Prob(x)) > tol {
   247  			t.Errorf("PDF mismatch case %v at %v: want %v, got %v", i, x, prob, p.Prob(x))
   248  		}
   249  		if math.Abs(math.Log(p.Prob(x))-p.LogProb(x)) > 1e-14 {
   250  			t.Errorf("Prob and LogProb mismatch case %v at %v: want %v, got %v", i, x, math.Log(x), p.LogProb(x))
   251  		}
   252  	}
   253  }
   254  
   255  // testRandLogProb tests that LogProb and Rand give consistent results. This
   256  // can be used when the distribution does not implement CDF.
   257  func testRandLogProbContinuous(t *testing.T, i int, min float64, x []float64, f LogProber, tol float64, bins int) {
   258  	for cdf := 1 / float64(bins); cdf <= 1-1/float64(bins); cdf += 1 / float64(bins) {
   259  		// Get the estimated CDF from the samples
   260  		pt := stat.Quantile(cdf, stat.Empirical, x, nil)
   261  
   262  		prob := func(x float64) float64 {
   263  			return math.Exp(f.LogProb(x))
   264  		}
   265  		// Integrate the PDF to find the CDF
   266  		estCDF := quad.Fixed(prob, min, pt, 10000, nil, 0)
   267  		if !scalar.EqualWithinAbsOrRel(cdf, estCDF, tol, tol) {
   268  			t.Errorf("Mismatch between integral of PDF and empirical CDF. Case %v. Want %v, got %v", i, cdf, estCDF)
   269  		}
   270  	}
   271  }
   272  
   273  func panics(fun func()) (b bool) {
   274  	defer func() {
   275  		err := recover()
   276  		if err != nil {
   277  			b = true
   278  		}
   279  	}()
   280  	fun()
   281  	return
   282  }