【问题标题】:Calculate distance between arrays that contain NaN计算包含 NaN 的数组之间的距离
【发布时间】:2020-05-08 10:34:14
【问题描述】:

考虑array1array2,与:

array1 = [a1 a2 NaN ... an]
array2 = [[NaN b2 b3 ... bn],
          [b21 NaN b23 ... b2n],
          ...]

两个数组都是 numpy 数组。有一种简单的方法可以计算array1array2的每一行之间的欧几里得距离:

EuclideanDistance = np.sqrt(((array1 - array2)**2).sum(axis=1))

让这个计算变得混乱的是 NaN 值。当然,我可以轻松地将 NaN 替换为某个数字。但相反,我想做以下事情:

当我将array1array2 中的row_x 进行比较时,我计算了其中一个数组具有NaN 而另一个不具有NaN 的列。假设count 为3。然后我将从两个数组中删除这些列并计算两者之间的欧几里得距离。最后,我将minus_value * count 添加到计算的距离。

现在,我想不出一种快速有效的方法来做到这一点。有人可以帮助我吗?

以下是我的一些想法:

minus = 1000
dist = np.zeros(shape=(array1.shape[0])) # this array will store the distance of array1 to each row of array2
array1 = np.repeat(array1, array2.shape[0], axis=0) # now array1 has the same dimensions as array2
for i in range(0, array1.shape[0]):
    boolarray = np.logical_or(np.isnan(array1[i]), np.isnan(array2[i]))
    count = boolarray.sum()
    deleteIdxs = boolarray.nonzero() # this should give the indices where boolarray is True
    dist[i] = np.sqrt(((np.delete(array1[i], deleteIdxs, axis=0) - np.delete(array2[i], deleteIdxs, axis=0))**2).sum(axis=0))
    dist[i] = dist[i] + count*minus

然而,这些线条对我来说看起来不仅丑陋。此外,我不断收到索引错误:显然 deleteIdxs 包含的索引超出了 array1 的范围。不知道这怎么可能。

【问题讨论】:

  • 我不太清楚 nan 值有什么问题。为什么不计算距离(也许使用 np.linalg.norm 而不是手动计算)然后简单地忽略 nans?

标签: python numpy


【解决方案1】:

您可以使用以下方法找到值为 nan 的所有索引:

indices_1 = np.isnan(array1)
indices_2 = np.isnan(array2)

你可以结合到:

indices_total = indices_1 + indices_2

您可以使用以下方法保留所有非 nan 值:

array_1_not_nan = array1[~indices_total]
array_2_not_nan = array2[~indices_total]

【讨论】:

  • 嘿内森,谢谢你的回答! ...是否可以访问array1 的所有元素,其中boolarr == TRUEarray1[boolarr],其中boolarr = np.logical_and.reduce((np.isscalar(array1), np.isscalar(array2[i]), ~boolarray))
  • 我的意思是:我试过了,但显然array1[boolarr] 只返回一个空列表
  • @luk 原始答案只返回了正确的索引,我已经修复了它以便它返回数组的值
【解决方案2】:

我会编写一个函数来处理距离计算。我确信有一种更快、更有效的方法来编写它(列表推导、聚合等),但可读性很重要,对吧? :)

import numpy as np
def calculate_distance(fixed_arr, var_arr, penalty):
    s_sum = 0.0
    counter = 0
    for num_1, num_2 in zip(fixed_arr, var_arr):
        if np.isnan(num_1) or np.isnan(num_2):
            counter += 1
        else:
            s_sum += (num_1 - num_2) ** 2
    return np.sqrt(s_sum) + penalty * counter, counter


array1 = np.array([1, 2, 3, np.NaN, 5, 6])
array2 = np.array(
    [
        [3, 4, 9, 3, 4, 8],
        [3, 4, np.NaN, 3, 4, 8],
        [np.NaN, 9, np.NaN, 3, 4, 8],
        [np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN],
    ]
)
dist = np.zeros(len(array2))


minus = 10
for index, arr in enumerate(array2):
    dist[index], _ = calculate_distance(array1, arr, minus)

print(dist)

您必须非常仔细地考虑 minus 变量的值。添加随机值真的有用吗?

正如@Nathan 建议的那样,可以轻松实现更高效的资源。

fixed_arr = array1
penalty = minus
dist = [
    (
        lambda indices=(np.isnan(fixed_arr) + np.isnan(var_arr)): np.linalg.norm(
            fixed_arr[~indices] - var_arr[~indices]
        )
        + (indices == True).sum() * penalty
    )()
    for var_arr in array2
]
print(dist)

但是,如果我绝对需要(如果它是瓶颈),我只会尝试实现这样的东西。对于所有其他时间,我很乐意牺牲一些资源来获得一些可读性和可扩展性。

【讨论】:

  • 酷,非常感谢! ...我知道,我不确定添加这个负值是否会对我有任何好处。但我必须尝试一下
  • 如果要迭代数组,使用数组有什么意义?我建议使用@QuangHoang 的答案,这会快几个数量级
  • 你是绝对正确的。我编辑了我的答案以反映使用面具的可能性。
【解决方案3】:

您可以过滤掉包含nan 的列:

mask1 = np.isnan(arr1)
mask2 = np.isnan(arr2).any(0)

mask = ~(mask1 | mask2)

# the two filtered arrays
arr1[mask], arr2[mask]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-21
    • 1970-01-01
    • 2015-02-25
    • 1970-01-01
    • 1970-01-01
    • 2014-11-29
    • 2021-04-05
    • 1970-01-01
    相关资源
    最近更新 更多