Numpy:使用字典作为地图有效地替换2D数组中的值

时间:2017-10-21 22:48:36

标签: python arrays numpy dictionary

我有一个2D Numpy整数数组,如下所示:

a = np.array([[  3,   0,   2,  -1],
              [  1, 255,   1,   2],
              [  0,   3,   2,   2]])

我有一个带有整数键和值的字典,我想用它来用新值替换a的值。该字典可能如下所示:

d = {0: 1, 1: 2, 2: 3, 3: 4, -1: 0, 255: 0}

我想将a中与d中的键匹配的值替换为d中的相应值。换句话说,d定义a中旧(当前)和新(所需)值之间的映射。以上玩具示例的结果将是:

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

实现这一目标的有效方法是什么?

这是一个玩具示例,但实际上阵列会很大,其形状将是例如(1024, 2048),字典将有数十个元素(在我的情况下为34)的顺序,虽然键是整数,但它们不一定都是连续的,它们可以是负数(如上例所示)

我需要在数十万个这样的阵列上执行此替换,因此需要快速。然而,字典是事先知道的并且保持不变,所以渐渐地,任何用于修改字典或将其转换为更合适的数据结构的时间都无关紧要。

我目前正在循环遍历两个嵌套for循环中的数组条目(在a的行和列上),但必须有更好的方法。

如果地图没有包含负值(例如示例中的-1),我只会在字典中创建一个列表或数组,其中键是数组索引,然后将其用于高效的Numpy花式索引程序。但由于也存在负面价值,因此无法发挥作用。

5 个答案:

答案 0 :(得分:3)

制作数组的副本,然后遍历字典项,然后使用布尔索引将新值分配给副本。

import numpy as np
b = np.copy(a)
for old, new in d.items():
    b[a == old] = new

答案 1 :(得分:3)

这是单向的,只要你有一个小字典/分钟和最大值,这可能会更有效率,你可以通过添加数组min来处理负面索引:

In [11]: indexer = np.array([d.get(i, -1) for i in range(a.min(), a.max() + 1)])

In [12]: indexer[(a - a.min())]
Out[12]:
array([[4, 1, 3, 0],
       [2, 0, 2, 3],
       [1, 4, 3, 3]])

注意:这会将for循环移动到查找表,但如果这比实际数组小得多,那么速度可能要快得多。

答案 2 :(得分:2)

这篇文章解决了数组和字典键之间的一对一映射案例。这个想法类似于@Andy Hayden's smart solution中的提议,但我们将创建一个包含Python's negative indexing的更大的数组,从而为我们提供简单索引的效率,而不需要输入输入数组所需的任何偏移量,这应该是显而易见的改善了。

要获取索引器,这将是一次性使用,因为字典保持不变,请使用此 -

def getval_array(d):
    v = np.array(list(d.values()))
    k = np.array(list(d.keys()))
    maxv = k.max()
    minv = k.min()
    n = maxv - minv + 1
    val = np.empty(n,dtype=v.dtype)
    val[k] = v
    return val

val_arr = getval_array(d)

要获得最终替换,只需索引即可。因此,对于输入数组a,请执行 -

out = val_arr[a]

示例运行 -

In [8]: a = np.array([[  3,   0,   2,  -1],
   ...:               [  1, 255,   1, -16],
   ...:               [  0,   3,   2,   2]])
   ...: 
   ...: d = {0: 1, 1: 2, 2: 3, 3: 4, -1: 0, 255: 0, -16:5}
   ...: 

In [9]: val_arr = getval_array(d) # one-time operation

In [10]: val_arr[a]
Out[10]: 
array([[4, 1, 3, 0],
       [2, 0, 2, 5],
       [1, 4, 3, 3]])

平铺样本数据的运行时测试 -

In [141]: a = np.array([[  3,   0,   2,  -1],
     ...:               [  1, 255,   1, -16],
     ...:               [  0,   3,   2,   2]])
     ...: 
     ...: d = {0: 1, 1: 2, 2: 3, 3: 4, -1: 10, 255: 89, -16:5}
     ...: 

In [142]: a = np.random.choice(a.ravel(), 1024*2048).reshape(1024,2048)

# @Andy Hayden's soln
In [143]: indexer = np.array([d.get(i, -1) for i in range(a.min(), a.max() + 1)])

In [144]: %timeit indexer[(a - a.min())]
100 loops, best of 3: 8.34 ms per loop

# Proposed in this post
In [145]: val_arr = getval_array(d)

In [146]: %timeit val_arr[a]
100 loops, best of 3: 2.69 ms per loop

答案 3 :(得分:0)

Numpy可以创建vectorized functions来对阵列执行映射操作。我不确定哪种方法会有最好的表现,所以我用timeit计算了我的方法。如果你想弄清楚什么是最好的表现,我建议尝试一些其他提供的方法。

# Function to be vectorized
def map_func(val, dictionary):
    return dictionary[val] if val in dictionary else val 

# Vectorize map_func
vfunc  = np.vectorize(map_func)

# Run
print(vfunc(a, d))

你可以这样做:

from timeit import Timer
t = Timer('vfunc(a, d)', 'from __main__ import a, d, vfunc')
print(t.timeit(number=1000))

这种方法的结果大约是0.014秒。

编辑:对于踢,我尝试使用相同的字典在(1024, 2048)大小numpy数组中随机数从-10到10。单个阵列花了大约四分之一秒。除非你运行了很多这些数组,否则如果这是一个可接受的性能水平可能并不值得优化。

答案 4 :(得分:0)

另一种选择,尚未对其进行基准测试:

    def replace_values(src: np.ndarray, new_by_old: Dict[int,int]) -> np.ndarray:
        dst = np.empty_like(src)
        for x in np.unique(src):
            dst[src==x] = new_by_old[x]
        return dst

这类似于 https://stackoverflow.com/a/46868897/2135504,但由于

  • 使用 np.empty_like() 而不是 np.copy()
  • 使用 np.unique(src) 而不是 new_by_old.keys()