24. NumPy
Arrays, shape, broadcasting, slicing avancé, algèbre linéaire, universal functions, random, performance.
24.1 Installation
pip install numpyimport numpy as np24.2 Création d’arrays
# À partir d'une liste
a = np.array([1, 2, 3]) # 1D: shape (3,)
b = np.array([[1, 2], [3, 4]]) # 2D: shape (2, 2)
# Zéros, uns, identité
np.zeros((3, 4)) # array de 0
np.ones((2, 3)) # array de 1
np.eye(3) # matrice identité 3×3
# Plages
np.arange(10) # [0, 1, ..., 9]
np.arange(0, 1, 0.1) # [0.0, 0.1, ..., 0.9]
np.linspace(0, 1, 5) # [0.0, 0.25, 0.5, 0.75, 1.0]
# Aléatoire
np.random.rand(3, 4) # Uniforme [0, 1)
np.random.randn(100) # Normale centrée réduite
np.random.randint(0, 10, size=5) # Entiers entre 0 et 10
np.random.seed(42) # Reproductibilité24.3 Attributs fondamentaux
a = np.array([[1, 2, 3], [4, 5, 6]])
a.ndim # 2 (nombre de dimensions)
a.shape # (2, 3) (lignes, colonnes)
a.size # 6 (nombre total d'éléments)
a.dtype # dtype('int64')
a.itemsize # 8 (octets par élément)
a.nbytes # 48 (mémoire totale)24.4 Indexation et slicing
a = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
a[0, 1] # 2
a[1] # [5, 6, 7, 8]
a[:, 1] # [2, 6, 10] (colonne 1)
a[0:2, 1:3] # [[2, 3], [6, 7]]
a[::2, ::2] # [[1, 3], [9, 11]]
a > 5 # mask booléen
a[a > 5] # [6, 7, 8, 9, 10, 11, 12]Indexation avancée
indices = [0, 2]
a[:, indices] # colonnes 0 et 2
lignes = [0, 1]
colonnes = [1, 2]
a[lignes, colonnes] # [a[0,1], a[1,2]] = [2, 7]24.5 Opérations vectorisées
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
a + 10 # [11, 12, 13]
a * 2 # [2, 4, 6]
a + b # [5, 7, 9]
a * b # [4, 10, 18] (élément par élément)
a @ b # 32 (produit scalaire)
np.dot(a, b) # 32
np.sqrt(a) # [1, 1.414, 1.732]
np.exp(a) # [2.718, 7.389, 20.085]
np.sin(a)
np.sum(a) # 6
np.mean(a) # 2.0
np.std(a) # écart-type
np.max(a) # 3
np.argmax(a) # 2 (index du max)
np.clip(a, 1, 2) # [1, 2, 2]24.6 Axes et agrégation
m = np.array([[1, 2, 3],
[4, 5, 6]])
np.sum(m) # 21 (tous les éléments)
np.sum(m, axis=0) # [5, 7, 9] somme par colonne
np.sum(m, axis=1) # [6, 15] somme par ligne
np.mean(m, axis=0) # [2.5, 3.5, 4.5]
# Garder la dimension
np.sum(m, axis=1, keepdims=True) # [[6], [15]]24.7 Broadcasting
Opérations entre arrays de shapes différentes :
a = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2, 3)
b = np.array([10, 20, 30]) # shape (3,)
a + b # [[11, 22, 33],
# [14, 25, 36]]Règles du broadcasting :
- Si les dimensions diffèrent, ajouter des 1 à gauche de la shape la plus petite
- Les dimensions sont compatibles si égales ou l’une vaut 1
- La dimension 1 est “étirée” pour correspondre
a = np.ones((3, 4))
b = np.ones((4,))
# broadcast: (3, 4) vs (1, 4) → (3, 4)
c = np.ones((3, 1))
d = np.ones((4,))
# broadcast: (3, 1) vs (1, 4) → (3, 4)Exemple : normalisation par colonne
data = np.random.randn(100, 5)
moyenne = np.mean(data, axis=0) # shape (5,)
écart = np.std(data, axis=0) # shape (5,)
normalisé = (data - moyenne) / écart # broadcasting24.8 Changement de forme
a = np.arange(12) # [0...11]
a.reshape(3, 4) # 3×4
a.reshape(2, -1) # -1 = déduire la dimension
a.flatten() # copie 1D
a.ravel() # vue 1D (si possible)
a.T # transposition
np.newaxis
a[:, np.newaxis] # (12,) → (12, 1)
a[np.newaxis, :] # (12,) → (1, 12)24.9 Concaténation et split
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
np.concatenate((a, b), axis=0) # [[1,2],[3,4],[5,6]]
np.vstack((a, b)) # idem, vertical
np.hstack((a, b.T)) # horizontal
np.stack((a, a), axis=2) # nouvelle dimension
np.split(a, 2, axis=0) # division en 2
np.array_split(a, 3, axis=0) # division inégale24.10 Algèbre linéaire
from numpy.linalg import inv, det, eig, svd, norm, qr
A = np.array([[1, 2], [3, 4]])
b = np.array([1, 2])
inv(A) # inverse
det(A) # déterminant
norm(A) # norme Frobenius
norm(A, ord=2) # norme spectrale
np.linalg.solve(A, b) # résoudre Ax = b
np.linalg.lstsq(A, b) # moindres carrés
eig(A) # valeurs/vecteurs propres
svd(A) # décomposition SVD
qr(A) # décomposition QR
# Produit matriciel
A @ A
A.dot(A)
np.matmul(A, A)24.11 Universal Functions (ufunc)
Opérations élément par élément optimisées en C :
np.add, np.subtract, np.multiply, np.divide
np.power, np.mod, np.abs
np.exp, np.log, np.log2, np.log10
np.sin, np.cos, np.tan
np.sinh, np.cosh
np.round, np.floor, np.ceil, np.trunc
np.greater, np.less, np.equal # retournent des booléensFonctions personnalisées :
# Avec frompyfunc
def sigmoid(x):
return 1 / (1 + np.exp(-x))
vec_sigmoid = np.frompyfunc(sigmoid, 1, 1)24.12 Arrays masqués
data = np.array([1, -999, 2, -999, 3])
mask = data == -999
ma_data = np.ma.MaskedArray(data, mask=mask)
np.mean(ma_data) # 2.0 (ignore les -999)24.13 Sauvegarde et chargement
# Binaire NumPy
np.save("data.npy", a)
a = np.load("data.npy")
# Multiples arrays
np.savez("data.npz", a=a, b=b)
data = np.load("data.npz")
data["a"]
# Texte
np.savetxt("data.csv", a, delimiter=",")
a = np.loadtxt("data.csv", delimiter=",")24.14 Performance
# LENT (boucle Python)
def lente(a, b):
résultat = np.empty_like(a)
for i in range(len(a)):
résultat[i] = a[i] * b[i] + a[i] ** 2
return résultat
# RAPIDE (vectorisé)
def rapide(a, b):
return a * b + a ** 2Règle d’or : éviter les boucles Python sur les arrays NumPy.
# Encore plus rapide : opérations sur place
a = np.random.rand(100_000)
b = np.random.rand(100_000)
np.multiply(a, b, out=a) # a *= b (pas de copie)24.15 einsum — notation Einstein
Notation compacte pour les opérations tensorielle :
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# Produit matriciel
np.einsum("ij,jk->ik", A, B) # = A @ B
# Trace
np.einsum("ii->", A) # = trace(A)
# Produit élément par élément
np.einsum("ij,ij->ij", A, B) # = A * B
# Somme sur un axe
np.einsum("ij->i", A) # = sum(A, axis=1)