NumPy Cheatsheet
Référence professionnelle - Calcul scientifique Python
Import conventionnel
import numpy as np
Convention universelle. Toutes les références ci-dessous supposent cet alias.
Création de tableaux
Depuis des données Python
a = np.array([1, 2, 3])
Tableau 1D depuis une liste.
a = np.array([[1, 2], [3, 4]], dtype=np.float64)
Tableau 2D avec type explicite.
a = np.asarray(existing_list)
Évite une copie si la source est déjà un tableau compatible.
Tableaux initialisés
np.zeros((3, 4))
Tableau 3x4 de zéros (float64 par défaut).
np.ones((2, 3), dtype=np.int32)
Tableau 2x3 de uns en entier 32 bits.
np.full((3, 3), 7.0)
Tableau rempli d'une valeur constante.
np.empty((100, 100))
Allocation sans initialisation. Plus rapide quand la valeur initiale importe peu.
np.eye(4)
Matrice identité 4x4.
np.diag([1, 2, 3])
Matrice diagonale depuis un vecteur. np.diag(A) extrait la diagonale d'une matrice.
np.zeros_like(a)
np.ones_like(a)
np.empty_like(a)
Crée un tableau de même forme et dtype que a.
Séquences
np.arange(0, 10, 2)
Entiers de 0 à 8, pas 2. Analogue de range.
np.linspace(0, 1, 50)
50 valeurs régulièrement espacées entre 0 et 1 inclus.
np.logspace(0, 3, 4)
4 valeurs espacées logarithmiquement entre 10^0 et 10^3.
array([1., 10., 100., 1000.])np.geomspace(1, 1000, 4)
Progression géométrique entre les valeurs spécifiées.
Grilles
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
X, Y = np.meshgrid(x, y)
Grille 2D pour tracer des surfaces ou évaluer des fonctions f(x, y).
xi = np.mgrid[0:5, 0:5]
xi = np.ogrid[0:5, 0:5]
mgrid retourne des tableaux complets. ogrid retourne des tableaux ouverts (économie mémoire).
Attributs d'un tableau
a.shape
Dimensions sous forme de tuple.
a.ndim
Nombre d'axes.
a.size
Nombre total d'éléments.
a.dtype
Type des éléments : float64, int32, complex128, bool, etc.
a.itemsize
Taille en octets d'un élément.
a.nbytes
Taille totale en mémoire : a.size * a.itemsize.
a.strides
Pas en octets pour chaque axe. Clé pour comprendre la disposition mémoire.
a.flags
Indique si le tableau est C-contigu, F-contigu, propriétaire, inscriptible.
a.T # transposé
a.real # partie réelle
a.imag # partie imaginaire
a.flat # itérateur sur les éléments
a.data # buffer mémoire brut
Attributs accesseurs courants.
Conversion de type
a.astype(np.float32)
Retourne une copie avec le nouveau dtype. Ne modifie pas a.
a.astype(np.int64, copy=False)
Évite la copie si le dtype est déjà compatible.
Reshape et manipulation des dimensions
a.reshape(3, 4)
Retourne une vue de forme (3, 4) si possible, sinon une copie. Ne modifie pas a.
a.reshape(3, -1)
-1 : dimension inférée automatiquement.
a.flatten()
Retourne une copie 1D. Toujours une copie.
a.ravel()
Retourne une vue 1D si possible, sinon une copie. Préférer à flatten pour les performances.
a.T
np.transpose(a)
np.transpose(a, axes=(2, 0, 1))
Transposition. Avec axes, permutation arbitraire des dimensions.
np.swapaxes(a, 0, 1)
Échange deux axes.
np.squeeze(a)
np.squeeze(a, axis=0)
Supprime les dimensions de taille 1.
np.expand_dims(a, axis=0)
a[np.newaxis, :]
a[:, np.newaxis]
Ajoute un axe. Indispensable pour aligner des formes en broadcasting.
Assemblage et découpage
np.concatenate([a, b], axis=0)
Concatène selon un axe existant.
np.stack([a, b], axis=0)
Crée un nouvel axe. Les tableaux doivent avoir la même forme.
np.vstack([a, b]) # axis=0
np.hstack([a, b]) # axis=1
Raccourcis pour l'empilement vertical et horizontal.
np.split(a, 3, axis=0)
np.array_split(a, 5)
split exige des parties égales. array_split accepte des tailles inégales.
Indexing et slicing
Slicing de base
a[2] # élément
a[-1] # dernier
a[1:4] # indices 1, 2, 3
a[::2] # un sur deux
a[::-1] # ordre inverse
Slicing 1D standard. Retourne toujours une vue.
A[0, 1] # ligne 0, colonne 1
A[1:3, :] # lignes 1 et 2, toutes colonnes
A[:, 2] # colonne 2 (vecteur 1D)
A[:, 2:3] # colonne 2 (matrice 2D)
Slicing multidimensionnel. A[:, 2] et A[:, 2:3] donnent des formes différentes.
Indexing booléen
mask = a > 5
a[mask] # éléments satisfaisant la condition
a[a % 2 == 0] # éléments pairs
Retourne une copie. La forme du résultat est 1D.
a[mask] = 0 # affectation conditionnelle
np.where(a > 5, a, 0) # ternaire vectorisé
np.where avec trois arguments remplace le masque sans créer de copie intermédiaire.
Fancy indexing
idx = [0, 2, 4]
a[idx] # sélection par liste d'indices
A[[0, 1], [2, 3]] # paires (0,2) et (1,3)
Retourne toujours une copie. Syntaxe différente du slicing.
rows = np.array([0, 2])
cols = np.array([1, 3])
A[rows[:, np.newaxis], cols]
Sélection d'un sous-bloc non contigu via broadcasting d'indices.
idx = np.nonzero(a > 5)
a[idx]
np.nonzero retourne les indices des éléments non nuls (ou vérifiant une condition via np.where(cond)).
Attention : le slicing (a[1:3]) produit une vue. Le fancy indexing (a[[1, 3]]) produit une copie. Modifier le
résultat d'un fancy indexing ne modifie pas le tableau source.
Broadcasting
Règles
# Alignement des formes depuis la droite.
# Une dimension vaut 1 : elle est étendue.
# Les dimensions incompatibles lèvent ValueError.
# Exemple : (3, 4) + (4,) -> (3, 4) OK
# Exemple : (3, 4) + (3,) -> erreur
# Exemple : (3, 1) + (1, 4) -> (3, 4) OK
Comparaison dimension par dimension de droite à gauche. Une dimension manquante est traitée comme 1.
Exemples courants
a = np.ones((3, 4))
b = np.array([1, 2, 3, 4]) # (4,)
a + b # -> (3, 4)
Addition ligne par ligne.
col = np.array([[1], [2], [3]]) # (3, 1)
row = np.array([10, 20, 30, 40]) # (4,)
col + row # -> (3, 4)
Produit extérieur-like.
X = np.random.default_rng().random((100, 5))
mean = X.mean(axis=0) # (5,)
X_centered = X - mean # (100, 5) - (5,)
Centrage d'un jeu de données par colonne.
d = np.linalg.norm(
X[:, np.newaxis] - X[np.newaxis, :],
axis=-1
)
Matrice de distances euclidiennes (N, N) sans boucle.
Pièges courants
a = np.arange(6).reshape(2, 3)
b = np.arange(2) # (2,) != (3,)
a + b # ValueError
L'alignement se fait par la droite : (2, 3) et (2,) sont incompatibles car 3 != 2.
# Corriger avec newaxis
b = b[:, np.newaxis] # (2, 1)
a + b # (2, 3) OK
Forcer la dimension pour l'axe correct.
Opérations vectorisées
Arithmétique élémentaire
a + b, a - b
a * b, a / b
a // b, a % b
a ** b
-a
Opérateurs Python standard, appliqués élément par élément.
np.add(a, b)
np.subtract(a, b)
np.multiply(a, b)
np.divide(a, b)
np.power(a, b)
Ufuncs équivalentes. Acceptent un paramètre out pour éviter une allocation.
Trigonométrie
np.sin(a)
np.cos(a)
np.tan(a)
np.arcsin(a)
np.arctan2(y, x)
Toutes les fonctions trig classiques. arctan2 gère le quadrant.
np.degrees(a) # rad -> deg
np.radians(a) # deg -> rad
np.pi # constante pi
Conversions angulaires.
Exponentielles et logarithmes
np.exp(a)
np.exp2(a) # 2**a
np.expm1(a) # exp(a) - 1, précis pour a proche de 0
Fonctions exponentielles.
np.log(a) # log naturel
np.log2(a)
np.log10(a)
np.log1p(a) # log(1 + a), précis pour a proche de 0
Fonctions logarithmiques.
Arrondi et valeurs spéciales
np.round(a, decimals=2)
np.floor(a)
np.ceil(a)
np.trunc(a)
Arrondi classique, plancher, plafond, troncature.
np.abs(a)
np.sign(a)
np.clip(a, a_min, a_max)
np.sqrt(a)
np.cbrt(a)
Valeur absolue, signe, clipping, racines carrée et cubique.
np.isnan(a)
np.isinf(a)
np.isfinite(a)
np.nan_to_num(a, nan=0.0)
Détection et traitement des valeurs spéciales IEEE 754.
np.maximum(a, b) # max élément par élément
np.minimum(a, b)
np.fmax(a, b) # ignore les NaN
Maximum et minimum élément par élément entre deux tableaux.
Statistiques
Réductions
a.sum()
a.sum(axis=0) # somme par colonne
a.sum(axis=1) # somme par ligne
a.sum(axis=0, keepdims=True)
keepdims=True conserve la dimension réduite pour le broadcasting.
a.mean()
a.mean(axis=0)
np.nanmean(a) # ignore les NaN
Moyenne arithmétique.
np.median(a)
np.median(a, axis=0)
Médiane. Pas de méthode .median() sur ndarray, utiliser np.median.
a.std() # écart-type
a.var() # variance
a.std(ddof=1) # correction de Bessel (échantillon)
Par défaut, division par N (population). ddof=1 divise par N-1.
a.min()
a.max()
a.min(axis=0)
np.nanmin(a)
np.nanmax(a)
Extremums globaux ou par axe.
np.percentile(a, [25, 50, 75])
np.quantile(a, [0.25, 0.75], axis=0)
percentile prend des valeurs entre 0 et 100. quantile prend des valeurs entre 0 et 1.
Cumul et comptage
np.cumsum(a)
np.cumsum(a, axis=0)
np.cumprod(a)
Sommes et produits cumulatifs.
np.diff(a)
np.diff(a, n=2) # différences d'ordre 2
np.gradient(a)
Différences consécutives. gradient utilise des différences centrées.
np.histogram(a, bins=10)
np.histogram2d(x, y, bins=20)
np.digitize(a, bins)
Histogrammes et assignation de bacs.
np.corrcoef(x, y) # matrice de corrélation
np.cov(X.T) # matrice de covariance
Corrélation et covariance. X.T : variables en lignes.
Tri et recherche
np.sort(a)
np.sort(a, axis=0)
a.sort() # en place
np.sort retourne une copie. a.sort() modifie a.
np.argsort(a)
np.argsort(a, axis=0)
a[np.argsort(a)] # équivaut à np.sort(a)
Retourne les indices qui trieraient le tableau.
np.unique(a)
np.unique(a, return_counts=True)
np.unique(a, return_inverse=True)
Valeurs uniques triées. return_counts : occurrences. return_inverse : indices de reconstruction.
np.where(a > 5) # indices des vrais
np.where(a > 5, a, 0) # remplacement conditionnel
Deux modes distincts selon le nombre d'arguments.
a.argmax()
a.argmax(axis=0)
a.argmin()
np.argmax(a, axis=1)
Indice du maximum ou minimum le long d'un axe.
np.searchsorted(sorted_a, v)
np.searchsorted(sorted_a, v, side='right')
Recherche binaire dans un tableau trié.
np.partition(a, k)
np.argpartition(a, k)
Les k plus petits éléments garantis avant la k-ième position. Plus rapide que le tri complet.
np.count_nonzero(a)
np.count_nonzero(a > 5)
Compte les éléments non nuls ou vérifiant une condition.
Algèbre linéaire
Produits
A @ B # produit matriciel
np.dot(A, B) # équivalent
np.matmul(A, B) # idem, pas de scalaire
@ est l'opérateur recommandé depuis Python 3.5. A * B est le produit élément par élément.
np.inner(a, b) # produit interne (vecteurs)
np.outer(a, b) # produit extérieur
np.cross(a, b) # produit vectoriel 3D
np.tensordot(A, B, axes=1)
Produits vectoriels et tensoriels.
np.einsum('ij,jk->ik', A, B) # @
np.einsum('ii->i', A) # diagonale
np.einsum('ij->i', A) # somme lignes
np.einsum('i,j->ij', a, b) # produit externe
Notation Einstein. Exprime tout contraction/produit en une ligne.
Décompositions et résolution
np.linalg.solve(A, b)
Résout Ax = b. Préférer à inv(A) @ b (plus stable et plus rapide).
np.linalg.inv(A)
Inverse. Coûteux : éviter si possible. Utiliser solve.
np.linalg.det(A)
Déterminant.
vals, vecs = np.linalg.eig(A)
vals, vecs = np.linalg.eigh(A) # A symétrique
eigh est plus rapide et garantit des valeurs propres réelles pour les matrices symétriques.
U, s, Vh = np.linalg.svd(A)
U, s, Vh = np.linalg.svd(A, full_matrices=False)
Décomposition en valeurs singulières. full_matrices=False : forme réduite (économique).
np.linalg.qr(A)
Décomposition QR.
np.linalg.cholesky(A)
Factorisation de Cholesky pour matrices définies positives.
x, res, rank, sv = np.linalg.lstsq(A, b, rcond=None)
Moindres carrés. Résout les systèmes surdéterminés.
Normes et rang
np.linalg.norm(a) # norme L2
np.linalg.norm(a, ord=1) # norme L1
np.linalg.norm(A, ord='fro') # norme de Frobenius
Normes vectorielles et matricielles.
np.linalg.matrix_rank(A)
np.linalg.cond(A) # nombre de condition
np.trace(A) # trace (somme diagonale)
Informations structurelles d'une matrice.
Génération aléatoire moderne
API recommandée : np.random.default_rng() remplace np.random.seed(). L'API legacy (np.random.rand,
np.random.randn, etc.) reste fonctionnelle mais n'est plus recommandée pour du code nouveau.
Initialisation
rng = np.random.default_rng()
rng = np.random.default_rng(seed=42)
Crée un générateur reproductible. L'état est local à l'objet rng.
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)
# rng1 et rng2 produisent les mêmes séquences
Reproductibilité garantie par la graine.
Distributions
rng.random((3, 4)) # U[0, 1)
Distribution uniforme continue.
rng.uniform(low=0, high=10, size=(3, 4))
Uniforme sur [low, high).
rng.normal(loc=0, scale=1, size=(100,))
Distribution normale.
rng.integers(0, 10, size=(5, 5))
rng.integers(0, 10, size=(5, 5), endpoint=True)
Entiers dans [low, high). Avec endpoint=True : [low, high].
rng.exponential(scale=1.0, size=100)
rng.poisson(lam=3.0, size=100)
rng.binomial(n=10, p=0.5, size=100)
rng.beta(a=2, b=5, size=100)
Distributions statistiques courantes.
rng.standard_normal(size=(3, 3))
N(0, 1). Légèrement plus rapide que normal(0, 1, ...).
Échantillonnage et permutations
rng.choice(a, size=5)
rng.choice(a, size=5, replace=False) # sans remise
rng.choice(a, size=5, p=[0.1, 0.2, ...])
Tirage dans un tableau ou une population.
rng.shuffle(a) # en place
b = rng.permutation(a) # copie mélangée
shuffle modifie a. permutation retourne une nouvelle copie.
Vues et copies
b = a.view() # même données, autre objet
b = a.copy() # copie indépendante
b = a[:] # VUE, pas une copie
b = a[[0, 1, 2]] # copie (fancy indexing)
b = a[1:3] # vue (slicing)
La distinction vue/copie est critique pour les performances et les effets de bord.
b = a.view()
b.base is a # True : b partage les données de a
b.flags.owndata # False : b ne possède pas ses données
c = a.copy()
c.base is None # True : c est indépendant
Vérifier si un tableau est une vue via .base et .flags.owndata.
a = np.arange(12).reshape(3, 4)
b = a[:, ::2] # vue des colonnes paires
b[0, 0] = 99 # modifie aussi a
Modifier une vue modifie le tableau source.
a = np.arange(12)
b = a.reshape(3, 4) # vue si possible
b = np.ascontiguousarray(a.T) # force une copie C-contiguë
reshape retourne une vue tant que la disposition mémoire le permet.
Piège classique : b = a[:] est une vue, pas une copie. Pour copier : b = a.copy(). Modifier b après
b = a[:] modifie a.
Concepts avancés
Strides et disposition mémoire
a = np.arange(12, dtype=np.float64).reshape(3, 4)
a.strides # (32, 8) : 32 octets pour changer de ligne, 8 pour changer de colonne
Les strides définissent comment NumPy navigue en mémoire. Comprendre les strides permet de prédire les performances d'accès.
a = np.array([[1, 2], [3, 4]], order='C') # C-contigu (row-major)
b = np.array([[1, 2], [3, 4]], order='F') # F-contigu (col-major, Fortran)
a.flags['C_CONTIGUOUS'] # True
b.flags['F_CONTIGUOUS'] # True
C-contigu : lignes consécutives en mémoire. F-contigu : colonnes consécutives. Impacte les performances des algos par ligne vs par colonne.
a = np.lib.stride_tricks.as_strided(
x,
shape=(n - k + 1, k),
strides=(x.strides[0], x.strides[0])
)
Manipulation directe des strides. Permet de créer des fenêtres glissantes sans copie. Utiliser avec précaution : pas de vérification de bornes.
Ufuncs
np.add(a, b, out=c) # sortie dans c
np.multiply(a, b, where=mask) # conditionnel
np.add.reduce(a) # équivaut à sum
np.add.accumulate(a) # équivaut à cumsum
np.add.outer(a, b) # produit extérieur
Les ufuncs exposent des méthodes de réduction et d'accumulation. out évite une allocation intermédiaire.
f = np.frompyfunc(lambda x: x**2 + 1, 1, 1)
f(a) # ufunc à partir d'une fonction Python
Transforme une fonction scalaire en ufunc. Utile mais plus lent que les ufuncs natives (pas de compilation).
einsum
# Produit matriciel
np.einsum('ij,jk->ik', A, B)
# Trace d'une matrice
np.einsum('ii->', A)
# Produit scalaire
np.einsum('i,i->', a, b)
# Transposition
np.einsum('ij->ji', A)
# Somme sur un axe
np.einsum('ij->i', A) # somme par ligne
# Produit externe
np.einsum('i,j->ij', a, b)
# Contraction de tenseur (batch matmul)
np.einsum('bij,bjk->bik', A, B)
La notation Einstein exprime n'importe quelle contraction. Les indices répétés à gauche et absents à droite sont contractés (sommés).
Réduction selon les axes
A = np.random.default_rng(0).random((4, 3, 2))
A.sum(axis=0) # -> (3, 2)
A.sum(axis=1) # -> (4, 2)
A.sum(axis=2) # -> (4, 3)
A.sum(axis=(0, 2)) # -> (3,)
Réduction multi-axes. L'axe réduit disparaît du résultat sauf avec keepdims=True.
A.sum(axis=0, keepdims=True) # -> (1, 3, 2)
# Permet le broadcasting direct
A / A.sum(axis=0, keepdims=True)
keepdims=True préserve la forme pour les opérations suivantes.
Vectorisation
vf = np.vectorize(scalar_func)
vf(a) # applique scalar_func élément par élément
np.vectorize est un wrapper pratique. N'apporte pas de performance : boucle interne en Python. Utiliser uniquement quand les ufuncs natives
ne suffisent pas.
# Lent
result = np.array([f(x) for x in a])
# Rapide : écrire f en opérations NumPy
result = np.sqrt(a**2 + 1)
La vraie vectorisation consiste à exprimer les calculs avec des opérations NumPy, pas à boucler sur les éléments.
Performances
Allocations et mémoire
out = np.empty_like(a)
np.add(a, b, out=out) # pas d'allocation
Le paramètre out des ufuncs évite une allocation intermédiaire dans les boucles critiques.
np.ascontiguousarray(a)
np.asfortranarray(a)
Forcer la contiguïté améliore les performances des algos qui accèdent à la mémoire séquentiellement.
a = np.float32(a) # moitié de la mémoire vs float64
a = a.astype(np.float32)
float32 divise l'empreinte mémoire par deux. Acceptable pour la plupart des applications ML.
Pièges courants
# Piège : copie inutile en concaténation itérative
for row in data:
result = np.concatenate([result, row]) # O(n^2)
# Correct : accumuler en liste, convertir une fois
rows = []
for row in data:
rows.append(row)
result = np.array(rows) # O(n)
Les concaténations itératives ont un coût quadratique. Construire la liste Python en entier puis convertir.
A * B # produit élément par élément
A @ B # produit matriciel
# Ne pas confondre
* n'est jamais un produit matriciel. C'est une source fréquente d'erreur silencieuse.
a[:, 0] # shape: (n,) vecteur 1D
a[:, 0:1] # shape: (n, 1) matrice colonne
a[:, [0]] # shape: (n, 1) copie
# Les trois ont des comportements différents en broadcasting
La distinction entre (n,) et (n, 1) impacte le broadcasting. Utiliser reshape ou np.newaxis pour lever l'ambiguïté.
view = a[1:3] # vue
copy = a[[1, 3]] # copie (fancy)
view[0] = 99 # modifie a
copy[0] = 99 # ne modifie pas a
Slicing simple : vue. Fancy indexing : copie. Comportements opposés lors d'une affectation.
Cas pratiques
Normalisation et centrage
X = rng.random((100, 5))
# Centrage-réduction (standardisation)
mean = X.mean(axis=0)
std = X.std(axis=0)
X_std = (X - mean) / std
Standardisation colonne par colonne via broadcasting.
# Normalisation min-max vers [0, 1]
xmin = X.min(axis=0)
xmax = X.max(axis=0)
X_norm = (X - xmin) / (xmax - xmin)
Chaque colonne mise à l'échelle indépendamment.
# Normalisation L2 par ligne
norms = np.linalg.norm(X, axis=1, keepdims=True)
X_unit = X / norms
Chaque ligne devient un vecteur unitaire.
Distances
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
# Distance euclidienne entre deux vecteurs
np.linalg.norm(a - b)
# Matrice de distances euclidiennes (N x N) sans boucle
X = rng.random((50, 3))
diff = X[:, np.newaxis] - X[np.newaxis, :] # (50, 50, 3)
D = np.linalg.norm(diff, axis=-1) # (50, 50)
Matrice de distances complète en deux lignes via broadcasting.
Filtrage et statistiques conditionnelles
data = rng.normal(0, 1, size=10000)
# Valeurs au-delà de 2 écarts-types
outliers = data[np.abs(data) > 2]
print(f"{len(outliers)} valeurs aberrantes")
Détection de valeurs aberrantes par seuillage.
A = rng.random((100, 5))
# Statistiques par colonne
col_means = A.mean(axis=0) # (5,)
col_stds = A.std(axis=0) # (5,)
# Statistiques par ligne
row_sums = A.sum(axis=1) # (100,)
Réductions selon les axes pour des statistiques agrégées.
labels = rng.integers(0, 3, size=100)
data = rng.random((100, 4))
# Moyenne par groupe
for k in range(3):
group_mean = data[labels == k].mean(axis=0)
Statistiques conditionnelles par groupe via masque booléen.
Algèbre linéaire appliquée
# Régression linéaire via moindres carrés
A = np.column_stack([X, np.ones(len(X))])
coeffs, _, _, _ = np.linalg.lstsq(A, y, rcond=None)
Régression OLS sans bibliothèque externe.
# ACP : décomposition en composantes principales
X_c = X - X.mean(axis=0)
U, s, Vh = np.linalg.svd(X_c, full_matrices=False)
# Composantes principales : Vh.T
# Coordonnées projetées : U * s
ACP via SVD sur la matrice centrée.
Signaux et transformées
t = np.linspace(0, 1, 1000, endpoint=False)
signal = np.sin(2 * np.pi * 50 * t)
F = np.fft.fft(signal)
freqs = np.fft.fftfreq(len(t), d=t[1] - t[0])
FFT d'un signal 1D avec calcul des fréquences associées.
np.fft.ifft(F) # FFT inverse
np.fft.fft2(image) # FFT 2D
np.fft.rfft(signal) # FFT sur signal réel (moitié du spectre)
Variantes de la FFT selon le contexte.
Erreurs fréquentes
# Erreur
A * B # produit élément par élément
# Attendu
A @ B # produit matriciel
Produit matriciel : * n'est jamais @. L'erreur est silencieuse si les formes sont compatibles par
broadcasting.
# Erreur : b est une vue de a
b = a[:]
b[0] = 99 # modifie aussi a
# Correct
b = a.copy()
b[0] = 99 # a est inchangé
Vue involontaire : le slicing retourne une vue. Utiliser .copy() pour une copie indépendante.
# Formes ambiguës
a = np.array([1, 2, 3]) # shape: (3,)
b = np.array([[1, 2, 3]]) # shape: (1, 3)
c = np.array([[1], [2], [3]])# shape: (3, 1)
a @ b.T # erreur
a.reshape(1, -1) @ b.T # (1, 1) correct
Vecteur 1D vs matrice : (n,) et (1, n) ont un comportement différent en produit matriciel et en broadcasting.
# Erreur : dtype entier coupe les décimales
a = np.array([1, 2, 3]) # int64
a / 2 # -> array([0.5, 1. , 1.5]) OK en Python 3
a // 2 # -> array([0, 1, 1]) division entière
# Attention à la troncature silencieuse
a = np.array([1, 2, 3], dtype=np.int32)
a[0] = 2.9 # stocke 2, pas 2.9
Troncature de type : affecter un float dans un tableau int tronque silencieusement.
# Erreur : concaténation quadratique
result = np.array([])
for x in data:
result = np.append(result, x) # O(n^2)
# Correct
result = np.array(list(data))
np.append en boucle : chaque appel crée une copie. Accumuler dans une liste Python, puis convertir une seule fois.
# Erreur : comparaison de flottants par ==
a = np.array([0.1 + 0.2])
a == 0.3 # -> array([False]) !
# Correct
np.isclose(a, 0.3) # tolérance relative
np.allclose(a, 0.3) # tous les éléments
Égalité flottante : ne jamais comparer des flottants avec ==. Utiliser np.isclose ou
np.allclose.
# Erreur : seed globale non reproductible en multiprocessing
np.random.seed(42)
# Correct : générateur local
rng = np.random.default_rng(42)
État global du random : l'état global n'est pas thread-safe. Utiliser un générateur local par thread ou processus.
Documentation : numpy.org/doc/stable - référence complète avec exemples.
That's all folks