识别由大数组中的最大距离分隔的Python数组单元对?

时间:2015-08-20 02:00:42

标签: python arrays numpy scipy distance

我有包含空间生态栖息地数据的栅格,我已将其转换为二维numpy数组。在此数组中,值1 =数据,0 =无数据。 根据这些数据,我想生成一个包含所有数据单元对的数组,其中每个单元格之间的距离小于最大欧几里得截止距离(即相隔2个单元格)。

我发现this answer很有用,但那里的答案似乎首先测量所有成对距离,然后通过最大截止值对结果进行阈值处理。我的数据集很大(13500 * 12000阵列中有超过100万个数据单元),因此尝试计算所有对单元格之间距离的任何成对距离度量将失败:我需要一个以某种方式停止的解决方案在某个搜索半径(或类似的东西)之外寻找可能的邻居。

我已经尝试过scipy.spatial.distance.pdist,但到目前为止还没有运气将其应用于我的二维数据,或者找到一种方法来阻止pdist计算距离甚至在遥远的细胞对之间。我附加了一个示例数组和一个所需的输出数组,用于最大欧几里得截止距离= 2个单元格:

Example array and desired output

import numpy as np
import matplotlib.pyplot as plt

# Example 2-D habitat array (1 = data)
example_array = np.array([[0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
                          [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
                          [0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1],
                          [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
                          [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
                          [1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
                          [1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1],
                          [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
                          [1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0],
                          [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                          [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                          [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

# Plot example array
plt.imshow(example_array, cmap="spectral", interpolation='nearest')

1 个答案:

答案 0 :(得分:2)

我必须承认我的傻瓜很弱 - 也许有办法直接做到。尽管如此,纯Python中的问题并不困难。以下代码将输出匹配数据的x / y坐标对。有很多潜在的优化可能会掩盖代码并使其更快,但考虑到数据集的大小和示例半径的大小(2.0),我怀疑这些是否值得(可能的例外)在数组而不是子列表中创建numpy视图。

已更新 - 代码修复了几个错误 - (1)它在起点以下的线条上看起来太远了,(2)它是在左边缘附近没做正确的事。现在,函数的调用使用半径2.5来显示如何拾取其他对。

example_array = [[0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
                [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
                [0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
                [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
                [1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
                [1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1],
                [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
                [1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0],
                [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

def findpairs(mylist, radius = 2.0):
    """
    Find pairs with data within a given radius.
    If we work from the top of the array down, we never
    need to look up (because we already would have found
    those, and we never need to look left on the same line.
    """

    # Create the parameters of a half circle, which is
    # the relative beginning and ending X coordinates to
    # search for each Y line starting at this one and
    # working down.  To avoid duplicates and extra work,
    # not only do we not look up, we never look left on
    # the same line as what we are matching, but we do
    # on subsequent lines.

    semicircle = []
    x = 1
    while x:
        y = len(semicircle)
        x = int(max(0, (radius ** 2 - y ** 2)) ** 0.5)
        # Don't look back on same line...
        semicircle.append((-x if y else 1, x + 1))

    # The maximum number of y lines we will search
    # at a time.
    max_y = len(semicircle)

    for y_start in range(len(mylist)):
        sublists = enumerate(mylist[y_start:y_start + max_y], y_start)
        sublists = zip(semicircle, sublists)
        check = (x for (x, value) in enumerate(mylist[y_start]) if value)
        for x_start in check:
            for (x_lo, x_hi), (y, ylist) in sublists:
                # Deal with left edge problem
                x_lo = max(0, x_lo + x_start)
                xlist = ylist[x_lo: x_start + x_hi]
                for x, value in enumerate(xlist, x_lo):
                    if value:
                        yield (x_start, y_start), (x, y)

print(list(findpairs(example_array, 2.5)))

执行时间将高度依赖数据。对于grins,我创建了您指定大小的数组(13500 x 12000)来测试时序。我使用了更大的半径(3.0而不是2.0)并尝试了两种情况:没有匹配,每次匹配。为了避免反复重新分配列表,我只需运行迭代器并抛出结果。这样做的代码如下。对于最佳情况(空)阵列,它在7秒内在我的机器上运行;最坏情况(全1s)阵列的时间大约是12分钟。

def dummy(val):
    onelist = 13500 * [val]
    listolists = 12000 * [onelist]

    for i in findpairs(listolists, 3.0):
      pass

dummy(0)
dummy(1)