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.

Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top