Brejuro
Brejuro

Reputation: 3541

Generate a distribution based on some external value

Lets say I have four currencies and I want to choose one at random:

Bronze, Silver, Gold, Platinum

Based on some "rating" value, I want to assign a probability to each currency for its chance to be chosen. Let's say this rating value is from 0.0 - 5.0

Now I'd like to weight the distribution towards the platinum side if my rating is higher, and weight it towards the bronze side if my rating is lower.

So a rating of 5.0 might look like:

Bronze: 0.0, Silver: 0.10, Gold: 0.30, Platinum: 0.60

And likewise a rating of 0.0 might look like:

Bronze: 0.60, Silver: 0.30, Gold: 0.10, Platinum: 0.0

A rating of 2.5 might look more evenly spread amongst the middle currencies.

I can't really think of an algorithm to handle this. How can I generate a distribution out of 100% based on some value that controls the distribution? Does anyone know where I can get started?

Upvotes: 0

Views: 128

Answers (3)

Jean A.
Jean A.

Reputation: 301

If {Bronze, Silver, Gold, Platinum} are set on x axis positions {0, 1, 2, 3]}, we can imagine the weights taking a triangular form that can be shifted between 0 and 3 as a cursor when the rate goes from 0 to 5.

enter image description hereenter image description hereenter image description here

The triangle is defined by 3 points: left, center (peak), right center = rate * 3 / 5

Doing so, center = 0 when rate = 0 and 3 when rate = 5 left = center - 2.5 and right = center + 2.5

Such a distribution can be defined in OpenTURNS just by writing dist = ot.Triangular(left, center, right) Then, it must be transformed to fit your needs. In fact, as the results must beinteger values among {0, 1, 2, 3}, we must truncate this distribution between at -0.5 left and 3.5 right then round it to the nearest integer so as [-0.5, 0.5[ => 0, [0.5, 1.5[=> 1, [1.5, 2.5[ => 2, [2.5, 3.5]=> 3

Here is the code to build the parametric distribution:

import openturns as ot

def distribution (r):
    # Define ot.Triangular()
    center = r * 3 / 5 
    left = center - 3
    right = center + 3
    dist = ot.Triangular(left, center, right)
 
    # Truncate ot.Triangular()
    trunc = ot.TruncatedDistribution(dist, -0.5, 3.5)
    
    # Transforme the distribution to round its realizations 
    f = ot.PythonFunction(1, 1, lambda x: [round(x[0])])
    round_distribution = ot.CompositeDistribution(f, trunc)

    return round_distribution

Trying it:

# draw a sample of size 5 when rate = 0
rate = 0
print(distribution (rate).getSample(5)
>>>    [ X0 ]
0 : [ 1  ]
1 : [ 0  ]
2 : [ 1  ]
3 : [ 0  ]
4 : [ 0  ]

Bronze (0) and Silver(1) are more likely to be drawn.

The same operation when rate = 5 will give:

 [ X0 ]
0 : [ 3  ]
1 : [ 2  ]
2 : [ 3  ]
3 : [ 3  ]
4 : [ 1  ]

The probability to have 'Bronze' in this case is 0.

Upvotes: 0

sascha
sascha

Reputation: 33532

Here is some idea / sketch, for which i maybe deserve punishment from statisticians (at least for the execution). I'm assuming that this is not a large-scale fitting problem, which might need other approaches.

The general idea is: use a dirichlet-distribution to generate the final distribution. The dirichlet-distribution itself has also parameters (see wiki) and we use a normal-distribution here because of symmetry and only 2 parameters needed (where we can fix the variance, so that we only need one variable as defined in the task; variance is still a design-parameter to control the mapping of this scalar -> dist function; actually this could also be used as optimization-variable in some 1d-optimization problem which is not trivial as probably non-convex and we got non-fast-evaluated function) as inner-distribution for defining our dirichlet-distribution.

Here is some example code (python), which probably is a theoretical nightmare and also not that nice in terms of numpy/scipy-usage, but hey, it's just an example:

import numpy as np
from scipy.stats import norm

def get_sample(param):
    # location = mean shifted because of the task (symmetry not at zero!)
    outer_normal = np.array([norm.pdf(x, scale=1, loc=param-2.5) for x in np.linspace(-1, 1, 4)])
    # shifting (we need positive reals as dirichlet-input) maybe critical in terms of theory
    shifted_outer_normal = outer_normal + np.amin(outer_normal)
    return np.random.dirichlet(shifted_outer_normal)

# Try 3 values (borders + mean) and sample 1000 times each; calculate means
print(np.mean([get_sample(0) for i in range(1000)], axis=0))    # input: 0
print(np.mean([get_sample(2.5) for i in range(1000)], axis=0))  # input: 2.5
print(np.mean([get_sample(5) for i in range(1000)], axis=0))    # input: 5

Output:

[ 0.73142688  0.21514722  0.04402889  0.00939702]  # remark: only approximating sum=1 as independent means
[ 0.21711426  0.27841426  0.28205054  0.22242094]
[ 0.00943747  0.04039373  0.22860444  0.72156436]

Upvotes: 0

btilly
btilly

Reputation: 46408

A trivial answer is to fit 4 straight lines to the data that you have and call it a day.

A more flexible approach is to define 4 non-negative relative weight functions in any way that you like, say bronze(r), silver(r), gold(r), platinum(r). And then you define total(r) = bronze(r) + silver(r) + gold(r)+ platinum(r). And now the probability of bronze is bronze(r)/total(r).

The advantage of this approach is that you can play around with functions like this: bronze(r) = 4 * 0.3^r, silver(r) = 2 * 0.7^r, gold(r) = 1, platinum(r) = 0.1 * 1.8^r. And now at r=0 bronze is most likely. At r=1 silver is most likely. At r=2 gold is most likely. And at r=5 platinum is most likely.

You should try a variety of functions, and settle with whatever results in your game being most playable.

Upvotes: 1

Related Questions