Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate random numbers biased towards one value in a range?

People also ask

How do you generate a random number from within a range?

Method 1: Using Math. random() function is used to return a floating-point pseudo-random number between range [0,1) , 0 (inclusive) and 1 (exclusive). This random number can then be scaled according to the desired range.

Is there bias in a random number generator?

Curiously, some popular random-number generators fail even in simulating a coin toss. Over time, they should produce roughly the same number of zeros and ones (heads and tails). Instead, random-number generators that are often used to produce such sequences tend to cluster zeros together, introducing a bias.

How do you create a weighted random number?

To generated a random number, weighted with a given probability, you can use a helper table together with a formula based on the RAND and MATCH functions. Notice, we are intentionally shifting the cumulative probability down one row, so that the value in D5 is zero.


Here is one way:

  • Get a random number in the min-max range
  • Get a random normalized mix value
  • Mix random with bias based on random mix

Ie., in pseudo:

Variables:
  min = 0
  max = 100
  bias = 67      (N)
  influence = 1  (D) [0.0, 1.0]

Formula:
  rnd = random() x (max - min) + min
  mix = random() x influence
  value = rnd x (1 - mix) + bias x mix

The mix factor can be reduced with a secondary factor to set how much it should influence (ie. mix * factor where factor is [0, 1]).

Demo

This will plot a biased random range. The upper band has 1 as influence, the bottom 0.75 influence. Bias is here set to be at 2/3 position in the range. The bottom band is without (deliberate) bias for comparison.

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red"; ctx.fillRect(399,0,2,110);  // draw bias target
ctx.fillStyle = "rgba(0,0,0,0.07)";

function getRndBias(min, max, bias, influence) {
    var rnd = Math.random() * (max - min) + min,   // random in range
        mix = Math.random() * influence;           // random mixer
    return rnd * (1 - mix) + bias * mix;           // mix full range and bias
}

// plot biased result
(function loop() {
  for(var i = 0; i < 5; i++) {  // just sub-frames (speedier plot)
    ctx.fillRect( getRndBias(0, 600, 400, 1.00),  4, 2, 50);
    ctx.fillRect( getRndBias(0, 600, 400, 0.75), 55, 2, 50);
    ctx.fillRect( Math.random() * 600          ,115, 2, 35);
  }
  requestAnimationFrame(loop);
})();
<canvas width=600></canvas>

Fun: use the image as the density function. Sample random pixels until you get a black one, then take the x co-ordinate.

enter image description here

Code:

getPixels = require("get-pixels"); // npm install get-pixels

getPixels("distribution.png", function(err, pixels) {
  var height, r, s, width, x, y;
  if (err) {
    return;
  }
  width = pixels.shape[0];
  height = pixels.shape[1];
  while (pixels.get(x, y, 0) !== 0) {
    r = Math.random();
    s = Math.random();
    x = Math.floor(r * width);
    y = Math.floor(s * height);
  }
  return console.log(r);
});

Example output:

0.7892316638026386
0.8595335511490703
0.5459279934875667
0.9044852438382804
0.35129814594984055
0.5352215224411339
0.8271261665504426
0.4871773284394294
0.8202084102667868
0.39301465335302055

Scale to taste.


Just for fun, here's a version that relies on the Gaussian function, as mentioned in SpiderPig's comment to your question. The Gaussian function is applied to a random number between 1 and 100, where the height of the bell indicates how close the final value will be to N. I interpreted the degree D to mean how likely the final value is to be close to N, and so D corresponds to the width of the bell - the smaller D is, the less likely is the bias. Clearly, the example could be further calibrated.

(I copied Ken Fyrstenberg's canvas method to demonstrate the function.)

function randBias(min, max, N, D) {
  var a = 1,
      b = 50,
      c = D;

  var influence = Math.floor(Math.random() * (101)),
    x = Math.floor(Math.random() * (max - min + 1)) + min;

  return x > N 
         ? x + Math.floor(gauss(influence) * (N - x)) 
         : x - Math.floor(gauss(influence) * (x - N));

  function gauss(x) {
    return a * Math.exp(-(x - b) * (x - b) / (2 * c * c));
  }
}

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(399, 0, 2, 110);
ctx.fillStyle = "rgba(0,0,0,0.07)";

(function loop() {
  for (var i = 0; i < 5; i++) {
    ctx.fillRect(randBias(0, 600, 400, 50), 4, 2, 50);
    ctx.fillRect(randBias(0, 600, 400, 10), 55, 2, 50);
    ctx.fillRect(Math.random() * 600, 115, 2, 35);
  }
  requestAnimationFrame(loop);
})();
<canvas width=600></canvas>

Say when you use Math.floor(Math.random() * (max - min + 1)) + min;, you are actually creating a Uniform distribution. To get the data distribution in your chart, what you need is a distribution with non-zero skewness.

There are different techniques to get those kinds of distributions. Here is an example of beta distribution found on stackoverflow.


Here is the example summarized from the link:

unif = Math.random()  // The original uniform distribution.

And we can transfer it into beta distribution by doing

beta = sin(unif*pi/2)^2 // The standard beta distribution

To get the skewness shown in your chart,

beta_right = (beta > 0.5) ? 2*beta-1 : 2*(1-beta)-1;

You can change the value 1 to any else to have it skew to other value.