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.