Как выбрать элемент из списка с известными процентами в Python - PullRequest
1 голос
/ 12 октября 2009

Я хочу выбрать случайное слово из списка, в котором есть известный шанс для каждого слова, например:

Фрукты с вероятностью

Оранжевый 0.10 Яблоко 0,05 Манго 0,15 и т.д.

Как лучше всего это реализовать? Фактический список, из которого я возьму, имеет длину до 100 наименований, и% не все соответствуют 100%, они не соответствуют учтенным для предметов, которые имели очень низкий шанс появления. В идеале я хотел бы взять это из CSV, где я храню эти данные. Это не критичная по времени задача.

Спасибо за любые советы о том, как лучше поступить.

Ответы [ 6 ]

2 голосов
/ 12 октября 2009

То, что вы хотите, это извлечь из полиномиального распределения . Предполагая, что у вас есть два списка элементов и вероятностей, и вероятности составляют 1 (если нет, просто добавьте некоторое значение по умолчанию, чтобы покрыть дополнительные):

def choose(items,chances):
    import random
    p = chances[0]
    x = random.random()
    i = 0
    while x > p :
        i = i + 1
        p = p + chances[i]
    return items[i]
2 голосов
/ 12 октября 2009

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

from random import random

class WeightedChoice(object):
    def __init__(self, weights):
        """Pick items with weighted probabilities.

            weights
                a sequence of tuples of item and it's weight.
        """
        self._total_weight = 0.
        self._item_levels = []
        for item, weight in weights:
            self._total_weight += weight
            self._item_levels.append((self._total_weight, item))

    def pick(self):
        pick = self._total_weight * random()
        for level, item in self._item_levels:
            if level >= pick:
                return item

Затем можно загрузить файл CSV с помощью модуля csv и передать его в класс WeightedChoice:

import csv

weighed_items = [(item,float(weight)) for item,weight in csv.reader(open('file.csv'))]
picker = WeightedChoice(weighed_items)
print(picker.pick())
1 голос
/ 12 октября 2009
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.69) ]

x = 0.0
lst2 = []
for fruit, chance in lst:
    tup = (x, fruit)
    lst2.append(tup)
    x += chance

tup = (x, None)
lst2.append(tup)

import random

def pick_one(lst2):
    if lst2[0][1] is None:
        raise ValueError, "no valid values to choose"
    while True:
        r = random.random()
        for x, fruit in reversed(lst2):
            if x <= r:
                if fruit is None:
                    break  # try again with a different random value
                else:
                    return fruit

pick_one(lst2)

Это создает новый список с возрастающими значениями, представляющими диапазон значений, которые выбирают фрукт; затем pick_one () идет назад по списку, ища значение, которое <= текущее случайное значение. Мы помещаем значение «часовой» в конец списка; если значения не достигают 1,0, существует вероятность случайного значения, которое не должно совпадать ни с чем, и оно будет соответствовать значению дозорного, а затем будет отклонено. random.random () возвращает случайное значение в диапазоне [0.0, 1.0), поэтому в конечном итоге оно обязательно будет соответствовать чему-либо в списке. </p>

Приятно то, что вы должны иметь одно значение с вероятностью совпадения 0,000001, и оно должно фактически совпадать с этой частотой; другие решения, в которых вы создаете список с повторяющимися элементами и просто используете random.choice (), чтобы выбрать один из них, потребовали бы список с миллионом элементов в нем для обработки этого случая.

0 голосов
/ 12 октября 2009
lst = [ ('Orange', 0.10), ('Apple', 0.05), ('Mango', 0.15), ('etc', 0.69) ]

x = 0.0
lst2 = []
for fruit, chance in lst:
    low = x
    high = x + chance
    tup = (low, high, fruit)
    lst2.append(tup)
    x += chance

if x > 1.0:
    raise ValueError, "chances add up to more than 100%"

low = x
high = 1.0
tup = (low, high, None)
lst2.append(tup)

import random

def pick_one(lst2):
    if lst2[0][2] is None:
        raise ValueError, "no valid values to choose"
    while True:
        r = random.random()
        for low, high, fruit in lst2:
            if low <= r < high:
                if fruit is None:
                    break  # try again with a different random value
                else:
                    return fruit

pick_one(lst2)


# test it 10,000 times
d = {}
for i in xrange(10000):
    x = pick_one(lst2)
    if x in d:
        d[x] += 1
    else:
        d[x] = 1

Я думаю, это немного яснее. Вместо хитрого способа представления диапазонов как возрастающих значений, мы просто сохраняем диапазоны. Поскольку мы тестируем диапазоны, мы можем просто пройти через значения lst2; нет необходимости использовать reversed().

0 голосов
/ 12 октября 2009
import random
d= {'orange': 0.10, 'mango': 0.15, 'apple': 0.05}
weightedArray = []
for k in d:
  weightedArray+=[k]*int(d[k]*100)
random.choice(weightedArray)

редактирует

Это по сути то, что сказал Брайан выше.

0 голосов
/ 12 октября 2009

Одним из решений является нормализация вероятностей целыми числами, а затем повторение каждого элемента один раз для каждого значения (например, список с 2 апельсинами, 1 яблоком, 3 манго). Это невероятно легко сделать (from random import choice). Если это нецелесообразно, попробуйте код здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...