Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Complément - niveau avancé

Nous allons maintenant voir qu’il est possible d’indexer un tableau numpy avec, non pas des entiers ou des tuples comme on l’a vu dans un complément précédent, mais aussi avec d’autres types d’objets qui permettent des manipulations très puissantes :

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.ion()
<contextlib.ExitStack at 0x7f1bc182e5d0>

Pour illustrer ceci, on va réutiliser la fonction background que l’on avait vue pour les indexations simples :

# une fonction qui crée un tableau
# tab[i, j] = i + 10 * j
def background(n):
    i = np.arange(n)
    j = i.reshape((n, 1))
    return i + 10 * j

Indexation par une liste

On peut indexer par une liste d’entiers, cela constitue une généralisation des slices.

b = background(6)
print(b)
[[ 0  1  2  3  4  5]
 [10 11 12 13 14 15]
 [20 21 22 23 24 25]
 [30 31 32 33 34 35]
 [40 41 42 43 44 45]
 [50 51 52 53 54 55]]

Si je veux référencer les lignes 1, 3 et 4, je ne peux pas utiliser un slice ; mais je peux utiliser une liste à la place :

# il faut lire ceci comme
# j'indexe b, avec comme indice la liste [1, 3, 4]
b[[1, 3, 4]]
array([[10, 11, 12, 13, 14, 15], [30, 31, 32, 33, 34, 35], [40, 41, 42, 43, 44, 45]])
# pareil pour les colonnes, en combinant avec un slice
b[:, [1, 3, 4]]
array([[ 1, 3, 4], [11, 13, 14], [21, 23, 24], [31, 33, 34], [41, 43, 44], [51, 53, 54]])
# et comme toujours on peut faire du broadcasting
b[:, [1, 3, 4]] = np.arange(1000, 1006).reshape((6, 1))
print(b)
[[   0 1000    2 1000 1000    5]
 [  10 1001   12 1001 1001   15]
 [  20 1002   22 1002 1002   25]
 [  30 1003   32 1003 1003   35]
 [  40 1004   42 1004 1004   45]
 [  50 1005   52 1005 1005   55]]

Indexation par un tableau

On peut aussi indexer un tableau A … par un tableau ! Pour que cela ait un sens :

Le cas simple : l’entrée et l’index sont de dimension 1.

# le tableau qu'on va indexer
cubes = np.arange(10) ** 3
print(cubes)
[  0   1   8  27  64 125 216 343 512 729]
# et un index qui est un tableau numpy
# doit contenir des entiers entre 0 et 9
tab = np.array([1, 7, 2])
print(cubes[tab])
[  1 343   8]
# donne - logiquement - le même résultat que
# si l'index était une liste Python
lis = [1, 7, 2]
print(cubes[lis])
[  1 343   8]

De manière générale

Dans le cas général, le résultat de A[index] :

A = np.array([[0, 'zero'], [1, 'un'], [2, 'deux'], [3, 'trois']])
print(A)
[['0' 'zero']
 ['1' 'un']
 ['2' 'deux']
 ['3' 'trois']]
index = np.array([[1, 0, 2], [3, 2, 3]])
print(index)
[[1 0 2]
 [3 2 3]]
parts
B = A[index]
print(B)
[[['1' 'un']
  ['0' 'zero']
  ['2' 'deux']]

 [['3' 'trois']
  ['2' 'deux']
  ['3' 'trois']]]
result
B[1, 2, 1]
np.str_('trois')
result

Et donc si :

Alors :

Ce que l’on vérifie ici :

# l'entrée
print(A.shape)
(4, 2)
# l'index
print(index.shape)
(2, 3)
# le résultat
print(A[index].shape)
(2, 3, 2)

Cas particulier : entrée de dimension 1, index de dim. > 1

Lorsque l’entrée A est de dimension 1, alors la sortie a exactement la même forme que l’index.

C’est comme si A était une fonction que l’on applique aux indices dans index.

