根据键将Numpy数组中的每个元素进行翻译。

119
我尝试根据给定的键来翻译numpy.array中的每个元素:
例如:
a = np.array([[1,2,3],
              [3,2,4]])

my_dict = {1:23, 2:34, 3:36, 4:45}

我希望得到:

array([[ 23.,  34.,  36.],
       [ 36.,  34.,  45.]])

我可以通过循环来看到如何做到这一点:

def loop_translate(a, my_dict):
    new_a = np.empty(a.shape)
    for i,row in enumerate(a):
        new_a[i,:] = map(my_dict.get, row)
    return new_a

有没有更高效和/或更纯的numpy方法?

编辑:

我测试了一下,DSM提出的np.vectorize方法对于更大的数组来说要快得多:

In [13]: def loop_translate(a, my_dict):
   ....:     new_a = np.empty(a.shape)
   ....:     for i,row in enumerate(a):
   ....:         new_a[i,:] = map(my_dict.get, row)
   ....:     return new_a
   ....: 

In [14]: def vec_translate(a, my_dict):    
   ....:     return np.vectorize(my_dict.__getitem__)(a)
   ....: 

In [15]: a = np.random.randint(1,5, (4,5))

In [16]: a
Out[16]: 
array([[2, 4, 3, 1, 1],
       [2, 4, 3, 2, 4],
       [4, 2, 1, 3, 1],
       [2, 4, 3, 4, 1]])

In [17]: %timeit loop_translate(a, my_dict)
10000 loops, best of 3: 77.9 us per loop

In [18]: %timeit vec_translate(a, my_dict)
10000 loops, best of 3: 70.5 us per loop

In [19]: a = np.random.randint(1, 5, (500,500))

In [20]: %timeit loop_translate(a, my_dict)
1 loops, best of 3: 298 ms per loop

In [21]: %timeit vec_translate(a, my_dict)
10 loops, best of 3: 37.6 ms per loop

In [22]:  %timeit loop_translate(a, my_dict)

3
好的,我会尽力完成翻译任务。以下是需要翻译的内容:Related question: https://dev59.com/tXA75IYBdhLWcg3wPmZ8 - John Vinyard
这个回答解决了你的问题吗?在numpy数组中快速替换值 - AMC
相关问题,我在SO上找到的最佳解决方案:https://dev59.com/qFMI5IYBdhLWcg3wi8Eb - toliveira
8个回答

156

我不确定是否更高效,但你可以在字典的.get方法上使用 np.vectorize:

>>> a = np.array([[1,2,3],
              [3,2,4]])
>>> my_dict = {1:23, 2:34, 3:36, 4:45}
>>> np.vectorize(my_dict.get)(a)
array([[23, 34, 36],
       [36, 34, 45]])

10
如果OP知道my_dict中包含每个键,就像a一样,那么使用my_dict.__getitem__会是更好的选择。+1 - jamylak
@Akavall:很奇怪。不过我现在手头上没有1.6.2版本来检查。 - DSM
当我使用 my_dict.get 时,我遇到了 ValueError 的问题,但是当我使用 my_dict.__getitem__ 时我没有这个问题。 我使用的是 numpy 1.6.2。 - Akavall
@Akavall 使用这个样本数据吗?如果不是,您的输入数据有什么区别吗? - jamylak
@jamylak,我正在使用相同的数据。 - Akavall
如果 a 包含 None 值会怎样? - rosefun

32

这是另一种方法,使用numpy.unique

>>> a = np.array([[1,2,3],[3,2,1]])
>>> a
array([[1, 2, 3],
       [3, 2, 1]])
>>> d = {1 : 11, 2 : 22, 3 : 33}
>>> u,inv = np.unique(a,return_inverse = True)
>>> np.array([d[x] for x in u])[inv].reshape(a.shape)
array([[11, 22, 33],
       [33, 22, 11]])

