Переведите каждый элемент в массив numpy в соответствии с ключом

Я пытаюсь перевести каждый элемент 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?

Edit:

Я приурочил это время, а метод np.vectorize, предложенный DSM, значительно быстрее для больших массивов:

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)

Ответ 1

Я не знаю об эффективности, но вы можете использовать np.vectorize в методе .get словарей:

>>> 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]])

Ответ 2

Здесь другой подход, используя 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]])

Ответ 3

Я думаю, что было бы лучше перебирать словарь и устанавливать значения во всех строках и столбцах "сразу":

>>> 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]])

Edit:

Хотя это может быть не так сексуально, как 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

Ответ 4

Пакет numpy_indexed (отказ от ответственности: я являюсь его автором) предоставляет элегантное и эффективное векторизованное решение этой проблемы:

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

Реализованный метод похож на подход, упомянутый Джоном Винярдом, но еще более общий. Например, элементы массива не обязательно должны быть int, но могут быть любыми типами, даже самими nd-subarrays.

Если вы установите необязательный "недостающий" kwarg в "raise" (по умолчанию "ignore" ), производительность будет немного лучше, и вы получите KeyError, если не все элементы "a" присутствуют в ключах.

Ответ 5

Если вы действительно не должны использовать словарь в качестве таблицы подстановок, простым решением будет (для вашего примера):

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 с введенными целыми числами.

Ответ 6

Предполагая, что ваши ключи dict являются положительными целыми числами, без огромных пробелов (аналогично диапазону от 0 до N), вам лучше было бы преобразовать ваш перевод dict в массив таким образом, чтобы 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 ms для этого подхода. Энциклопедия np.get занимает около 5.8 s и unique_translate вокруг 8 s.

Возможные обобщения:

  • Если у вас есть отрицательные значения для перевода, вы можете сдвинуть значения в 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, чтобы ускорить эту функцию.