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 (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.
| [reply] [d/l] |
|
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.
| [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 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.
| [reply] [d/l] [select] |
|
|