[python] random.choice의 가중치 버전

random.choice의 가중치 버전을 작성해야했습니다 (목록의 각 요소는 선택 될 확률이 다릅니다). 이것이 내가 생각해 낸 것입니다.

def weightedChoice(choices):
    """Like random.choice, but each element can have a different chance of
    being selected.

    choices can be any iterable containing iterables with two items each.
    Technically, they can have more than two items, the rest will just be
    ignored.  The first item is the thing being chosen, the second item is
    its weight.  The weights can be any numeric values, what matters is the
    relative differences between them.
    """
    space = {}
    current = 0
    for choice, weight in choices:
        if weight > 0:
            space[current] = choice
            current += weight
    rand = random.uniform(0, current)
    for key in sorted(space.keys() + [current]):
        if rand < key:
            return choice
        choice = space[key]
    return None

이 기능은 나에게 지나치게 복잡해 보이고 추악합니다. 나는 여기의 모든 사람들이 그것을 개선하거나 다른 방법으로 제안 할 수 있기를 바랍니다. 효율성은 코드 청결도와 가독성만큼 중요하지 않습니다.



답변

버전 1.7.0부터 NumPy에는 choice확률 분포를 지원 하는 기능이 있습니다.

from numpy.random import choice
draw = choice(list_of_candidates, number_of_items_to_pick,
              p=probability_distribution)

참고 probability_distribution동일한 순서 시퀀스이다 list_of_candidates. 또한 키워드 replace=False를 사용하여 그려진 항목이 교체되지 않도록 동작을 변경할 수 있습니다 .


답변

Python 3.6부터는 모듈 의 메소드가 choices있습니다 random.

Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.0.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import random

In [2]: random.choices(
...:     population=[['a','b'], ['b','a'], ['c','b']],
...:     weights=[0.2, 0.2, 0.6],
...:     k=10
...: )

Out[2]:
[['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b']]

참고 random.choices샘플 것이다 교체와 당, 문서 :

k대체물로 모집단에서 선택한 크기가 지정된 요소 목록을 반환합니다 .

당신은 다음과 같이 교체없이 샘플을해야하는 경우 @ 로난 – paixão의 화려한 응답 상태, 당신은 사용할 수 있습니다 numpy.choice누구의 replace인수 제어 등의 동작을.


답변

def weighted_choice(choices):
   total = sum(w for c, w in choices)
   r = random.uniform(0, total)
   upto = 0
   for c, w in choices:
      if upto + w >= r:
         return c
      upto += w
   assert False, "Shouldn't get here"


답변

  1. 가중치를 누적 분포로 배열합니다.
  2. random.random () 을 사용 하여 임의의 float을 선택하십시오 0.0 <= x < total.
  3. http://docs.python.org/dev/library/bisect.html#other-examples 의 예제에 표시된대로 bisect.bisect 를 사용하여 배포를 검색 하십시오 .
from random import random
from bisect import bisect

def weighted_choice(choices):
    values, weights = zip(*choices)
    total = 0
    cum_weights = []
    for w in weights:
        total += w
        cum_weights.append(total)
    x = random() * total
    i = bisect(cum_weights, x)
    return values[i]

>>> weighted_choice([("WHITE",90), ("RED",8), ("GREEN",2)])
'WHITE'

하나 이상의 선택을해야하는 경우, 이것을 두 가지 기능으로 나누십시오. 하나는 누적 가중치를 만들고 다른 하나는 임의의 점으로 이등분합니다.


답변

numpy를 사용하지 않는다면 numpy.random.choice 를 사용할 수 있습니다 .

예를 들면 다음과 같습니다.

import numpy

items  = [["item1", 0.2], ["item2", 0.3], ["item3", 0.45], ["item4", 0.05]
elems = [i[0] for i in items]
probs = [i[1] for i in items]

trials = 1000
results = [0] * len(items)
for i in range(trials):
    res = numpy.random.choice(items, p=probs)  #This is where the item is selected!
    results[items.index(res)] += 1
results = [r / float(trials) for r in results]
print "item\texpected\tactual"
for i in range(len(probs)):
    print "%s\t%0.4f\t%0.4f" % (items[i], probs[i], results[i])

얼마나 많은 선택을해야하는지 알고 있다면 다음과 같이 반복하지 않아도됩니다.

numpy.random.choice(items, trials, p=probs)


답변

조잡하지만 충분할 수 있습니다.

import random
weighted_choice = lambda s : random.choice(sum(([v]*wt for v,wt in s),[]))

작동합니까?

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

# initialize tally dict
tally = dict.fromkeys(choices, 0)

# tally up 1000 weighted choices
for i in xrange(1000):
    tally[weighted_choice(choices)] += 1

print tally.items()

인쇄물:

[('WHITE', 904), ('GREEN', 22), ('RED', 74)]

모든 가중치가 정수라고 가정합니다. 그들은 100을 더할 필요가 없으며 테스트 결과를 더 쉽게 해석하기 위해 그렇게했습니다. 가중치가 부동 소수점 숫자 인 경우 모든 가중치가 1보다 크거나 같을 때까지 반복적으로 10을 곱합니다.

weights = [.6, .2, .001, .199]
while any(w < 1.0 for w in weights):
    weights = [w*10 for w in weights]
weights = map(int, weights)


답변

목록 대신 가중 사전이 있다면 이것을 쓸 수 있습니다.

items = { "a": 10, "b": 5, "c": 1 }
random.choice([k for k in items for dummy in range(items[k])])

[k for k in items for dummy in range(items[k])]이 목록 을 생성합니다['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']