Dessinez un élément aléatoire dans Numpy
Question
J'ai un éventail de probabilités d'élément, disons [0.1, 0.2, 0.5, 0.2]
. Le tableau résume jusqu'à 1,0.
En utilisant Plain Python ou Numpy, je veux dessiner des éléments proportionnels à leur probabilité: le premier élément environ 10% du temps, deuxième 20%, troisième 50%, etc. L'indice de retour de l'élément tiré.
J'ai trouvé ceci:
def draw(probs):
cumsum = numpy.cumsum(probs / sum(probs)) # sum up to 1.0, just in case
return len(numpy.where(numpy.random.rand() >= cumsum)[0])
Cela fonctionne, mais c'est trop alambiqué, il doit y avoir une meilleure façon. Merci.
La solution
import numpy as np
def random_pick(choices, probs):
'''
>>> a = ['Hit', 'Out']
>>> b = [.3, .7]
>>> random_pick(a,b)
'''
cutoffs = np.cumsum(probs)
idx = cutoffs.searchsorted(np.random.uniform(0, cutoffs[-1]))
return choices[idx]
Comment ça fonctionne:
In [22]: import numpy as np
In [23]: probs = [0.1, 0.2, 0.5, 0.2]
Calculez la somme cumulée:
In [24]: cutoffs = np.cumsum(probs)
In [25]: cutoffs
Out[25]: array([ 0.1, 0.3, 0.8, 1. ])
Calculer un nombre aléatoire uniformément distribué dans l'intervalle à moitié ouvert [0, cutoffs[-1])
:
In [26]: np.random.uniform(0, cutoffs[-1])
Out[26]: 0.9723114393023948
Utilisation fouillé pour trouver l'index où le nombre aléatoire serait inséré dans cutoffs
:
In [27]: cutoffs.searchsorted(0.9723114393023948)
Out[27]: 3
Revenir choices[idx]
, où idx
est cet index.
Autres conseils
Vous souhaitez échantillonner à partir de la distribution catégorique, qui n'est pas implémentée dans Numpy. Cependant, le multination La distribution est une généralisation du catégorique distribution et peut être utilisé à cette fin.
>>> import numpy as np
>>>
>>> def sampleCategory(p):
... return np.flatnonzero( np.random.multinomial(1,p,1) )[0]
...
>>> sampleCategory( [0.1,0.5,0.4] )
1
utilisation numpy.random.multinomial
- le plus efficace
Je n'ai jamais utilisé Numpy, mais je suppose que mon code ci-dessous (Python uniquement) fait la même chose que ce que vous avez accompli en une seule ligne. Je le mets ici au cas où vous le voudriez.
Ça a l'air très C-ish, donc les excuses de ne pas avoir été très pythonique.
poids_total serait 1 pour vous.
def draw(probs)
r = random.randrange(weight_total)
running_total = 0
for i, p in enumerate(probs)
running_total += p
if running_total > r:
return i
utilisation couper en deux
import bisect
import random
import numpy
def draw(probs):
cumsum=numpy.cumsum(probs/sum(probs))
return bisect.bisect_left(cumsum, numpy.random.rand())
devrait faire l'affaire.