生成具有特定(数值)分布的随机数
我有一个文件,里面记录了不同值的概率,比如:
1 0.1
2 0.05
3 0.05
4 0.2
5 0.4
6 0.2
我想用这个概率分布来生成随机数。有没有现成的模块可以处理这个?其实自己写代码也很简单(先构建累积分布函数,然后生成一个[0,1]之间的随机值,再根据这个值找出对应的结果),但我觉得这个应该是个常见的问题,可能有人已经为此写了函数或模块。
我需要这个是因为我想生成一组生日(这些生日并不符合标准的random
模块中的任何分布)。
13 个回答
使用CDF生成列表的一个好处是你可以进行二分查找。虽然在预处理阶段需要花费O(n)的时间和空间,但之后你可以用O(k log n)的时间找到k个数字。因为普通的Python列表效率不高,所以你可以使用array
模块。
如果你坚持要使用常量空间,可以这样做;时间复杂度是O(n),空间复杂度是O(1)。
def random_distr(l):
r = random.uniform(0, 1)
s = 0
for item, prob in l:
s += prob
if s >= r:
return item
return item # Might occur because of floating point inaccuracies
从Python 3.6开始,Python的标准库里有一个解决方案,叫做 random.choices
。
举个例子:我们先设置一个人群和权重,这些和提问者的问题中的设置是一样的:
>>> from random import choices
>>> population = [1, 2, 3, 4, 5, 6]
>>> weights = [0.1, 0.05, 0.05, 0.2, 0.4, 0.2]
现在,使用 choices(population, weights)
可以生成一个样本,这个样本会放在一个长度为1的列表里:
>>> choices(population, weights)
[4]
还有一个可选的参数 k
,它只能通过关键字的方式传入,允许我们一次请求多个样本。这一点很重要,因为每次调用 random.choices
时,它需要做一些准备工作,才能生成样本;如果一次生成多个样本,我们只需要做一次准备工作。这里我们生成了一百万个样本,并使用 collections.Counter
来检查我们得到的分布大致是否符合我们给定的权重。
>>> million_samples = choices(population, weights, k=10**6)
>>> from collections import Counter
>>> Counter(million_samples)
Counter({5: 399616, 6: 200387, 4: 200117, 1: 99636, 3: 50219, 2: 50025})
scipy.stats.rv_discrete
可能是你需要的工具。你可以通过 values
参数来提供你的概率。然后,你可以使用这个分布对象的 rvs()
方法来生成随机数。
正如Eugene Pakhomov在评论中提到的,你还可以给 numpy.random.choice()
传递一个 p
关键字参数,比如:
numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])
如果你使用的是Python 3.6或更高版本,你可以使用标准库中的 random.choices()
– 具体可以参考 Mark Dickinson的回答。