当数组中唯一元素的数量较少时,此方法比np.vectorize方法快得多。 说明: Python运行速度较慢,在此方法中使用Python循环来转换唯一元素,之后我们依靠极其优化的NumPy索引操作(由C完成)来进行映射。因此,如果唯一元素的数量与数组的总大小相当,则不会有加速效果。另一方面,如果只有几个唯一元素,则可以观察到高达x100的加速效果。


1
这与使用vectorize(dict.get)在速度上有什么比较? - william_grisaitis
1
附言 - 我发现这个是最快的(与向量字典获取和遍历键相比)!你的情况可能会有所不同... - william_grisaitis
1
我会进行一项小修改,用 d.get(x, default_value) 替换 d[x],其中 default_value 可以是任何你想要的值。对于我的用例,我只是替换了一些值,其他的我想保持原样,所以我使用了 d.get(x, x) - william_grisaitis
2
这真的是一个天才的解决方案。我用它来为灰度图像(这里是 a)着色,使用了将一维像素值映射到rgb颜色的字典查找(这里是 d)。我尝试过 numpy.vectorizepandas.DataFrame.apply(顺便说一句,后者比vectorize更快),但这个速度最快。谢谢! - mjkvaak

10

我认为最好迭代字典,并一次性设置所有行和列中的值:

>>> a = np.array([[1,2,3],[3,2,1]])
>>> a
array([[1, 2, 3],
       [3, 2, 1]])
>>> d = {1 : 11, 2 : 22, 3 : 33}
>>> for k,v in d.iteritems():
...     a[a == k] = v
... 
>>> a
array([[11, 22, 33],
       [33, 22, 11]])

编辑:

虽然这个方法可能没有 DSM的(非常好的)答案 使用numpy.vectorize让人感到那么神奇,但我的所有测试显示,这种方法(使用@jamylak的建议)实际上更快一些:

from __future__ import division
import numpy as np
a = np.random.randint(1, 5, (500,500))
d = {1 : 11, 2 : 22, 3 : 33, 4 : 44}

def unique_translate(a,d):
    u,inv = np.unique(a,return_inverse = True)
    return np.array([d[x] for x in u])[inv].reshape(a.shape)

def vec_translate(a, d):    
    return np.vectorize(d.__getitem__)(a)

def loop_translate(a,d):
    n = np.ndarray(a.shape)
    for k in d:
        n[a == k] = d[k]
    return n

def orig_translate(a, d):
    new_a = np.empty(a.shape)
    for i,row in enumerate(a):
        new_a[i,:] = map(d.get, row)
    return new_a


if __name__ == '__main__':
    import timeit
    n_exec = 100
    print 'orig'
    print timeit.timeit("orig_translate(a,d)", 
                        setup="from __main__ import np,a,d,orig_translate",
                        number = n_exec) / n_exec
    print 'unique'
    print timeit.timeit("unique_translate(a,d)", 
                        setup="from __main__ import np,a,d,unique_translate",
                        number = n_exec) / n_exec
    print 'vec'
    print timeit.timeit("vec_translate(a,d)",
                        setup="from __main__ import np,a,d,vec_translate",
                        number = n_exec) / n_exec
    print 'loop'
    print timeit.timeit("loop_translate(a,d)",
                        setup="from __main__ import np,a,d,loop_translate",
                        number = n_exec) / n_exec

输出:

orig
0.222067718506
unique
0.0472617006302
vec
0.0357889199257
loop
0.0285375618935

考虑到速度可能是一个问题,像 for k in d 这样迭代会使其尽可能快。 - jamylak
4
在我的情况下,我发现向量化更快。其中a的形状为(50, 50, 50)d有5000个键,数据类型为numpy.uint32。这两种方法的速度差距不小...向量化大约需要0.1秒,而另一种方法需要大约1.4秒。将数组展平并没有帮助。 :/ - william_grisaitis
4
这种方法的速度取决于映射中存在多少个唯一键。在您的情况下,键的数量要比二维数组的维度小得多,这就是为什么性能接近矢量化解决方案的原因。如果键的数量与数组的维度相当,则矢量化会快得多。 - Ataxias
一个常常被忽视的大问题(就像我一样):如果你的字典中有任何键和值匹配(例如 {1:2, 2:3}),那么值为1的元素将被替换为2,然后它们变成了3 - 因此1和2都会转换为3。在将其提供给 for 循环之前谨慎地重新排序迭代器可能有所帮助,但如果字典形成循环图,则无济于事。 - yumemio

