Search code examples
gorandomnormal-distribution

How to generate random number in range of truncated normal distribution


I need to generate a value in range of truncated normal distribution, for example, in python you could use scipy.stats.truncnorm() to make

def get_truncated_normal(mean=.0, sd=1., low=.0, upp=10.):
    return truncnorm((low - mean) / sd, (upp - mean) / sd, loc=mean, scale=sd)

how it is described here

Is there any package to make something in go or should I write the following function myself?

I tried following, how the doc says, but it makes number not in the needed range:

func GenerateTruncatedNormal(mean, sd uint64) float64 {
    return rand.NormFloat64() * (float64)(sd + mean)
}
GenerateTruncatedNormal(10, 5)

makes 16.61, -14.54, or even 32.8, but I expect a small chance of getting 15 due to mean = 10 -> 10 + 5 = 15 is maximum value which we can get. What is wrong here? 😅


Solution

  • One way of achieving this consists of

    • generating a number x from the Normal distribution, with the desired parameters for mean and standard deviation,
    • if it's outside the range [low..high], then throw it away and try again.

    This respects the Probability Density Function of the Normal distribution, effectively cutting out the left and right tails.

    func TruncatedNormal(mean, stdDev, low, high float64) float64 {
        if low >= high {
            panic("high must be greater than low")
        }
    
        for {
            x := rand.NormFloat64()*stdDev + mean
            if low <= x && x < high {
                return x
            }
            // fmt.Println("missed!", x)
        }
    }
    

    Playground

    If the [low..high] interval is very narrow, then it will take a bit more computation time, as more generated numbers will be thrown away. However, it still converges very fast in practice.

    I checked the code above by plotting its results against and compare them to the results of scipy's truncnorm, and they do produce equivalent charts.