Introduction à la bibliothèque numpy

Cette introduction s'inspire d'un cours de physique numérique de l'Ecole Polytechnique. Un grand merci à son auteur Michel Ferrero (CPHT, Ecole Polytechnique) pour son autorisation.

Comme vous l'avez vu dans le cours d'introduction au langage python, les listes sont un moyen pratique pour rassembler des données de divers types. En tant que mécanicien, vous aurez très souvent envie de travailler avec des vecteurs et des matrices. Il pourrait être tentant d'utiliser des listes pour le faire. Par exemple, vous pourriez très bien définir un vecteur v = [1,2,3] et accéder à son deuxième élément avec, par exemple,v[1]

Toutefois, si les listes sont des objets très génériques, elles présentent certaines limites. Par exemple, elles ne se comportent pas comme on pourrait s'y attendre pour un vecteur. Pour s'en rendre compte, essayez d'exécuter le code ci-dessous et voyez si vous pouvez anticiper le résultat

In [1]:
# Quel est le résultat de cette opération?
print([1, 2, 3] + [2, 0, 1])
# Vous pensez obtenir [3,2,4]?

# Et pour cette opération ?
print(2 * [1, 2, 3])
[1, 2, 3, 2, 0, 1]
[1, 2, 3, 1, 2, 3]

Comme vous le voyez, les listes ne se comportent pas comme des vecteurs. La raison vient de ce que les listes peuvent contenir des types différents. Vous pouvez créer une liste [1, "a",3.2]. Que signifierait une multiplication par 2 dans ce cas ? Enfin, en raison de cette flexibilité, certaines opérations sur les listes sont assez lentes.

Pour surmonter ces limitations, il existe une bibliothèque très utile appelée numpy (pour num eric py thon en anglais) qui permet de faire des tableaux multidimensionnels. Les tableaux peuvent être utilisés pour créer des vecteurs et des matrices. Comme nous le verrons, La bibliothèque numpy dispose également d'outils pour effectuer des opérations mathématiques sur des tableaux en une seule commande. Pour utiliser numpy, vous devez d'abord l'importer. En général, en l'important, vous donnez à numpy le nom d'alias np :

In [2]:
import numpy as np

Créez vos premiers tableaux

Ci-dessous, nous montrons comment créer des tableaux avec des exemples.

In [3]:
a = np.array([1, 2, 3])               # crée un tableau à partir d'une liste
b = np.zeros(3)                       # un tableau rempli de zéros (1-dimension)
c = np.zeros((2, 3))                  # une matrice pleine de zéros (2-dimension)
d = np.zeros((2, 3), dtype=complex)   # par défaut un tableau contient des floats mais cela peut être changé
e = np.ones((3,3))                    # Matrice 3x3 remplie de 1.

print("a =", a)
print("b =", b)
print("c =\n", c)
print("d =\n", d)
print("Dimensions de d:", d.shape)         # afin de connaître les dimensions du tableau
a = [1 2 3]
b = [0. 0. 0.]
c =
 [[0. 0. 0.]
 [0. 0. 0.]]
d =
 [[0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j]]
Dimensions de d: (2, 3)

Autres méthodes communes pour créer des tableaux

Pour éviter d'avoir à créer des tableaux manuellement, les fonctions arange et linspace peuvent être utiles.

  • np.arange(start,stop,pas) a un fonctionnement similaire à range. Les deux différences sont que arange génére un tableau numpy et le pas peut être un 'float'. Comme pour range start est inclus mais stop est exclu.
  • np.linspace(start,stop,N) génère N valeurs entre start et stop. Les valeurs start et stop sont inclus.
In [4]:
# Créer une suite de nombres (comme la commande python range)
a = np.arange(5, 6, 0.1)         # comme pour range cette série s'arrête avant 6 donc ici 5.9
print("a =",a)

# Créer un intervalle de points discrétisés
b = np.linspace(1, 2, 5)       # start, stop, nombre de points
print(b)

# Il est possible de récupérer le pas (step en anglais) en ajoutant un argument
c, pas = np.linspace(1, 2, 5, retstep=True)
print("pas = ", pas)
a = [5.  5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9]
[1.   1.25 1.5  1.75 2.  ]
pas =  0.25

