随机加权选择

时间:2010-01-15 16:43:42

标签: python

我有这样的数据:

d = (
  (701, 1, 0.2),
  (701, 2, 0.3),
  (701, 3, 0.5),
  (702, 1, 0.2),
  (702, 2, 0.3),
  (703, 3, 0.5)
)

其中(701,1,0.2)=(id1,id2,priority)

如果我使用优先级知道id1,是否有一种选择id2的漂亮方法?

Func(701)应该返回:
1 - 在20%的情况下 2 - 30%
3 - 50%

百分比当然很粗糙

7 个答案:

答案 0 :(得分:6)

为每个ID1生成累积分布函数:

cdfs = defaultdict()
for id1,id2,val in d:
    prevtotal = cdfs[id1][-1][0]
    newtotal = prevtotal + val
    cdfs[id1].append( (newtotal,id2) )

所以你会有

cdfs = { 701 : [ (0.2,1), (0.5,2), (1.0,3) ], 
         702 : [ (0.2,1), (0.5,2) ],
         703 : [ (0.5,3) ] }

然后生成一个随机数并在列表中搜索它。

def func(id1):
    max = cdfs[id1][-1][0]
    rand = random.random()*max
    for upper,id2 in cdfs[id1]:
        if upper>rand:
            return id2
    return None

答案 1 :(得分:3)

意识到我的第一个答案是数学上非常错误,我已经提出了一个新想法。我相信这里的算法类似于其他几个答案的算法,但是这个实现似乎有资格获得问题的“漂亮”(如果等于简单)要求:

def func(id):
    rnd = random()
    sum = 0
    for row in d:
        if row[0] == id:
            sum = sum + row[2]
            if rnd < sum:
                return row[1]

使用来自OP的示例数据,它是这样的:

  • 选择0到1.0之间的随机数
  • 如果数字为< 0.2,则返回第一个元素
  • 如果数字为< 0.5,则返回第二个元素
  • 否则(如果数字为< 1.0)返回第三个元素

答案 2 :(得分:2)

random module上使用足够数量的值的离散均匀分布,然后对其进行分区:

例如,对于案例701,使用超过10个值的分布,对于2个值,返回1,对于另一个值,返回2,对于其他5,返回3.

您可以使用足够的统一分布构建任何分布:)

答案 3 :(得分:1)

如果您的百分比值不会比整个百分比值更精确,请使用随机数生成器生成数字0-99。

然后在您的函数中,使用(编程)案例来选择正确的数字。例如(清理它):

if 701
  if random_num < 20
    return 1
  else if random number < 50   // ( 20 + 30 )
    return 2
  else if random number < 100  // ( 20 + 30 + 50 )
    return 3
  else
    // error

答案 4 :(得分:1)

非常快速的黑客攻击:

import random

d = {
    701: [(1,0.2),(2,0.3),(3,0.5)],
    702: [(1,0.2),(2,0.3),(3,0.5)]
}

def func(value):
    possible_values=d[value]
    total=sum(p[-1] for p in possible_values)
    random_value=random.random()
    prob=possible_values[0][-1]/total
    index=1
    while index<len(possible_values) and prob<random_value:
        prob+=possible_values[index][-1]/total
        index+=1
    return possible_values[index-1][0]

if __name__=='__main__':
    testcases=1000
    cnt=[0,0,0]
    for case in xrange(testcases):
        answer=func(701)
        cnt[answer-1]+=1
    for i in xrange(3):
        print "Got %d %f%% of the time"%(i+1,float(cnt[i])/testcases*100)

它并不漂亮,但这是我想到的第一件事,似乎按预期工作。

这样做是为了在区间[0,1)中获得随机值(使用random.random())。然后使用随机值是否落在区间[0,0.2),[0.2,0.5]或[0.5,1]中,以确定要返回的值。

答案 5 :(得分:0)

两个想法(允许我用分开的选项和比例来说明它,为了清楚地说明参数名称,如果它们被包装在一个元组中,你可以保存“zip”):

a)对权重进行非规范化以获得整数比率,然后将一个副本放入列表中,并使用random.choice

def choice_with_ratios(options, ratios):
    tmp = sum([[v]*n for v, n in zip(options, ratios)], [])
    return random.choice(tmp)

b)使用标准化权重并开始求和,直到达到随机生成的均匀值

def choice_with_weights(options, weights):
    s = 0
    r = random.random()
    for v, w in zip(options, weights):
        s += w
        if s >= r: break
    return v

顺便说一下,如果第一个字段用作键,则应将其放在字典中,如:

d = {
  701: ((1, 0.2), (2, 0.3), (3, 0.5),
  702: ((1, 0.3), (2, 0.2), (3, 0.5)
}

答案 6 :(得分:0)

您还可以为每个值创建一个100个元素的列表,然后让random.choice从一个种子列表中进行选择,该列表的成员以您想要的权重加载:

import random
from collections import defaultdict

d = ( 
  (701, 1, 0.2), 
  (701, 2, 0.3), 
  (701, 3, 0.5), 
  (702, 1, 0.2), 
  (702, 2, 0.3), 
  (702, 3, 0.5) 
) 

class WeightedLookup(object):
    def __init__(self, valueTupleList):
        self.valdict = defaultdict(list)
        for key, val, prob in valueTupleList:
            self.valdict[key] += [val]*(int)(prob*100)

    def __getitem__(self,key):
        return random.choice(self.valdict[key])


lookup = WeightedLookup(d)

# test out our lookup distribution, sample it 100000 times
res = { 1:0, 2:0, 3:0 }
for i in range(100000):
    res[lookup[701]] += 1

# print how many times each value was returned
for k in (1,2,3):
    print k, res[k]

打印:

1 20059
2 30084
3 49857