基于多个条件,如何更高效地处理numpy数组?

3
我已经编写了一些代码,用于处理一系列年份的数据(例如15年)。使用ndimage.filters.convolve对一个数组(例如array1)进行卷积运算,然后如果生成的新数组(例如array2)超过一个随机数,那么另外一个数组(例如array3)将被赋值为1。一旦array3被赋值为1,它就会逐年计数,当它最终达到某个特定值(例如5)时,array1就会在这个位置上更新。

如果这有点令人困惑,我很抱歉。实际上,我已经通过使用numpy.where(boolean expression, value, value)使脚本正常工作,但当我需要多个表达式时(例如where array2 == 1 and array3 == 0),我使用了循环来迭代每个数组中的每个值。这在这个例子中非常好用,但是当我用更大的数组代替数组时(完整的脚本导入GIS网格并将其转换为数组),这个循环需要几分钟才能处理每一年的数据。由于我们需要在1000次中运行60年的模型,所以我需要找到一种更有效的处理这些数组的方法。

我尝试在numpy.where函数内使用多个表达式,但无法弄清楚如何让它工作。我还尝试过使用zip(array)将数组组合在一起,但无法更新它们,我认为这是因为它创建了数组元素的元组。

我已经附上了脚本副本,就像之前提到的,它可以完全满足我的需求。然而,它需要更高效的运行。如果有人有任何建议,那就太好了。这是我第一次用python发布帖子,所以我仍然认为自己是个初学者。

import numpy as np
from scipy import ndimage
import random
from pylab import *

###################### FUNCTIONS ###########################

def convolveArray1(array1, kern1):

    newArray = ndimage.filters.convolve(array1, kern1, mode='constant')

    return newArray


######################## MAIN ##############################

## Set the number of years
nYears = range(1,16)

## Cretae array1
array1 = np.zeros((10,10), dtype=np.int) # vegThreshMask

# Add some values to array1
array1[[4,4],[4,5]] = 8
array1[5,4] = 8
array1[5,5] = 8

## Create kerna; array
kernal = np.ones((3,3), dtype=np.float32)

## Create an empty array to be used as counter
array3 = np.zeros((10,10), dtype=np.int)

## iterate through nYears
for y, yea in enumerate(nYears):

    # Create a random number for the year
    randNum = randint(7, 40)
    print 'The random number for year %i is %i' % (yea, randNum)
    print

    # Call the convolveArray function
    convArray = convolveArray1(array1, kernal)

    # Update array2 where it is greater than the random number    
    array2 = np.where(convArray > randNum, 1, 0)
    print 'Where convArray > randNum in year %i' % (yea)
    print array2
    print 

    # Iterate through array2 
    for a, ar in enumerate(array2):
        for b, arr in enumerate(ar):
            if all(arr == 1 and array3[a][b] == 0):
                array3[a][b] = 1
            else:
                if array3[a][b] > 0:
                    array3[a][b] = array3[a][b] + 1
            if array3[a][b] == 5:
                array1[a][b] = 8

    # Remove the initial array (array1) from the updated array3   
    array3 = np.where(array1 > 0, 0, array3)
    print 'New array3 after %i years' % (yea)
    print '(Excluding initial array)'
    print array3
    print    

print 'The final output of the initial array'
print array1
2个回答

3

我认为如果你开始使用广播,你可以获得很大的加速。例如,从你的# Iterate through array2这行开始,我们可以去掉显式的循环,只需在要更改的变量上进行广播。请注意,我使用AX而不是arrayX以增强清晰度:

# Iterate through A2

idx  = (A2==1) & (A3==0)
idx2 = (~idx)  & (A3>0)
A3[idx ]  = 1
A3[idx2] += 1
A1[A3==5] = 8

此外,一旦你适应了这种风格,这样做大大提高了代码的清晰度,因为你不必明确地处理索引(在这里是你的ab)。
值得麻烦吗?
我试过上面的代码后,向OP要求进行速度测试:
如果您实现循环更改,请告诉我您的真实代码加速情况。知道这个建议是简单的语法糖还是有显着影响将非常有用。
测试后,回应是大幅提升40倍的速度!当处理大量连续数据数组并执行简单掩码操作时,numpy比本机python列表是更好的选择。

这个完美地运行了!我进行了几次测试,比较了这个和我的原始代码的速度,这个只用了18秒,而不是12分钟以上!所以它绝对有显著的效果。谢谢(它看起来也好多了)。 - TravisMoon
@TravisMoon 非常棒!我真的很感激你发布这些结果,当有人问是否值得使用numpy进行基本数组操作时,我会直接引导他们来这里。我预计会有加速,但没想到竟然达到了40倍的速度提升! - Hooked

1

看起来你试图使用多个条件在np.where中使用表达式,例如array1 > 0 and array2 < 0。这不起作用是因为布尔运算在Python中的工作方式,如 这里所述。首先,array1 > 0被评估,然后将其转换为布尔值使用__nonzero__方法(在Python 3中重命名为__bool__)。将数组转换为布尔值没有唯一有用的方法,并且目前无法覆盖布尔运算符的行为(尽管我相信这是未来版本正在讨论的问题),因此在numpy中,ndarray.__nonzero__被定义为引发异常。相反,您可以使用np.logical_andnp.logical_ornp.logical_not,它们具有您期望的行为。

我不知道这会给你带来多大的加速,但如果你最终在循环中执行了大量的数组索引操作,那么值得考虑使用Cython。通过将它们移动到C扩展中,你可以轻松地加速数组操作。


感谢您澄清了Python如何处理布尔表达式,我工作中的另一个人也提到了在numpy数组中使用np.logical_and。对于这个任务,我使用了上面给出的答案,但如果有机会,我也会尝试一下,看看哪个更快。我不确定我在工作中使用的计算机是否有CPython,但我可能还是会研究一下。谢谢。 - TravisMoon

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