如何随机更改排除某些行的数组的非零条目的位置

时间:2015-05-31 16:21:18

标签: python arrays performance numpy

我有一个由大量0和一些非零条目组成的numpy数组,例如像这样(只是一个玩具示例):

myArray = np.array([[ 0.       ,  0.       ,  0.79],
       [ 0.       ,  0.       ,  0.       ],
       [ 0.       ,  0.       ,  0.       ],
       [ 0.       ,  0.435    ,  0.       ]])

现在我想以给定的概率移动每个非零项,这意味着一些条目被移动,一些可能保留在当前位置。某些行不允许包含非零条目,这意味着不允许在那里移动值。我实现了如下:

import numpy as np

# for reproducibility
np.random.seed(2)

myArray = np.array([[ 0.       ,  0.       ,  0.79],
       [ 0.       ,  0.       ,  0.       ],
       [ 0.       ,  0.       ,  0.       ],
       [ 0.       ,  0.435    ,  0.       ]])

# list of rows where numbers are not allowed to be moved to   
ignoreRows = [2]

# moving probability
probMove =  0.3

# get non-zero entries
nzEntries = np.nonzero(myArray) 

# indices of the non-zero entries as tuples
indNZ = zip(nzEntries[0], nzEntries[1]) 

# store values
valNZ = [myArray[i] for i in indNZ] 

# generating probabilities for moving for each non-zero entry
lProb = np.random.rand(len(nzEntries)) 

allowedRows = [ind for ind in xrange(myArray.shape[0]) if ind not in ignoreRows]  # replace by "range" in python 3.x
allowedCols = [ind for ind in xrange(myArray.shape[1])]  # replace by "range" in python 3.x

for indProb, prob in enumerate(lProb):
    # only move with a certain probability
    if prob <= probMove:
        # randomly change position
        myArray[np.random.choice(allowedRows), np.random.choice(allowedCols)] = valNZ[indProb]

        # set old position to zero
        myArray[indNZ[indProb]] = 0.

print myArray   

首先,我确定非零项的所有索引和值。然后我为这些条目中的每一个分配一定的概率,以确定是否将移动条目。然后我得到允许的目标行。

在第二步中,我遍历索引列表并根据它们的移动概率移动它们,这是通过从允许的行和列中进行选择,为这些新索引分配相应的值并设置“旧”值来完成的。到0。

上面的代码可以正常工作,但是在这种情况下速度真的很重要,我想知道是否有更有效的方法来做到这一点。

编辑: Hpaulj的回答帮助我摆脱了很好的for循环以及我接受他答案的原因。我收录了他的评论,并在下面发布了一个答案,以防万一其他人偶然发现这个例子,并想知道我最终是如何使用他的答案的。

2 个答案:

答案 0 :(得分:2)

您可以使用数组索引元素,因此:

valNZ=myArray[nzEntries]

可以取代zip和理解。

简化这两项任务:

allowedCols=np.arange(myArray.shape[1]);
allowedRows=np.delete(np.arange(myArray.shape[0]), ignoreRows)

使用:

I=lProb<probMove; valNZ=valNZ[I];indNZ=indNZ[I]

您不需要每次在循环中执行prog<probMove测试;只需迭代valNZindNZ

我认为您可以同时为所有这些random.choice生成valNZ

np.random.choice(np.arange(10), 10, True)  
# 10 choices from the range with replacement

有了它,应该可以在没有循环的情况下移动所有点。

我还没有弄清楚细节。

有一种方法可以使您的迭代移动与任何并行移动不同。如果目标选择是另一个值,则迭代方法可以重写,并且可能移动给定值几次。并行代码不会执行顺序移动。你必须决定一个是否正确。

有一个ufunc方法.at,它执行无缓冲的操作。它适用于add之类的操作,但我不知道是否适用于这样的索引移动。

迭代移动的简化版本:

