6.12. Sélection et réorganisation

Les réductions condensent un tableau en un scalaire ou un résultat de rang inférieur. La sélection couvre les opérations qui choisissent quels éléments survivent et ils aboutissent : choix conditionnel, écrêtage, tri, recherche d’indices, réorganisation le long d’un axe.

6.12.1. Choix conditionnel

where() renvoie un tableau qui prend les éléments de x là où la condition est vraie et de y sinon. Les trois opérandes sont diffusés ensemble

a = np.array([1, 2, 3, 4, 5], dtype=np.float)
np.where(a < 3, a, 0.0)
# array([1.0, 2.0, 0.0, 0.0, 0.0])

C’est l’outil idéal pour un « if/else par élément » sans écrire de boucle Python.

clip() est un raccourci pour maximum(lo, minimum(a, hi)) – saturer les valeurs dans une plage

np.clip(a, 2.0, 4.0)
# array([2.0, 2.0, 3.0, 4.0, 4.0])

maximum() et minimum() prennent deux opérandes et renvoient le plus grand / plus petit élément par élément

np.maximum(a, 3.0)
np.minimum(a, np.array([5, 4, 3, 2, 1]))

6.12.2. Recherche d’indices

nonzero() renvoie les coordonnées de chaque élément non nul, réparties en un tableau d’indices par dimension. Pour une entrée 2-D, le résultat est un tuple de deux tableaux : le premier contient les indices de ligne, le second les indices de colonne. En les appariant colonne par colonne, on obtient le (row, col) de chaque position non nulle

m = np.array([[0, 2, 0],
              [3, 0, 0]], dtype=np.float)
np.nonzero(m)
# (array([0, 1], dtype=uint16), array([1, 0], dtype=uint16))

Les entrées non nulles de m sont m[0, 1] = 2 et m[1, 0] = 3. Le premier tableau renvoyé [0, 1] donne leurs indices de ligne ; le second [1, 0] donne leurs indices de colonne. Lire les deux tableaux côte à côte permet de retrouver les positions (0, 1) et (1, 0).

Deux réductions produisent également des indices :

  • argmin() / argmax() – indice du plus petit / plus grand élément.

  • argsort() – un tableau d’entiers qui trierait l’entrée le long de l’axe donné (le dernier par défaut)

    a = np.array([40, 10, 30, 20], dtype=np.uint8)
    idx = np.argsort(a)             # array([1, 3, 2, 0], dtype=uint16)
    a[idx]                          # array([10, 20, 30, 40])
    

    argsort renvoie toujours des uint16 ; le tableau trié ne doit donc pas comporter plus de 65 535 éléments sur l’axe trié.

bincount() compte les occurrences de chaque entier non négatif dans une entrée 1-D uint8 / uint16

histogram = np.bincount(np.array([0, 1, 1, 2, 2, 2], dtype=np.uint8))
# array([1, 2, 3], dtype=uint16)

Utile pour construire des histogrammes de valeurs de pixels à petits entiers sans écrire de boucle Python.

6.12.3. Tri et réorganisation

sort() renvoie une copie triée du tableau le long de l’axe donné (le dernier par défaut). Utilisez sort() directement sur le tableau pour une version sur place

np.sort(np.array([3, 1, 2], dtype=np.float))
# array([1.0, 2.0, 3.0])

flip() inverse l’ordre le long de l’axe donné (tous les axes lorsqu’aucun axis n’est passé)

np.flip(np.array([1, 2, 3, 4]))
# array([4, 3, 2, 1])

roll() décale cycliquement les éléments du nombre donné. Utile pour implémenter un registre à décalage de type tampon circulaire

np.roll(np.array([1, 2, 3, 4]), 1)
# array([4, 1, 2, 3])

take() est la forme explicite de l’indexation avancée – choisir des éléments à des indices arbitraires

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.take(a, [0, 2, 4])
# array([10, 30, 50], dtype=uint8)

6.12.4. Filtrage et modifications structurelles

compress() est la forme explicite de l’indexation booléenne – renvoie les tranches de a sélectionnées par la condition booléenne

a = np.array([10, 20, 30, 40], dtype=np.uint8)
np.compress(a > 15, a)
# array([20, 30, 40], dtype=uint8)

delete() renvoie une copie dont les entrées aux indices donnés sont supprimées

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.delete(a, [1, 3])
# array([10, 30, 50], dtype=uint8)

diff() renvoie la n-ième différence avant discrète du tableau le long d’un axe. Utilisé pour calculer les variations du premier ordre entre échantillons adjacents

samples = np.array([1, 3, 6, 10, 15], dtype=np.float)
np.diff(samples)
# array([2.0, 3.0, 4.0, 5.0])

6.12.5. Le coût de chaque opération

Presque toutes les fonctions de cette page renvoient un tableau fraîchement alloué. Deux exceptions :

  • sort() trie sur place ; la fonction libre sort() renvoie une copie triée.

  • take() accepte un mot-clé out= pour écrire dans un tampon déjà existant.

Dans une boucle qui s’exécute plusieurs fois par seconde, préférez le tri sur place sort() et réutilisez des tampons pré-alloués partout ailleurs. Les masques booléens eux-mêmes sont alloués à chaque exécution de la comparaison – construisez un masque une seule fois et réutilisez-le entre les opérations plutôt que de le reconstruire à chaque itération.