Ответ 1
Поскольку numpy версия 1.7 вы можете используйте numpy.random.choice()
:
elements = ['one', 'two', 'three']
weights = [0.2, 0.3, 0.5]
from numpy.random import choice
print(choice(elements, p=weights))
Если у меня есть набор элементов в списке. Я хочу выбрать из этого списка в соответствии с другим списком весов.
Например, моя коллекция ['one', 'two', 'three']
и веса [0.2, 0.3, 0.5]
, я бы ожидал, что метод даст мне "три" примерно в половине всех ничьих.
Каков самый простой способ сделать это?
Поскольку numpy версия 1.7 вы можете используйте numpy.random.choice()
:
elements = ['one', 'two', 'three']
weights = [0.2, 0.3, 0.5]
from numpy.random import choice
print(choice(elements, p=weights))
Эта функция принимает два аргумента: список весов и список, содержащий объекты на выбор:
from numpy import cumsum
from numpy.random import rand
def weightedChoice(weights, objects):
"""Return a random item from objects, with the weighting defined by weights
(which must sum to 1)."""
cs = cumsum(weights) #An array of the weights, cumulatively summed.
idx = sum(cs < rand()) #Find the index of the first weight over a random value.
return objects[idx]
Он не использует петли python.
С Python 3.6 вы можете сделать взвешенный случайный выбор (с заменой) с помощью random.choices
.
> выбор (совокупность, вес = нет, *, cum_weights = нет, k = 1)
Пример использования:
import random
random.choices(['one', 'two', 'three'], [0.2, 0.3, 0.5], k=10)
# ['three', 'two', 'three', 'three', 'three',
# 'three', 'three', 'two', 'two', 'one']
Вы можете использовать многочленное распространение (от numpy), чтобы делать то, что вы хотите. Например.
elements = ['one', 'two', 'three']
weights = [0.2, 0.3, 0.5]
import numpy as np
indices = np.random.multinomial( 100, weights, 1)
#=> array([[20, 32, 48]]), YMMV
results = [] #A list of the original items, repeated the correct number of times.
for i, count in enumerate(indices[0]):
results.extend( [elements[i]]*count )
Итак, элемент в первой позиции поднялся в 20 раз, элемент во втором положении поднялся 32 раза, а элемент в третьей позиции поднялся 48 раз, примерно то, что вы ожидали бы с учетом веса.
Если вам нелегко обернуть вокруг многомиллиардного распространения, я нашел полезную документацию.
Если вы не хотите использовать numpy
, вы можете следовать тому же методу с чем-то вроде этого:
from random import random
from itertools import takewhile
def accumulate(iterator):
"""Returns a cumulative sum of the elements.
accumulate([1, 2, 3, 4, 5]) --> 1 3 6 10 15"""
current = 0
for value in iterator:
current += value
yield current
def weightedChoice(weights, objects):
"""Return a random item from objects, with the weighting defined by weights
(which must sum to 1)."""
limit = random()
return objects[sum(takewhile(bool, (value < limit for value in accumulate(weights))))]
Мы используем itertools.takewhile()
, чтобы избежать проверки значений, как только мы достигнем точки, которую хотим остановить, в противном случае это по сути та же идея, что и Ответ Mischa Obrecht, просто без numpy
.
Как просто инициализировать список, чтобы он соответствовал вашим выборам с ожидаемыми весами. Здесь я составляю список из 100 значений, представляющих желаемый процент "тянуть".
>>> import random
>>> elements = ['one', 'two', 'three']
>>> weights = [0.2, 0.3, 0.5]
>>>
>>> # get "sum" of result list of lists (flattens list)
>>> choices = sum([[element] * int(weight * 100)for element, weight in zip(elements, weights)], [])
>>> random.choice(choices)
three
Это не кумулятивный, но похоже, что это может быть то, что вы ищете.
Чтобы построить ответ Maus, это здорово, если вы хотите повторно получать взвешенные случайные значения, если вы хотите только одно значение, вы можете сделать это очень просто путем объединения numpy.random.multinomial()
и itertools.compress()
:
from itertools import compress
from numpy.random import multinomial
def weightedChoice(weights, objects):
"""Return a random item from objects, with the weighting defined by weights
(which must sum to 1)."""
return next(compress(objects, multinomial(1, weights, 1)[0]))