29

给定一个元组列表,其中每个元组由一个概率和一个项目组成,我想根据其概率对一个项目进行采样。例如,给出列表 [ (.3, 'a'), (.4, 'b'), (.3, 'c')] 我想在 40% 的时间对 'b' 进行采样。

在 python 中这样做的规范方法是什么?

我查看了 random 模块,它似乎没有适当的功能,并且在 numpy.random 中,虽然它具有多项式函数,但似乎并没有针对这个问题以一种很好的形式返回结果。我基本上是在 matlab 中寻找类似 mnrnd 的东西。

非常感谢。

感谢所有的答案这么快。澄清一下,我不是在寻找如何编写采样方案的解释,而是指出一种简单的方法来从给定一组对象和权重的多项分布中采样,或者被告知不存在这样的函数在标准库中,因此应该自己编写。

4

9 回答 9

19

这可能会做你想要的:

numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
于 2011-06-21T22:18:58.183 回答
12

由于没有人使用numpy.random.choice函数,所以这里有一个可以在一条紧凑的行中生成您需要的内容:

numpy.random.choice(['a','b','c'], size = 20, p = [0.3,0.4,0.3])
于 2015-09-30T06:20:53.277 回答
11
import numpy

n = 1000
pairs = [(.3, 'a'), (.3, 'b'), (.4, 'c')]
probabilities = numpy.random.multinomial(n, zip(*pairs)[0])
result = zip(probabilities, zip(*pairs)[1])
# [(299, 'a'), (299, 'b'), (402, 'c')]
[x[0] * x[1] for x in result]
# ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccc']

您希望如何收到结果?

于 2011-06-21T22:24:22.253 回答
3

例如,如果您的概率非常适合百分比等,您可以采取一些技巧。

例如,如果您对百分比没问题,以下将起作用(以高内存开销为代价):

但是使用任意浮点概率的“真正”方法是在构建累积分布之后对其进行采样。这相当于将单位区间[0,1]细分为3个线段,分别标记为'a'、'b'和'c';然后在单位间隔上选择一个随机点并查看它是哪条线段。

#!/usr/bin/python3
def randomCategory(probDict):
    """
        >>> dist = {'a':.1, 'b':.2, 'c':.3, 'd':.4}

        >>> [randomCategory(dist) for _ in range(5)]
        ['c', 'c', 'a', 'd', 'c']

        >>> Counter(randomCategory(dist) for _ in range(10**5))
        Counter({'d': 40127, 'c': 29975, 'b': 19873, 'a': 10025})
    """
    r = random.random() # range: [0,1)
    total = 0           # range: [0,1]
    for value,prob in probDict.items():
        total += prob
        if total>r:
            return value
    raise Exception('distribution not normalized: {probs}'.format(probs=probDict))

必须小心返回值的方法,即使它们的概率为 0。幸运的是,这种方法不会,但以防万一,可以插入if prob==0: continue.


作为记录,这是一种骇人听闻的方法:

import random

def makeSampler(probDict):
    """
        >>> sampler = makeSampler({'a':0.3, 'b':0.4, 'c':0.3})
        >>> sampler.sample()
        'a'
        >>> sampler.sample()
        'c'
    """
    oneHundredElements = sum(([val]*(prob*100) for val,prob in probDict.items()), [])
    def sampler():
        return random.choice(oneHundredElements)
    return sampler

但是,如果您没有解决问题……这实际上可能是最快的方法。=)

于 2011-06-21T22:09:55.647 回答
1

如何在列表中创建 3 个“a”、4 个“b”和 3 个“c”,然后随机选择一个。通过足够的迭代,您将获得所需的概率。

于 2011-06-21T22:04:32.390 回答
1

我认为多项式函数仍然是一种以随机顺序获取分布样本的相当简单的方法。这只是一种方式

import numpy
from itertools import izip

def getSamples(input, size):
    probabilities, items = zip(*input)
    sampleCounts = numpy.random.multinomial(size, probabilities)
    samples = numpy.array(tuple(countsToSamples(sampleCounts, items)))
    numpy.random.shuffle(samples)
    return samples

def countsToSamples(counts, items):
    for value, repeats in izip(items, counts):
        for _i in xrange(repeats):
            yield value

其中输入是指定[(.2, 'a'), (.4, 'b'), (.3, 'c')]的,大小是您需要的样本数。

于 2011-06-21T22:57:30.710 回答
0

我不确定这是否是按照您的要求做的 Pythonic 方式,但是您可以使用 random.sample(['a','a','a','b','b','b','b','c','c','c'],k) 其中 k 是您想要的样本数。

对于更稳健的方法,根据累积概率将单位间隔分成多个部分,并使用 random.random() 从均匀分布 (0,1) 中提取。在这种情况下,子区间将是 (0,.3)(.3,.7)(.7,1)。您可以根据元素所在的子区间来选择元素。

于 2011-06-21T22:04:55.677 回答
0

只是受到sholte's 非常简单(且正确)的答案的启发:我将演示将其扩展为处理任意项目是多么容易,例如:

In []: s= array([.3, .4, .3]).cumsum().searchsorted(sample(54))
In []: c, _= histogram(s, bins= arange(4))
In []: [item* c[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccc']

更新
基于 的反馈phant0m,事实证明可以基于 实现更直接的解决方案multinomial,例如:

In []: s= multinomial(54, [.3, .4, .3])
In []: [item* s[i] for i, item in enumerate('abc')]
Out[]: ['aaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccc']

恕我直言,我们在这里对产生类似结果的采样进行了很好的empirical cdf总结multinomial。因此,总而言之,选择一个最适合您的目的。

于 2011-06-22T18:28:44.083 回答
0

这可能是边际收益,但我是这样做的:

import scipy.stats as sps
N=1000
M3 = sps.multinomial.rvs(1, p = [0.3,0.4,0.3], size=N, random_state=None)
M3a = [ np.where(r==1)[0][0] for r in M3 ] # convert 1-hot encoding to integers

这类似于@eat 的回答。

于 2017-11-24T16:59:54.803 回答