8

numpy_indexed 包(免责声明:我是它的作者)提供了一种优雅且高效的向量化解决方案来解决这种类型的问题:

import numpy_indexed as npi
remapped_a = npi.remap(a, list(my_dict.keys()), list(my_dict.values()))

实现的方法类似于John Vinyard提到的方法,但更加通用。例如,数组的项不需要是整数,可以是任何类型,甚至是nd子数组本身。

如果将可选的'missing' kwarg设置为'raise'(默认为'ignore'),性能会稍微提高,并且如果不是所有元素都在键中出现,则会收到一个KeyError。


这给了我TypeError: invalid type promotion。也许需要先将a重塑为一维数组? - Thomas Ahle

4
假设您的字典键是正整数,且没有巨大的间隙(类似于从0到N的范围),最好将翻译字典转换为数组,使my_array[i] = my_dict[i],并使用numpy索引进行翻译。
使用此方法的代码如下:
def direct_translate(a, d):
    src, values = d.keys(), d.values()
    d_array = np.arange(a.max() + 1)
    d_array[src] = values
    return d_array[a]

使用随机数组进行测试:

N = 10000
shape = (5000, 5000)
a = np.random.randint(N, size=shape)
my_dict = dict(zip(np.arange(N), np.random.randint(N, size=N)))

对于这些尺寸,我使用此方法的时间约为140毫秒。np.get向量化大约需要5.8秒,而unique_translate大约需要8秒可能的推广:
  • 如果您有负值需要翻译,您可以通过将字典的键和a中的值偏移一个常数来将它们映射回正整数。
def direct_translate(a, d): # handles negative source keys
    min_a = a.min()
    src, values = np.array(d.keys()) - min_a, d.values()
    d_array = np.arange(a.max() - min_a + 1)
    d_array[src] = values
    return d_array[a - min_a]
  • 如果源键之间存在巨大的间隔,初始数组创建将会浪费内存。我会使用cython来加快该函数的速度。

2

如果你不是非得使用字典作为替代表,简单的解决方案是(以你的例子为例):

a = numpy.array([your array])
my_dict = numpy.array([0, 23, 34, 36, 45])     # your dictionary as array

def Sub (myarr, table) :
    return table[myarr] 

values = Sub(a, my_dict)

当且仅当d的索引覆盖了您的a的所有可能值时,这将起作用。换句话说,仅适用于具有无符号整数的a


当然!更简单、更容易被忽视的聪明解决方案。 - Milo Wielondek
1
这不就是:a = np.array(); b = np.array(); c = a[b]吗?你假设b的值是a的索引,这意味着你根本不需要字典。这是这个问题的一个微不足道的例子。 - Robin De Schepper

1

def dictonarize(np_array, dictonary, el_type='float'):
    
    final_array = np.zeros_like(np_array).astype(el_type)
    for x in dictonary:
        x_layer = (np_array == x)
        x_layer = (x_layer* dictonary[x]).astype(el_type)
        final_array += x_layer
        
    return final_array

0

结合 @DSM@John Vinyard 的最佳解决方案:

  • 仅对唯一值向量化 dict.__getitem__
  • 使用numpy优化索引进行映射。

代码:

>>> a = np.array([[1,2,3],[3,2,1]])
>>> a
array([[1, 2, 3],
       [3, 2, 1]])
>>> d = {1 : 11, 2 : 22, 3 : 33}

>>> u, inv = np.unique(a, return_inverse=True)
>>> np.vectorize(d.get)(u)[inv].reshape(a.shape)
array([[11, 22, 33],
       [33, 22, 11]])

这具有与 @DSM 回答相同的优点,同时避免了在数组中寻找唯一元素的 Python 循环。


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