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
}
Re: Curved Random Distribution
by fglock (Vicar) on Oct 18, 2005 at 16:33 UTC

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;
 [reply] [d/l] 

 [reply] 
Re: Curved Random Distribution
by Limbic~Region (Chancellor) on Oct 18, 2005 at 16:55 UTC

 [reply] 
Re: Curved Random Distribution
by BrowserUk (Pope) 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.
 [reply] [d/l] 

Technically, that would be peakshaped rather than bellshaped. That is characteristic of probability curves generated with 2 random inputs. Consider the probability table for 2 6sided 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.
 [reply] 

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.
 [reply] [d/l] [select] 
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 bellcurve, you can use this quickanddirty 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(x^{2}). A reasonable firstorder approximation of exp(x) is (1+x), so we can try using 1/(1+x^{2}) to approximate the normal curve. This function's integral (accumulating probability distribution) is none other than arctan.
This gives you a bellshaped curve in the shape of 1/(1+x^{2}), which is qualitatively "bellshaped", 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 wellserved to scale it down a bit. Although it's nice that the tails do extend really far.
 [reply] [d/l] [select] 