In [106]: arr=np.arange(20).reshape(4,5)
In [107]: I=np.nonzero(arr>10)
In [108]: v=arr[I]
In [109]: rows,cols=np.arange(4),np.arange(5)

In [110]: for i in range(len(v)):
    dest=(np.random.choice(rows),np.random.choice(cols))
    arr[dest]=v[i]
    arr[I[0][i],I[1][i]] = 0

In [111]: arr
Out[111]: 
array([[ 0, 18,  2, 14, 11],
       [ 5, 16,  7, 13, 19],
       [10,  0,  0,  0,  0],
       [ 0, 17,  0,  0,  0]])

可能的矢量化版本:

In [117]: dest=(np.random.choice(rows,len(v),True),np.random.choice(cols,len(v),True)) 
In [118]: dest
Out[118]: (array([1, 1, 3, 1, 3, 2, 3, 0, 0]), array([3, 0, 0, 1, 2, 3, 4, 0, 1]))

In [119]: arr[dest]
Out[119]: array([ 8,  5, 15,  6, 17, 13, 19,  0,  1])
In [120]: arr[I]=0
In [121]: arr[dest]=v

In [122]: arr
Out[122]: 
array([[18, 19,  2,  3,  4],
       [12, 14,  7, 11,  9],
       [10,  0,  0, 16,  0],
       [13,  0, 15,  0, 17]])

如果我之后设置了0,则会有更多的零。

In [124]: arr[dest]=v    
In [125]: arr[I]=0
In [126]: arr
Out[126]: 
array([[18, 19,  2,  3,  4],
       [12, 14,  7, 11,  9],
       [10,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0]])

相同dest,但迭代完成:

In [129]: for i in range(len(v)):
   .....:     arr[dest[0][i],dest[1][i]] = v[i]
   .....:     arr[I[0][i],I[1][i]] = 0

In [130]: arr
Out[130]: 
array([[18, 19,  2,  3,  4],
       [12, 14,  7, 11,  9],
       [10,  0,  0, 16,  0],
       [ 0,  0,  0,  0,  0]])

由于这种小尺寸和高移动密度,迭代和矢量化解决方案之间的差异很大。对于稀疏数组,它们会更少。

答案 1 :(得分:1)

下面你可以找到我在提到hpaulj的答案和this question的答案后提出的代码。这样,我摆脱了for循环,它改进了很多代码。因此,我接受了hpaulj的回答。也许下面的代码可以帮助处于类似情况的其他人。

import numpy as np
from itertools import compress

# for reproducibility
np.random.seed(2)

myArray = np.array([[ 0.       ,  0.2       ,  0.79],
       [ 0.       ,  0.       ,  0.       ],
       [ 0.       ,  0.       ,  0.       ],
       [ 0.       ,  0.435    ,  0.       ]])

# list of rows where numbers are not allowed to be moved to   
ignoreRows= []

# moving probability
probMove =  0.5

# get non-zero entries
nzEntries = np.nonzero(myArray) 

# indices of the non-zero entries as tuples
indNZ = zip(nzEntries[0],nzEntries[1]) 

# store values
valNZ = myArray[nzEntries]

# generating probabilities for moving for each non-zero entry
lProb = np.random.rand(len(valNZ)) 

# get the rows/columns where the entries are allowed to be moved
allowedCols = np.arange(myArray.shape[1]);
allowedRows = np.delete(np.arange(myArray.shape[0]), ignoreRows)

# get the entries that are actually moved
I = lProb < probMove
print I
# get the values of the entries that are moved
valNZ = valNZ[I]

# get the indices of the entries that are moved
indNZ = list(compress(indNZ, I))

# get the destination for the entries that are moved
dest = (np.random.choice(allowedRows, len(valNZ), True), np.random.choice(allowedCols, len(valNZ), True))
print myArray
print indNZ
print dest

# set the old indices to 0
myArray[zip(*indNZ)] = 0

# move the values to their respective destination
myArray[dest] = valNZ
print myArray