Indices et sections de tableaux

Vous pouvez :

  • accéder à un élément d'un tableau avec les crochets [ ] et éventuellement les virgules , si le tableau a de multiples dimensions.
  • accéder à plusieurs éléments : avec les crochets [ ], les virgules , et les :

Remarque : comme pour une liste, l'indice commence à 0 pour les tableaux numpy.

In [5]:
a = np.random.randint(10, size=(3,3))     # matrice aléatoire 3x3 d'entiers de 0 à 9
print(a)

# Indices
print(a[0, 0])                            # Voici le premier élément de la matrice
print(a[1, 2])                            # deuxième ligne, troisième colonne

# Sections 
print(a[0, :])                            # Ceci est la première ligne
print(a[:, 1])                            # Ceci est la deuxième colonne
print(a[0:2, 0:2])                        # Ceci est le coin en haut à gauche 2x2 

b = a[0:2, :]                             # Ceci est la section supérieure 2x3 
b[0, 1] = 2                               # b est une section de a, si a est modifié b le sera aussi!
print(a)
[[7 4 8]
 [2 5 2]
 [3 3 2]]
7
2
[7 4 8]
[4 5 3]
[[7 4]
 [2 5]]
[[7 2 8]
 [2 5 2]
 [3 3 2]]

PRUDENCE : Observez le dernier exemple avec la commande b = a[0:2, :]. En utilisant le signe =, vous ne dupliquez pas le tableau original. Le tableau b est simplement associé à la partie supérieure 2x3 du tableau a ! Si vous modifiez le tableau b, le tableau a sera également modifié !

C'est une possibilité intéressante car cela n'introduit pas de copie inutile des données mais comme ce comportement est différent d'autres langages, il peut être source d'erreurs. Nous verrons comme procéder pour faire une vraie copie (c-à-d une duplication en mémoire).

Opérations standard avec les tableaux numpy

La règle est que lorsque vous écrivez une opération sur un tableau, elle s'applique sur tous les éléments du tableau. Il en est de même pour des opérations entre deux tableaux de même dimensions. Nous utiliserons beaucoup cette possibilité pour éviter d'écrire des boucles.

In [6]:
a = np.arange(7) # Je crée un tableau 
print(a)
print(a**2) # Je mets au carré tous les éléments du tableau
print(2 * a) # Je multiple par 2 tous les éléments du tableau.

a = np.array([1, 2, 3])
b = np.array([2, 1, 4])

print("a+b  =",a + b)
print("a*b  =",a * b)
[0 1 2 3 4 5 6]
[ 0  1  4  9 16 25 36]
[ 0  2  4  6  8 10 12]
a+b  = [3 3 7]
a*b  = [ 2  2 12]

Application d'une fonction sur un tableau

Les opérations standard ci-dessus agissent sur tous les éléments d'un tableau. Bien entendu, on peut aussi vouloir le faire avec des fonctions, comme par exemple cos. Si vous utilisez la fonction cosinus math.cos() qui est fournie dans la bibliothèque math de la bibliothèque standard de python, elle n'est pas prévue pour agir sur un tableau numpy. Pour cette raison, il faut utiliser les fonctions mathématiques incluses dans le module numpy. Pour cosinus, il faut utiliser np.cos().

In [7]:
a = np.linspace(0, 2*np.pi, 10)    # remarquez que np.pi donne la constante pi 
b = np.cos(a)                      # renvoie dans b un tableau avec les valeurs de cos(a) 
print(b)
[ 1.          0.76604444  0.17364818 -0.5        -0.93969262 -0.93969262
 -0.5         0.17364818  0.76604444  1.        ]

Copie de tableaux : une erreur courante

Lorsque vous voulez copier un tableau, veillez à utiliser .copy() comme dans cet exemple :

a = np.array([1,2,3])
b = a.copy()

En effet, vous pourriez avoir des surprise!. Vérifiez la sortie de ce code :

In [8]:
a = np.array([1, 2, 3])
print("original de a: ", a)

