Вопрос по python, numpy – Взвешенный выбор короткий и простой [дубликат]

43

This question already has an answer here:

A weighted version of random.choice 20 answers

Если у меня есть коллекция предметов в списке. Я хочу выбрать из этого списка в соответствии с другим списком весов.

Например, моя коллекция['one', 'two', 'three'] и веса[0.2, 0.3, 0.5]Я бы ожидал, что метод даст мне «три». примерно в половине всех розыгрышей.

Какой самый простой способ сделать это?

Ваш Ответ

7   ответов
21

Начиная с Python 3.6, вы можете сделать взвешенный случайный выбор (с заменой), используяrandom.choices.

random.choices(population, weights=None, *, cum_weights=None, 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']
4

Вы можете использоватьполиномиальное распределение (от 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 раз, примерно так, как вы ожидаете, учитывая веса.

Если вам трудно обдумать полиномиальный дистрибутив, я обнаружил, чтодокументация действительно полезно.

Обратите внимание, что вы можете уменьшить ваши результаты доitertools.chain.,from_iterable([elements[i]]*count, for i, count in enumerate(indices[0])), который будет быстрее.
Фактически, вы можете улучшить его еще больше, заменив умножение списка наitertools.repeat(elements[i], count) тоже.
11

Эта функция принимает два аргумента: список весов и список, содержащий объекты на выбор:

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.

Предполагая, что веса положительны, cs - отсортированный список. Использование numpy.searchsorted приведет к значительному ускорению поиска индекса
Комментарии кажутся вводящими в заблуждение.cumsum() дает совокупные значения, а не логические значения. Чтобы было ясно, это работает, но комментарии не соответствуют тому, что на самом деле происходит.
Я отредактировал, чтобы исправить, а также поместил строку документации в одну строку, как рекомендуется вPEP 257.
3

Если вы не хотите использовать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() чтобы избежать проверки значений, как только мы достигнем точки, которую мы хотим остановить, в противном случае это по сути та же идея, что иОтвет Мишы Обрехтапросто безnumpy.

1

Строить наМаус & APOS; ответЭто здорово, если вы хотите многократно получать взвешенные случайные значения, если вам нужно только одно значение, вы можете сделать это очень просто, комбинируя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]))
@aix Случайно разбил ваше редактирование, откатился на вашу (лучшую) ссылку.
77

поскольку версию 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))
Идеальное решениеl = [choice(elements, p=weights) for _ in range(1000)] а такжеfrom collections import Counter; Counter(l) обеспечивает:Counter({'three': 498, 'two': 281, 'one': 221}).
Этот ответ должен быть подтвержден.
3

Как насчет простой инициализации вашего списка, чтобы соответствовать вашему выбору с ожидаемыми весами. Здесь я составляю список из 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

Это не кумулятивно, но, похоже, это то, что вы ищете.

Похоже, что это имеет тот же эффект, но выделение вектора 3 * 100 просто для выбора кажется немного излишним. Особенно, если бы я использовал это в контексте проблемы, которая возникла впервые, а именно, в симуляции Монте-Карло, где вы хотите быть как можно быстрее ... Mischa Obrecht
Вы должны добавить эту информацию к вопросу. Но вы выделяете список только один раз, вызывая "random.choice ()" будет быстро
да, но я бы сказал, если есть дешевый и дорогой способ достижения того же результата, само собой разумеется, что выбирают дешевый. Судьи правят? :) Mischa Obrecht

Похожие вопросы