NumPy : le Guide Complet du Calcul Numérique en Python
Guide complet de NumPy pour maîtriser les tableaux ndarray, le broadcasting, les opérations vectorisées et l'algèbre linéaire en Python avec des exemples concrets.
InSkillCoach
NumPy : le Guide Complet du Calcul Numérique en Python
NumPy (Numerical Python) est la bibliothèque fondamentale du calcul numérique en Python. Elle fournit un objet central, le tableau multidimensionnel ndarray, ainsi qu’un vaste ensemble de fonctions mathématiques optimisées écrites en C. Pratiquement tout l’écosystème scientifique de Python (Pandas, SciPy, scikit-learn, Matplotlib) repose sur NumPy. Ce guide vous présente les concepts essentiels pour manipuler efficacement des tableaux numériques : création, indexation, broadcasting, vectorisation et algèbre linéaire.
Pourquoi NumPy plutôt que les listes Python ?
Les listes Python sont flexibles, mais elles sont lentes pour le calcul numérique. Chaque élément d’une liste est un objet Python complet, avec son propre en-tête mémoire, alors qu’un tableau NumPy stocke ses valeurs dans un bloc mémoire contigu et homogène. Les opérations sont alors déléguées à du code C compilé, ce qui élimine le coût de l’interpréteur.
import numpy as np
import time
n = 10_000_000
liste = list(range(n))
tableau = np.arange(n)
# Multiplication par 2 avec une liste
debut = time.perf_counter()
resultat_liste = [x * 2 for x in liste]
print(f"Liste : {time.perf_counter() - debut:.3f} s") # Liste : 0.520 s (ordre de grandeur)
# Multiplication par 2 avec NumPy
debut = time.perf_counter()
resultat_np = tableau * 2
print(f"NumPy : {time.perf_counter() - debut:.3f} s") # NumPy : 0.015 s (ordre de grandeur)
Le gain est typiquement d’un facteur 10 à 100 selon l’opération.
| Critère | Liste Python | Tableau NumPy |
|---|---|---|
| Types des éléments | Hétérogènes | Homogènes (un seul dtype) |
| Stockage mémoire | Pointeurs vers objets dispersés | Bloc contigu compact |
| Opérations élément par élément | Boucle Python explicite | Vectorisées en C |
| Consommation mémoire | Élevée | Faible |
| Dimensions | Imbrication manuelle | N dimensions natives |
Création de tableaux
À partir de données existantes
import numpy as np
# Depuis une liste
a = np.array([1, 2, 3, 4])
print(a) # [1 2 3 4]
# Tableau 2D depuis une liste de listes
m = np.array([[1, 2, 3], [4, 5, 6]])
print(m)
# [[1 2 3]
# [4 5 6]]
Fonctions de génération
# Tableau de zéros (3 lignes, 4 colonnes)
zeros = np.zeros((3, 4))
# Tableau de uns
uns = np.ones((2, 3))
# Tableau non initialisé (plus rapide, contenu arbitraire)
vide = np.empty((2, 2))
# Valeur constante
sept = np.full((2, 2), 7)
# Matrice identité
identite = np.eye(3)
# Séquence avec un pas (comme range, mais renvoie un ndarray)
seq = np.arange(0, 10, 2)
print(seq) # [0 2 4 6 8]
# Nombre fixe de points également espacés entre deux bornes (bornes incluses)
lin = np.linspace(0, 1, 5)
print(lin) # [0. 0.25 0.5 0.75 1. ]
Préférez linspace à arange pour les valeurs flottantes : le nombre de points est garanti, alors que le pas flottant d’arange peut produire des surprises d’arrondi.
dtype et shape : la carte d’identité d’un tableau
Chaque tableau possède un type d’éléments (dtype) et une forme (shape).
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.shape) # (2, 3) -> 2 lignes, 3 colonnes
print(a.ndim) # 2 -> nombre de dimensions
print(a.size) # 6 -> nombre total d'éléments
print(a.dtype) # int64 (int32 sous Windows)
# Imposer un type à la création
b = np.array([1, 2, 3], dtype=np.float64)
print(b.dtype) # float64
# Conversion de type (crée un nouveau tableau)
c = b.astype(np.int32)
print(c) # [1 2 3]
# Changer la forme sans copier les données
d = np.arange(12).reshape(3, 4)
print(d.shape) # (3, 4)
# Aplatir en 1D
print(d.ravel()) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
Attention aux dépassements de capacité : un int8 ne peut contenir que des valeurs entre -128 et 127. Pour la data science, float64 et int64 sont les choix par défaut sûrs ; réduisez la précision uniquement si la mémoire devient une contrainte.
Indexation et slicing
L’indexation NumPy généralise celle des listes Python aux tableaux multidimensionnels.
a = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(a[0]) # 0
print(a[-1]) # 9
print(a[2:7]) # [2 3 4 5 6]
print(a[::2]) # [0 2 4 6 8]
print(a[::-1]) # [9 8 7 6 5 4 3 2 1 0]
m = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(m[1, 2]) # 6 -> ligne 1, colonne 2
print(m[0]) # [0 1 2 3] -> première ligne
print(m[:, 1]) # [1 5 9] -> deuxième colonne
print(m[1:, 2:]) # [[ 6 7]
# [10 11]]
Point important : un slice NumPy renvoie une vue, pas une copie. Modifier la vue modifie le tableau d’origine.
v = m[0, :]
v[0] = 99
print(m[0, 0]) # 99 -> le tableau original a été modifié
# Pour obtenir une copie indépendante
c = m[0, :].copy()
Masques booléens
Le filtrage par condition est l’un des idiomes les plus puissants de NumPy.
notes = np.array([12, 8, 15, 17, 9, 14])
masque = notes >= 10
print(masque) # [ True False True True False True]
# Sélection des éléments qui vérifient la condition
print(notes[masque]) # [12 15 17 14]
# Conditions combinées : & (et), | (ou), ~ (non), avec parenthèses obligatoires
print(notes[(notes >= 10) & (notes < 16)]) # [12 15 14]
# Modification conditionnelle
notes[notes < 10] = 10
print(notes) # [12 10 15 17 10 14]
# np.where : choisir entre deux valeurs selon une condition
mention = np.where(notes >= 14, "admis avec mention", "admis")
print(mention)
# ['admis' 'admis' 'admis avec mention' 'admis avec mention' 'admis' 'admis avec mention']
Le broadcasting expliqué pas à pas
Le broadcasting est le mécanisme qui permet à NumPy de combiner des tableaux de formes différentes sans copier les données. Les règles sont les suivantes :
- Les formes sont alignées par la droite.
- Deux dimensions sont compatibles si elles sont égales, ou si l’une des deux vaut 1.
- Une dimension de taille 1 est virtuellement étirée pour correspondre à l’autre.
Cas 1 : tableau et scalaire
a = np.array([1, 2, 3])
print(a + 10) # [11 12 13]
# Le scalaire 10 est traité comme un tableau de forme (1,) étiré en (3,)
Cas 2 : matrice et vecteur ligne
m = np.arange(6).reshape(2, 3) # forme (2, 3)
v = np.array([10, 20, 30]) # forme (3,)
# Alignement par la droite : (2, 3) et ( 3) -> compatible
print(m + v)
# [[10 21 32]
# [13 24 35]]
# v est ajouté à chaque ligne de m
Cas 3 : vecteur colonne et vecteur ligne
col = np.array([[1], [2], [3]]) # forme (3, 1)
lig = np.array([10, 20, 30]) # forme (3,)
# (3, 1) et (3,) -> alignées en (3, 1) et (1, 3) -> résultat (3, 3)
print(col + lig)
# [[11 21 31]
# [12 22 32]
# [13 23 33]]
Cas incompatible
a = np.ones((2, 3))
b = np.ones((2, 2))
# a + b -> ValueError: operands could not be broadcast together
# (2, 3) et (2, 2) : la dernière dimension diffère et aucune ne vaut 1
Pour centrer les colonnes d’une matrice, le broadcasting rend le code très concis :
donnees = np.array([[1.0, 200.0], [2.0, 300.0], [3.0, 400.0]])
centrees = donnees - donnees.mean(axis=0)
print(centrees)
# [[-1. -100.]
# [ 0. 0.]
# [ 1. 100.]]
Opérations vectorisées et fonctions universelles
Les fonctions universelles (ufuncs) s’appliquent élément par élément, sans boucle Python.
a = np.array([1.0, 4.0, 9.0])
print(np.sqrt(a)) # [1. 2. 3.]
print(np.exp(a)) # [2.71828183e+00 5.45981500e+01 8.10308393e+03]
print(np.log(a)) # [0. 1.38629436 2.19722458]
angles = np.linspace(0, np.pi, 3)
print(np.sin(angles)) # [0.0000000e+00 1.0000000e+00 1.2246468e-16]
# Opérateurs arithmétiques : tous vectorisés
b = np.array([10.0, 20.0, 30.0])
print(a + b) # [11. 24. 39.]
print(a * b) # [ 10. 80. 270.] -> produit élément par élément
print(b / a) # [10. 5. 3.33333333]
print(a ** 2) # [ 1. 16. 81.]
# Comparaisons vectorisées
print(a > 3) # [False True True]
# Arrondis
x = np.array([1.234, 5.678])
print(np.round(x, 1)) # [1.2 5.7]
print(np.floor(x)) # [1. 5.]
print(np.ceil(x)) # [2. 6.]
La règle d’or : si vous écrivez une boucle for sur les éléments d’un tableau NumPy, il existe presque toujours une formulation vectorisée plus rapide et plus lisible.
Agrégations : sum, mean et le paramètre axis
m = np.array([[1, 2, 3],
[4, 5, 6]])
# Agrégation globale
print(m.sum()) # 21
print(m.mean()) # 3.5
print(m.min(), m.max()) # 1 6
print(m.std()) # 1.707825127659933
# axis=0 : on réduit le long des lignes -> un résultat par colonne
print(m.sum(axis=0)) # [5 7 9]
# axis=1 : on réduit le long des colonnes -> un résultat par ligne
print(m.sum(axis=1)) # [ 6 15]
# Indice du maximum
print(m.argmax()) # 5 (indice dans le tableau aplati)
print(m.argmax(axis=0)) # [1 1 1]
# Sommes cumulées
print(np.cumsum([1, 2, 3, 4])) # [ 1 3 6 10]
Un moyen mnémotechnique : axis désigne la dimension qui disparaît. Avec axis=0 sur une forme (2, 3), le résultat a la forme (3,).
Algèbre linéaire
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
# Produit matriciel : opérateur @ (recommandé) ou np.dot
print(A @ B)
# [[19 22]
# [43 50]]
print(np.dot(A, B)) # résultat identique
# Attention : * est le produit élément par élément, pas matriciel
print(A * B)
# [[ 5 12]
# [21 32]]
# Transposée
print(A.T)
# [[1 3]
# [2 4]]
# Déterminant et inverse
print(np.linalg.det(A)) # -2.0000000000000004
print(np.linalg.inv(A))
# [[-2. 1. ]
# [ 1.5 -0.5]]
# Résolution du système linéaire A x = b (préférable à inv(A) @ b)
b = np.array([5, 11])
x = np.linalg.solve(A, b)
print(x) # [1. 2.]
# Valeurs propres et vecteurs propres
valeurs, vecteurs = np.linalg.eig(A)
print(valeurs) # [-0.37228132 5.37228132]
# Norme d'un vecteur
v = np.array([3.0, 4.0])
print(np.linalg.norm(v)) # 5.0
Génération de nombres aléatoires avec default_rng
Depuis NumPy 1.17, l’API recommandée passe par un générateur explicite créé avec np.random.default_rng, plus rapide et mieux conçue que les anciennes fonctions np.random.rand et compagnie.
# Générateur avec graine pour des résultats reproductibles
rng = np.random.default_rng(seed=42)
# Flottants uniformes dans [0, 1)
print(rng.random(3)) # [0.77395605 0.43887844 0.85859792]
# Entiers uniformes dans [0, 10)
print(rng.integers(0, 10, size=5)) # [8 6 5 2 3]
# Loi normale (moyenne 0, écart-type 1)
print(rng.normal(loc=0, scale=1, size=3)) # [ 0.7611397 -0.86157352 0.36371758]
# Tirage sans remise dans un ensemble
print(rng.choice([10, 20, 30, 40], size=2, replace=False)) # [20 40]
# Mélange en place
a = np.arange(5)
rng.shuffle(a)
print(a) # [4 0 3 1 2] (par exemple)
Lecture et écriture de tableaux
a = np.arange(12).reshape(3, 4)
# Format binaire natif .npy : rapide, préserve dtype et shape
np.save("tableau.npy", a)
b = np.load("tableau.npy")
print(np.array_equal(a, b)) # True
# Plusieurs tableaux dans une archive .npz
np.savez("archive.npz", donnees=a, labels=np.array([0, 1, 2]))
archive = np.load("archive.npz")
print(archive["labels"]) # [0 1 2]
# Format texte : lisible mais plus lent et moins précis
np.savetxt("tableau.csv", a, delimiter=",", fmt="%d")
c = np.loadtxt("tableau.csv", delimiter=",")
print(c.dtype) # float64 (loadtxt lit en float par défaut)
Pour les fichiers CSV avec en-têtes, colonnes hétérogènes ou valeurs manquantes, préférez pandas.read_csv, bien plus robuste que loadtxt.
Bonnes pratiques
- Vectorisez systématiquement : remplacez les boucles
forpar des opérations sur tableaux entiers ; c’est plus rapide et plus lisible. - Méfiez-vous des vues : les slices partagent la mémoire du tableau d’origine ; utilisez
.copy()quand vous avez besoin d’un tableau indépendant. - Fixez la graine aléatoire avec
np.random.default_rng(seed)pour rendre vos expériences reproductibles. - Utilisez
@pour le produit matriciel et réservez*au produit élément par élément afin d’éviter les confusions. - Préférez
np.linalg.solveà l’inversion de matrice pour résoudre un système linéaire : c’est plus stable numériquement. - Vérifiez
shapeetdtypedès qu’un résultat vous surprend : la plupart des bugs NumPy viennent d’un broadcasting involontaire ou d’un type inattendu. - Choisissez le format
.npy/.npzpour sauvegarder des tableaux entre sessions Python, et le CSV uniquement pour l’échange avec d’autres outils.
Conclusion
NumPy est le socle sur lequel repose toute l’analyse de données en Python. En maîtrisant le ndarray, l’indexation, les masques booléens, le broadcasting et les opérations vectorisées, vous disposez des outils nécessaires pour écrire du code numérique à la fois concis et performant. Ces concepts se retrouvent directement dans Pandas (dont les colonnes sont des tableaux NumPy), dans SciPy (qui étend NumPy au calcul scientifique avancé) et dans les bibliothèques de machine learning. La prochaine étape naturelle consiste à explorer Pandas pour la manipulation de données tabulaires, puis Matplotlib pour la visualisation.
À propos de InSkillCoach
Expert en formation et technologies
Coach spécialisé dans les technologies avancées et l'IA, porté par GNeurone Inc.
Certifications:
- AWS Certified Solutions Architect – Professional
- Certifications Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion