生成具有特定(数值)分布的随机数

240 投票
13 回答
376268 浏览
提问于 2025-04-16 07:32

我有一个文件,里面记录了不同值的概率,比如:

1 0.1
2 0.05
3 0.05
4 0.2
5 0.4
6 0.2

我想用这个概率分布来生成随机数。有没有现成的模块可以处理这个?其实自己写代码也很简单(先构建累积分布函数,然后生成一个[0,1]之间的随机值,再根据这个值找出对应的结果),但我觉得这个应该是个常见的问题,可能有人已经为此写了函数或模块。

我需要这个是因为我想生成一组生日(这些生日并不符合标准的random模块中的任何分布)。

13 个回答

33

使用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
194

从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})
211

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的回答

撰写回答