Calcul scientifique avec NumPy
Cours · Python (école d'ingénieur), chapitre 8 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
8.1 Introduction et Motivation
Pour faire du calcul numérique, de la simulation physique ou du traitement d'images, les listes standard de Python se révèlent inefficaces en temps de calcul et en mémoire car elles contiennent des objets hétérogènes pointés individuellement. La bibliothèque externe NumPy (Numerical Python) introduit les tableaux multidimensionnels homogènes (ndarray) contigus en mémoire. Écrits en C et optimisés pour le CPU, ces tableaux permettent d'effectuer des calculs complexes sur des millions d'éléments sans écrire de boucles lentes en Python, grâce au mécanisme de la vectorisation.
NumPy ne fait pas partie de la bibliothèque standard. Il doit être installé avec :
pip install numpy
L'import standard est : import numpy as np
8.2 Création de Tableaux et Attributs
Le composant central de NumPy est l'objet ndarray (N-dimensional array).
8.2.1 Fonctions de création
Il existe plusieurs fonctions de commodité pour initialiser des tableaux NumPy :
np.array(liste): Convertit une liste Python standard.np.zeros(shape): Tableau rempli de 0.np.ones(shape): Tableau rempli de 1.np.eye(n): Matrice identité .np.empty(shape): Tableau non initialisé (contenu aléatoire).np.full(shape, value): Tableau rempli d'une valeur constante.np.arange(start, stop, step): Séquence d'entiers ou réels réguliers.np.linspace(start, stop, num): points régulièrement espacés.np.logspace(start, stop, num): points espacés logarithmiquement.
import numpy as np
# À partir d'une liste
a = np.array([1, 2, 3, 4]) # [1 2 3 4]
# Zéros et uns
zeros = np.zeros((2, 3)) # matrice 2x3 de zéros
uns = np.ones((3, 2)) # matrice 3x2 de uns
identite = np.eye(4) # matrice identité 4x4
# Séquences
x = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
y = np.linspace(0, 1, 5) # [0., 0.25, 0.5, 0.75, 1.]
z = np.logspace(0, 3, 4) # [1., 10., 100., 1000.]
8.2.2 Le module np.random
Pour les simulations et les initialisations aléatoires :
# Fixer la graine pour la reproductibilité
np.random.seed(42)
# Distribution uniforme sur [0, 1[
uniformes = np.random.rand(3, 4) # 3x4 de valeurs dans [0, 1[
# Loi normale centrée réduite
normales = np.random.randn(1000) # 1000 valeurs N(0,1)
# Entiers aléatoires
entiers = np.random.randint(0, 10, size=(2, 3)) # 2x3 d'entiers entre 0 et 9
# Échantillonnage
echantillon = np.random.choice([1, 2, 3, 4, 5], size=10, replace=True)
8.2.3 Attributs fondamentaux
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.shape) # (2, 3) : dimensions
print(a.ndim) # 2 : nombre de dimensions
print(a.size) # 6 : nombre total d'éléments
print(a.dtype) # int64 : type des éléments
8.2.4 Types de données (dtype)
NumPy permet de contrôler finement les types pour optimiser la mémoire :
# Types courants : int32, int64, float32, float64, bool, complex128
a = np.array([1, 2, 3], dtype=np.float32)
b = a.astype(np.int64) # Conversion de type
c = np.array([True, False, True]) # booléen
8.3 Opérations Vectorisées et Broadcasting
La force de NumPy réside dans sa capacité à appliquer des opérateurs directement sur les tableaux, sans boucles explicites.
8.3.1 Opérations arithmétiques élément par élément
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
print(a + b) # [11, 22, 33, 44]
print(a * b) # [10, 40, 90, 160]
print(b - a) # [9, 18, 27, 36]
print(b / a) # [10., 10., 10., 10.]
print(a ** 2) # [1, 4, 9, 16]
8.3.2 Fonctions mathématiques universelles (ufuncs)
x = np.array([0, np.pi/2, np.pi])
print(np.sin(x)) # [0., 1., 0.]
print(np.cos(x)) # [1., 0., -1.]
print(np.exp(x)) # exponentielle
print(np.log(x)) # logarithme népérien
print(np.sqrt(x)) # racine carrée
8.3.3 Broadcasting
Le broadcasting permet d'effectuer des opérations entre tableaux de formes différentes.
Deux dimensions sont compatibles si :
- Elles sont égales, ou
- L'une d'elles vaut 1.
Le tableau de dimension 1 est ``étiré'' pour correspondre à l'autre.
# Scalaire + tableau
v = np.array([1, 2, 3])
print(v + 10) # [11, 12, 13] (10 est broadcasté)
# Vecteur + matrice
mat = np.array([[1, 2, 3], [4, 5, 6]])
vec = np.array([10, 20, 30])
print(mat + vec) # [[11, 22, 33], [14, 25, 36]]
# Vecteur colonne + vecteur ligne
col = np.array([[1], [2], [3]]) # (3, 1)
row = np.array([10, 20, 30]) # (3,)
print(col + row) # [[11, 21, 31], [12, 22, 32], [13, 23, 33]]
8.4 Opérations d'Agrégation
NumPy fournit des fonctions pour résumer les données d'un tableau.
| Fonction | Description | Paramètre axis |
|---|---|---|
np.sum() | Somme des éléments | Oui |
np.mean() | Moyenne | Oui |
np.std() | Écart-type | Oui |
np.var() | Variance | Oui |
np.min() | Valeur minimale | Oui |
np.max() | Valeur maximale | Oui |
np.argmin() | Indice du minimum | Oui |
np.argmax() | Indice du maximum | Oui |
np.cumsum() | Somme cumulée | Oui |
np.cumprod() | Produit cumulé | Oui |
mat = np.array([[1, 2, 3],
[4, 5, 6]])
print(np.sum(mat)) # 21 (tous les éléments)
print(np.sum(mat, axis=0)) # [5, 7, 9] (somme par colonne)
print(np.sum(mat, axis=1)) # [6, 15] (somme par ligne)
print(np.mean(mat, axis=0)) # [2.5, 3.5, 4.5]
print(np.max(mat, axis=1)) # [3, 6]
print(np.argmax(mat, axis=0)) # [1, 1, 1] (indices des max par colonne)
8.5 Indexation et Slicing
L'indexation des tableaux NumPy est similaire à celle des listes Python, mais étendue aux dimensions multiples.
8.5.1 Indexation classique (1D et 2D)
# 1D
a = np.array([10, 20, 30, 40, 50])
print(a[0]) # 10
print(a[-1]) # 50
print(a[1:4]) # [20, 30, 40]
print(a[::2]) # [10, 30, 50]
# 2D
mat = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(mat[0, 1]) # 2 (ligne 0, colonne 1)
print(mat[1]) # [4, 5, 6] (ligne 1 entière)
print(mat[:, 0]) # [1, 4, 7] (colonne 0)
print(mat[0:2, 1:3]) # [[2, 3], [5, 6]] (sous-matrice)
8.5.2 Masques booléens (filtrage)
a = np.array([10, -5, 8, -2, 12, -1])
positifs = a[a > 0] # [10, 8, 12]
negatifs = a[a < 0] # [-5, -2, -1]
entre = a[(a > 0) & (a < 10)] # [8] (ET logique)
# Modification conditionnelle
a[a < 0] = 0 # remplace les négatifs par 0
print(a) # [10, 0, 8, 0, 12, 0]
8.5.3 Indexation par listes d'indices
a = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4]
print(a[indices]) # [10, 30, 50]
mat = np.array([[1, 2], [3, 4], [5, 6]])
lignes = [0, 2]
colonnes = [1, 0]
print(mat[lignes, colonnes]) # [2, 5] (éléments (0,1) et (2,0))
8.6 Manipulation de la Forme
8.6.1 reshape, ravel, flatten
a = np.arange(12) # [0, 1, 2, ..., 11]
# Reshape (réorganisation)
b = a.reshape(3, 4) # 3 lignes, 4 colonnes
c = a.reshape(2, 2, 3) # 3 dimensions
# Aplatissement
d = b.flatten() # Copie aplatie
e = b.ravel() # Vue aplatie (plus efficace)
# Transposition
f = b.T # Transposée (4, 3)
8.6.2 Concaténation et stacking
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# Concaténation
c = np.concatenate([a, b], axis=0) # vertical (4, 2)
d = np.concatenate([a, b], axis=1) # horizontal (2, 4)
# Stacking
e = np.vstack([a, b]) # équivalent à axis=0
f = np.hstack([a, b]) # équivalent à axis=1
g = np.dstack([a, b]) # empilement en profondeur (2, 2, 2)
8.7 Copies et Vues
La distinction entre vue (view) et copie est cruciale pour comprendre les performances et éviter les effets de bord.
- Vue : Partage les données avec le tableau original. Toute modification affecte l'original. Créée par le slicing (
a[::2]) oureshape(). - Copie : Nouveau tableau indépendant. Créée par
copy()ouflatten().
original = np.arange(10)
vue = original[::2] # vue (indices pairs)
copie = original.copy() # copie indépendante
vue[0] = 99
print(original) # [99, 1, 2, 3, 4, 5, 6, 7, 8, 9] (modifié !)
copie[0] = 999
print(original) # [99, 1, 2, 3, 4, 5, 6, 7, 8, 9] (inchangé)
# Vérification
print(vue.base is original) # True (vue partage les données)
print(copie.base is original) # False (copie indépendante)
8.8 Sauvegarde et Chargement
# Sauvegarde d'un seul tableau (format binaire .npy)
np.save("mon_tableau.npy", a)
a_charge = np.load("mon_tableau.npy")
# Sauvegarde de plusieurs tableaux (format .npz)
np.savez("mes_donnees.npz", x=x, y=y, z=z)
donnees = np.load("mes_donnees.npz")
print(donnees['x'])
# Sauvegarde en CSV (texte)
np.savetxt("donnees.csv", mat, delimiter=",", header="col1,col2,col3")
mat_charge = np.loadtxt("donnees.csv", delimiter=",")
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.