2

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?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Ivan
  • 316
  • 3
  • 15
  • 1
    You haven't used NormFloat64() correctly (it should be `rand.NormFloat64() * float64(sd) + float64(mean)`) but that's not a truncated normal distribution -- it's a normal distribution. I don't believe there's any package that provides that distribution so you'll have to implement it yourself. – Paul Hankin Jun 16 '21 at 12:41
  • 2
    Although it's not exactly clear that that's what you want -- your `GenerateTruncatedNormal` function doesn't have `low` and `upp` parameters like `get_truncated_normal` from python, so something seems off. – Paul Hankin Jun 16 '21 at 12:43
  • @PaulHankin, as the docs says `NormFloat64 returns a normally distributed float64 in the range [-math.MaxFloat64, +math.MaxFloat64] with standard normal distribution (mean = 0, stddev = 1) from the default Source. To produce a different normal distribution, callers can adjust the output using`, so I suppose the result of the func can be adjusted using those 2 params I pass in the function – Ivan Jun 16 '21 at 12:46
  • @PaulHankin,`GenerateTruncatedNormal(5, 1)` makes smaller numbers, than with 10 and 5 passed – Ivan Jun 16 '21 at 12:47
  • 4
    Sorry, you're replying to me but I'm not sure what response you would like. Perhaps you're saying that you think the docs are saying you can write `rand.NormFloat64() * a + b` to get a truncated normal distribution, but if so that's wrong -- you get a normal distribution with sd a and mean b. – Paul Hankin Jun 16 '21 at 12:51
  • 1
    @PaulHankin, you are right, I treated the docs wrong. Thanks for the reply :D – Ivan Jun 16 '21 at 12:54

1 Answers1

1

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.

Deleplace
  • 6,812
  • 5
  • 28
  • 41
  • Now I realize that "if the [low..high] interval is very narrow", then the desired PDF looks like a straight line, which is maybe not a typical usage of a normal-based distribution. – Deleplace Jun 25 '21 at 13:18