Disegna elemento casuale in polpera
Domanda
Ho una serie di probabilità di elementi, diciamo [0.1, 0.2, 0.5, 0.2]
.L'array somma fino a 1,0.
Utilizzando Pianura Python o Numpy, voglio disegnare elementi proporzionali alla loro probabilità: il primo elemento circa il 10% del tempo, il secondo 20%, il terzo 50% ecc. Il "pareggio" dovrebbe restituire l'indice attinto.
Ho trovato questo:
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])
.
funziona, ma è troppo contorto, ci deve essere un modo migliore.Grazie.
Soluzione
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]
.
.
Come funziona:
.
In [22]: import numpy as np
In [23]: probs = [0.1, 0.2, 0.5, 0.2]
Computa la somma cumulativa:
.
In [24]: cutoffs = np.cumsum(probs)
In [25]: cutoffs
Out[25]: array([ 0.1, 0.3, 0.8, 1. ])
Computa un numero casuale distribuito uniformemente nell'intervallo semi-aperto [0, cutoffs[-1])
:
.
In [26]: np.random.uniform(0, cutoffs[-1])
Out[26]: 0.9723114393023948
Usa SearchSorted per trovare l'indice dove il casualeil numero sarebbe inserito in cutoffs
:
.
In [27]: cutoffs.searchsorted(0.9723114393023948)
Out[27]: 3
Ritorna choices[idx]
, dove idx
è quell'indice.
Altri suggerimenti
Si desidera campione dalla distribuzione categoriale, che non è implementata in Numpy.Tuttavia, il la distribuzione multinomiale è una generalizzazione del categorico distribuzione e può essere utilizzato per tale scopo.
>>> 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
. Usa numpy.random.multinomial
- La più efficiente
Non ho mai usato Ninpy, ma presumo il mio codice qui sotto (solo Python) fa la stessa cosa di ciò che hai realizzato in una riga.Lo sto mettendo qui solo nel caso tu lo desideri.
Guarda molto c-ish quindi ci scusiamo per non essere molto pythic.
weight_total sarebbe 1 per te.
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
. Usa Bisect
import bisect
import random
import numpy
def draw(probs):
cumsum=numpy.cumsum(probs/sum(probs))
return bisect.bisect_left(cumsum, numpy.random.rand())
.
dovrebbe fare il trucco.