Как сделать взвешенную случайную выборку категорий в питоне - PullRequest
27 голосов
/ 22 июня 2011

Учитывая список кортежей, где каждый кортеж состоит из вероятности и элемента, который я хотел бы выбрать для элемента в соответствии с его вероятностью. Например, приведите список [(.3, 'a'), (.4, 'b'), (.3, 'c')], который я хотел бы выбрать для "b" 40% времени.

Какой канонический способ сделать это в Python?

Я посмотрел на случайный модуль, который, кажется, не имеет соответствующей функции, и на numpy.random, который, хотя и имеет полиномиальную функцию, похоже, не возвращает результаты в хорошей форме для этой задачи. В основном я ищу что-то вроде mnrnd в matlab.

Большое спасибо.

Спасибо за все ответы так быстро. Чтобы уточнить, я не ищу объяснения о том, как написать схему выборки, а скорее указываю на простой способ выборки из полиномиального распределения по набору объектов и весов, или чтобы мне сказали, что такой функции не существует в стандартной библиотеке, и поэтому нужно написать свою собственную.

Ответы [ 9 ]

19 голосов
/ 22 июня 2011

Это может делать то, что вы хотите:

numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
10 голосов
/ 30 сентября 2015

Поскольку никто не использовал функцию numpy.random.choice , вот та, которая сгенерирует то, что вам нужно, в одной компактной строке:

numpy.random.choice(['a','b','c'], size = 20, p = [0.3,0.4,0.3])
10 голосов
/ 22 июня 2011
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']

Как именно вы хотели бы получить результаты?

3 голосов
/ 22 июня 2011

Есть хаки, которые вы можете сделать, если, например, ваши вероятности хорошо вписываются в проценты и т. Д.

Например, если у вас все в порядке с процентами, будет работать следующее (за счетвысокая нагрузка на память):

Но «реальный» способ сделать это с произвольными вероятностями с плавающей запятой - это произвести выборку из совокупного распределения после его построения.Это эквивалентно разделению единичного интервала [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

Однако, если у вас нет проблем с разрешением... это на самом деле, вероятно, самый быстрый путь.=)

1 голос
/ 22 июня 2011

Я считаю, что полиномиальная функция - все еще довольно простой способ получить выборки распределения в случайном порядке.Это только один способ

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')], а размер - это количество необходимых вам выборок.

1 голос
/ 22 июня 2011

Как создать 3 "a", 4 "b" и 3 "c" в списке, а затем просто случайным образом выбрать один.При достаточном количестве итераций вы получите желаемую вероятность.

0 голосов
/ 24 ноября 2017

Это может быть незначительной выгодой, но я сделал это так:

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.

0 голосов
/ 22 июня 2011

Просто вдохновленный очень простым (и правильным) ответом 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, дающей аналогичные результаты. Итак, в итоге, выберите тот, который подходит вам лучше всего.

0 голосов
/ 22 июня 2011

Я не уверен, является ли это питоническим способом выполнения того, что вы просите, но вы можете использовать random.sample(['a','a','a','b','b','b','b','c','c','c'],k), где k - это количество выборок, которое вы хотите.

Для более надежного метода разделите пополам единичный интервал на секции на основе совокупной вероятности и извлеките из равномерного распределения (0,1), используя random.random ().В этом случае подинтервалы будут (0, .3) (. 3, .7) (. 7,1).Вы выбираете элемент в зависимости от того, в какой подинтервал он попадает.

...