119 votes

Traduire chaque élément d'un tableau numpy en fonction de la clé.

J'essaie de traduire chaque élément d'un numpy.array en fonction d'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 avoir :

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 une méthode plus efficace et/ou purement numpy ?

Edita:

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

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)

156voto

DSM Points 71975

Je ne sais pas pour l'efficacité, mais vous pourriez 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]])

32voto

John Vinyard Points 2425

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

Cette approche est beaucoup plus rapide que np.vectorize lorsque le nombre d'éléments uniques dans le tableau est faible. Explication : Python est lent, dans cette approche, la boucle in-python est utilisée pour convertir les éléments uniques, puis nous nous appuyons sur une opération d'indexation numpy extrêmement optimisée (réalisée en C) pour effectuer le mappage. Par conséquent, si le nombre d'éléments uniques est comparable à la taille globale du tableau, il n'y aura pas d'accélération. En revanche, s'il n'y a que quelques éléments uniques, vous pouvez observer un gain de vitesse allant jusqu'à x100.

10voto

John Vinyard Points 2425

Je pense qu'il serait préférable d'itérer sur le dictionnaire et de définir des valeurs dans toutes les lignes et colonnes "en une 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]])

Edita:

Même si ce n'est pas aussi sexy que La (très bonne) réponse de DSM en utilisant numpy.vectorize En effet, mes tests de toutes les méthodes proposées montrent que cette approche (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

Sorties :

orig
0.222067718506
unique
0.0472617006302
vec
0.0357889199257
loop
0.0285375618935

8voto

Eelco Hoogendoorn Points 2218

En numpy_indexé (disclaimer : je suis son auteur) fournit une solution vectorielle é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 à l'approche mentionnée par John Vinyard, mais encore plus générale. Par exemple, les éléments du tableau ne doivent pas nécessairement être des ints, mais peuvent être de n'importe quel type, même des nd-sous-réseaux eux-mêmes.

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

4voto

Maxim Points 2192

En supposant que les clés de votre dict sont des nombres entiers positifs, sans grands écarts (similaires à 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 effectuer 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]

Test 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, j'obtiens environ 140 ms pour cette approche. La vectorisation np.get prend environ 5.8 s et le unique_translate autour de 8 s .

Généralisations possibles :

  • Si vous avez des valeurs négatives à traduire, vous pouvez décaler les valeurs en a et dans les clés du dictionnaire par une constante pour les faire correspondre à 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 sources présentent des écarts importants, la création initiale du tableau gaspillerait de la mémoire. J'aurais recours à Cython pour accélérer cette fonction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X