【问题标题】:Python Numpy get difference between 2 two-dimensional arrayPython Numpy获取2个二维数组之间的差异
【发布时间】:2021-03-17 13:53:37
【问题描述】:

好吧,我有一个简单的问题让我很头疼,基本上我有两个二维数组,全是 [x,y] 坐标,我想比较第一个和第二个并生成第三个数组它包含第一个数组中没有出现在第二个数组中的所有元素。这很简单,但我根本无法让它工作。大小变化很大,第一个数组可以有 1000 到 200 万个坐标,而第一个数组有 1 到 1000 个坐标。

这个操作会发生很多次,第一个数组越大,发生的次数越多

样本:

arr1 = np.array([[0, 3], [0, 4], [1, 3], [1, 7], ])

arr2 = np.array([[0, 3], [1, 7]])

result = np.array([[0, 4], [1, 3]])

深入:基本上我有一个分辨率可变的二进制图像,它由 0 和 1 (255) 组成,我单独分析每个像素(使用已经优化的算法),但是(故意)每次函数被执行,它只分析一小部分像素,当它完成时,它会返回这些像素的所有坐标。问题是它在执行时会运行以下代码:

ones = np.argwhere(img == 255) # ones = pixels array

大约需要 0.02 秒,是迄今为止代码中最慢的部分。我的想法是创建一次这个变量,每次函数结束时,它都会删除解析的像素并将新数组作为参数传递以继续,直到数组为空

【问题讨论】:

  • 你能提供一个你想要达到的目标的最小例子吗? (即样本输入和预期输出?)
  • 我很困惑,ones = np.argwhere(img == 255) 没有按照您的要求进行操作。那段代码中的arr1arr2 是什么?也就是说,我不认为你可以优化np.argwhere 函数。
  • @QuangHoang 所以,正如我在那里解释的那样,这是旧代码,我现在正在使用的那个,我想要提高性能的方法是只调用一次 argwhere 并将其转换为 arr1 ,并且在每个函数结束时它会返回我 arr2,这样我打算让新的“那些”成为 arr1 和 arr2 之间的区别,所以我不必多次调用 argwhere(这是最慢的部分代码)
  • 我认为这可能是一个 XY 问题。也许您应该更好地描述您的原始问题,并展示一个更完整的示例,并使用一些实际数据,特别是如果您追求某个性能目标。

标签: python numpy multidimensional-array set-difference


【解决方案1】:

不确定您打算如何处理额外的维度,因为设置的差异与任何过滤一样,本质上会丢失形状信息。

无论如何,NumPy 确实提供了np.setdiff1d() 来优雅地解决这个问题。


编辑通过提供的说明,您似乎正在寻找一种方法来计算给定轴上的集合差异,即集合的元素实际上是数组。

在 NumPy 中有专门针对此的 no built-in,但制作一个并不太难。 为简单起见,我们假设操作轴是第一个(因此集合的元素是arr[i]),只有唯一元素出现在第一个数组中,并且数组是二维的。

它们都基于渐近最佳方法是构建第二个数组的set(),然后使用它从第一个数组中过滤掉条目的想法。

在 Python / NumPy 中构建此类集合的惯用方法是使用:

set(map(tuple, arr))

tuple 的映射冻结arr[i],允许它们是可散列的,因此可以与set() 一起使用。

不幸的是,由于过滤会产生不可预测的大小的结果,NumPy 数组不是结果的理想容器。

要解决这个问题,可以使用:

  1. 中间体list
import numpy as np


def setdiff2d_list(arr1, arr2):
    delta = set(map(tuple, arr2))
    return np.array([x for x in arr1 if tuple(x) not in delta])
  1. np.fromiter() 后跟 np.reshape()
import numpy as np


def setdiff2d_iter(arr1, arr2):
    delta = set(map(tuple, arr2))
    return np.fromiter((x for xs in arr1 if tuple(xs) not in delta for x in xs), dtype=arr1.dtype).reshape(-1, arr1.shape[-1])
  1. NumPy's advanced indexing
def setdiff2d_idx(arr1, arr2):
    delta = set(map(tuple, arr2))
    idx = [tuple(x) not in delta for x in arr1]
    return arr1[idx]
  1. 将两个输入都转换为set()(将强制输出元素的唯一性并会丢失排序):
import numpy as np


def setdiff2d_set(arr1, arr2):
    set1 = set(map(tuple, arr1))
    set2 = set(map(tuple, arr2))
    return np.array(list(set1 - set2))

或者,可以使用broadcastingnp.any()np.all() 构建高级索引:

def setdiff2d_bc(arr1, arr2):
    idx = (arr1[:, None] != arr2).any(-1).all(1)
    return arr1[idx]

上述方法的某些形式最初是在@QuangHoang's answer 中提出的。

类似的方法也可以在 Numba 中实现,遵循与上述相同的想法,但使用哈希而不是实际的数组视图 arr[i](因为 Numba 对 set() 中支持的内容有限制)和预先计算输出大小(为了速度):