b = a                              # Cette commande  associe b et a
b[0] = 4                           # Comme b est le même que a cette ligne modifie a aussi
print("a  est modifié: ", a)

b = a.copy()
b[0] = 12
print("a est inchangé: ", a)       # car b est indépendant de a
print("b est changé: ", b)
original de a:  [1 2 3]
a  est modifié:  [4 2 3]
a est inchangé:  [4 2 3]
b est changé:  [12  2  3]

Produit scalaire et produit matriciel

Comme déjà vu, pour deux vecteurs ou deux matrices de même taille en écrivant a * b, le résultat correspond à une multiplication éléments par éléments. Si vous voulez plutôt faire un produit scalaire ou une multiplication matricielle, vous devez l'écrire avec @ :

In [9]:
a = np.array([1, 2, 3])
b = np.array([2, 1, 1])

print("produit scalaire de a et b = ", a@b) 

a = np.array([[1, 2], [2, 3]])
b = np.array([[1, 1], [2, 2]])

print("produit matriciel = \n", a@b)

# Si vous utilisez Python version 3.4 ou inférieur, le symbole `@` ne peut pas être utilisé pour la multiplication des matrices
# a @ b -> np.dot(a, b)
# c = np.array([[4, 7], [-10, 15]])
# a @ b @ c -> a.dot(b).dot(c)
produit scalaire de a et b =  7
produit matriciel = 
 [[5 5]
 [8 8]]

Algèbre linéaire

Dans numpy, on peut utiliser une bibliothèque d'algèbre linéaire. numpy.linalg pour :

  • Résoudre des systèmes linéaires numpy.linalg.solve()
  • Calculer le determinant numpy.linalg.det(), les valeurs propres d'une matrice numpy.linalg.eigvals()

Par exemple si on cherche à résoudre le système $Ax = b$ suivant :

$$ \left( {\begin{array}{cc} 1 & 2 \\ 3 & 5 \\ \end{array} } \right) \left( {\begin{array}{c} x_0 \\ x_1 \\ \end{array} } \right) = \left( {\begin{array}{c} 1 \\ 2 \\ \end{array} }\right) $$ les commandes sont :

In [10]:
import numpy as np

A = np.array([[1, 2], [3, 5]]) 
b = np.array([1, 2])
x = np.linalg.solve(A, b)
print(x)
[-1.  1.]

Lecture et écriture dans un fichier avec numpy

Un exemple basique :

In [11]:
tabTest  = np.linspace (0,1,9)  # créer un tableau numpy : [0. 0.125 0.25  0.375 0.5 0.625 0.75  0.875 1.]

# Ecriture de tabTest dans le fichier monFichier.dat 
np.savetxt('monFichier.dat',tabTest)

#Lecture dans un fichier:
mesdata = np.loadtxt('monFichier.dat') # charge les données du fichier monFichier.dat dans le tableau mesdata

Les fichiers ont parfois plusieurs colonnes avec des délimiteurs qui ne sont pas nécessairement un espace (délimiteur par défaut). Il arrive que les premières lignes soient un entête pour décrire les données. Enfin, si on connait à l'avance le nombre de colonnes, on peut vouloir assigner directement un nom de variable à une colonne. Il y a pour tout cela quelques options utiles :

  • Pour lire/écrire sur plusieurs colonnes indiquer/utiliser un délimiteur (espace, virgule, point virgule ...):
    • mesdata = np.loadtxt('monFichier.dat',delimiter=',')
    • np.savetxt('monFichier.dat',tab2Col,delimiter=',')
  • Pour sauter un entête (premières lignes d'un fichier) utiliser skiprows
    • mesdata = np.loadtxt('monFichier.dat',skiprows=3,delimiter=',') # saute trois lignes
  • Pour assigner chaque colonne directement à une variable, utiliser unpack
    • temps,vitesse = np.loadtxt('Mavitesse.dat',unpack=True)

Demander de l'aide

Dans spyder, l'aide sur une fonction numpy (ou matplotlib.pyplot) s'obtient en selectionnant une fonction et la séquence de touches CTRL+i (i comme information)