使用NumPy随机选择函数生成一个包含所有唯一值的二维数组

16

我想知道是否有更有效的方法使用np.random.choice生成一个二维数组,其中每一行都具有唯一的值。

例如,对于形状为(3,4)的数组,我们期望输出如下:

# Expected output given a shape (3,4)
array([[0, 1, 3, 2],
       [2, 3, 1, 0],
       [1, 3, 2, 0]])

这意味着每行的值在列数方面必须是唯一的。因此,对于out中的每一行,整数应该只落在0到3之间。

我知道可以通过将replace参数设置为False来实现它。但我只能针对每一行而不是整个矩阵这样做。例如,我可以这样做:

>>> np.random.choice(4, size=(1,4), replace=False)
array([[0,2,3,1]])

但是当我尝试做这个时:

>>> np.random.choice(4, size=(3,4), replace=False)

我遇到了这样的错误:

 File "<stdin>", line 1, in <module>
 File "mtrand.pyx", line 1150, in mtrand.RandomState.choice 
 (numpy\random\mtrand\mtrand.c:18113)
 ValueError: Cannot take a larger sample than population when 
 'replace=False'

我猜是因为它尝试着在不重复的情况下绘制 3 x 4 = 12 个样本,但是我只给了它一个 4 的限制。

我知道可以通过使用 for-loop 来解决:

 >>> a = (np.random.choice(4,size=4,replace=False) for _ in range(3))
 >>> np.vstack(a)
 array([[3, 1, 2, 0],
        [1, 2, 0, 3],
        [2, 0, 3, 1]])

但是我想知道是否有一种方法可以不使用任何for循环来解决?(我认为如果行数大于1000,添加for循环可能会使它变慢。但是,正如您所看到的,我实际上正在a中创建一个生成器,因此我也不确定它是否会产生影响。)

3个回答

27

我经常使用的一个技巧是生成一个随机数组,并使用 argsort 来获取所需的唯一索引作为唯一数字。 因此,我们可以执行 -

一个常用的技巧是生成随机数组,然后使用argsort来获取唯一的索引作为所需的唯一数字。因此,我们可以进行以下操作-

def random_choice_noreplace(m,n, axis=-1):
    # m, n are the number of rows, cols of output
    return np.random.rand(m,n).argsort(axis=axis)

示例运行 -

In [98]: random_choice_noreplace(3,7)
Out[98]: 
array([[0, 4, 3, 2, 6, 5, 1],
       [5, 1, 4, 6, 0, 2, 3],
       [6, 1, 0, 4, 5, 3, 2]])

In [99]: random_choice_noreplace(5,7, axis=0) # unique nums along cols
Out[99]: 
array([[0, 2, 4, 4, 1, 0, 2],
       [1, 4, 3, 2, 4, 1, 3],
       [3, 1, 1, 3, 2, 3, 0],
       [2, 3, 0, 0, 0, 2, 4],
       [4, 0, 2, 1, 3, 4, 1]])

运行时测试 -

# Original approach
def loopy_app(m,n):
    a = (np.random.choice(n,size=n,replace=False) for _ in range(m))
    return np.vstack(a)

时间 -

In [108]: %timeit loopy_app(1000,100)
10 loops, best of 3: 20.6 ms per loop

In [109]: %timeit random_choice_noreplace(1000,100)
100 loops, best of 3: 3.66 ms per loop

太棒了!非常感谢你! - Lj Miranda
等一下,也许这并不是天才的想法,除非我漏掉了什么......如果我在没有替换的情况下对x进行抽样,那么这意味着我正在取一个小于len(x)的样本大小,否则样本将始终与x相同,除了顺序。因此,如果我想从x中随机抽取y个元素,这种方法将确保我只能x的前y个值中进行抽取... - pretzlstyle
@jphollowed 这基本上生成覆盖整个长度的唯一数字。因此,一个很好的用例是对数组进行洗牌。现在,如果你想要取样本数小于数组长度的情况,只需切片 random_choice_noreplace(1000,100)[:,:20],其中 100 是数组的长度,而你想要 20 个样本。 - Divakar

3

这是我基于Divakar的答案修改后解决无需替换的重复采样的方案。在他的评论中,他建议如果样本数量小于数组长度,则对结果进行切片。然而,如果数组长度较大但样本数量较小,则这可能不是最有效的方法,因为argsort需要很长时间。我建议使用argpartition代替。

def random_choice_noreplace2(l, n_sample, num_draw):
    '''
    l: 1-D array or list
    n_sample: sample size for each draw
    num_draw: number of draws

    Intuition: Randomly generate numbers, get the index of the smallest n_sample number for each row.
    '''
    l = np.array(l)
    return l[np.argpartition(np.random.rand(num_draw,len(l)), n_sample-1,axis=-1)[:,:n_sample]]

时间 -

def loopy_app(l, n_sample, num_draw):
    l = np.array(l)
    a = [np.random.choice(l,size=n_sample,replace=False) for _ in range(num_draw)]
    return np.vstack(a)

def random_choice_noreplace(l, n_sample, num_draw):
    # m, n are the number of rows, cols of output
    l = np.array(l)
    return l[np.random.rand(num_draw,len(l)).argsort(axis=-1)[:,:n_sample]]

In [2]: %timeit loopy_app(range(100),2,1000)   
48.5 ms ± 2.91 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [3]: %timeit random_choice_noreplace(range(100),2,1000)   
7.8 ms ± 210 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit random_choice_noreplace2(range(100),2,1000)   
2.71 ms ± 57.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

正确性 -

In [5]: np.random.seed(42)      
   ...: random_choice_noreplace(range(100),2,10)                                                                                                          
Out[5]: 
array([[72, 10],
       [28, 71],
       [ 8,  5],
       [32, 71],
       [ 7, 56],
       [63, 15],
       [40, 28],
       [94, 64],
       [21, 98],
       [45, 36]])

In [6]: np.random.seed(42)      
   ...: random_choice_noreplace2(range(100),2,10)                                                                                                          
Out[6]: 
array([[72, 10],
       [28, 71],
       [ 8,  5],
       [32, 71],
       [ 7, 56],
       [63, 15],
       [40, 28],
       [94, 64],
       [21, 98],
       [45, 36]])

是的,对于绘制较小的集合来说,这是有意义的。这在以下链接中有所涉及 - https://stackoverflow.com/questions/45881540/, https://stackoverflow.com/questions/35572381/ 等等。 - Divakar
谢谢!我一直在寻找解决这个问题的方法。这似乎是添加到numpy.random中的一个有用功能。你觉得这值得提交PR吗? - Lala La
1
生成随机数是一种权宜之计,而不是直接解决方案。因此,不要认为开发人员会对此非常兴奋。 - Divakar

1
除了Divakar的好答案,这里有另一种选择,在我的机器上甚至更快,速度快大约20%:
def random_choice_noreplace_2(m, n):
    data = np.arange(m * n).reshape(n, m) % m
    for row in data: np.random.shuffle(row)
    return data

时间:

In [3]: %timeit random_choice_noreplace(1000, 100)
3.85 ms ± 1.52 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit random_choice_noreplace_2(1000, 100)
3.1 ms ± 10.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接