仅按行对多维数组进行Numpy随机重排,保持列顺序不变

79

如何在Python中仅按行对多维数组进行洗牌(因此不要洗牌列)。

我正在寻找最有效的解决方案,因为我的矩阵非常大。是否也可能在原始数组上高效地执行此操作(以节省内存)?

示例:

import numpy as np
X = np.random.random((6, 2))
print(X)
Y = ???shuffle by row only not colls???
print(Y)

我现在期望的是原始矩阵:

[[ 0.48252164  0.12013048]
 [ 0.77254355  0.74382174]
 [ 0.45174186  0.8782033 ]
 [ 0.75623083  0.71763107]
 [ 0.26809253  0.75144034]
 [ 0.23442518  0.39031414]]

输出结果将行打乱而不是列,例如:

输出结果将行打乱而不是列,例如:

[[ 0.45174186  0.8782033 ]
 [ 0.48252164  0.12013048]
 [ 0.77254355  0.74382174]
 [ 0.75623083  0.71763107]
 [ 0.23442518  0.39031414]
 [ 0.26809253  0.75144034]]

选项1:对数组进行洗牌视图。我猜这意味着需要自定义实现。几乎不会对内存使用产生影响,但在运行时可能会有一些影响。这真的取决于您打算如何使用此矩阵。 - Dima Tisnek
3
选项2:原地打乱数组。使用np.random.shuffle(x),文档中指出,“此函数仅沿多维数组的第一个索引洗牌数组”,这对您来说已经足够了,对吧?显然,启动时需要一些时间,但从那时起,它与原始矩阵一样快。 - Dima Tisnek
np.random.shuffle(x)相比,对nd-array的索引进行洗牌并从洗牌后的索引获取数据是解决此问题的更有效方法。有关详细的比较,请参见我在下面的回答中(https://dev59.com/Z1sV5IYBdhLWcg3w6iV8#43716153)。 - John
5个回答

84
你可以使用numpy.random.shuffle()函数。
此函数仅沿多维数组的第一轴对数组进行洗牌。子数组的顺序会被改变,但其内容不变。
In [2]: import numpy as np                                                                                                                                                                                  

In [3]:                                                                                                                                                                                                     

In [3]: X = np.random.random((6, 2))                                                                                                                                                                        

In [4]: X                                                                                                                                                                                                   
Out[4]: 
array([[0.71935047, 0.25796155],
       [0.4621708 , 0.55140423],
       [0.22605866, 0.61581771],
       [0.47264172, 0.79307633],
       [0.22701656, 0.11927993],
       [0.20117207, 0.2754544 ]])

In [5]: np.random.shuffle(X)                                                                                                                                                                                

In [6]: X                                                                                                                                                                                                   
Out[6]: 
array([[0.71935047, 0.25796155],
       [0.47264172, 0.79307633],
       [0.4621708 , 0.55140423],
       [0.22701656, 0.11927993],
       [0.20117207, 0.2754544 ],
       [0.22605866, 0.61581771]])

除了之前提到的功能,你还可以查看以下函数:

random.Generator.permuted 函数是在 Numpy 1.20.0 版本中引入的。

shufflepermutation 不同的是,该函数对一个轴索引的子数组进行排列操作,而不是将该轴作为独立的一维数组来处理。例如,现在可以对二维数组的行或列进行排列。


我想知道是否可以通过numpy加速,也许利用并发的优势。 - Georg Schölly
@GeorgSchölly 我认为这是Python中最可用的优化方法。如果你想加速它,你需要对算法进行更改。 - Mazdak
1
我完全同意。我刚刚意识到你正在使用np.random而不是Python的random模块,后者也包含一个shuffle函数。对于造成的混淆,我感到抱歉。 - Georg Schölly
这个洗牌并不总是有效的,可以看看我下面的新答案。为什么它不总是有效? - robert
1
这个方法返回一个NoneType对象 - 有什么办法可以保持对象为numpy数组吗?编辑:抱歉,一切都好了:我之前写的是X = np.random.shuffle(X),它返回一个NoneType对象,但关键在于只需要写np.random.shuffle(X),因为它是原地洗牌。 - MJimitater
标签Y怎么办呢?例如,如果我想在scikitlearn数据集上使用它,如何正确地将标签洗牌以匹配数据集? - wwjdm

30
你还可以使用np.random.permutation生成行索引的随机排列,然后使用带有axis = 0np.take来索引X的行。此外,np.take可通过使用out=选项在原始数组X上进行重写,这将节省内存。因此,实现应如下所示-
np.take(X,np.random.permutation(X.shape[0]),axis=0,out=X)

运行示例 -

In [23]: X
Out[23]: 
array([[ 0.60511059,  0.75001599],
       [ 0.30968339,  0.09162172],
       [ 0.14673218,  0.09089028],
       [ 0.31663128,  0.10000309],
       [ 0.0957233 ,  0.96210485],
       [ 0.56843186,  0.36654023]])

In [24]: np.take(X,np.random.permutation(X.shape[0]),axis=0,out=X);

In [25]: X
Out[25]: 
array([[ 0.14673218,  0.09089028],
       [ 0.31663128,  0.10000309],
       [ 0.30968339,  0.09162172],
       [ 0.56843186,  0.36654023],
       [ 0.0957233 ,  0.96210485],
       [ 0.60511059,  0.75001599]])

额外的性能提升

这里有一个技巧可以通过使用np.argsort()来加速np.random.permutation(X.shape[0]) -

np.random.rand(X.shape[0]).argsort()

加速结果 -

In [32]: X = np.random.random((6000, 2000))

In [33]: %timeit np.random.permutation(X.shape[0])
1000 loops, best of 3: 510 µs per loop

In [34]: %timeit np.random.rand(X.shape[0]).argsort()
1000 loops, best of 3: 297 µs per loop

因此,洗牌方案可以修改为 -

np.take(X,np.random.rand(X.shape[0]).argsort(),axis=0,out=X)

运行时测试 -

这些测试包括本帖中列出的两种方法以及@Kasramvd的解决方案中基于np.shuffle的方法。

In [40]: X = np.random.random((6000, 2000))

In [41]: %timeit np.random.shuffle(X)
10 loops, best of 3: 25.2 ms per loop

In [42]: %timeit np.take(X,np.random.permutation(X.shape[0]),axis=0,out=X)
10 loops, best of 3: 53.3 ms per loop

In [43]: %timeit np.take(X,np.random.rand(X.shape[0]).argsort(),axis=0,out=X)
10 loops, best of 3: 53.2 ms per loop

所以,似乎只有在内存受限的情况下才应该使用基于np.take的解决方案,否则基于np.random.shuffle的解决方案看起来更合适。


1
这听起来不错。你能否在你的帖子中添加一个定时信息,以比较你的np.take与标准shuffle之间的性能?在我的系统上,np.shuffle更快(27.9毫秒)vs你的take (62.9毫秒),但正如我在你的帖子中所读到的,有一个内存优势? - robert
2
@robert 刚刚添加了,看看吧! - Divakar

13

经过一些实验,我发现了一种最节约内存和时间的方式来对 n 维数组进行行级别的数据洗牌。首先,对一个数组的索引进行洗牌,然后使用洗牌后的索引获取数据。例如:

rand_num2 = np.random.randint(5, size=(6000, 2000))
perm = np.arange(rand_num2.shape[0])
np.random.shuffle(perm)
rand_num2 = rand_num2[perm]

更详细的说明
在这里,我使用memory_profiler来查找内存使用情况,并使用Python的内置“时间”模块记录时间并比较所有先前的答案。

def main():
    # shuffle data itself
    rand_num = np.random.randint(5, size=(6000, 2000))
    start = time.time()
    np.random.shuffle(rand_num)
    print('Time for direct shuffle: {0}'.format((time.time() - start)))
    
    # Shuffle index and get data from shuffled index
    rand_num2 = np.random.randint(5, size=(6000, 2000))
    start = time.time()
    perm = np.arange(rand_num2.shape[0])
    np.random.shuffle(perm)
    rand_num2 = rand_num2[perm]
    print('Time for shuffling index: {0}'.format((time.time() - start)))
    
    # using np.take()
    rand_num3 = np.random.randint(5, size=(6000, 2000))
    start = time.time()
    np.take(rand_num3, np.random.rand(rand_num3.shape[0]).argsort(), axis=0, out=rand_num3)
    print("Time taken by np.take, {0}".format((time.time() - start)))

时间的结果

Time for direct shuffle: 0.03345608711242676   # 33.4msec
Time for shuffling index: 0.019818782806396484 # 19.8msec
Time taken by np.take, 0.06726956367492676     # 67.2msec

内存分析结果

Line #    Mem usage    Increment   Line Contents
================================================
    39  117.422 MiB    0.000 MiB   @profile
    40                             def main():
    41                                 # shuffle data itself
    42  208.977 MiB   91.555 MiB       rand_num = np.random.randint(5, size=(6000, 2000))
    43  208.977 MiB    0.000 MiB       start = time.time()
    44  208.977 MiB    0.000 MiB       np.random.shuffle(rand_num)
    45  208.977 MiB    0.000 MiB       print('Time for direct shuffle: {0}'.format((time.time() - start)))
    46                             
    47                                 # Shuffle index and get data from shuffled index
    48  300.531 MiB   91.555 MiB       rand_num2 = np.random.randint(5, size=(6000, 2000))
    49  300.531 MiB    0.000 MiB       start = time.time()
    50  300.535 MiB    0.004 MiB       perm = np.arange(rand_num2.shape[0])
    51  300.539 MiB    0.004 MiB       np.random.shuffle(perm)
    52  300.539 MiB    0.000 MiB       rand_num2 = rand_num2[perm]
    53  300.539 MiB    0.000 MiB       print('Time for shuffling index: {0}'.format((time.time() - start)))
    54                             
    55                                 # using np.take()
    56  392.094 MiB   91.555 MiB       rand_num3 = np.random.randint(5, size=(6000, 2000))
    57  392.094 MiB    0.000 MiB       start = time.time()
    58  392.242 MiB    0.148 MiB       np.take(rand_num3, np.random.rand(rand_num3.shape[0]).argsort(), axis=0, out=rand_num3)
    59  392.242 MiB    0.000 MiB       print("Time taken by np.take, {0}".format((time.time() - start)))

2
我丢失了产生memory_profiler输出的代码。但是,通过按照给定链接中的步骤,它可以非常容易地再现。 - John
我喜欢这个答案的原因是,如果我有两个匹配的数组(碰巧我确实有),那么我可以同时打乱它们,并确保相应位置上的数据仍然匹配。这对于随机化我的训练集的顺序非常有用。 - Spoonless

8

我尝试了很多解决方案,最终选择了这个简单的方法:

from sklearn.utils import shuffle
x = np.array([[1, 2],
              [3, 4],
              [5, 6]])
print(shuffle(x, random_state=0))

输出:

[
[5 6]  
[3 4]  
[1 2]
]

如果你有一个3D数组,遍历第1个轴(axis=0),并应用这个函数,例如:
np.array([shuffle(item) for item in 3D_numpy_array])

3

您可以使用np.vectorize()函数按打乱二维数组A:

shuffle = np.vectorize(np.random.permutation, signature='(n)->(n)')

A_shuffled = shuffle(A)

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