使用SciPy或NumPy生成具有指定权重的离散随机变量。

60
我正在寻找一个简单的函数,可以根据它们相应的概率生成指定随机值的数组。我只需要它生成浮点值,但我认为它也可以生成任何标量。我可以想到很多从现有函数构建这个功能的方法,但我可能错过了一个明显的SciPy或NumPy函数。
例如:
>>> values = [1.1, 2.2, 3.3]
>>> probabilities = [0.2, 0.5, 0.3]
>>> print some_function(values, probabilities, size=10)
(2.2, 1.1, 3.3, 3.3, 2.2, 2.2, 1.1, 2.2, 3.3, 2.2)

注意:我找到了scipy.stats.rv_discrete,但我不明白它是如何工作的。特别地,我不明白下面这段代码的含义和作用:

numargs = generic.numargs
[ <shape(s)> ] = ['Replace with resonable value', ]*numargs

如果rv_discrete是我应该使用的,请您提供一个简单的示例和对上述“shape”语句的解释。

5个回答

91
从离散分布中抽取样本的功能已直接包含在numpy中。 这个函数被称为random.choice(如果numpy文档中没有任何有关离散分布的参考,可能很难找到它)。
elements = [1.1, 2.2, 3.3]
probabilities = [0.2, 0.5, 0.3]
np.random.choice(elements, 10, p=probabilities)

3
好的!但是,正确的语法是:np.random.choice(elements, 10, p=list(probabilities))。 - Sina
1
不错。我认为这个版本是在我发布原始问题之后推出的(我认为这是在2013年发布的1.7.0版本)。 - TimY
2
非常好!似乎不需要转换为列表也可以工作:np.random.choice(elements, 10, p=probabilities))。 - zeycus
除了Sinazeycus的评论之外,elementsprobabilites也可以是普通的list而不是numpy.array,代码仍然可以正常工作。 - arekolek

26

这是一个简短、相对简单的函数,返回带权值的值,它使用了NumPy的digitizeaccumulaterandom_sample

import numpy as np
from numpy.random import random_sample

def weighted_values(values, probabilities, size):
    bins = np.add.accumulate(probabilities)
    return values[np.digitize(random_sample(size), bins)]

values = np.array([1.1, 2.2, 3.3])
probabilities = np.array([0.2, 0.5, 0.3])

print weighted_values(values, probabilities, 10)
#Sample output:
[ 2.2  2.2  1.1  2.2  2.2  3.3  3.3  2.2  3.3  3.3]

它的工作原理如下:

  1. 首先使用accumulate创建bins。
  2. 然后使用random_sample创建一堆随机数(介于01之间)。
  3. 我们使用digitize来查看这些数字落入哪些bin中。
  4. 并返回相应的值。

1
是的,这基本上就是我想的,但我认为可能有一个内置函数可以完全做到这一点。听起来似乎没有这样的东西。我必须承认 - 我不会像这样优雅地完成它。-谢谢 - TimY
NumPy直接提供了numpy.cumsum()函数,可代替np.add.accumulate()np.add()不是很常用,因此建议使用cumsum())。 - Eric O. Lebigot
非常感谢有用的numpy.digitize()!然而,SciPy实际上提供了一个直接回答问题的函数——请参见我的答案。 - Eric O. Lebigot
PS:正如Tim_Y所指出的那样,在处理1万个元素时,使用SciPy的函数比使用您的“手动”解决方案要慢得多。 - Eric O. Lebigot
1
这个需要对概率进行归一化吗? - IssamLaradji
@Curious:是的,概率必须被归一化,因为random_sample()返回[0;1)范围内的数字,所以区间不能超出这个范围(如果概率总和大于1,则会超出)。 - Eric O. Lebigot

18

你走在正确的方向上了:内置的scipy.stats.rv_discrete()可以直接创建一个离散随机变量。它的工作原理如下:

>>> from scipy.stats import rv_discrete  

>>> values = numpy.array([1.1, 2.2, 3.3])
>>> probabilities = [0.2, 0.5, 0.3]

>>> distrib = rv_discrete(values=(range(len(values)), probabilities))  # This defines a Scipy probability distribution

>>> distrib.rvs(size=10)  # 10 samples from range(len(values))
array([1, 2, 0, 2, 2, 0, 2, 1, 0, 2])

>>> values[_]  # Conversion to specific discrete values (the fact that values is a NumPy array is used for the indexing)
[2.2, 3.3, 1.1, 3.3, 3.3, 1.1, 3.3, 2.2, 1.1, 3.3]

因此,上述分布distribvalues列表中返回索引

更一般地,rv_discrete() 在其values=(…,…)参数的第一个元素中采用一个整数序列值,并返回这些值,在本例中不需要转换为特定的(浮点)值。这是一个例子:

>>> values = [10, 20, 30]
>>> probabilities = [0.2, 0.5, 0.3]
>>> distrib = rv_discrete(values=(values, probabilities))
>>> distrib.rvs(size=10)
array([20, 20, 20, 20, 20, 20, 20, 30, 20, 20])

在这里,(整数)输入值将以所需的概率直接返回。


4
请问您知道它为什么比fraxel的纯numpy版本慢了100倍吗?我已经尝试在上面运行timeit,结果如此。 - TimY
@TimY 我的幼稚猜测是,速度慢可能是因为更多的工作在纯Python中完成,而在C语言下进行的工作相对较少(Python中的数学/科学软件包倾向于封装C代码)。 - abcd
假设我要从概率分布方程开始。使用该方程为每个值生成概率,然后将其馈送到rv_discrete中并从中获取我最初的分布的近似值,这似乎很愚蠢。有没有办法直接在scipy中使用用户定义方程? - abcd
@EOL 不是的,我正在使用离散随机变量。不确定为什么你认为我没有在使用它。结果我正在使用泊松随机变量,并且在numpy中有一个函数可以从泊松分布中抽取样本(np.random.poisson)。我相信大多数标准分布也是如此。然而,我的问题仍未得到回答,因为它涉及到更为特殊的分布。 - abcd
1
@dbliss 现在我明白你考虑的是具有无限可能值的离散分布情况(这不符合此问题)。rv_discrete()没有针对此选项。我不确定处理此类问题的标准方法是什么。(我只能想到略微复杂的变体,将均匀随机变量转换为具有非均匀分布的变量,其中累积概率仅计算最常见的值,并在需要时扩展到其他值。) - Eric O. Lebigot
显示剩余2条评论

6
最简单的DIY方法是将概率累加到累积分布中。这样,您可以将单位间隔分成与原始概率相等长度的子间隔。现在生成一个均匀分布在[0,1)上的随机数,并查看它落在哪个间隔中。

欣赏这种更数学化、不那么依赖Python包的方式。 - jarvis

4
你也可以使用Lea,这是一个专门用于离散概率分布的纯Python包。
>>> distrib = Lea.fromValFreqs((1.1,2),(2.2,5),(3.3,3))
>>> distrib
1.1 : 2/10
2.2 : 5/10
3.3 : 3/10
>>> distrib.random(10)
(2.2, 2.2, 1.1, 2.2, 2.2, 2.2, 1.1, 3.3, 1.1, 3.3)

就这样!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接