print(cubes)
[  0   1   8  27  64 125 216 343 512 729]
i2 = np.array([[2, 4], [8, 9]])
print(i2)
[[2 4]
 [8 9]]
print(cubes[i2])
[[  8  64]
 [512 729]]

Application au codage des couleurs dans une image

# je crée une image avec 6 valeurs disposées en diagonale
N = 32
colors = 6

image = np.empty((N, N), dtype = np.int32)
for i in range(N):
    for j in range(N):
       image[i, j] = (i+j) % colors
plt.imshow(image, cmap='gray');
<Figure size 640x480 with 1 Axes>

Les couleurs ne sont pas significatives, ce sont des valeurs entières dans range(colors). On voudrait pouvoir choisir la vraie couleur correspondant à chaque valeur. Pour cela on peut utiliser une simple indexation par tableau :

# une palette de couleurs
palette = np.array([
  [255, 255, 255], # 0 -> blanc
  [255, 0, 0],     # 1 -> rouge
  [0, 255, 0],     # 2 -> vert
  [0, 0, 255],     # 3 -> bleu
  [0, 255, 255],   # 4 -> cyan
  [255, 255, 0],   # 5 -> magenta
 ], dtype=np.uint8)
plt.imshow(palette[image]);
<Figure size 640x480 with 1 Axes>

Remarquez que la forme générale n’a pas changé, mais le résultat de l’indexation a une dimension supplémentaire de 3 couleurs :

image.shape
(32, 32)
palette[image].shape
(32, 32, 3)

Indexation multiple (par tuple)

Une fois que vous avez compris ce mécanisme d’indexation par un tableau, on peut encore généraliser pour définir une indexation par deux (ou plus) tableaux de formes identiques.

Ainsi, lorsque index1 et index2 ont la même forme :

# un tableau à indexer
ix, iy = np.indices((4, 3))
A = 10 * ix + iy
print(A)
[[ 0  1  2]
 [10 11 12]
 [20 21 22]
 [30 31 32]]
# les deux tableaux d'indices sont carrés 2x2
index1 = [[3, 1], [0, 1]]  # doivent être < 4
index2 = [[2, 0], [0, 2]]  # doivent être < 3
# le résultat est donc carré 2x2
print(A[index1, index2])
[[32 10]
 [ 0 12]]

Et donc si :

Alors :

Application à la recherche de maxima

Imaginons que vous avez des mesures pour plusieurs instants :

times = np.linspace(1000, 5000, num=5, dtype=int)
print(times)
[1000 2000 3000 4000 5000]
# on aurait 3 mesures à chaque instant
series = np.array([
    [10, 25, 32, 23, 12],
    [12, 8, 4, 10, 7],
    [100, 80, 90, 110, 120]])
print(series)
[[ 10  25  32  23  12]
 [ 12   8   4  10   7]
 [100  80  90 110 120]]

Avec la fonction np.argmax on peut retrouver les indices des points maxima dans series :

max_indices = np.argmax(series, axis=1)
print(max_indices)
[2 0 4]

Pour trouver les maxima en question, on peut faire :

# les trois maxima, un par serie
maxima = series[ range(series.shape[0]), max_indices ]
print(maxima)
[ 32  12 120]
# et ils correspondent à ces instants-ci
times[max_indices]
array([3000, 1000, 5000])

Indexation par un tableau de booléens

Une forme un peu spéciale d’indexation consiste à utiliser un tableau de booléens, qui agit comme un masque :

suite = np.array([1, 2, 3, 4, 5, 4, 3, 2, 1])

Je veux filtrer ce tableau et ne garder que les valeurs < 4 :

# je construis un masque
hauts = suite >= 4
print(hauts)
[False False False  True  True  True False False False]
# je peux utiliser ce masque pour calculer les indices qui sont vrais
suite[hauts]
array([4, 5, 4])
# et utiliser maintenant ceci par un index de tableau
# par exemple pour annuler ces valeurs
suite[hauts] = 0
print(suite)
[1 2 3 0 0 0 3 2 1]