【问题标题】:Replacing entries of an array with those of different array用不同数组的条目替换数组的条目
【发布时间】:2016-05-02 11:58:16
【问题描述】:

考虑一个 numpy 二维整数数组,其中一些条目为 0(array1)。考虑一个不同的二维数组(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.]])

【问题讨论】:

    标签: python arrays numpy


    【解决方案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 开头,因此我们需要减一以找到替换值的适当行。如果您的 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 的顺序,所以如果有更复杂的条件,它就会中断。但这要么需要找到 value 和 index 之间的关系并将其插入,而不是我所做的简单 -1,要么进行另一个中间 np.where/boolean 索引。

    扩展

    如果您没有已排序的 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 µs

    %%timeit
    array3 = array1.astype(float)
    array3[array1 != 0] = array2[np.where(array2[:, 0][None, :] == array1[array1 != 0][:, None])[1], 2]
    

    10000 次循环,3 次中的最佳:每个循环 74.8 µs

    # 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 µs

    【讨论】:

    • 嗯,在我给出的示例中,array2 确实是排序的,但通常它应该是任何数组,不一定是排序的,随机数可能无法以统一的顺序排序,只要因为这些数字不会重复 - 将该列视为 ID。你能在此基础上改进你的答案吗?我将更新我的示例。
    • @Bella 这要困难得多,我还没有考虑太多。但请参阅答案的最后一部分。这是非常低效的,我可能更愿意建议在这些情况下使用pandas 或自定义的numba 函数。
    • 哦,我之前没有看过你的最终编辑!当我看到另一个答案时,我马上就明白了,这很棒。你的有点难,但你是对的,我测试了这两种解决方案的速度,你的更快,尽管对于更大的阵列来说它只快一个数量级。现在我有点犹豫要接受哪个答案,因为我可能会同时保留这两个答案。 :/ 我没有得到你提到的关于数组大小的内容 - 从我可以看到 array3 的大小与 array1 相同,并且在两种解决方案中 getsizeof 给我相同的大小。
    • @Bella 选择最适合您的答案,如果您接受另一个答案,我不会生气。但是:由于我是numba 的狂热爱好者,所以我也包含了这样的功能。它比我的原始解决方案和其他解决方案快得多,但 numba 不是轻量级的,但至少阅读起来非常简单。 :-) 而且它的扩展性可能比任何一个都好。
    • 谢谢。我的电脑现在可以启动了。
    【解决方案2】:

    我不确定我是否理解您的要求,但让我们试试list comprehensions

    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
    ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-13
      • 1970-01-01
      • 2015-03-10
      • 2018-12-05
      • 1970-01-01
      • 1970-01-01
      • 2019-04-19
      相关资源
      最近更新 更多