如何根据加权概率从python字典中选择键?

时间:2016-12-02 07:49:38

标签: python random probability

我有一个Python字典,其中键表示一些项目,值表示所述项目的一些(标准化)加权。例如:

d = {'a': 0.0625, 'c': 0.625, 'b': 0.3125}
# Note that sum([v for k,v in d.iteritems()]) == 1 for all `d`

鉴于项目与权重的这种相关性,我如何从d中选择一个密钥,使得结果为'a'的时间为6.25%,结果为'b'的时间为32.25%,以及62.5%的结果是'c'?

7 个答案:

答案 0 :(得分:8)

def weighted_random_by_dct(dct):
    rand_val = random.random()
    total = 0
    for k, v in dct.items():
        total += v
        if rand_val <= total:
            return k
    assert False, 'unreachable'

应该做的伎俩。遍历每个密钥并保持运行总和,如果随机值(介于0和1之间)落入槽中,则返回该密钥

答案 1 :(得分:6)

从Python 3.6开始,您可以使用内置的random.choices(),而不必使用Numpy。

然后,如果我们要从字典中采样(替换)25个键,其中值是被采样的权重/概率,我们可以简单地写:

import random
random.choices(list(my_dict.keys()), weights=my_dict.values(), k=25)

这将输出采样键列表:

['c', 'b', 'c', 'b', 'b', 'c', 'c', 'c', 'b', 'c', 'b', 'c', 'b', 'c', 'c', 'c', 'c', 'c', 'a', 'b']

如果只需要一个键,请将k设置为1,然后从random.choices返回的列表中提取单个元素:

random.choices(list(my_dict.keys()), weights=my_dict.values(), k=1)[0]

(如果不将my_dict.keys()转换为列表,则会收到有关TypeType不可下标的TypeError信息。)

以下是docs中的相关代码段:

random.choices(人口,体重=无,*,cum_weights =无,k = 1)

返回从总体中选择的k个大小的元素列表,并进行替换。如果填充为空,则引发IndexError。

如果指定了权重序列,则会根据相对权重进行选择。或者,如果给出了cum_weights序列,则根据累积权重(可能使用itertools.accumulate()计算)进行选择。例如,相对权重[10、5、30、5]等于累积权重[10、15、45、50]。在内部,相对权重会在进行选择之前转换为累积权重,因此提供累积权重可以节省工作。

如果未指定权重或cum_weights,则选择的可能性均等。如果提供了权重序列,则其长度必须与总体序列的长度相同。同时指定权重和cum_weights是TypeError。

权重或cum_weights可以使用与random()返回的浮点值(包括整数,浮点和小数,但不包括小数)互操作的任何数值类型。权重假定为非负。

对于给定的种子,具有相同权重的choices()函数通常产生与重复调用choice()不同的序列。 choices()使用的算法使用浮点算法来实现内部一致性和速度。 choice()所使用的算法默认为带有重复选择的整数算术,以避免舍入误差产生小的偏差。

根据https://stackoverflow.com/a/39976962/5139284处的注释,对于小型数组,random.choices更快,对于大型数组,numpy.random.choice更快。 numpy.random.choice还提供了一个无需替换即可进行采样的选项,而对此却没有内置的Python标准库功能。

答案 2 :(得分:4)

如果您计划大量执行此操作,则可以使用numpy从使用np.random.choice()的加权概率列表中选择您的密钥。以下示例将使用加权概率选择您的密钥10,000次。

import numpy as np

probs = [0.0625, 0.625, 0.3125]
keys = ['a', 'c', 'b']

choice_list = np.random.choice(keys, 10000, replace=True, p=probs)

答案 3 :(得分:2)

不确定您的用例是什么,但您可以查看NLTK包中的频率分布/概率分布类,它们处理所有细节。

FreqDist是计数器的扩展,可以传递给ProbDistI接口。 ProbDistI接口公开了一个“generate()”方法,该方法可用于对分布进行采样,以及可用于获取给定密钥概率的“prob(sample)”方法。

对于您的情况,您需要使用最大似然估计,因此MLEProbDist。如果你想平滑分布,可以试试LaplaceProbDist或SimpleGoodTuringProbDist。

例如:

from nltk.probability import FreqDist, MLEProbDist

d = {'a': 6.25, 'c': 62.5, 'b': 31.25}
freq_dist = FreqDist(d)
prob_dist = MLEProbDist(freq_dist)

print prob_dist.prob('a')
print prob_dist.prob('b')
print prob_dist.prob('c')
print prob_dist.prob('d')

将打印“0.0625 0.3125 0.625 0.0”。

要生成新样本,您可以使用:

prob_dist.generate()

答案 4 :(得分:0)

我所理解的:您需要一个简单的随机函数,它将在0和1之间统一生成一个随机数。如果该值介于0 to 0.0625之间,您将选择键a,如果它在0.0625 and (0.0625 + 0.625)之间,那么您将选择键c等。这就是answer中实际提到的内容。

由于随机数将统一生成,预期与较大权重相关的关键字与其他关键字相比将被选择得更多。

答案 5 :(得分:0)

如果你能够使用numpy,你可以使用numpy.random.choice功能,如下所示:

import numpy as np

d = {'a': 0.0625, 'c': 0.625, 'b': 0.3125}

def pick_by_weight(d):
    d_choices = []
    d_probs = []
    for k,v in d.iteritems():
      d_choices.append(k)
      d_probs.append(v)
    return np.random.choice(d_choices, 1, p=d_probs)[0]


d = {'a': 0.0625, 'c': 0.625, 'b': 0.3125}
choice = pick_by_weight(d)

答案 6 :(得分:0)

保留“倒置”字典可能很有用,其中键是权重值,值是您可以获得的键列表。这样,如果更多的键具有相同的权重,则更容易分发它:

from collections import defaultdict
import random

dict = {'a': 0.0625, 'd': 0.0625, 'c': 0.625, 'b': 0.3125}

inverted_dict = defaultdict(list)

for k, v in dict.items():
    inverted_dict[v].append(k)

# Here first you get a random value between 0 and 1, which is your weigth
# Then, you choose a random value from the list of keys that have the same weight
print(random.choice(inverted_dict[random.choice(inverted_dict.keys())]))