用另一个数组中的条目替换数组的条目

3
考虑一个包含整数的numpy 2D数组,其中某些条目为0(array1)。再考虑另一个2D数组(array2),其中第一列具有与array1相同的非零值,而另一列,比如索引2,具有不同的数值(浮点数)。
你如何通过将array1中的每个非零条目替换为array2的第2列对应值来创建一个新的array3?如何使这样做更简洁明了?
示例:
>>> array1
array([[0, 27, 43, 10],
       [0, 80, 15,  2],
       [0,  3,  6,  9]])

>>> array2
array([[ 10.,  4., 88.],
       [  2.,  2., 95.],
       [  9.,  2., 65.],
       [ 43.,  1., 62.],
       [ 15.,  5., 64.],
       [  6.,  6., 67.],
       [ 27.,  5., 62.],
       [ 80.,  8., 73.],
       [  3.,  9., 59.]])

>>> array3
array([[0., 62., 62., 88.],
       [0., 73., 64., 95.],
       [0., 59., 67., 65.]])
2个回答

1
你可以将布尔索引与高级numpy数组索引结合使用:
array3 = array1.astype(float) # this copies the array by default.
array3[array1 != 0] = array2[array1[array1 != 0]-1, 2]

结果是:
array([[ 0, 62., 62., 88.],
       [ 0, 73., 64., 95.],
       [ 0, 59., 67., 65.]])

解释

首先创建一个布尔数组,指示哪些位置有非零条目:

>>> non_zero_mask = array1 != 0
array([[False,  True,  True,  True],
       [False,  True,  True,  True],
       [False,  True,  True,  True]], dtype=bool)

这将用于查找应替换的元素。 然后您需要找到这些元素的值:
>>> non_zero_values = array1[non_zero_mask]
array([7, 4, 1, 8, 5, 2, 9, 6, 3])

由于你的array2是有序的,并且以值1开头,因此我们需要减去1才能找到适当的替换值行。如果你的array2没有排序,你可能需要对其进行排序或在其中进行另一个索引:

>>> replacement_rows = array2[non_zero_values-1]
array([[  7.,   7.,  62.],
       [  4.,   4.,  62.],
       [  1.,   1.,  88.],
       [  8.,   8.,  73.],
       [  5.,   5.,  64.],
       [  2.,   2.,  95.],
       [  9.,   9.,  59.],
       [  6.,   6.,  67.],
       [  3.,   3.,  65.]])

>>> replacement_values = array2[non_zero_values-1, 2] # third element of that row!
array([ 62.,  62.,  88.,  73.,  64.,  95.,  59.,  67.,  65.])

然后只需将这些值分配给原始或新数组:
array3[non_zero_mask] = replacement_values

这种方法依赖于array2的排序顺序,因此如果存在更复杂的条件,则会出现错误。但是,要解决这个问题,需要找到值和索引之间的关系,并将其插入到简单的-1中,或者进行另一个中间步骤的np.where/布尔索引。

扩展

如果您没有排序过的array2,并且无法对其进行排序,则可以执行以下操作:

>>> array3 = array1.astype(float)
>>> array3[array1 != 0] = array2[np.where(array2[:, 0][None, :] == array1[array1 != 0][:, None])[1], 2]
>>> array3
array([[  0.,  62.,  62.,  88.],
       [  0.,  73.,  64.,  95.],
       [  0.,  59.,  67.,  65.]])

由于这个方法使用广播数组相互作用,你将创建一个大小为array1.size * array1.size的数组。因此,这可能不是非常节省内存,但仍然完全向量化。

Numba(如果你想要速度)

非常适合加速那些没有本地numpy或scipy版本会很慢的事情。如果你有anaconda或conda,它已经安装了,所以这可能是一个可行的选择:

import numba as nb
import numpy as np

@nb.njit
def nb_replace_values(array, old_new_array):
    res = np.zeros(array.shape, dtype=np.float64)

    rows = array.shape[0]
    columns = array.shape[1]
    rows_replace_array = old_new_array.shape[0]

    for row in range(rows):
        for column in range(columns):
            val = array[row, column]
            # only replace values that are not zero
            if val != 0:
                # Find the value to replace the element with
                for ind_replace in range(rows_replace_array):
                    if old_new_array[ind_replace, 0] == val:
                        # Match found. Replace and break the innermost loop
                        res[row, column] = old_new_array[ind_replace, 2]
                        break

    return res

nb_replace_values(array1, array2)
array([[  0.,  62.,  62.,  88.],
       [  0.,  73.,  64.,  95.],
       [  0.,  59.,  67.,  65.]])

特别对于大数组而言,这将是最快和最节省内存的解决方案,因为不会创建任何临时数组。第一次调用将更慢,因为函数需要即时编译。

时间记录:

%timeit nb_replace_values(array1, array2)

100000次循环,3次中的最佳结果:每个循环6.23微秒。
%%timeit
array3 = array1.astype(float)
array3[array1 != 0] = array2[np.where(array2[:, 0][None, :] == array1[array1 != 0][:, None])[1], 2]

10000次循环,3次中的最佳结果:每个循环花费74.8微秒。
# Solution provided by @PDRX
%%timeit 
array3 = array1.astype(float)
for i in array2[:,0]:
    i_arr1,j_arr1 = np.where(array1 == i)
    i_arr2 = np.where(array2[:,0] == i)
    array3[i_arr1,j_arr1] = array2[i_arr2,2]

1000次循环,3次中的最佳结果:每个循环689微秒。

好的,在我给出的例子中,array2确实是已排序的,但通常它应该是任何数组,不一定是已排序的,包含随机数字,可能无法按照统一序列排序,只要这些数字不重复 - 把那一列看作ID。请根据这个改进你的答案,好吗?我会更新我的例子。 - Bella
@Bella 这个比较难,我还没有仔细考虑过。但是请看答案的最后一部分。这种方法非常低效,我可能更建议使用 pandas 或自定义的 numba 函数来处理这些情况。 - MSeifert
哦,我之前没有看到你的最终编辑!当我看到另一个答案时,我立刻理解了,这太棒了。你的答案有点难以理解,但是你是对的,我测试了两种解决方案的速度,你的更快,尽管对于更大的数组来说只快了一个数量级。现在我有点犹豫要接受哪个答案,因为我可能会保留两个。 :/ 不过我没有明白你提到的数组大小是什么意思 - 从我看到的,array3 和 array1 的大小相同,在两种解决方案中 getsizeof 给出的大小也相同。 - Bella
@Bella 选择最适合你的答案,如果你接受其他答案,我不会生气。但是,由于我是一个“numba”迷,所以我也包括了这样一个函数。它比我的原始解决方案和其他解决方案快得多,但是numba并不轻量级,但至少很容易阅读。 :-) 它可能比任何一种方法都更好扩展。 - MSeifert
谢谢。我的电脑现在将启动。 - Bella

-1

我不确定我理解了你的要求,但让我们尝试使用列表推导式

array3 = [[array2[subitem1 - 1][2] if subitem1 != 0 else 0 for subitem1 in subarray1] for subarray1 in array1]

但是这段文字很难阅读,我更喜欢它表格化:

array3 = [
    [
        array2[subitem1 - 1][2] if subitem1 != 0 else 0
        for subitem1 in subarray1
    ]
    for subarray1 in array1
]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接