Reputation: 3541
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
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.
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
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
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