提高NumPy数组操作的性能

9

我正在使用numpy.delete来从while循环内的数组中删除元素。只有当数组不为空时,此while循环才有效。这段代码可以正常工作,但是当数组有超过1e6个元素时,速度会明显变慢。以下是一个示例:

while(array.shape[0] > 0):
     ix = where((array >= x) & (array <= y))[0]
     array = delete(array,ix,None)

我已经尝试过优化这段代码,但是我找不到加速while循环的好方法。我认为瓶颈在于删除操作必须涉及某种形式的复制。我尝试使用掩码数组来避免复制,但我不太擅长Python,并且掩码数组不容易搜索。是否有一种好的快速方法来使用delete或替换它,以便可以处理7e6个元素而不需要花费24小时以上?谢谢。

1
在 while 循环内部,xy 是否会发生变化? - JoshAdel
1
你为什么要一个一个地删除元素,而不是一次性全部删除? - mgilson
我认为我们可能需要更多的while循环来弄清楚你在这里想要实现什么。正如@JoshAdel所指出的那样,如果x和y是静态的,你将会有一个(可能)无限循环--你将(可能)到达ix为空数组的地步,然后你将永远循环下去(即使在小列表上)。如果你没有达到那个点,你只是清空数组,你可以用一个单独的命令来完成... - mgilson
是的,在 while 循环中,x 和 y 会随着我搜索数组而改变。我需要在下一次搜索条件之前“删除”这些元素,以便不重复搜索相同的区域。数组是一个时间序列的值,我需要通过取最强烈的情况来找到事件发生的频率。因此,当我找到一个由 x 和 y 确定的事件时,我会删除这个值和在移动到下一个值之前的一个小窗口内的所有值。这就是为什么我认为我需要一个 while 循环,但我正在学习。是的,(array >= x) & (array <= y) 是正确的形式。 - Shejo284
3个回答

9

如果你想大幅提高代码的性能,可以采取以下措施:

  • 消除循环;并且

  • 避免删除操作(会导致原始数组的复制)

NumPy 1.7引入了一种比原来更易于使用的新掩码,其性能也要好得多,因为它是NumPy核心数组对象的一部分。我认为这对你有用,因为使用它可以避免昂贵的删除操作

换句话说,不要删除你不想要的数组元素,而是将它们掩盖起来。这已经被建议在其他答案中,但我建议使用新掩码

要使用NA,只需导入NA。

>>> from numpy import NA as NA

然后针对给定的数组,将maskna标志设置为True

>>> A.flags.maskna = True

另外,大多数数组构造函数(自1.7版本起)都有一个名为maskna的参数,您可以将其设置为True

>>> A[3,3] = NA

array([[7, 5, 4, 8, 4],
       [2, 4, 3, 7, 3],
       [3, 1, 3, 2, 1],
       [8, 2, 0, NA, 7],
       [0, 7, 2, 5, 5],
       [5, 4, 2, 7, 4],
       [1, 2, 9, 2, 3],
       [7, 5, 1, 2, 9]])

>>> A.sum(axis=0)
array([33, 30, 24, NA, 36])

通常这并不是您想要的 - 即,您仍然希望将该列的总和视为0处理NA:

要获得该行为,请对skipma参数传入True(在NumPy 1.7中,大多数NumPy数组构造函数都具有此参数):

>>> A.sum(axis=0, skipna=True)
array([33, 30, 24, 33, 36])

总之,要加快代码的速度,消除循环并使用新的掩码:
>>> A[(A<=3)&(A<=6)] = NA

>>> A
array([[8, 8, 4, NA, NA],
       [7, 9, NA, NA, 8],
       [NA, 6, 9, 5, NA],
       [9, 4, 6, 6, 5],
       [NA, 6, 8, NA, NA],
       [8, 5, 7, 7, NA],
       [NA, 4, 5, 9, 9],
       [NA, 8, NA, 5, 9]])

