提问者:小点点

无替换的多个随机数序列


在numpy中,我可以使用代码

from numpy.random import default_rng
rng = default_rng()
M, N, n = 10000, 1000, 3
rng.choice(np.arange(0, N), size=n, replace=False)

在不替换的情况下,从0到9获得三个随机样本。

我想得到成千上万个这样的随机序列。正确的做法是什么?我知道我能做到

np.array([rng.choice(np.arange(0, N), size=(n,), replace=False) for i in range(0, M)])

但是我想知道是否有更有效的方法来使用Numpy来实现这一点。

在这个答案中,建议采用以下方法

np.argsort(rng.random((M,N)),axis=1)[:, :n]

这是超快速和优雅的。然而,成本规模像nxm,而不是我希望实现的nxm

还有其他方法吗?


共1个答案

匿名用户

方法#1

对于N

R = np.arange(M)
mask = np.ones((M,N), dtype=bool)
idx = np.random.randint(0,N,(M))
mask[R,idx] = 0

for i in range(1,n):
    lim = N-i
    m2 = np.ones((M,lim), dtype=bool)
    idx2 = np.random.randint(0,lim,(M))
    m2[R,idx2] = 0
    mask[mask] = m2.ravel()

out = np.nonzero(~mask)[1].reshape(-1,n)

如果您需要随机化每行的数字,请使用问题帖子中链接的兰德技巧:

out = np.take_along_axis(out, np.random.rand(M,n).argsort(1), axis=1)

如果使用m2创建常量数组让您感到困扰,请在循环前初始化后重新使用,同时保持其余代码不变-

m2 = np.ones((M,N-1), dtype=bool)
for i in range(1,n):
    lim = N-i
    idx2 = np.random.randint(0,lim,(M))
    m2[R,idx2] = 0
    mask[mask] = m2.ravel()
    m2[R,idx2] = 1
    m2 = m2[:,:-1]

方法#2类似于方法#1,但初始化部分的大部分工作是设置每行的unqiue随机数。另一个while迭代部分负责处理无法分配唯一行的行。使用N

# https://stackoverflow.com/a/51915131/ @Divakar
def random_num_per_grp(L):
    # For each element in L pick a random number within range specified by it
    r1 = np.random.rand(np.sum(L)) + np.repeat(np.arange(len(L)),L)
    offset = np.r_[0,np.cumsum(L[:-1])]
    return r1.argsort()[offset] - offset

R = np.arange(M)
mask = np.ones((M,N), dtype=bool)
idx = np.random.randint(0,N,(M,n))
mask[R[:,None],idx] = 0

rows_notdone = mask.sum(1)!=N-n
while np.any(rows_notdone):    
    idx0 = random_num_per_grp(mask[rows_notdone].sum(1))
    steps = np.r_[0,mask.sum(1).cumsum()[:-1]]
    flat_idx0 = steps[rows_notdone] + idx0
    
    m2 = np.ones(mask.sum(), dtype=bool)
    m2[flat_idx0] = 0
    mask[mask] = m2
    
    rows_notdone = mask.sum(1)!=N-n

out = np.nonzero(~mask)[1].reshape(-1,n)