import numpy as np
import numba as nb


@nb.njit
def mul_xor_hash(arr, init=65537, k=37):
    result = init
    for x in arr.view(np.uint64):
        result = (result * k) ^ x
    return result


@nb.njit
def setdiff2d_nb(arr1, arr2):
    # : build `delta` set using hashes
    delta = {mul_xor_hash(arr2[0])}
    for i in range(1, arr2.shape[0]):
        delta.add(mul_xor_hash(arr2[i]))
    # : compute the size of the result
    n = 0
    for i in range(arr1.shape[0]):
        if mul_xor_hash(arr1[i]) not in delta:
            n += 1
    # : build the result
    result = np.empty((n, arr1.shape[-1]), dtype=arr1.dtype)
    j = 0
    for i in range(arr1.shape[0]):
        if mul_xor_hash(arr1[i]) not in delta:
            result[j] = arr1[i]
            j += 1
    return result

虽然它们都给出相同的结果:

funcs = setdiff2d_iter, setdiff2d_list, setdiff2d_idx, setdiff2d_set, setdiff2d_bc, setdiff2d_nb

arr1 = np.array([[0, 3], [0, 4], [1, 3], [1, 7]])
print(arr1)
# [[0 3]
#  [0 4]
#  [1 3]
#  [1 7]]

arr2 = np.array([[0, 3], [1, 7], [4, 0]])
print(arr2)
# [[0 3]
#  [1 7]
#  [4 0]]

result = funcs[0](arr1, arr2)
print(result)
# [[0 4]
#  [1 3]]

for func in funcs:
    print(f'{func.__name__:>24s}', np.all(result == func(arr1, arr2)))
#           setdiff2d_iter True
#           setdiff2d_list True
#            setdiff2d_idx True
#            setdiff2d_set False  # because of ordering
#             setdiff2d_bc True
#             setdiff2d_nb True

他们的表现似乎有所不同:

for func in funcs:
    print(f'{func.__name__:>24s}', end='  ')
    %timeit func(arr1, arr2)
#           setdiff2d_iter  16.3 µs ± 719 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#           setdiff2d_list  14.9 µs ± 528 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#            setdiff2d_idx  17.8 µs ± 1.75 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#            setdiff2d_set  17.5 µs ± 1.31 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#             setdiff2d_bc  9.45 µs ± 405 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#             setdiff2d_nb  1.58 µs ± 51.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

所提议的基于 Numba 的方法似乎在相当大的程度上优于其他方法(使用给定输入时大约 10 倍)。

较大的输入观察到类似的时序:

np.random.seed(42)

arr1 = np.random.randint(0, 100, (1000, 2))
arr2 = np.random.randint(0, 100, (1000, 2))
print(setdiff2d_nb(arr1, arr2).shape)
# (736, 2)


for func in funcs:
    print(f'{func.__name__:>24s}', end='  ')
    %timeit func(arr1, arr2)
#           setdiff2d_iter  3.51 ms ± 75.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
#           setdiff2d_list  2.92 ms ± 32.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
#            setdiff2d_idx  2.61 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
#            setdiff2d_set  3.52 ms ± 67.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
#             setdiff2d_bc  25.6 ms ± 198 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
#             setdiff2d_nb  192 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

(附带说明,setdiff2d_bc() 受第二个输入大小的负面影响最大)。

【讨论】:

  • @PedroBzz 不确定它是否满足您的性能要求,但请看一下基于 Numba 的快速实现,以及它与其他几种方法的比较。
  • 请注意,还应实现散列的冲突处理。
【解决方案2】:

取决于您的阵列有多大。如果它们不是太大(几千),你可以

  1. 使用广播将x中的每个点与y中的每个点进行比较
  2. 使用any 检查最后一维的不等式
  3. 使用all 来检查是否匹配

代码:

idx = (arr1[:,None]!=arr2).any(-1).all(1)

arr1[idx]

输出:

array([[0, 4],
       [1, 3]])

更新:对于更长的数据,您可以尝试设置和for 循环:

set_y = set(map(tuple, y))
idx = [tuple(point) not in set_y for point in x]

x[idx]

【讨论】:

  • 大小变化很大,从一千到两百万,我会在原帖中补充一下
  • @PedroBzz 如果有帮助,请查看更新的答案。
  • 谢谢!这个有效,但它在 ~0.2 秒内执行,我正在寻找执行低于 0.02 的东西,这就是我当前的代码所做的。是对一张图片的分析,我会在原帖中详细介绍一下
  • @PedroBzz 这真是令人印象深刻的表现。也许您可以/应该分享您的代码(在发布问题时建议这样做)。另外,如果你不关心arr1中点的排序,你也可以把它变成set,这样会快很多。
  • @QuangHoang 它seems 将两个输入转换为set() 毕竟不是那么快,因为它需要进行所有转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-15
  • 1970-01-01
  • 2021-12-08
  • 1970-01-01
相关资源
最近更新 更多