Aller au contenu principal

Module 4 — NumPy : tableaux, diffusion et calcul vectorisé

NumPy est la fondation de tout l'écosystème : pandas, scikit-learn, et jusqu'aux tenseurs de PyTorch reprennent ses concepts. Son objet unique — le tableau homogène ndarray — et son principe unique — vectoriser au lieu de boucler — suffisent à accélérer les calculs d'un facteur 10 à 100.

Le ndarray : un bloc homogène et typé

Contrairement à la liste Python (des références vers des objets dispersés), un tableau NumPy est un bloc mémoire contigu d'éléments du même type. C'est cette homogénéité qui permet la vitesse : les opérations descendent en C compilé au lieu d'interpréter chaque élément.

import numpy as np

a = np.array([1.5, 2.0, 3.5])
m = np.zeros((3, 4)) # matrice 3×4 de zéros
x = np.arange(0, 10, 0.5) # 0, 0.5, 1.0, …, 9.5
g = np.random.default_rng(42).normal(size=(1000, 3)) # aléatoire reproductible

a.shape # (3,) — les dimensions
a.dtype # float64 — le type unique des éléments
m.ndim # 2 — le nombre d'axes

shape et dtype sont les deux attributs à vérifier en premier devant tout comportement étrange : la moitié des bugs NumPy sont des formes inattendues.

La vectorisation : l'opération s'applique à tout le tableau

# Style Python pur — lent
resultats = []
for prix in liste_prix:
resultats.append(prix * 1.15)

# Style NumPy — 10 à 100 fois plus rapide, et plus lisible
resultats = prix * 1.15

Toutes les opérations arithmétiques et les fonctions universelles (np.log, np.exp, np.sqrt…) s'appliquent élément par élément sur le tableau entier. La compréhension de liste du module 2 devient une expression sans boucle :

normalise = (x - x.mean()) / x.std()      # standardisation en une ligne

La règle professionnelle : si vous écrivez une boucle for sur un tableau NumPy, arrêtez-vous — il existe presque sûrement une formulation vectorisée, plus rapide et plus proche des mathématiques.

La diffusion (broadcasting) : opérer entre formes différentes

NumPy étend automatiquement les tableaux de formes compatibles :

m = np.array([[1., 2., 3.],
[4., 5., 6.]]) # forme (2, 3)

m * 10 # scalaire diffusé partout
m - m.mean(axis=0) # soustrait la moyenne de CHAQUE COLONNE — forme (3,) diffusée sur (2, 3)

La règle formelle : deux dimensions sont compatibles si elles sont égales ou si l'une vaut 1, en alignant les formes par la droite. En pratique, le cas qui compte est celui ci-dessus : centrer ou réduire des colonnes sans boucle. C'est exactement l'opération de standardisation qui précède la plupart des modèles.

Quand la diffusion se trompe en silence

Soustraire un tableau de forme (n,) à un tableau (n, 1) produit une matrice (n, n) — sans erreur. Si un calcul renvoie une forme absurde, cherchez une diffusion involontaire ; x.reshape(-1, 1) et x.ravel() remettent les formes au clair.

Les masques booléens : filtrer par condition

La construction la plus utilisée de tout NumPy — et le mécanisme exact des filtres pandas du module suivant :

ages = np.array([22, 35, 58, 41, 17, 63])

masque = ages >= 40 # array([False, False, True, True, False, True])
ages[masque] # array([58, 41, 63]) — sélection
(ages >= 40).mean() # 0.5 — proportion (True vaut 1)

# Conditions combinées : & (et), | (ou), avec parenthèses OBLIGATOIRES
actifs_seniors = ages[(ages >= 40) & (ages < 65)]

# Remplacement conditionnel
plafonnes = np.where(ages > 60, 60, ages)

Les parenthèses autour de chaque condition ne sont pas optionnelles : & et | ont priorité sur les comparaisons, et leur oubli produit une erreur cryptique.

Agrégations et axes : résumer dans la bonne direction

notes = np.array([[12, 15, 9],
[14, 11, 16]]) # 2 étudiants × 3 matières

notes.mean() # 12.83 — moyenne globale
notes.mean(axis=0) # [13. 13. 12.5] — par matière (on écrase les lignes)
notes.mean(axis=1) # [12. 13.67] — par étudiant (on écrase les colonnes)

La mnémonique qui tient : axis désigne l'axe qui disparaît. axis=0 écrase les lignes, il reste une valeur par colonne. Les mêmes agrégations existent partout : sum, min, max, std, argmax (l'indice du maximum — celui qui donne la classe prédite d'un réseau de neurones).

Deux subtilités du monde réel : les valeurs manquantes se propagent (np.nan contamine toute somme — utiliser np.nanmean et sa famille), et les découpages NumPy sont des vues sur la même mémoire, pas des copies (.copy() pour découpler).

Ce qu'il faut retenir

  • Le ndarray est homogène et typé ; shape et dtype sont les premiers réflexes de diagnostic.
  • Vectoriser remplace les boucles : opérations élément par élément, 10 à 100 fois plus rapides, plus lisibles.
  • La diffusion permet d'opérer entre formes compatibles — centrer des colonnes en une ligne — mais peut fabriquer des formes absurdes en silence.
  • Masques booléens : filtrer, compter, remplacer par condition ; parenthèses obligatoires avec & et |.
  • axis = l'axe qui disparaît ; np.nanmean face aux valeurs manquantes ; les tranches sont des vues.

Au module suivant : pandas, qui pose des étiquettes — noms de colonnes, index — sur ces tableaux et devient l'outil quotidien du travail sur données tabulaires.