Numpy: Replacing values in a 2D array efficiently using a dictionary as a map
我有一个类似这样的二维整数数组:
1 2 3 | a = np.array([[ 3, 0, 2, -1], [ 1, 255, 1, 2], [ 0, 3, 2, 2]]) |
我有一个字典,里面有整数键和值,我想用新值替换
1 | d = {0: 1, 1: 2, 2: 3, 3: 4, -1: 0, 255: 0} |
我想用
1 2 3 | a_new = np.array([[ 4, 1, 3, 0], [ 2, 0, 2, 3], [ 1, 4, 3, 3]]) |
实现这一点的有效方法是什么?
这是一个玩具例子,但实际上数组将是大的,它的形状将如
我需要在数十万个这样的数组上执行这个替换,所以它需要很快。然而,字典是预先知道的,并且保持不变,因此渐进地,任何时候用来修改字典或将其转换为更合适的数据结构都不重要。
我目前正在两个嵌套的
如果映射不包含负值(如示例中的-1),那么我只需要从字典中创建一个列表或数组,其中键是数组索引,然后将其用于一个有效的nummy-fashing索引例程。但既然也有负值,这就行不通了。
这里有一种方法,如果您有一个小的dictionary/min和max值,这可能更有效,您可以通过添加数组min来解决负索引:
1 2 3 4 5 6 7 | 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循环移动到查找表中,但如果它明显小于实际数组,则速度可能会更快。
复制数组,然后迭代字典项,然后使用布尔索引将新值分配给副本。
1 2 3 4 | import numpy as np b = np.copy(a) for old, new in d.items(): b[a == old] = new |
本文解决了数组和字典键之间的一对一映射问题。这一想法与
要获取索引器,这是由于字典保持不变而一次性使用的,请使用此-
1 2 3 4 5 6 7 8 9 10 11 | 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) |
要获得最终替换,只需索引即可。因此,对于输入数组
1 | out = val_arr[a] |
样品运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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]]) |
平铺样本数据的运行时测试-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
numpy可以创建用于对数组执行映射操作的向量化函数。我不确定这里的哪个方法会有最好的性能,所以我已经用timeit对我的方法进行了计时。如果你想知道什么性能最好,我建议尝试其他一些方法。
1 2 3 4 5 6 7 8 9 | # 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)) |
您可以通过执行以下操作来计时:
1 2 3 | from timeit import Timer t = Timer('vfunc(a, d)', 'from __main__ import a, d, vfunc') print(t.timeit(number=1000)) |
这种方法的结果大约是0.014秒。
编辑:为了好玩,我在