循环遍历2D数组的有效方法

时间:2019-06-12 13:34:50

标签: python python-3.x

我有一个2D数组2000x4000,对于该数组中的每个单元,我必须比较该单元的值与由10个相邻单元(在+/- X和+/-中,是)。

例如,这就是我现在正在做的事情:

import numpy as np
from astropy.stats import sigma_clipped_stats

BPmap=[]
N=10
a=np.random.random((2000,4000))
for row in range(N,a.shape[0]-N):
    BPmap_row=[]
    for column in range(N,a.shape[1]-N):
        Bpmap_data=np.array(a[row-N:row+N,column-N:column+N].ravel()) 
        mean, median, std = sigma_clipped_stats(Bpmap_data, sigma=3,iters=5)
        BPmap_Nsigma=float(a[row][column]-median)/std                 
        BPmap_row.append(BPmap_Nsigma)
    BPmap.append(BPmap_row)

这有一个明显的问题,我正在制作2000x4000 = 8000000循环,这花费了很长时间。我需要找到一种非常有效的方法来执行这些操作,但我不知道如何。

2 个答案:

答案 0 :(得分:8)

我们通常避免对numpy使用double-for循环;它很慢,而且有了智能索引(array [:, i-N] ...),我们可以在一个循环中做很多事情。
但是对于您的卷积问题,这可能是最简单的~~(并且只有?)~~方式来完成您想要的事情。 (编辑:不是。请参见下面@Masoud的回答)。

Bpmap_data=np.array(a[row-N:row+N,column-N:column+N].ravel())在每个循环中创建一个新数组。
在不创建新数组(即直接使用numpy视图)的情况下计算中值和标准差会更快。

实际上,速度要快40倍(在我的Google colab实例上)

要获得快400倍的算法,请查看将scipy过滤器用于2D阵列的@Masoud answer

import numpy as np
from astropy.stats import sigma_clipped_stats


N=10
a=np.random.random((80,40))


def f():
  """Your original code"""
  BPmap=[]
  for row in range(N,a.shape[0]-N):
      BPmap_row=[]
      for column in range(N,a.shape[1]-N):
          Bpmap_data=np.array(a[row-N:row+N,column-N:column+N].ravel()) 
          mean, median, std = sigma_clipped_stats(Bpmap_data, sigma=3,iters=5)
          BPmap_Nsigma=float(a[row][column]-median)/std                 
          BPmap_row.append(BPmap_Nsigma)
      BPmap.append(BPmap_row)
  return BPmap

def f2():
  """this little guy is improving a lot your work"""
  BPmap=[]
  for row in range(N,a.shape[0]-N):
      BPmap_row=[]
      for column in range(N,a.shape[1]-N):
          # the next 3 lines do not need any more memory
          view = a[row-N:row+N,column-N:column+N]
          std_without_outliers = view[view - view.mean() < 3*view.std()].std()
          median = np.median(view)
          # back to your workflow
          BPmap_Nsigma=float(a[row][column]-median)/std_without_outliers                 
          BPmap_row.append(BPmap_Nsigma)
      BPmap.append(BPmap_row)
  return BPmap

%time _ = f()
%time _ = f2()

f() == f2()
>>>CPU times: user 39.7 s, sys: 14.2 ms, total: 39.7 s
Wall time: 39.7 s
CPU times: user 969 ms, sys: 2.99 ms, total: 972 ms
Wall time: 976 ms
True

修改
实际上,sigma_clipped_stats(a[row-N:row+N,column-N:column+N])确实会使循环变慢。我怀疑sigma_clipped_stats正在创建其参数的副本。

  

在消除3 sigma剪切中的异常值后立即执行std

我在这里展示了一种使用纯numpy做到这一点的方法;这确实比您以前使用的功能要快。

最后,f()= f2()那么为什么还要使用此astropy函数呢?

答案 1 :(得分:5)

代码中存在一些降低性能的问题:

  1. here所述,避免使用for循环。
  2. 您实际上将每个数字平方10 * 10次。

您可以使用Scipy.ndimageopencv图书馆员代替for循环来进行卷积。尽管这些库用于图像处理,但它们对于处理任何2D数组非常有效。这是一段代码,其执行的任务与使用Scipy.ndimage工具所需的任务相同,但是速度提高了1000倍(对于200X400阵列,则为23ms vs 27s)。我使用here提供的算法来计算标准差:

import numpy as np
from scipy.ndimage.filters import uniform_filter, median_filter

a=np.random.random((200,400))
c1 = uniform_filter(a, size = (10,10))
c2 = uniform_filter(a*a, size = (10,10))
std = ((c2 - c1*c1)**.5)
med = median_filter(a, size=(10, 10))

BPmap = (a - med)/std