¿Cómo simulo un troquel sesgado en Python?
-
20-08-2019 - |
Pregunta
¿Quiero simular un troquel sesgado del lado N?
def roll(N,bias):
'''this function rolls N dimensional die with biasing provided'''
# do something
return result
>> N=6
>> bias=( 0.20,0.20,0.15,0.15,0.14,0.16,)
>> roll(N,bias)
2
Solución
Un poco de matemática aquí.
Un dado regular dará a cada número 1-6 con la misma probabilidad, es decir, 1/6
. Esto se conoce como distribución uniforme (la versión discreta del mismo, en oposición a La versión continua). Lo que significa que si X
es una variable aleatoria que describe el resultado de un solo rol, entonces X~U[1,6]
- lo que significa [0,1)
se distribuye equitativamente contra todos los resultados posibles de la tirada del dado, del 1 al 6.
Esto equivale a elegir un número en [0,1/6)
mientras lo divide en 6 secciones: [1/6,2/6)
, [2/6,3/6)
, [3/6,4/6)
, [4/6,5/6)
, [5/6,1)
, [0,0.2)
.
Está solicitando una distribución diferente, que está sesgada.
La forma más fácil de lograr esto es dividir la sección [0.2,0.4)
en 6 partes según el sesgo que desee. Entonces, en su caso, desearía dividirlo en lo siguiente:
[0.4,0.55)
, 0.55,0.7)
, [0.7,0.84)
, [0.84,1)
, <=>, <=>.
Si echa un vistazo a la entrada de wikipedia , verá que en este caso, la función de probabilidad acumulativa no estará compuesta de 6 partes de igual longitud, sino más bien de 6 partes que difieren en longitud de acuerdo con el sesgo que usted les dio. Lo mismo ocurre con la distribución masiva.
Volviendo a la pregunta, dependiendo del idioma que esté utilizando, simplemente traduzca esto a su tirada de dados. En Python, aquí hay un ejemplo muy incompleto, aunque funciona,
import random
sampleMassDist = (0.2, 0.1, 0.15, 0.15, 0.25, 0.15)
# assume sum of bias is 1
def roll(massDist):
randRoll = random.random() # in [0,1)
sum = 0
result = 1
for mass in massDist:
sum += mass
if randRoll < sum:
return result
result+=1
print roll(sampleMassDist)
Otros consejos
Más agnóstico al lenguaje, pero podría usar una tabla de búsqueda.
Use un número aleatorio en el rango 0-1 y busque el valor en una tabla:
0.00 - 0.20 1
0.20 - 0.40 2
0.40 - 0.55 3
0.55 - 0.70 4
0.70 - 0.84 5
0.84 - 1.00 6
import random
def roll(sides, bias_list):
assert len(bias_list) == sides
number = random.uniform(0, sum(bias_list))
current = 0
for i, bias in enumerate(bias_list):
current += bias
if number <= current:
return i + 1
El sesgo será proporcional.
>>> print roll(6, (0.20, 0.20, 0.15, 0.15, 0.14, 0.16))
6
>>> print roll(6, (0.20, 0.20, 0.15, 0.15, 0.14, 0.16))
2
También podría usar enteros (mejor):
>>> print roll(6, (10, 1, 1, 1, 1, 1))
5
>>> print roll(6, (10, 1, 1, 1, 1, 1))
1
>>> print roll(6, (10, 1, 1, 1, 1, 1))
1
>>> print roll(6, (10, 5, 5, 10, 4, 8))
2
>>> print roll(6, (1,) * 6)
4
It is a little surprising that the np.random.choice
answer hasn't been given here.
from numpy import random
def roll(N,bias):
'''this function rolls N dimensional die with biasing provided'''
return random.choice(np.range(N),p=bias)
The p option gives "the probabilities associated with each entry in a", where a is np.range(N)
for us. "If not given the sample assumes a uniform distribution over all entries in a".
See the recipe for Walker's alias method for random objects with different probablities.
An example, strings A B C or D with probabilities .1 .2 .3 .4 --
abcd = dict( A=1, D=4, C=3, B=2 )
# keys can be any immutables: 2d points, colors, atoms ...
wrand = Walkerrandom( abcd.values(), abcd.keys() )
wrand.random() # each call -> "A" "B" "C" or "D"
# fast: 1 randint(), 1 uniform(), table lookup
cheers
-- denis
Just to suggest a more efficient (and pythonic3) solution, one can use bisect to search in the vector of accumulated values — that can moreover be precomputed and stored in the hope that subsequent calls to the function will refer to the same "bias" (to follow the question parlance).
from bisect import bisect
from itertools import accumulate
from random import uniform
def pick( amplitudes ):
if pick.amplitudes != amplitudes:
pick.dist = list( accumulate( amplitudes ) )
pick.amplitudes = amplitudes
return bisect( pick.dist, uniform( 0, pick.dist[ -1 ] ) )
pick.amplitudes = None
In absence of Python 3 accumulate, one can just write a simple loop to compute the cumulative sum.
from random import random
biases = [0.0,0.3,0.5,0.99]
coins = [1 if random()<bias else 0 for bias in biases]
i have created a code for a dictionary giving a event and corresponding probability, it gives back the corresponding key ie the event of that probability.
import random
def WeightedDie(Probabilities):
high_p = 0
rand = random.uniform(0,1)
for j,i in Probabilities.items():
high_p = high_p + i
if rand< high_p:
return j