首页 > 解决方案 > 无替换的多个随机数序列

问题描述

在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]

这是超快和优雅的。然而,成本规模就像我希望实现N x M的那样。n x M

还有其他方法吗?

标签: pythonnumpyrandom

解决方案


方法#1

对于N>> 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)

如果您需要对每行的数字进行随机化,请使用问题帖子中链接的 rand-trick:

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与 类似Approach #1,但初始化部分完成了为每行设置 unqiue 随机数的大部分工作。额外的while迭代部分负责处理无法分配唯一行的行。使用N>> 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)

推荐阅读