如何在python中对类别进行加权随机抽样

|| 给定一个元组列表,其中每个元组都包含一个概率和一个项目,我想根据一个项目的概率对其进行采样。例如,给列表[(.3,\'a \'),(.4,\'b \'),(.3,\'c \')]我想对\'b进行采样40%的时间。 在python中执行此操作的规范方法是什么? 我看了看似没有合适函数的随机模块,看了numpy.random,尽管它具有多项式函数,但似乎并没有以很好的形式返回结果。我基本上是在Matlab中寻找mnrnd之类的东西。 非常感谢。 非常感谢您的所有答复。为了澄清,我不是在寻找有关如何编写采样方案的解释,而是要指出一种简单的方法,该方法是从给定一组对象和权重的多项式分布中采样,或者被告知没有此类函数存在于标准库中,因此应该自己编写。     
已邀请:
        
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\']
您想如何准确地收到结果?     
        这可能会满足您的要求:
numpy.array([.3,.4,.3]).cumsum().searchsorted(numpy.random.sample(5))
    
        由于没有人使用numpy.random.choice函数,因此这里的函数可以在一个紧凑的行中生成所需的内容:
numpy.random.choice([\'a\',\'b\',\'c\'], size = 20, p = [0.3,0.4,0.3])
    
        例如,如果您的概率很好地适合于百分比等,那么您可以采取一些措施。 例如,如果您对百分比满意,则可以使用以下命令(以高昂的内存开销为代价): 但是,使用任意浮点概率实现此目标的“真实”方法是在构造累积分布后对其进行采样。这等效于将单位间隔[0,1]细分为标记为'a \',\'b \'和\'c \'的3个线段;然后在单位间隔上选择一个随机点,并查看将其分段的线段。
#!/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
但是,如果您没有解决问题的方法,那么实际上这可能是最快的方法。 =)     
        然后在列表中创建3个“ a”,4个“ b”和3个“ c”,然后随机选择一个。经过足够的迭代,您将获得所需的概率。     
        我认为多项式函数是一种以随机顺序获取分布样本的相当简单的方法。这只是一种方式
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
输入指定为“ 7”,大小是您需要的样本数量。     
        我不确定这是否是您所要求的Python方式,但是您可以使用     
random.sample([\'a\',\'a\',\'a\',\'b\',\'b\',\'b\',\'b\',\'c\',\'c\',\'c\'],k)
其中k是您想要的样本数。 对于更可靠的方法,请根据累积概率将单位间隔分为两部分,并使用random.random()从均匀分布(0,1)中提取。在这种情况下,子间隔为(0,.3)(。3,.7)(。7,1)。您可以根据元素所属的子间隔来选择元素。     
        刚刚从ѭ9的非常简单(也是正确的)答案中得到了启发:我将演示扩展它以处理任意项目的容易程度,例如:
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
的反馈,事实证明可以基于implemented12 implemented实现更直接的解决方案,例如:
In []: s= multinomial(54, [.3, .4, .3])
In []: [item* s[i] for i, item in enumerate(\'abc\')]
Out[]: [\'aaaaaaaaaaaaaaa\', \'bbbbbbbbbbbbbbbbbbbbbbbbbbb\', \'cccccccccccc\']
恕我直言,我们对基于ѭ14和
multinomial
的采样有一个很好的总结,得出相似的结果。因此,在总结中,选择最适合您目的的一个。     
        这可能会有边际收益,但是我这样做是这样的:
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的答案。     

要回复问题请先登录注册