4.2. Manipulation de tableaux#

Les tableaux numpy sont fournis avec de nombreuses fonctions. En particuler, il est possible de faire des manipulations algébriques terme à terme ou des manipulations vectorielles rapidement.

Nous noterons x un tableau à une dimension et A un tableau à deux dimensions pour nos exemples.

Nota Bene

Il est pratiquement tout le temps préférable d’utiliser des commandes vectorielles (codées directement dans un langage de bas niveau) plutôt que de faire les boucles soit même.

import numpy as np

x = np.random.rand(5)     # ndarray de dimension 1 avec 5 éléments
A = np.random.rand(5, 4)  # ndarray de dimension 2 avec 5x4 éléments

print(x)
print(A)
[0.22242044 0.83883725 0.14060445 0.29807027 0.05786952]
[[0.84022668 0.68945572 0.67542859 0.48252788]
 [0.64021936 0.3617065  0.46724622 0.6023039 ]
 [0.15183671 0.72368522 0.184325   0.74776311]
 [0.12729357 0.81443759 0.06295319 0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]

4.2.1. Caractéristiques des tableaux#

Il est possible d’accéder facilement à la forme d’un tableau (attribut shape), au nombre total d’éléments (attribut size), au type des éléments (attribut dtype) et au nombre de dimensions (attribut ndim).

Attention

Les indices des tableaux commencent à 0 !

for T, name_T in zip([x, A], ["x", "A"]):
    print(f"Information du tableau {name_T}")
    print(f"# forme du tableau (tuple)                           shape -> {T.shape}")
    print(f"# taille du tableau (int)                            size  -> {T.size}")
    print(f"# type des éléments du tableau                       dtype -> {T.dtype}")
    print(f"# nombre de dimensions du tableau (taille de shape)  ndim  -> {T.ndim}")
Information du tableau x
# forme du tableau (tuple)                           shape -> (5,)
# taille du tableau (int)                            size  -> 5
# type des éléments du tableau                       dtype -> float64
# nombre de dimensions du tableau (taille de shape)  ndim  -> 1
Information du tableau A
# forme du tableau (tuple)                           shape -> (5, 4)
# taille du tableau (int)                            size  -> 20
# type des éléments du tableau                       dtype -> float64
# nombre de dimensions du tableau (taille de shape)  ndim  -> 2

4.2.2. Accès aux éléments en lecture et écriture : les slices#

L’accès aux éléments en lecture et en écriture se fait grâce à l’opérateur [].

Il est possible d’accéder à un seul élément : x[i], pour i entre 0 et x.size-1, et A[i, j], pour i entre 0 et A.shape[0]-1, j entre 0 et A.shape[1]-1.

print(x[1])
print(A[1, 2])
print(A[(1, 2)])
print(x[x.size-1])
print(A[1][2])  # solution interdite même si elle est syntaxiquement correcte !!!
print(A[1, 2])
0.8388372454667418
0.4672462206951987
0.4672462206951987
0.05786952148134006
0.4672462206951987
0.4672462206951987
print(A)
b = A[0]                  # b est un alias pour la 1ère ligne de A
print(type(b))
print(b.ndim, b.shape)
print(b[1])
b[1] = 0                  # la modification d'un élément de b modifie aussi A !
print(A)
[[0.84022668 0.68945572 0.67542859 0.48252788]
 [0.64021936 0.3617065  0.46724622 0.6023039 ]
 [0.15183671 0.72368522 0.184325   0.74776311]
 [0.12729357 0.81443759 0.06295319 0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]
<class 'numpy.ndarray'>
1 (4,)
0.6894557193050432
[[0.84022668 0.         0.67542859 0.48252788]
 [0.64021936 0.3617065  0.46724622 0.6023039 ]
 [0.15183671 0.72368522 0.184325   0.74776311]
 [0.12729357 0.81443759 0.06295319 0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]

Il est possible d’accéder aux éléments de la fin du tableau en utilisant les nombres négatifs : le -1 signifie le dernier élément, le -2 l’avant-dernier, etc.

print(x[-1])
print(A[1, -2])
0.05786952148134006
0.4672462206951987

L’intérêt majeur des tableaux numpy est que l’on peut accéder directement à une tranche du tableau en utilisant les :. Voici quelques exemples :

print(x[:])     # tous les éléments
print(x[1:-1])  # tous les éléments sauf le premier et le dernier
[0.22242044 0.83883725 0.14060445 0.29807027 0.05786952]
[0.83883725 0.14060445 0.29807027]
print(A[:, 0])        # la première colonne
print(A[1:-1, 1:-1])  # on enlève les premières et dernières lignes/colonnes
[0.84022668 0.64021936 0.15183671 0.12729357 0.81189737]
[[0.3617065  0.46724622]
 [0.72368522 0.184325  ]
 [0.81443759 0.06295319]]
print(A[::2, ::-1])  # on peut aussi faire varier le pas de la tranche !!!
[[0.48252788 0.67542859 0.         0.84022668]
 [0.74776311 0.184325   0.72368522 0.15183671]
 [0.56355912 0.45737284 0.97047986 0.81189737]]

4.2.3. Modification des éléments#

Un point très important sur les tableaux numpy : l’affectation ne fait pas de copie… Pour faire une copie, il est nécessaire d’utiliser la fonction membre copy(). Sinon il y a un risque de modifier le tableau original…

B = A.copy()
C = np.zeros(A.shape)
print(B)
B_in = B[1:-1, 1:-1]    # B_in est une sous-partie de B
B_in[:] = C[1:-1, 1:-1] # on copie les valeurs de C
print(B_in)
print(B)                # B a aussi été modifié
[[0.84022668 0.         0.67542859 0.48252788]
 [0.64021936 0.3617065  0.46724622 0.6023039 ]
 [0.15183671 0.72368522 0.184325   0.74776311]
 [0.12729357 0.81443759 0.06295319 0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]
[[0. 0.]
 [0. 0.]
 [0. 0.]]
[[0.84022668 0.         0.67542859 0.48252788]
 [0.64021936 0.         0.         0.6023039 ]
 [0.15183671 0.         0.         0.74776311]
 [0.12729357 0.         0.         0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]

4.2.4. Redimensionnement d’un tableau#

L’attribut shape d’un tableau peut être modifié sans faire de copie. Pour faire simple, les tableaux numpy sont stockés de manière mono-dimensionnelle même si on peut les voir comme des matrices, … Les dimensions supérieures à 2 ne servent en réalité qu’à accéder aux valeurs par d’autres vues.

Pour modifier la forme d’un tableau, on modifie l’attribut shape

x = np.arange(10)
print(x)
print(x.shape)
x.shape = (2, 5)
print(x.shape)
print(x)
x.shape = (5, 2)
print(x.shape)
print(x)
[0 1 2 3 4 5 6 7 8 9]
(10,)
(2, 5)
[[0 1 2 3 4]
 [5 6 7 8 9]]
(5, 2)
[[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]

on peut aussi utiliser la commande reshape qui crée une vue (pas de copie)

x = np.arange(10)
x.shape = (5, 2)
y = x.reshape((10,))
y[::2] = -1
print(y)
print(x)
[-1  1 -1  3 -1  5 -1  7 -1  9]
[[-1  1]
 [-1  3]
 [-1  5]
 [-1  7]
 [-1  9]]

la fonction membre flatten fait une copie du tableau en écrasant toutes les dimensions

print(B)
C = B.flatten()  # shape = (size, )
print(C)
print(B.shape)
print(C.shape)
B[0, :] = 1
print(B)
print(C)
[[0.84022668 0.         0.67542859 0.48252788]
 [0.64021936 0.         0.         0.6023039 ]
 [0.15183671 0.         0.         0.74776311]
 [0.12729357 0.         0.         0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]
[0.84022668 0.         0.67542859 0.48252788 0.64021936 0.
 0.         0.6023039  0.15183671 0.         0.         0.74776311
 0.12729357 0.         0.         0.51411788 0.81189737 0.97047986
 0.45737284 0.56355912]
(5, 4)
(20,)
[[1.         1.         1.         1.        ]
 [0.64021936 0.         0.         0.6023039 ]
 [0.15183671 0.         0.         0.74776311]
 [0.12729357 0.         0.         0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]
[0.84022668 0.         0.67542859 0.48252788 0.64021936 0.
 0.         0.6023039  0.15183671 0.         0.         0.74776311
 0.12729357 0.         0.         0.51411788 0.81189737 0.97047986
 0.45737284 0.56355912]

4.2.5. Boucle sur les tableaux#

Il y a plusieurs façons de faire des boucles sur les tableaux : soit sur les indices, soit directement sur les éléments car un tableau peut être vu comme un itérable.

for i in range(A.shape[0]):
    for j in range(A.shape[1]):
        print(f"A[{i:1d}, {j:1d}] = {A[i, j]}")
A[0, 0] = 0.840226683104879
A[0, 1] = 0.0
A[0, 2] = 0.6754285911108663
A[0, 3] = 0.4825278788240779
A[1, 0] = 0.6402193642554622
A[1, 1] = 0.36170650447597896
A[1, 2] = 0.4672462206951987
A[1, 3] = 0.6023039028752601
A[2, 0] = 0.1518367092168702
A[2, 1] = 0.7236852199663778
A[2, 2] = 0.18432500143165143
A[2, 3] = 0.7477631142333341
A[3, 0] = 0.12729356895791188
A[3, 1] = 0.8144375945386696
A[3, 2] = 0.06295319298426516
A[3, 3] = 0.5141178831641072
A[4, 0] = 0.8118973670968342
A[4, 1] = 0.9704798558235888
A[4, 2] = 0.4573728436082991
A[4, 3] = 0.5635591214793524
for Ai in A:
    for Aij in Ai:
        print(Aij)
0.840226683104879
0.0
0.6754285911108663
0.4825278788240779
0.6402193642554622
0.36170650447597896
0.4672462206951987
0.6023039028752601
0.1518367092168702
0.7236852199663778
0.18432500143165143
0.7477631142333341
0.12729356895791188
0.8144375945386696
0.06295319298426516
0.5141178831641072
0.8118973670968342
0.9704798558235888
0.4573728436082991
0.5635591214793524
for i, Ai in enumerate(A):
    for j, Aij in enumerate(Ai):
        print(f"A[{i:1d}, {j:1d}] = {Aij}")
A[0, 0] = 0.840226683104879
A[0, 1] = 0.0
A[0, 2] = 0.6754285911108663
A[0, 3] = 0.4825278788240779
A[1, 0] = 0.6402193642554622
A[1, 1] = 0.36170650447597896
A[1, 2] = 0.4672462206951987
A[1, 3] = 0.6023039028752601
A[2, 0] = 0.1518367092168702
A[2, 1] = 0.7236852199663778
A[2, 2] = 0.18432500143165143
A[2, 3] = 0.7477631142333341
A[3, 0] = 0.12729356895791188
A[3, 1] = 0.8144375945386696
A[3, 2] = 0.06295319298426516
A[3, 3] = 0.5141178831641072
A[4, 0] = 0.8118973670968342
A[4, 1] = 0.9704798558235888
A[4, 2] = 0.4573728436082991
A[4, 3] = 0.5635591214793524
# accès en écriture car Ai est un ndarray
print(A)

for Ai in A:
    Ai[:] = 0
print(A)

# accès en lecture seule (copie) car Aij est un float
for Ai in A:
    for Aij in Ai:
        Aij = 1
print(A)
[[0.84022668 0.         0.67542859 0.48252788]
 [0.64021936 0.3617065  0.46724622 0.6023039 ]
 [0.15183671 0.72368522 0.184325   0.74776311]
 [0.12729357 0.81443759 0.06295319 0.51411788]
 [0.81189737 0.97047986 0.45737284 0.56355912]]
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]