Python - 具有不同分布的“x”列表中的示例

时间:2016-11-27 11:41:54

标签: python numpy random

在下面的代码中,我创建了一个项目和用户列表。我已将这些项目分为3个非常受欢迎,流行和常规项目的列表。

import numpy as np


N_USERS = 20000
N_ITEMS = 1000

items = range(0, N_ITEMS)
users = range(0, N_USERS)

vpop = int(len(items)*0.1)
pop = int(len(items)*0.3)

np.random.shuffle(items)
vpop_items = items[:vpop]
pop_items = items[vpop:pop]
reg_items = items [pop:]

我想从具有不同分布的列表中抽样X个样本。例如:

list_of_items = sample(vpop_items, pop_items, reg_items, p = [0.5, 0.35, 0.15], X)

其中X是我想要制作的样本数量,P是与列表相对应的分布列表(vpop_itemspop_items,{{1 }})。

所以最后我会reg_items中有X个“项目”。

我们说list_of_items。我总共需要100个样本,X = 100的概率为0.5,vpop_items的概率为0.35,pop_items的概率为0.15。采样必须无需更换,即不能多次选择任何项目。

2 个答案:

答案 0 :(得分:2)

这是一个简单的Python算法,可以满足您的需求。它比您目前正在做的更有效,但我确信这是一种更聪明的方法。 :)

num为所需的样本总数。我们首先生成0-1范围内的num个随机数,并根据所需的累积概率对其进行测试,并计算每个概率范围内出现的数量。接下来,我们使用我们在第一步中找到的计数作为样本大小对每个序列进行采样。最后,我们将这些样本混合在一起。

在下面的代码中,我已经注释了执行重排的行,以便在测试代码时更容易查看正在进行的操作。

from random import seed, random, sample, shuffle
from itertools import accumulate

def multi_sample(seqs, probs, num):
    ''' Sample from each sequence in list/tuple `seqs` with the corresponding 
        probability in list/tuple `probs`. Return a list containing `num` samples
    '''
    # Compute the cumulative probability
    # This really should raise ValueError if aprobs[-1] != 1.0
    # and we ought to check that len(seqs) == len(probs)...
    aprobs = list(accumulate(probs))

    # Determine how many samples to take from each seq
    counts = [0] * len(seqs)
    for _ in range(num):
        x = random()
        for i, p in enumerate(aprobs):
            if x < p:
                break
        counts[i] += 1

    lst = []
    for seq, count in zip(seqs, counts):
        lst.extend(sample(seq, count))

    #shuffle(lst)
    return lst

# Test

N_ITEMS = 1000
items = list(range(N_ITEMS))
vpop = int(N_ITEMS * 0.1)
pop = int(N_ITEMS * 0.3)

#shuffle(items)
vpop_items = items[:vpop]
pop_items = items[vpop:pop]
reg_items = items[pop:]

all_items = (vpop_items, pop_items, reg_items)

list_of_items = multi_sample(all_items, probs=[0.5, 0.35, 0.15], num=100)
print(list_of_items)

# Verify

#list_of_items.sort()
#print(list_of_items)

# Should be ~50
print(sum(1 for x in list_of_items if x < vpop))
# Should be ~35
print(sum(1 for x in list_of_items if vpop <= x < pop))

典型输出

[65, 16, 81, 97, 30, 33, 52, 92, 96, 72, 50, 4, 75, 7, 44, 18, 90, 9, 91, 56, 85, 28, 84, 88, 76, 21, 14, 77, 8, 59, 22, 34, 93, 95, 63, 10, 99, 41, 60, 36, 66, 2, 13, 64, 51, 43, 11, 106, 153, 235, 189, 132, 150, 226, 196, 247, 245, 194, 172, 227, 202, 256, 163, 205, 131, 192, 295, 147, 246, 108, 291, 155, 128, 171, 141, 124, 102, 210, 294, 284, 276, 148, 122, 290, 948, 566, 894, 884, 310, 476, 562, 313, 357, 846, 794, 317, 335, 599, 370, 988]
47
37

请注意,此功能可能会失败:如果您致电sample(seq, count) count > len(seq),则会ValueError: Sample larger than population。因此,您需要确保num足够小,以便不会发生这种情况。为了完全安全,请确保num num为100,最小的序列为vpop_items,其中包含100个项目,因此我们不必担心。

感谢Andras Deak引起我的注意。

正如我之前所说,必须采用更智能的方法:不是在循环中计算counts,而是能够生成这些计数直接使用适当的数学,但我担心我不知道(或不记得)如何做到这一点。当然,我们可以&#34;欺骗&#34;。 :)使用给定的数据,我们需要来自vpop_items的约50个项目,来自pop_items的35个项目和来自reg_items的其余15个项目。因此,我们可以将counts设置为[50, 35, 15],然后对每个计数进行小幅随机调整,注意保持总数等于100。

答案 1 :(得分:1)

这是一个直截了当的numpy解决方案,它利用了你只有三个类别的事实。对于太多的类别,这可能不会很好地扩展,因为它只是循环了三个选项。

首先,生成统一的伪随机数以确定从哪个组中采集多少个样本。然后,使用numpy.random.choice执行抽样:

import numpy as np

# data setup
N_ITEMS = 1000

items = list(range(0, N_ITEMS)) #python 3

vpop = int(len(items)*0.1)
pop = int(len(items)*0.3)

np.random.shuffle(items)
vpop_items = items[:vpop]
pop_items = items[vpop:pop]
reg_items = items[pop:]

# actual answer
def randsample(data1,data2,data3,probs,samples):
    # "samples" is the number of samples to take
    uniforms = np.random.rand(samples)
    inds1 = uniforms<=probs[0]
    inds2 = (probs[0]<uniforms) & (uniforms<=probs[0]+probs[1])
    inds3 = ~(inds1|inds2)

    output = np.empty(samples,dtype=type(data1[0])) #set dtype
    for ind,dat in zip((inds1,inds2,inds3),(data1,data2,data3)):
        output[ind] = np.random.choice(dat,ind.sum(),replace=False)

    #TODO: guard against depletion of one of the data sources...

    return output

res = randsample(vpop_items, pop_items, reg_items, [0.5, 0.35, 0.15], 100)

对于每个采样点,数组uniforms包含0到1之间的伪随机统一编号。我们将这些数字与输入中给出的(累积)概率进行比较,以便从具有规定概率的相应类别中进行选择。通常,对于给定样本,如果相应的伪随机数介于isum(probs[:i])之间,我们会从类型sum(probs[:i+1])中选择。三个索引数组inds1,inds2,inds3给出了输出样本的不相交分区,指定了给定样本点的类别类型。然后我们要做的就是根据给定类别中的随机选择设置输出数组的相应索引。

只是为了检查结果样本是否正确且具有代表性:

>>> np.in1d(res, vpop_items).sum()/res.size
0.53000000000000003
>>> np.in1d(res, pop_items).sum()/res.size
0.34000000000000002
>>> np.in1d(res, reg_items).sum()/res.size
0.13
>>> (np.in1d(res, reg_items) & np.in1d(res,pop_items)).sum()
0
>>> (np.in1d(res, reg_items) & np.in1d(res,vpop_items)).sum()
0
>>> (np.in1d(res, pop_items) & np.in1d(res,vpop_items)).sum()
0