NA占位符在这个上下文中的行为类似于0,我认为这正是你想要的:
>>> A.sum(axis=0, skipna=True)
array([32, 50, 39, 32, 31])

非常有趣。我不知道这个功能(+1)...尽管我仍然坚持认为我们需要更多了解while循环,才能找出最佳解决方案。 - mgilson
@mgilson 我完全同意;我对那段代码应该做什么做了一些假设,这样我才能回答它。这些假设可能完全错误,但至少原则(不要循环,不要删除)仍然可能有用/有效。 - doug
太棒了。我想到使用掩码数组这样的东西,但很难按照这里描述的方式使其正常工作。在while循环中Y和x是变化的,我需要在下一次搜索条件之前“删除”这些元素。数组是一系列值的时间序列,我需要通过取最强烈的情况来找到事件的发生频率。当我找到一个事件时,我会删除这个值以及移动到下一个之前的所有值。这就是为什么我认为我需要一个while循环,但我正在学习这里。我将在今天尝试一下并看看它是否有效。 - Shejo284
很抱歉,我的numpy版本是1.6.1。由于我在专业的Linux集群上使用它,无法自行更新numpy。Python 2.7.2能否与numpy 1.7一起使用?我会尝试找到解决方法,但同时我会尝试第一个建议,将搜索条件中的所有命中设置为False。 - Shejo284
@Shejo284 非常令人沮丧。您可以使用 NumPy 1.7 和 Python 2.7.2 确实是“兼容的”(我使用的是1.7 dev和2.7.2)。当然,您始终可以使用常规的遮罩数组类(NumPy 1.6)。我没有提到它,因为我认为1.7中的新类更好。 - doug
4
据说 np.NA 内容实际上并没有在1.7版本中得以实现。 - Reid

3

如果我说错了,请纠正我,但我认为你可以这样做:

mask=np.where((array >= x) & (array <= y),True,False)
array=array[mask]

放弃整个循环吗?

此外,在我的解释器中,array >= x & array <= y会产生异常。您可能的意思是:(array >= x) & (array <= y)


是的,在 while 循环中,x 和 y 会随着我搜索数组而改变。我需要在下一次搜索条件之前“删除”这些元素,以便不重复搜索相同的区域。数组是一个时间序列的值,我需要通过取最强烈的情况来找到事件发生的频率。因此,当我找到一个由 x 和 y 确定的事件时,我会删除这个值和在移动到下一个值之前的一个小窗口内的所有值。这就是为什么我认为我需要一个 while 循环,但我正在学习。是的,(array >= x) & (array <= y) 是正确的。 - Shejo284

1
根据numpy.delete的文档,该函数返回一个将指定元素删除的输入数组的副本。因此,复制的数组越大,函数的速度就会越慢。

http://docs.scipy.org/doc/numpy/reference/generated/numpy.delete.html

为什么您需要频繁删除数组的块?如果您的数组非常动态,最好使用list来存储数组的部分,并仅对较小的部分进行删除。


当我搜索数组以满足这些条件时,我需要在下一次搜索之前“删除”这些元素,以便不重复搜索相同的区域。该数组是一个时间序列值,我需要通过获取最强烈的情况来查找事件发生的频率。因此,当我找到一个由x和y确定的事件时,我会删除这个值以及在移动到下一个值之前的小窗口中的所有值。这就是为什么我认为我需要一个while循环,但我正在学习。我之前尝试过使用列表,但性能同样差或者我不知道如何高效地完成这项任务。 - Shejo284
我根据上面的建议尝试了以下方法,因为我还没有numpy 1.7: mask=np.where((array >= x) & (array <= y),False,True)# reversed array=array[mask] 这个方法快得多。实际上,这是一个简单而优雅的解决方案。我想我过度思考了问题。这个解决方案将一个需要24小时的操作缩短到了大约2小时。谢谢大家,我学到了很多! - Shejo284
太好了,听到这个消息我很高兴,你找到了一个简单的解决方案。 - Brendan Wood

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