Question Traduire chaque élément du tableau numpy en fonction de la clé


J'essaie de traduire chaque élément d'un numpy.array selon une clé donnée:

Par exemple:

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

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

Je veux obtenir:

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

Je peux voir comment le faire avec une boucle:

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

Existe-t-il un moyen plus efficace et / ou purement numpy?

Modifier:

Je l'ai chronométré et np.vectorize La méthode proposée par DSM est considérablement plus rapide pour les baies plus grandes:

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)

34
2018-06-07 20:49


origine


Réponses:


Je ne sais pas sur l'efficacité, mais vous pouvez utiliser np.vectorize sur le .get méthode des dictionnaires:

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

42
2018-06-07 20:53



Voici une autre approche, en utilisant 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]])

5
2018-06-07 21:38



Je pense qu'il serait préférable d'itérer sur le dictionnaire et de définir les valeurs dans toutes les lignes et colonnes "à la fois":

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

Modifier:

Bien que ce ne soit pas aussi sexy que La réponse de DSM (vraiment bien) en utilisant numpy.vectorize, mes tests de toutes les méthodes proposées montrent que cette approche (en utilisant la suggestion de @ jamylak) est en fait un peu plus rapide:

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

Les sorties:

orig
0.222067718506
unique
0.0472617006302
vec
0.0357889199257
loop
0.0285375618935

5
2018-06-07 21:00



le numpy_indexed package (disclaimer: je suis son auteur) fournit une solution vectorisée élégante et efficace à ce type de problème:

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

La méthode mise en œuvre est similaire à celle mentionnée par John Vinyard, mais plus générale encore. Par exemple, les éléments du tableau n'ont pas besoin d'être ints, mais peuvent être de n'importe quel type, même les sous-réseaux eux-mêmes.

Si vous définissez l'option 'missing' kwarg facultatif sur 'raise' (la valeur par défaut est 'ignore'), les performances seront légèrement meilleures et vous obtiendrez une erreur KeyError si tous les éléments de 'a' ne sont pas présents dans les clés.


3
2017-07-26 18:27



En supposant que vos clés dict sont des entiers positifs, sans grandes différences (similaire à une plage de 0 à N), il serait préférable de convertir votre dict de traduction en un tableau tel que my_array[i] = my_dict[i], et en utilisant l'indexation numpy pour faire la traduction.

Un code utilisant cette approche est:

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]

Tester avec des tableaux aléatoires:

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

Pour ces tailles je me déplace 140 ms pour cette approche. La vectorisation np.get prend environ 5.8 s et le unique_translate autour 8 s.

Généralisations possibles:

  • Si vous avez des valeurs négatives à traduire, vous pouvez déplacer les valeurs dans a et dans les clés du dictionnaire par une constante pour les ramener à des entiers positifs:

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]
  • Si les clés source ont d'énormes lacunes, la création initiale du tableau gaspillerait de la mémoire. Je recourrais à Cython pour accélérer cette fonction.

1
2018-01-15 13:00



Si tu ne le fais pas vraiment devoir utiliser le dictionnaire comme table de substitution, solution simple serait (pour votre exemple):

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)

Cela fonctionnera bien sûr que si les index de d couvrir toutes les valeurs possibles de votre a, autrement dit, uniquement pour a avec des entiers utilisés.


0
2018-03-15 00:34