在numpy中比较值与邻居元素

时间:2016-07-05 23:32:56

标签: python numpy scipy

我们说我有一个numpy数组

    a b c
A = i j k
    u v w

我想比较值中心元素与其八个相邻元素中的一些(沿轴或沿对角线)。有没有更快的方法,除了嵌套的for循环(它对于大矩阵来说太慢了)?

更具体地说,我想要做的是将元素的值与它的邻居进行比较并分配新的值。

例如:

    if (j == 1):
      if (j>i) & (j>k):
        j = 999
      else:
        j = 0
    if (j == 2):
      if (j>c) & (j>u):
        j = 999
      else:
        j = 0
   ...
像这样的东西。

2 个答案:

答案 0 :(得分:1)

您的操作包含许多条件,因此在一般情况下(任何类型的条件,任何类型的操作)执行此操作的最有效方法是使用循环。这可以使用numba或cython有效地完成。在特殊情况下,您可以使用numpy / scipy中的更高级别函数来实现它。我将为您提供的具体示例展示一个解决方案,希望您可以从那里进行概括。

从一些假数据开始:

A = np.asarray([
    [1, 1, 1, 2, 0],
    [1, 0, 2, 2, 2],
    [0, 2, 0, 1, 0],
    [1, 2, 2, 1, 0],
    [2, 1, 1, 1, 2]
])

我们会在A中找到适用各种条件的地点。

  • 1a)值为1
  • 1b)该值大于其水平邻居
  • 2a)值为2
  • 2b)该值大于其对角线邻居

A中查找指定值出现的位置:

cond1a = A == 1
cond2a = A == 2

这给出了布尔值的矩阵,其大小与A相同。条件成立时该值为true,否则为false。

A中查找每个元素与其邻居具有指定关系的位置:

# condition 1b: value greater than horizontal neighbors
f1 = np.asarray([[1, 0, 1]])
cond1b = A > scipy.ndimage.maximum_filter(
    A, footprint=f1, mode='constant', cval=-np.inf)

# condition 2b: value greater than diagonal neighbors
f2 = np.asarray([
    [0, 0, 1],
    [0, 0, 0],
    [1, 0, 0]
])
cond2b = A > scipy.ndimage.maximum_filter(
    A, footprint=f2, mode='constant', cval=-np.inf)

和以前一样,这给出了表示条件为真的布尔值的矩阵。此代码使用scipy.ndimage.maximum_filter()。该功能迭代地移动一个“足迹”。以A的每个元素为中心。该位置的返回值是足迹为1的所有元素的最大值。mode参数指定如何处理矩阵边界外的隐式值,其中足迹落在边缘之外。在这里,我们将它们视为负无穷大,这与忽略它们相同(因为我们使用了最大操作)。

根据条件设置结果的值。如果条件1a和1b都为真,或者条件2a和2b都为真,则值为999。否则,该值为0.

result = np.zeros(A.shape)
result[(cond1a & cond1b) | (cond2a & cond2b)] = 999

结果是:

[
    [  0,   0,   0,   0,   0],
    [999,   0,   0, 999, 999],
    [  0,   0,   0, 999,   0],
    [  0,   0, 999,   0,   0],
    [  0,   0,   0,   0, 999]
]

您可以通过更改过滤器占用空间来将此方法推广到其他邻居模式。您可以使用其他类型的过滤器推广到其他操作(最小值,中位数,百分位数等)(请参阅scipy.ndimage)。对于可以表示为加权和的操作,请使用2d cross correlation

这种方法应该比在python中循环快得多。但是,它确实执行了不必要的计算(例如,当值为1或2时,它只需要计算最大值,但我们会为所有元素执行此操作)。手动循环可以让您避免这些计算。在python中循环可能比这里的代码慢得多。但是,在numba或cython中实现它可能会更快,因为这些工具会生成编译代码。

答案 1 :(得分:0)

我使用了numpy的:

  • concatenate用零填充
  • dstackroll正确对齐

沿不同尺寸应用custom_roll两次并减去原件。

import numpy as np

def custom_roll(a, axis=0):
    n = 3
    a = a.T if axis==1 else a

    pad = np.zeros((n-1, a.shape[1]))
    a = np.concatenate([a, pad], axis=0)
    ad = np.dstack([np.roll(a, i, axis=0) for i in range(n)])
    a = ad.sum(2)[1:-1, :]

    a = a.T if axis==1 else a
    return a

考虑以下ndarray

A = np.arange(25).reshape(5, 5)
A

array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])


sum_of_eight_around_me = custom_roll(custom_roll(A), axis=1) - A

sum_of_eight_around_me

array([[  12.,   20.,   25.,   30.,   20.],
       [  28.,   48.,   56.,   64.,   42.],
       [  53.,   88.,   96.,  104.,   67.],
       [  78.,  128.,  136.,  144.,   92.],
       [  52.,   90.,   95.,  100.,   60.]])