Skip to content

Weighted Random

Weighted randoms are an important feature for any data analysis where the system does not want to flood the user with options, but at the same time, they also do not want to always show the same items.
In the use case of advertising, you want to show the user the ad most appropriate for them, however, you also do not want to always show the same ad all the time; you want to keep things fresh. However you also still should show the user the most relevant item most of the time.

This is where weighted random systems come into play; they allow you to show the best answer the majority of the time while still occasionally showing other relevant content. Doing this can actually be good for the system because it can allow the eco-system to discover new trends and remove some feedback loops that might develop otherwise in adaptive recommendation systems.
For instance, if the same ad always shows to the same user, and then click on it multiple times it will reinforce the same choice, thus it will display on other peoples recommendations, and the reinforcement will continue. However, it is never showing that original user or any others another recommendation, so eventually you could end up with a system that always displays the same options to all users due to its own feedback loop; and it also has no chance to discover by chance any other correlations.
Whereas if a system occasionally shows a recommendation of which does not fit any current trends, but that options now gets clicks under a pattern that previously would have gone undiscovered

Foundational Knowledge

Presume that rand() produces random a number 0 < x < 1

The simplest generation of a random value would be the use of;

let y = rand();
If we instead replace rand() with x you can see how this is a simple 1:1 relationship. However, if we instead use a more complex geometric relationship like y=x^2, we can see how the average y value between 0 < x < 1 is less than 0.5. Thus when we use that relationship to generate a random number it is more likely to get a number below 0.5 than above it.
let y = rand()**2;

Custom Functions

If you are randomly picking between an apple and a banana there is a 50-50 chance of either being selected; however, if you want the apple to have double the chance of being selected then you would pick from [apple, apple, bannana].
While this works as a basic implementation it does not create a range of variety and customisation for how to weight each object, what if you want a 13-87 chance between apple and banana? Are you really going to create a list of 100 items then randomly select an item?
The whole process can be simplified.

Custom Weighted Randoms

Using the previous examples we can say;

\[ \begin{align*} \text{Apple} &= 2 && \\ \text{Bannana} &= 1 && \\ \text{Total} &= 3 && \\ \end{align*} \]

So now we can simply stretch our random function to fit over the domain of our options;

y = rand() * 3
Then simply deduce that if y is < 2 it must be an apple and otherwise it is a banana.

Generalisation

We can now expand on this to make a general algorithm.
Assume that the system is given the weights of each option and the options its self.

let weights = [...];
let opts = [...]
First, we tally up the total weight
let total = 0;
for (let weight of weights){
    total += weight;
}
Apply are random function to the domain
let y = rand() * total;
Now deduce which option the result belongs to.
for (let index in weights){
    if (y < weights[index]){
        return opt[index];
    }

    y -= weights[index];
}

Back to Numeric

While it's great to be able to have a weighted random system that picks specific options, if you want actual numeric values then it will be more complex.
First, you need to ensure that your weights opts pairs are sorted by opt value.
Then we can use a basic interpolation operation.
This method will allow you to weight specific numbers within the system and the range of output is defined by the lowest and highest option available.

for (let index in weights){
    if (y < weights[index]){
        // If it is the last option
        if (index == opts.length -1){
            return opts[index];
        }

        let p = y / weights[index];
        let val = (opts[index+1] - opts[index]) * p + opts[index];

        return val;
    }

    y -= weights[index];
}