Masque
Concernant l'accès aux éléments d'un tableau, on peut aller plus loin que les techniques vues à la section précédente. On pourrait indiquer, pour chaque élément d'un tableau, si on veut l'extraire ou non. Pour ce faire, on va devoir utiliser un tableau de booléens, appelé masque, avec exactement la même taille et la même forme que le tableau dont on veut extraire des éléments.
Tableau de booléens
Jusqu'à ici, les tableaux multidimensionnels que l'on a pu voir étaient constitués de nombres entiers, flottants ou complexes. On peut également créer des tableaux dont les valeurs sont des booléens :
Ces instructions créent donc un tableau dont les dimensions sont $(2, 3)$ :
[[ True False True] [False False True]]
Une autre façon de construire un tableau de booléens consiste à utiliser des opérateurs booléens de comparaison avec des tableaux multidimensionnels. Voici un exemple d'une telle construction :
Le tableau créé par l'expression data > 2
possède exactement le même nombre d'éléments que data
et les mêmes dimensions. Ses éléments valent True
ou False
, selon que l'élément à la même position dans le tableau data
est strictement plus grand que $2$ ou non :
[[-1 4 0] [-3 2 1]] [[False True False] [False False False]]
Expression complexe
L'expression utilisée pour créer un tableau de booléens à partir d'un tableau de données peut combiner plusieurs opérateurs, tant que cela reste bel et bien une expression booléenne. On peut, par exemple, identifier tous les éléments pairs d'un tableau comme suit :
On se retrouve donc avec un tableau de booléens avec des True
aux positions des éléments pairs du tableau data
, et des False
ailleurs. Le résultat de l'exécution est :
[[False True True] [False True False]]
Les opérations possibles sur des tableaux sont détaillées à la section 2.7.
Opérateur booléen
Enfin, on peut combiner des expressions booléennes avec des opérateurs logiques (NON, ET et OU). Ils ne sont pas représentés par les mots réservés not
, and
et or
comme en Python « classique », mais par les opérateurs ~
, &
et |
. Voici un exemple avec un OU logique :
On identifie donc les éléments soit strictement plus grand que $2$, soit strictement plus petit que $-2$ :
[[False True False] [ True False False]]
Application de masque
Une fois un tableau de booléens créé, on peut l'utiliser comme indice pour accéder aux éléments d'un tableau. Dans ce cas, le tableau de booléens est utilisé comme un masque. Voici, par exemple, comment on peut extraire les valeurs paires d'un tableau multidimensionnel :
Dans ce cas, le résultat est un tableau unidimensionnel qui contient tous les éléments de data
pour lesquels le tableau de booléens utilisé comme indice vaut True
. Dans cet exemple, il y a trois éléments du tableau data
qui sont pairs :
[4 0 2]
Cette opération, appelée application d'un masque, est illustrée par la figure 6. C'est comme si on plaçait le tableau de booléens devant le tableau de données et qu'on ne gardait que les éléments pour lesquels la valeur du masque vaut True
(c'est un peu comme si on plaçait une carte perforée devant le tableau, pour regarder à travers les trous).
Si le tableau de booléens utilisé comme indice est de dimension plus petite que le tableau dont on veut extraire des données, on va pouvoir sélectionner plusieurs éléments avec un seul True
. Si on reprend le tableau créé précédemment, on peut, par exemple, sélectionner uniquement la seconde « ligne » comme suit :
Dans ce cas, le résultat est un tableau à deux dimensions qui contient la première « ligne » de data
:
[[-3 2 1]]
Contrairement à l'extraction d'un sous-tableau avec une opération de slicing simple, les tableaux qui sont le résultat de l'application d'un masque sont toujours des copies.
Un deuxième type d'opération est la multiplication par un masque. Il remplace par zéro tous les éléments situés à des positions où un masque est vrai. Supprimons, par exemple, les valeurs négatives de data
:
Le masque permet d'identifier tous les éléments négatifs. Ensuite, on multiplie data
par l'inverse du masque (obtenu avec l'opérateur NON logique) pour ne garder que les valeurs positives du tableau :
[[0 4 0] [0 2 1]]
Recherche
On peut aussi vouloir connaitre les indices des éléments qui satisfont une condition. Pour cela, il suffit d'utiliser la fonction where
qui prend en paramètre la condition, sous forme d'un tableau de booléens. Retrouvons, par exemple, les indices des valeurs négatives du tableau data
:
La fonction where
renvoie un tuple avec deux listes d'indices, puisque le tableau data
a deux dimensions. Comme il y a trois valeurs négatives, chacune des deux listes aura trois éléments :
(array([0, 0, 1]), array([0, 2, 0]))
Les trois valeurs négatives se trouvent donc aux positions $(0, 0)$, $(0, 2)$ et $(1, 0)$ dans le tableau : il s'agit des éléments $-1$, $0$ et $-3$. Notez que ce tuple de listes d'indices peut directement être utilisé avec l'opérateur d'accès pour extraire ces valeurs depuis le tableau :
L'exécution de cette instruction donne bel et bien le résultat attendu :
[-1 0 -3]
On peut également utiliser la fonction nonzero
à la place de la fonction where
. Celle-ci renvoie un tuple de tableaux d'indices, un par dimension, localisant les éléments qui sont différents de zéro. Dans le cas d'un tableau de booléens, la fonction nonzero
identifie donc les True
. Il est recommandé d'utiliser la fonction nonzero
, la fonction where
telle qu'utilisée pour le moment se contentant d'appeler nonzero
. On aurait donc plutôt dû écrire l'instruction suivante :
On se rend vite compte de toute la puissance des masques, permettant d'extraire d'un tableau les éléments qui satisfont une condition, ou les positions de ces derniers. En combinant tout ce qu'on a vu jusqu'à présent, on peut même aller remplacer les éléments négatifs du tableau data
par leurs valeurs absolues ! Voici comment :
Le tableau neg
contient donc un tableau unidimensionnel avec tous les éléments négatifs de data
. On multiplie ensuite tous les éléments de ce tableau par $-1$, les rendant ainsi positifs. Ensuite, on va les replacer au bon endroit dans data
, en ne modifiant que les éléments dont les positions sont dans ind
. Le résultat est bien celui attendu :
[[1 4 0] [3 2 1]]
Remplacement
Enfin, terminons cette section avec une variante de la fonction where
, pas remplaçable par la fonction nonzero
. On peut, en effet, combiner la recherche d'éléments dans un tableau et le remplacement de ces derniers, en une seule opération :
La fonction where
construit cette fois-ci un nouveau tableau, de même taille et avec les mêmes dimensions que data
, mais dont les éléments sont soit ceux de data
, soit ceux de data * -1
, en fonction de la valeur de la condition. On peut comparer cette opération avec l'instruction if
compacte de Python (on aurait écrit data * -1 if data <= 0 else data
en Python).
Dans cet exemple, on remplace donc les éléments négatifs par leur valeur absolue (en les multipliant par $-1$) et on ne touche pas aux autres éléments. On refait donc la même chose que précédemment, mais de manière bien plus compacte. Le résultat attendu est bien celui obtenu suite à l'exécution de cette instruction :
[[1 4 0] [3 2 1]]
Ici, dans cet exemple, on n'a modifié les éléments du tableau que dans l'un des deux cas, et on a gardé les éléments originaux dans l'autre cas. Lorsque c'est ce que l'on veut faire, on n'a pas besoin de passer par la fonction where
et on peut se contenter de putmask
. Cette fonction permet de juste remplacer les éléments qui satisfont une condition. L'exemple précédent se réécrit donc comme suit :
Comme on peut le constater, il y a néanmoins une différence majeure entre les deux fonctions. La fonction where
crée un nouveau tableau tandis que la fonction putmask
fait son remplacement directement sur le tableau cible. Selon la situation, il faudra donc d'abord faire une copie ou alors utiliser la fonction adéquate.