нарисовать случайный элемент в numpy
Вопрос
У меня есть массив вероятностей элементов, скажем [0.1, 0.2, 0.5, 0.2]
.Сумма массива равна 1,0.
Используя простой Python или numpy, я хочу рисовать элементы, пропорциональные их вероятности:первый элемент примерно 10% времени, второй 20%, третий 50% и т.д.«Рисование» должно возвращать индекс нарисованного элемента.
Я придумал это:
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])
Это работает, но слишком запутанно, должен быть лучший способ.Спасибо.
Решение
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]
Как это работает:
In [22]: import numpy as np
In [23]: probs = [0.1, 0.2, 0.5, 0.2]
Вычислите накопительную сумму:
In [24]: cutoffs = np.cumsum(probs)
In [25]: cutoffs
Out[25]: array([ 0.1, 0.3, 0.8, 1. ])
Вычислите равномерно распределенное случайное число в полуоткрытом интервале [0, cutoffs[-1])
:
In [26]: np.random.uniform(0, cutoffs[-1])
Out[26]: 0.9723114393023948
Использовать отсортированный по поиску чтобы найти индекс, в который будет вставлено случайное число cutoffs
:
In [27]: cutoffs.searchsorted(0.9723114393023948)
Out[27]: 3
Возвращаться choices[idx]
, где idx
это тот индекс.
Другие советы
Вы хотите выполнить выборку из категориального распределения, которое не реализовано в numpy.Однако многочленный распределение представляет собой обобщение категоричный распространение и может быть использован для этой цели.
>>> 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
использовать numpy.random.multinomial
- Наиболее эффективным
Я никогда не использовал numpy, но предполагаю, что мой код ниже (только на Python) делает то же самое, что вы выполнили в одной строке.Я помещу это здесь, на случай, если вам это понадобится.
Выглядит очень круто, так что извините за не очень питонический подход.
Weight_total для вас будет равен 1.
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
использовать разделить пополам
import bisect
import random
import numpy
def draw(probs):
cumsum=numpy.cumsum(probs/sum(probs))
return bisect.bisect_left(cumsum, numpy.random.rand())
должен сделать свое дело.