Re: list of random numbers with given avarage
by jepri (Parson) on Mar 12, 2002 at 12:28 UTC
|
Cool. We used to do this in Physics (undergrad, I hasten to add) when we couldn't get lab results, but we knew what the 'perfect' results were.
You are doing it the hard way. Rather than painfully adjusting your random number function, just fake the random numbers using a noise function. If you do a web search for 'Gaussian' or 'Lorentzian' you should get something useful for random functions.
____________________
Jeremy
I didn't believe in evil until I dated it. | [reply] |
|
|
<Humor> *Sigh* Typical physicists. I think they all subscribe to the "Measure with micrometer, mark with chalk, cut with axe" method. My roommate in college (now a Phd physics student) used to say "It's only a power of ten, that's well within our desired range". GAH!!</Humor> C-.
| [reply] |
Re: list of random numbers with given avarage
by ChOas (Curate) on Mar 12, 2002 at 12:21 UTC
|
Hi!,
Sorry... not much time, have to go to a meeting, but I whiped something up quickly
I`ll have another look later, but I like this one:
#!/usr/bin/perl -w
use strict;
my $Min=0;
my $Max=100;
my $Count=50;
my $Average=50;
my $RealAverage=0;
my $Total=0;
my @Result;
for(1..$Count)
{
#my $New=int(rand($Max/2)+$Min+(($RealAverage>$Average)||$Average));
# Sorry, this line was totally screwed up, as it would
# give results outside the bounds given, substitute by
# this:
my $New=int(rand($Max-$Min))+$Min;
$New+=$Average if ((($New+$Average)<$Max)&&($RealAverage<$Average));
$New-=$Average if ((($New-$Average)>$Min)&&($RealAverage>$Average));
$Total+=$New;
$RealAverage=$Total/$_;
push @Result,$New;
};
print join "\n",@Result;
print "\nAverage: $RealAverage\n";
GreetZ!,
print "profeth still\n" if /bird|devil/; | [reply] [d/l] |
Re: list of random numbers with given avarage
by YuckFoo (Abbot) on Mar 12, 2002 at 15:34 UTC
|
Fun problem, here's a suggestion and a solution.
Your algorithm doesn't work very well if the desired
average is near your minimum or maximum, like from 0 - 100,
desired average = 10.
If you want random numbers from $start to $end, instead of
$number = rand($end) and testing to see if it's bigger than
$start, use $number = rand($end - $start) + $start.
This program will either choose a random number from $start
to $average or from $average to $end, based on whether the
actual average is lesser or greater than the target average.
I have no idea what the distribution will look like, or what
you want the distribution to look like.
YuckFoo
#!/usr/bin/perl
use strict;
my ($MIN, $MAX, $NUM, $AVERAGE) = qw(0 100 64 32);
my $small = $AVERAGE - $MIN;
my $large = $MAX - $AVERAGE;
my ($i, $rand, $total, $actual);
for ($i = 1; $i <= $NUM; $i++) {
if ($actual < $AVERAGE) {
$rand = int(rand($large) + $AVERAGE);
}
else {
$rand = int(rand($small) + $MIN);
}
$total += $rand;
$actual = $total / $i;
print "$rand $actual\n";
}
| [reply] [d/l] |
Re: list of random numbers with given avarage
by johannz (Hermit) on Mar 12, 2002 at 16:58 UTC
|
I rewrote your code and made a stab at my own.
Some of the changes I made to your code:
- No empty cycles without outputing a random number.
- No possibilities of infinite loop.
- could happen if ($myend/2 < $mystart)
- example: mystart = 60, myend = 100).
- Actually use int function, not a split on a period
- Use += instead of $sum = $sum + $number
In my code, instead of only choosing numbers from part of the range, I weigh the results smaller or larger as needed. This is an advantage if the desired average is close to the real average, but a disadvantage if the desired average is radically different. The reason I consider it an advantage when the desired average is close to the real average is that you still could get the entire ranger of numbers; not a sequence of numbers below the average, then a sequence of numbers above the average, etc.
use strict;
use warnings;
# Comment out the tests you don't want to run.
my @choices;
@choices = (0, 100, 50, 50);
@choices = (25, 75, 50, 50);
@choices = (0, 100, 50, 10);
my @numbers = his_method(@choices);
#my @numbers = my_method(@choices);
print "Results:\n@numbers\n";
exit;
sub his_method { # re-written
my $mystart = shift;
my $myend = shift;
my $count = shift;
my $my_average = shift;
my ($start, $end) = ($mystart, $myend);
my ($number, @numbers);
my ($sum, $real_average) = 0;
print "\nList of random values:\n";
for (my $i = 1; $i <= $count; $i++) {
$number = int(rand($end+1)) + $start;
push @numbers, $number;
$sum += $number;
$real_average = $sum/$i;
print "Random number $i: $number ($start, $end)\n";
print "Average: $real_average\n\n";
($start, $end) =
($real_average < $my_average) ? # make larger
( $my_average, $myend-$my_average)
: ($real_average > $my_average) ? # make smaller
( $mystart, $my_average - $mystart)
: # if no adjustment needed.
( $mystart, $myend);
}
return @numbers;
}
sub my_method {
my $mystart = shift;
my $myend = shift;
my $count = shift;
my $my_average = shift;
my ($number, @numbers);
my ($sum, $real_average) = 0;
my $next_adj = 1;
print "\nList of random values:\n";
for (my $i = 1; $i <= $count; $i++) {
$number = int(rand()**$next_adj * ($myend+1)) + $mystart;
push @numbers, $number;
$sum += $number;
$real_average = $sum/$i;
print "Random number $i: $number ($next_adj)\n";
print "Average: $real_average\n\n";
$next_adj =
($real_average < $my_average) ? # make larger
.5 # so take square root of fraction
: ($real_average > $my_average) ? # make smaller
2 # so take power of 2
: # if no adjustment needed.
1;
}
return @numbers;
}
| [reply] [d/l] |
Re: list of random numbers with given avarage
by belg4mit (Prior) on Mar 12, 2002 at 15:33 UTC
|
| [reply] [d/l] |
|
|
| [reply] |
|
|
Average=50, Range=10
Average: Avg - Rng/2: Avg - Rng/2 + rnd(Rng):
45----50----55 45----50----55 45----50----55
* * ????? ??????
Since rnd can return anything between 0
(minimum) or Range (maximum) we get a
full spread. It may also be interesting
to note that this is equivalent to
sub f{average+range/2-rnd(range)}
--
perl -pe "s/\b;([st])/'\1/mg"
| [reply] [d/l] |
Re: list of random numbers with given avarage
by Zaxo (Archbishop) on Mar 12, 2002 at 21:46 UTC
|
Your requirements are met most naturally by using another probability distribution than the uniform one rand is crafted to deliver. The requirement of a lower bound $Nl is easily dealt with by just considering the interval 0..($Nu-$Nl) and adding $Nl to all results. The requirement for an upper bound restricts us to distributions with a finite range. The requirement for a specified mean forces us to look at distributions with more parameters than rand provides.
The ad-hoccery one's tempted into, simply filtering or adjusting a uniform distribution, will lead to 'random' numbers with bizarre statistical properties.
Abramowitz and Stegun reveals one standard probability distribution for continuous finite intervals, the beta disribution, and two for integers, the binomial distribution and the hypergeometeric.distribution. All three have enough adjustable parameters to satisfy your requirements.
Both integer distributions make use of the binomial coefficients <wish cap="MathMl"/>, so I'll give a simple recursive implementation:
sub binomial {
my ($n,$s)=@_;
$_[1]<=$_[0]/2
? $_[1]
? 0 | ($_[0] - $_[1] + 1) * binomial( $_[0], $_[1] - 1) /
+$_[1]
: 1
: $_[1] != $_[0]
? 0 | ($_[1] + 1) * binomial( $_[0], $_[1] + 1) / ($_[0] -
+ $_[1])
: 1;
}
This is not very efficient, but it staves off large intermediate numbers and avoids copying as much as possible in a recursive definition. You may wish to do this using &Math::BigInt::bfac instead.
The plan is to make a largeish array of numbers from the interval, each represented in proportion to its probability, and pick numbers from that array with rand as index. For the binomial distribution: # Usage:
# my @probs = prob_binomial( $span, $avg);
# these are 0 based arguments, subtract the min you want to get them.
sub prob_binomial {
my ($max, $avg) = @_;
my $p = $avg / $max;
map {binomial($max, $_) * $p**$_ * (1-$p)**($max-$_)} 0..$max;
}
my $min = 6;
my $max = 21;
my $avg = 17;
my $num = 40;
my @problist = prob_binomial($max-$min,$avg-$min);
# using ~1000 element array, adjust to suit ranges
my @picklist = map {($min+$_) x int( .5 + 1000 * $problist[$_])} 0..$m
+ax-$min;
print $picklist[rand @picklist],$" for 1..$num;
For the hypergeometric distribution you get a free extra parameter you can use to tweak your results. this is its point probability function, in terms of three integers $N1 < $N2 and $n with $n < $N1+$N2:sub prob_hyper {
my ($N1,$N2,$n)=@_;
my $span = $N1<$n? $N1 : $n;
map {binomial($N1,$_) * binomial($N2,$n-$_) / binomial($N1+$N2,$n)
+} 0..$span;
}
This has the great advantage that the relative probabilities can be made integers by making its @picklist binomial($N1+$N2,$n) in length. Then your results will be exact.
Note that these methods don't force a particular list of randoms to take on the average. That will happens in the long run, it's all statistics! Enjoy.
Update: Forgot to mention, the mean for the hypergeometric distribution is $n*$N1/($N1+$N2).
After Compline, Zaxo
| [reply] [d/l] [select] |
Re: list of random numbers with given avarage
by thraxil (Prior) on Mar 12, 2002 at 16:57 UTC
|
| [reply] |
Re: list of random numbers with given average
by I0 (Priest) on Mar 13, 2002 at 04:17 UTC
|
#!/usr/bin/perl -w
use strict;
my $mystart = 0; # What is the minimum value of random numbers
+ you want to generate
my $myend = 100; # What is the maximum value of the random num
+bers to generate
my $count = 50; # How many numbers should be generated
my $my_average = 50; # What is the decided avarage of the generate
+d numbers
my $number;
my @number;
my $summ = 0;
my $i = 0;
my $real_average;
print "\nList of random values:\n";
for $i(1.. $count ){
my $start = $mystart;
my $end = $myend;
my $more = ($my_average*$count-$summ);
my $average = $more/(1+$count-$i);
if( ($count-$i)*$myend < $more-$mystart ){
$start = $more-($count-$i)*$myend;
}
if( ($count-$i)*$mystart > $more-$myend ){
$end = $more-($count-$i)*$mystart;
}
$number = random($start,$end,$average);
$summ += int $number;
$real_average = $summ / $i;
print "Random number: $number\n";
print "Average: $real_average\n\n";
}
sub random{
my($start,$end,$average) = @_;
# print "$start,$end,$average\n";
if( $start >= $end ){ return $average; }
my $b = ($average-$start)/($end-$start);
if( 0 ){
my $p = $b<=.5?1/$b-1:(1-$b)/$b; #Update: this was silly
return $start + ($end-$start)*(rand)**$p;
}else{#Update: smoother skew function
return $b >= 0.5 ?
$start + ($end-$start)*(rand)**(1/$b-1)
:
$end + ($start-$end)*(rand)**(1/(1-$b)-1)
;
}
}
| [reply] [d/l] |
|
|
| [reply] |
Re: list of random numbers with given avarage
by pizza_milkshake (Monk) on Mar 13, 2002 at 01:41 UTC
|
this conforms to what you were talking about. gets flakier the closer avg gets to min || max. comments?
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
my ($acceptable_laziness) = 1.0e-15;
sub rand_list {
my ($howmany, $min, $max, $avg, @a) = (@_, ());
print STDERR "ERROR: Make sure (min < avg < max)\n", return if ($avg
+ <= $min || $avg >= $max);
push @a, rand($max-$min)+$min for 1..$howmany;
# nudge list toward avg w/o pushing numbers above/below min/max
while (avg_list(@a) != $avg){
my ($nudge, $tmp) = (avg_list(@a)-$avg, undef);
last if (abs $nudge < $acceptable_laziness);
for (@a){
$tmp = ($_ - $nudge);
$_ = $tmp if ($min <= $tmp && $max >= $tmp);
}
}
@a;
}
sub avg_list {
my ($c, $t) = (0, 0);
$t+=pop, $c++ while @_;
($t / $c) if $c
}
# 20 nums between 1 and 5 that average to 3
my @b = rand_list(20, 1, 5, 3);
print Dumper(\@b);
perl -e'@a=split//," \n/)\"={\"";print $a[$_] for split//,"00002731000223155264555102401"' | [reply] [d/l] |
Re: list of random numbers with given avarage
by pizza_milkshake (Monk) on Mar 13, 2002 at 01:29 UTC
|
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
sub rand_list {
my ($howmany, $min, $max, @a) = (@_, ());
push @a, rand($max-$min)+$min for 1..$howmany;
@a;
}
sub avg_list {
my ($c, $t) = (0, 0);
$t+=pop, $c++ while @_;
($t / $c)
}
my @b = rand_list(100, 1, 5); # 100 nums between 1 and 5
print Dumper(\@b);
perl -e'@a=split//," \n/)\"={\"";print $a[$_] for split//,"00002731000223155264555102401"' | [reply] [d/l] |