import random
pos = ["A", "B", "C"]
x = random.choice["A", "B", "C"]
这段代码给出了概率相等的“A”,“B”或“C”。 当你想要30%的“A”,40%的“B”和30%概率的“C”时,有没有一种很好的表达方式?
答案 0 :(得分:37)
权重定义概率分布函数(pdf)。任何此类pdf的随机数可以由applying its associated inverse cumulative distribution function生成,以统一0到1之间的随机数。
另请参阅此SO explanation,或Wikipedia解释:
如果Y具有U [0,1]分布,则F -1(Y)分布为F.这是 用于随机数生成使用 逆变换采样方法。
import random
import bisect
import collections
def cdf(weights):
total = sum(weights)
result = []
cumsum = 0
for w in weights:
cumsum += w
result.append(cumsum / total)
return result
def choice(population, weights):
assert len(population) == len(weights)
cdf_vals = cdf(weights)
x = random.random()
idx = bisect.bisect(cdf_vals, x)
return population[idx]
weights=[0.3, 0.4, 0.3]
population = 'ABC'
counts = collections.defaultdict(int)
for i in range(10000):
counts[choice(population, weights)] += 1
print(counts)
# % test.py
# defaultdict(<type 'int'>, {'A': 3066, 'C': 2964, 'B': 3970})
上面的choice
函数使用bisect.bisect
,因此在O(log n)
中选择加权随机变量,其中n
是weights
的长度。
请注意,从版本1.7.0开始,NumPy有一个Cythonized np.random.choice function。例如,这会从人口[0,1,2,3]
生成1000个样本,权重为[0.1, 0.2, 0.3, 0.4]
:
import numpy as np
np.random.choice(4, 1000, p=[0.1, 0.2, 0.3, 0.4])
np.random.choice
还有一个replace
参数,可以替换或不替换。
理论上更好的算法是Alias Method。它构建了一个需要O(n)
时间的表格,但在此之后,可以在O(1)
时间内绘制样本。因此,如果您需要绘制许多样本,理论上Alias方法可能会更快。 Walker别名方法here和numpy version here的Python实现。
答案 1 :(得分:26)
不......太多......
pos = ['A'] * 3 + ['B'] * 4 + ['C'] * 3
print random.choice(pos)
或
pos = {'A': 3, 'B': 4, 'C': 3}
print random.choice([x for x in pos for y in range(pos[x])])
答案 2 :(得分:9)
这是一个暴露一堆具有相对概率的项目的类,而不实际扩展列表:
import bisect
class WeightedTuple(object):
"""
>>> p = WeightedTuple({'A': 2, 'B': 1, 'C': 3})
>>> len(p)
6
>>> p[0], p[1], p[2], p[3], p[4], p[5]
('A', 'A', 'B', 'C', 'C', 'C')
>>> p[-1], p[-2], p[-3], p[-4], p[-5], p[-6]
('C', 'C', 'C', 'B', 'A', 'A')
>>> p[6]
Traceback (most recent call last):
...
IndexError
>>> p[-7]
Traceback (most recent call last):
...
IndexError
"""
def __init__(self, items):
self.indexes = []
self.items = []
next_index = 0
for key in sorted(items.keys()):
val = items[key]
self.indexes.append(next_index)
self.items.append(key)
next_index += val
self.len = next_index
def __getitem__(self, n):
if n < 0:
n = self.len + n
if n < 0 or n >= self.len:
raise IndexError
idx = bisect.bisect_right(self.indexes, n)
return self.items[idx-1]
def __len__(self):
return self.len
现在,只说:
data = WeightedTuple({'A': 30, 'B': 40, 'C': 30})
random.choice(data)
答案 3 :(得分:5)
您也可以使用此表单,该表单不会创建任意大的列表(并且可以使用整数或十进制概率):
pos = [("A", 30), ("B", 40), ("C", 30)]
from random import uniform
def w_choice(seq):
total_prob = sum(item[1] for item in seq)
chosen = random.uniform(0, total_prob)
cumulative = 0
for item, probality in seq:
cumulative += probality
if cumulative > chosen:
return item
答案 4 :(得分:5)
这里提供了一些很好的解决方案,但我建议你看一下这个问题的Eli Bendersky's thorough discussion,它会在选择之前比较各种算法(用Python实现)。
答案 5 :(得分:0)
试试这个:
import random
from decimal import Decimal
pos = {'A': Decimal("0.3"), 'B': Decimal("0.4"), 'C': Decimal("0.3")}
choice = random.random()
F_x = 0
for k, p in pos.iteritems():
F_x += p
if choice <= F_x:
x = k
break