http://www.perlmonks.org?node_id=501053

Adam has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to figure out an algorithm for generating a random number that is randomly close to a fixed number such that the results will have a bell curve like distribution. That is, if you ran it thousands of times the answer would be closer to the input most of the time but not all the time. Can anyone help me out with this?


To start you out, here is code to get a smooth distribution (P is short for percentage):
my $pErr = 5; # for plus/minus 5. sub MakeP { my $p = $_[0]; $p -= $pErr; $p += int(rand() * 2 * ($pErr ++ 0.5)); return $p / 100 } #Test my @foo; push @foo, MakeP( 50 ) for 0 .. 20000; @foo = sort @foo; while ( @foo ) { my $next = shift @foo; my $count = 1; while ( @foo and $foo[0] == $next ) { ++$count; shift @foo } printf "%f\t%6d\n", $next, $count; }
Note that the distribution is reasonably smooth... but I want it to clump to the middle in either a bell curve or triangular fashion.

Update

I suppose I could randomly choose the width of the distribution, that clumps things... it is less than elegant though.
sub MakeP { my $p = $_[0]; my $e = 1 + int( rand() * $pErr ); $p -= $e; $p += int( rand() * 2 * ( $e + 0.5 ) ); return $p / 100 }

Replies are listed 'Best First'.
Re: Curved Random Distribution
by fglock (Vicar) on Oct 18, 2005 at 16:33 UTC

    How about Math::Random::OO::Normal

    use Math::Random::OO::Normal; push @prngs, Math::Random::OO::Normal->new(), # mean 0, stdev 1 Math::Random::OO::Normal->new(5), # mean 5, stdev 1 Math::Random::OO::Normal->new(1,3); # mean 1, stdev 3 $_->seed(0.42) for @prngs; print( $_->next() . "\n" ) for @prngs;

      I wrote M::R::OO::N, but I now recommend Jerry Hedden's Math::Random::MT::Auto instead. It's faster, more robust and supports more types of distributions.

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Curved Random Distribution
by Limbic~Region (Chancellor) on Oct 18, 2005 at 16:55 UTC
Re: Curved Random Distribution
by BrowserUk (Patriarch) on Oct 18, 2005 at 17:27 UTC

    Like this?


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
      Technically, that would be peak-shaped rather than bell-shaped. That is characteristic of probability curves generated with 2 random inputs. Consider the probability table for 2 6-sided dice, for example.

      The higher the number of random inputs, the more the resulting distribution looks like a bell curve.

      Update: Yep, I glossed right over the mention of "triangle"; all I saw was bell and curve.


      Caution: Contents may have been coded under pressure.

        True, but the OP did mention a "triangular" distribution. For a better approximation of a bell curve he might use this:

        sub bRand { my( $target, $variance ) = @_; my $v = sum map rand() * $variance, 1 .. 4; return $target - $variance + ( $v / 2 ); }

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
Re: Curved Random Distribution
by blokhead (Monsignor) on Oct 18, 2005 at 17:08 UTC
    If all you care about is that your probability distribution qualitatively looks like the classic bell-curve, you can use this quick-and-dirty approximation:
    sub almost_normal { my ($mean, $variance) = @_; return sqrt($variance) * atan2( rand(2) - 1, 1 ) + $mean; }
    The reason it works is because the normal distribution is shaped like exp(-x2). A reasonable first-order approximation of exp(x) is (1+x), so we can try using 1/(1+x2) to approximate the normal curve. This function's integral (accumulating probability distribution) is none other than arctan.

    This gives you a bell-shaped curve in the shape of 1/(1+x2), which is qualitatively "bell-shaped", but has a lot more probability in its "tails" than the normal distribution.

    Update: Augh, this is completely backwards, please ignore. In any case, it should be taking tangents of a random angle (or something?), but I can't get the scaling factors to work out right now. So much for a quick and easy alternative. ;)

    Can someone who actually knows this math say (correctly) what I was trying to say? I want the accumulating probability distribution to be shaped like arctan.

    Update 2: (trying to salvage this node) After thinking about it for a while, this seems to work (stealing code from BrowserUk's reply):

    #!/usr/bin/perl -slw my $PI = 4*atan2(1,1); sub almost_normal { my ($mean, $variance) = @_; my $x = rand(2*$PI); return sqrt($variance) * sin($x)/cos($x) + $mean; } our $PRECISION ||= 1; our $ITERS = 1000 * $PRECISION; my %plot; $plot{ int( $PRECISION * almost_normal( 100, 5 ) ) / $PRECISION }++ for 1 .. $ITERS; print "Rand 100 +-5"; printf "%7.2f : %-3d : %s\n", $_, $plot{ $_ }, '#' x ( $plot{ $_ } / $ITERS * 100 * $PRECISION ) for sort{ $a <=> $b } keys %plot;
    The variance is pretty wide, though ... you might be well-served to scale it down a bit. Although it's nice that the tails do extend really far.

    blokhead