Как сделать взвешенную случайную выборку категорий в python
Учитывая список кортежей, где каждый кортеж состоит из вероятности и элемента, который я хотел бы пробовать в зависимости от его вероятности. Например, дайте список [(.3, 'a'), (.4, 'b'), (.3, 'c')] Я хотел бы попробовать "b" в 40% случаев.
Каков канонический способ сделать это в python?
Я посмотрел на случайный модуль, который, похоже, не имеет соответствующей функции, и в numpy.random, который, хотя он имеет многочленную функцию, похоже, не возвращает результаты в приятной форме для этой проблемы. Я в основном ищу что-то вроде mnrnd в matlab.
Большое спасибо.
Спасибо за все ответы так быстро. Чтобы уточнить, я не ищу объяснений, как писать схему выборки, а скорее указывать на простой способ отбора из мультиномиального распределения с учетом набора объектов и весов или сказать, что такая функция не существует в стандартной библиотеке, и поэтому нужно написать одну собственную.
Ответы
Ответ 1
import numpy
n = 1000
pairs = [(.3, 'a'), (.3, 'b'), (.4, 'c')]
probabilities = numpy.random.multinomial(n, zip(*pairs)[0])
result = zip(probabilities, zip(*pairs)[1])
# [(299, 'a'), (299, 'b'), (402, 'c')]
[x[0] * x[1] for x in result]
# ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccc']
Как именно вы хотели бы получить результаты?
Ответ 2
Это может сделать то, что вы хотите:
numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
Ответ 3
Поскольку никто не использовал функцию numpy.random.choice, здесь, которая будет генерировать то, что вам нужно в одной компактной строке:
numpy.random.choice(['a','b','c'], size = 20, p = [0.3,0.4,0.3])
Ответ 4
Есть хаки, которые вы можете сделать, если, например, ваши вероятности хорошо вписываются в проценты и т.д.
Например, если вы в порядке с процентами, будет работать следующее (за счет высокой накладной памяти):
Но "реальный" способ сделать это с произвольными вероятностями с плавающей точкой - это выборка из кумулятивного распределения после его построения. Это эквивалентно разбиению единичного интервала [0,1] на 3 отрезка, обозначенных как "a", "b" и "c"; затем выбираем случайную точку на единичном интервале и видим, какой из ее сегментов она занимает.
#!/usr/bin/python3
def randomCategory(probDict):
"""
>>> dist = {'a':.1, 'b':.2, 'c':.3, 'd':.4}
>>> [randomCategory(dist) for _ in range(5)]
['c', 'c', 'a', 'd', 'c']
>>> Counter(randomCategory(dist) for _ in range(10**5))
Counter({'d': 40127, 'c': 29975, 'b': 19873, 'a': 10025})
"""
r = random.random() # range: [0,1)
total = 0 # range: [0,1]
for value,prob in probDict.items():
total += prob
if total>r:
return value
raise Exception('distribution not normalized: {probs}'.format(probs=probDict))
Нужно быть осторожным с методами, возвращающими значения, даже если их вероятность равна 0. К счастью, этого метода нет, но на всякий случай можно вставить if prob==0: continue
.
Для записи здесь хакерский способ сделать это:
import random
def makeSampler(probDict):
"""
>>> sampler = makeSampler({'a':0.3, 'b':0.4, 'c':0.3})
>>> sampler.sample()
'a'
>>> sampler.sample()
'c'
"""
oneHundredElements = sum(([val]*(prob*100) for val,prob in probDict.items()), [])
def sampler():
return random.choice(oneHundredElements)
return sampler
Однако, если у вас нет проблем с разрешением... это на самом деле возможно самый быстрый способ. =)
Ответ 5
Howabout создайте 3 "a", 4 "b" и 3 "c" в списке, а затем просто произвольно выберите один. При достаточном количестве итераций вы получите желаемую вероятность.
Ответ 6
Я считаю, что многочлена - это еще довольно простой способ получить образцы распределения в случайном порядке. Это всего лишь один из способов.
import numpy
from itertools import izip
def getSamples(input, size):
probabilities, items = zip(*input)
sampleCounts = numpy.random.multinomial(size, probabilities)
samples = numpy.array(tuple(countsToSamples(sampleCounts, items)))
numpy.random.shuffle(samples)
return samples
def countsToSamples(counts, items):
for value, repeats in izip(items, counts):
for _i in xrange(repeats):
yield value
Если входные данные указаны как [(.2, 'a'), (.4, 'b'), (.3, 'c')]
, а размер - это количество необходимых вам образцов.
Ответ 7
Я не уверен, что это питонический способ делать то, что вы просите, но вы можете использовать random.sample(['a','a','a','b','b','b','b','c','c','c'],k)
где k - количество требуемых образцов.
Для более надежного метода разделите единичный интервал на секции на основе кумулятивной вероятности и выведите из равномерного распределения (0,1) с помощью random.random(). В этом случае подинтервалы будут (0,.3) (. 3,.7) (. 7,1). Вы выбираете элемент, на основе которого он попадает.
Ответ 8
Просто вдохновленный sholte
очень простой (и правильный) ответ: я просто продемонстрирую, насколько легко будет распространять его на обработку произвольных элементов, например:
In []: s= array([.3, .4, .3]).cumsum().searchsorted(sample(54))
In []: c, _= histogram(s, bins= arange(4))
In []: [item* c[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccc']
Обновление:
Основываясь на обратной связи phant0m
, оказывается, что на основе multinomial
можно реализовать еще более прямое решение, например:
In []: s= multinomial(54, [.3, .4, .3])
In []: [item* s[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccc']
ИМХО, здесь мы имеем хорошее резюме выборки на основе empirical cdf
и multinomial
, что дает аналогичные результаты. Итак, в сводке, подберите ее, которая лучше всего подходит для ваших целей.
Ответ 9
Это может быть предельной выгоды, но я сделал это следующим образом:
import scipy.stats as sps
N=1000
M3 = sps.multinomial.rvs(1, p = [0.3,0.4,0.3], size=N, random_state=None)
M3a = [ np.where(r==1)[0][0] for r in M3 ] # convert 1-hot encoding to integers
Это похоже на ответ @eat.