1
votes

Fonction d'application Numpy à chaque élément du tableau

Disons que j'ai un tableau 2D. Comment puis-je appliquer une fonction à chaque élément du tableau et remplacer cet élément par le retour? De plus, le retour de la fonction sera un tuple, donc le tableau deviendra 3d.

Voici le code en tête.

def filter_func(item):
    if 0 <= item < 1:
        return (1, 0, 1)
    elif 1 <= item < 2:
        return (2, 1, 1)
    elif 2 <= item < 3:
        return (5, 1, 4)
    else:
        return (4, 4, 4)

myarray = np.array([[2.5, 1.3], [0.4, -1.0]])

# Apply the function to an array

print(myarray)

# Should be array([[[5, 1, 4],
#                   [2, 1, 1]],
#                  [[1, 0, 1],
#                   [4, 4, 4]]])

Des idées comment je pourrais le faire? Une façon est de faire np.array (list (map (filter_func, myarray.reshape ((12,))))). Reshape ((2, 2, 3)) mais c'est assez lent, surtout quand j'ai besoin de le faire sur un tableau de formes (1024, 1024).

J'ai aussi vu des gens utiliser np.vectorize, mais cela finit en quelque sorte par (array ([[ 5, 2], [1, 4]]), tableau ([[1, 1], [0, 4]]), tableau ([[4, 1], [1, 4]])). code> Ensuite, il a la forme (3, 2, 2).


0 commentaires

3 Réponses :


1
votes

vous pouvez utiliser cette fonction, avec l'implémentation vectorisée

myarray = func(myarray)
myarray

>>>   [[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]]

vous ne pourrez pas réécrire votre tableau en raison d'une incompatibilité de forme mais vous pouvez écraser la variable myarray

def func(arr):
    
    elements = np.array([
        [1, 0, 1],
        [2, 1, 1],
        [5, 1, 4],
        [4, 4, 4],
    ])
    
    arr  = arr.astype(int)
    mask = (arr != 0) & (arr != 1) & (arr != 2)

    arr[mask] = -1
    
    return elements[arr]


1 commentaires

Euh, comment le ferais-je si j'avais déjà une fonction? Je ne comprends pas vraiment ce que fait votre code



1
votes

Pas besoin de changer quoi que ce soit dans votre fonction.

Il suffit d'appliquer la version vectorisée de votre fonction à votre tableau et empiler le résultat:

array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])

Le résultat est:

np.stack(np.vectorize(filter_func)(myarray), axis=2)


3 commentaires

Ce paramètre spécifie, le long de quel axe l'empilement doit avoir lieu (décrit dans la documentation Numpy ). Essayez mon code avec axis == 1 et 0 pour voir la différence.


Merci! C'est exactement ce dont j'avais besoin. C'est environ 8 fois plus rapide que mon premier essai!


Dans mes horaires, cette vectorisation est plus lente que votre liste (carte ...) . J'ai toujours trouvé que vectorize était plus lent qu'une itération simple.



0
votes

Votre list-map:

In [32]: timeit func(myarray)                                                                        
25 µs ± 60.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Une variante utilisant la compréhension de liste imbriquée:

In [29]: timeit np.stack(np.frompyfunc(filter_func, 1,3)(myarray),2).astype(int)                     
28.7 µs ± 125 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Utilisation de vectorize , le résultat est un tableau pour chaque élément renvoyé par la fonction.

In [8]: timeit np.array(list(map(filter_func, myarray.reshape((4,))))).reshape((2, 2, 3))            
17.2 µs ± 47.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [9]: timeit np.array([[filter_func(j) for j in row] for row in myarray])                          
20.5 µs ± 78.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [10]: timeit np.stack(np.vectorize(filter_func)(myarray),2)                                       
75.2 µs ± 297 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Comme @Vladi le montre, ils peuvent être combinés avec stack (ou np.array suivi d'une transposition):

In [7]: np.stack(np.vectorize(filter_func)(myarray),2)                                               
Out[7]: 
array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])

Votre liste-map est la plus rapide. Je n'ai jamais trouvé vectorize plus rapide:

In [6]: np.vectorize(filter_func)(myarray)                                                           
Out[6]: 
(array([[5, 2],
        [1, 4]]),
 array([[1, 1],
        [0, 4]]),
 array([[4, 1],
        [1, 4]]))

En supprimant np.vectorize (filter_func) la boucle de synchronisation aide juste un peu.

frompyfunc est similaire à vectorize , mais renvoie l'objet dtype. C'est généralement plus rapide:

In [5]: np.array([[filter_func(j) for j in row] for row in myarray])                                 
Out[5]: 
array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])

En général, si vous avez une fonction qui ne prend que des entrées scalaires, il est difficile de faire mieux qu'une simple itération. vectorize / frompyfunc ne s’améliore pas. L'utilisation optimale de numpy nécessite de réécrire la fonction pour qu'elle fonctionne directement avec des tableaux, comme le montre @Hammad.

Bien qu'avec ce petit exemple, même ce numpy approprié la solution n'est pas plus rapide. Je pense qu'il évoluera mieux:

In [4]: np.array(list(map(filter_func, myarray.reshape((4,))))).reshape((2, 2, 3))                   
Out[4]: 
array([[[5, 1, 4],
        [2, 1, 1]],

       [[1, 0, 1],
        [4, 4, 4]]])


1 commentaires

La carte de liste a pris 6,34 secondes sur un tableau de 1024 x 1024, mais la vectorisation n'a pris que 1,18 secondes. Mabye, le list-map est meilleur pour les petits tableaux.