NumPy : calculer sur des milliers de mesures
Ce que ce chapitre apporte 10 points
- Distinguer un tableau NumPy d'une liste Python, et savoir quand chacun convient.
- Calculer sur tout un tableau sans écrire de boucle.
- Sélectionner des valeurs par une condition plutôt que par leur position.
- Lire un fichier de mesures directement en tableau.
- Résumer une série de mesures : moyenne, médiane, écart-type, quartiles, effectifs et cumuls, et savoir où NumPy ne suit pas les conventions du cours de statistiques.
- Traiter les valeurs manquantes.
- Tirer des nombres au hasard de façon reproductible.
- Comprendre la diffusion, et savoir pourquoi (3, 4) et (4,) s'entendent quand (3, 4) et (3,) échouent.
- Distinguer une vue d'une copie en découpant un tableau.
- Reconnaître les deux erreurs qui font perdre le plus de temps : la forme et le nan.
Une liste Python sait tout faire, et c'est bien son problème : elle peut contenir un nombre, un texte et une autre liste en même temps, donc Python doit vérifier le type de chaque élément à chaque opération. Un tableau NumPy à deux dimensions est une matrice au sens du chapitre de mathématiques sur les matrices et les systèmes linéaires, et les opérations qu'il propose en sont la traduction directe. Sur dix valeurs, personne ne le remarque. Sur un million de relevés de capteur, cela devient la différence entre une seconde et une minute. NumPy propose l'autre marché : un seul type pour tout le tableau, et des opérations qui s'appliquent d'un coup.
Le tableau, et ce qu'il échange
Trois différences avec une liste, et elles découlent toutes de la même contrainte.
Un seul type pour tout le tableau. dtype le donne. NumPy peut donc ranger les valeurs les unes derrière les autres en mémoire, sans indirection, et le processeur les lit d'un bloc.
Une taille fixée. Ajouter un élément à un tableau demande d'en recopier tout le contenu ailleurs. Un tableau se construit d'un coup, il ne se remplit pas au fil de l'eau comme une liste.
Une forme. shape dit combien de lignes et de colonnes. Un relevé de capteur est un tableau à une dimension, une image en niveaux de gris un tableau à deux.
Sur une liste, * répète et + concatène. Sur un tableau, les deux calculent. Convertir en tableau change donc le sens du code déjà écrit, et c'est une bonne raison de convertir tôt plutôt que tard.
1.Pourquoi un tableau NumPy est-il plus rapide qu'une liste sur un million de valeurs ?
2.liste * 2 et tableau * 2 donnent quoi ?
Fabriquer un tableau
Un tableau se construit rarement à la main. Quatre fonctions couvrent presque tous les cas, et les deux dernières reviennent dans chaque chapitre qui suit.
| Appel | Ce qu'il produit |
|---|---|
np.zeros(5) | cinq zéros, pour un tableau qu'on remplira |
np.ones((2, 3)) | une grille de 1, la forme se donne en couple |
np.arange(0, 10, 2) | comme range, mais en tableau : 0, 2, 4, 6, 8 |
np.linspace(0, 1, 5) | cinq valeurs régulièrement réparties de 0 à 1, bornes comprises |
La différence entre arange et linspace est celle du pas et du nombre de points. arange reçoit le pas et la borne haute est exclue ; linspace reçoit le nombre de valeurs et inclut les deux bornes. Pour tracer une courbe, c'est linspace qu'on veut : le nombre de points est précisément ce qu'on règle.
reshape réarrange les mêmes valeurs dans une autre forme, sans en recalculer aucune. Le produit des dimensions doit rester le même, sinon NumPy refuse.
Calculer sans boucle
C'est la raison d'être de NumPy. Une opération écrite sur le tableau entier s'applique à chaque élément, et la boucle disparaît du code.
La ligne (mv - 500) / 10 traite les huit valeurs. Elle en traiterait un million sans changer d'un caractère, et sans que la durée d'exécution soit multipliée par le nombre de tours d'une boucle Python.
La règle de lecture. Devant du code NumPy, se demander non pas « que fait cette ligne à un élément » mais « que fait cette ligne au tableau entier ». C'est le changement de point de vue qui coûte le plus au début, et celui qui rend le reste évident.
Le gain, mesuré
L'écart n'est pas une optimisation de confort. Sur un jeu de mesures réel, il décide de ce qui est faisable dans la séance et de ce qui ne l'est pas, exactement comme au chapitre sur la complexité.
Sélectionner par une condition
Voici la seconde idée du chapitre, et celle qui change le plus la façon d'écrire. Plutôt que de parcourir en testant, on écrit la condition sur le tableau entier et l'on obtient un masque : un tableau de True et de False, de la même forme.
Deux détails qui rendent ce mécanisme plus utile qu'il n'y paraît. sum() sur un masque compte les cas vrais, puisque True vaut 1. Et mean() sur un masque donne directement la proportion, ce qui évite une division.
Les conditions se combinent, avec une syntaxe qui surprend une fois :
(a > 1) and (b < 2) sur des tableaux lève une erreur explicite : « The truth value of an array with more than one element is ambiguous ». Python demande à convertir le tableau en un seul booléen, et NumPy refuse de choisir.La raison est bonne : un tableau de dix valeurs dont trois vérifient la condition n'est ni vrai ni faux. Employer
& et |, qui travaillent terme à terme, et parenthéser chaque comparaison parce que ces opérateurs sont plus prioritaires que >.
Les valeurs manquantes
Un capteur débranché, une trame perdue, une ligne vide dans le fichier : les mesures réelles ont des trous. NumPy les note nan, pour not a number, et son comportement est délibéré.
Pourquoi la moyenne rend nan, et pourquoi c'est la bonne réponse. Une moyenne calculée en ignorant silencieusement les mesures manquantes serait un mensonge : elle porterait sur un échantillon différent de celui qu'on croit, sans que rien ne le dise. NumPy propage donc le nan jusqu'au résultat.
Les fonctions nanmean, nanstd, nanmax font le calcul en écartant les trous. Les employer est légitime ; le faire sans le savoir ne l'est pas. C'est pourquoi il faut deux noms de fonction et non un réglage par défaut.
1.(a > 1) and (b < 2) sur des tableaux produit quoi ?
2.Sur un masque de booléens, que donne .sum() ?
3.mesures.mean() sur un tableau contenant un nan rend quoi ?
Deux dimensions
Un tableau à deux dimensions range des lignes et des colonnes : quatre capteurs relevés sur cinq instants, par exemple.
axis=0 parcourt les lignes et rend un résultat par colonne. axis=1 parcourt les colonnes et rend un résultat par ligne.Le moyen de ne plus se tromper : l'axe indiqué est celui qui disparaît. Sur une forme
(4, 5), axis=1 supprime la seconde dimension et laisse quatre valeurs, une par capteur. Vérifier la forme du résultat coûte une seconde et tranche à coup sûr.
La diffusion : quand deux formes différentes s'entendent
La section précédente laisse une question ouverte. Retirer une valeur unique de tout un tableau fonctionne, mv - 500, alors que les deux formes diffèrent. Et retrancher une moyenne par capteur à une grille de relevés fonctionne parfois, échoue d'autres fois, apparemment sans logique.
La logique existe, et elle tient en trois lignes. Les formes s'alignent par la droite, la plus courte se complétant par des 1 à gauche. Deux dimensions s'entendent si elles sont égales, ou si l'une vaut 1. Une dimension de taille 1 est alors étirée jusqu'à l'autre.
C'est l'alignement par la droite qui surprend, et la figure suivante le montre : un vecteur de la taille des colonnes s'ajoute à une matrice, un vecteur de la taille des lignes est refusé.
| releves(3, 4) | 3 | 4 |
| correction(4) | 1 | 4 |
| résultat | 3 | 4 |
| le second est étiré | tailles égales |
forme du résultat (3, 4)
releves
| 11 | 12 | 13 | 14 |
| 21 | 22 | 23 | 24 |
| 31 | 32 | 33 | 34 |
correction
| 11 | 12 | 13 | 14 |
| 11 | 12 | 13 | 14 |
| 11 | 12 | 13 | 14 |
Les cases estompées sont relues, pas recopiées : NumPy repasse sur la même valeur autant de fois qu'il le faut, et n'occupe pas un octet de plus.
Le cas symétrique échoue, et c'est le piège le plus fréquent du chapitre :
| releves(3, 4) | 3 | 4 |
| moyennes(3) | 1 | 3 |
| résultat | 3 | ✗ |
| le second est étiré | aucune ne vaut 1 : refus |
operands could not be broadcast together
Retrancher une moyenne par ligne demande donc de rendre à ce vecteur une seconde dimension, pour qu'il s'aligne en colonne :
L'écriture moyennes[:, np.newaxis] fait la même chose que reshape(3, 1) et se rencontre partout ; keepdims=True sur le mean l'évite en rendant directement la forme (3, 1).
Les erreurs de forme
NumPy refuse d'aligner deux tableaux de tailles incompatibles, et c'est une bonne nouvelle. Un relevé décalé d'une ligne, un fichier tronqué, deux séries qui ne couvrent pas la même période : l'erreur apparaît immédiatement, à l'endroit du problème, plutôt que de produire un résultat plausible et faux.
print(a.shape, b.shape) résout la grande majorité des messages incompréhensibles, et bien plus vite que de relire le code.
1.Sur un tableau de forme (4, 5), que rend mean(axis=1) ?
2.Un tableau de forme (3, 4) et un vecteur de forme (4,) s'additionnent-ils ?
3.Additionner un tableau de 1000 valeurs et un de 999 donne quoi ?
Découper : une vue, pas une copie
Un tableau se découpe comme une liste, tab[1:4], et sur deux dimensions avec une virgule, grille[0:2, 1:3]. Une différence essentielle sépare pourtant les deux : une découpe de liste est une copie, une découpe de tableau est une vue.
Ce n'est pas une bizarrerie : c'est la même raison que pour la diffusion. Une vue ne coûte rien, quelle que soit la taille du tableau, alors qu'une copie systématique doublerait la mémoire à chaque découpe. Sur un relevé d'un million de points, extraire une fenêtre glissante serait impraticable autrement.
La règle pratique en découle : .copy() dès qu'on va modifier une découpe dont on veut préserver l'original. C'est exactement la précaution du chapitre sur les fonctions, pour exactement la même raison.
Deux découpages reviennent constamment sur une grille :
Le deux-points seul signifie « toute cette dimension ». grille[:, 2] se lit donc « toutes les lignes, colonne 2 ».
fenetre = mesures[1:4] puis fenetre[0] = 999 : que devient mesures ?
Depuis un fichier de mesures
Un relevé arrive le plus souvent dans un fichier texte, une ligne par instant, une colonne par grandeur. np.loadtxt le lit directement en tableau, sans boucle ni conversion à la main. Ici, le fichier est simulé par un texte en mémoire, io.StringIO, parce que l'éditeur du navigateur n'a pas de disque ; sur une machine, on écrit simplement le nom du fichier à la place.
delimiter donne le séparateur de colonnes, skiprows=1 saute la ligne d'en-tête. Le résultat est un tableau à deux dimensions, une ligne par instant ; donnees[:, 0] en extrait la première colonne, l'écriture détaillée plus haut avec les découpes. Si le fichier contient des cases vides, np.loadtxt échoue : np.genfromtxt les remplace par nan, ce qui ramène aux valeurs manquantes traitées plus haut. Il prend les mêmes arguments à un près, et celui-là se venge : l'en-tête se saute par skip_header=1, jamais par skiprows, qu'il refuse par une TypeError.
Résumer une série
Calculer sur un tableau entier sert d'abord à le résumer : où se situent les valeurs, et à quel point elles s'étalent. Le chapitre de statistiques définit chaque indicateur et dit lequel choisir ; cette section montre comment NumPy les calcule, et les trois endroits où il ne fait pas ce qu'on croit.
La position : moyenne, médiane, extrêmes
Dix temps de réponse d'un service, dont une requête qui a expiré : la situation que le chapitre de statistiques prend pour exemple.
La moyenne vaut 441,4 ms et la médiane 46,5 ms : une seule valeur a suffi à tirer la moyenne à dix fois la durée d'une requête ordinaire. C'est la raison pour laquelle on publie la médiane d'un temps de réponse, et NumPy la donne aussi facilement que la moyenne.
argmin et argmax ne rendent pas la valeur extrême mais sa position dans le tableau, ici 2 : c'est elle qui permet de remonter à la requête fautive, à son heure, à son client. Comme partout en Python, la position se compte à partir de 0.
t.mean(), t.sum(), t.min() et t.std() s'écrivent comme des méthodes du tableau, mais t.median() déclenche une AttributeError. La médiane s'obtient par la fonction np.median(t), et il en va de même pour np.percentile. Les deux écritures se valent, np.mean(t) fonctionne aussi : dans le doute, la forme np.fonction(t) marche toujours.
La dispersion : variance, écart-type, quartiles
Deux lignes de la sortie méritent qu'on s'y arrête, parce qu'elles donnent chacune deux nombres pour une seule notion.
std() et var() divisent par l'effectif N : c'est l'écart-type qui décrit la série qu'on a sous les yeux. Pour estimer l'écart-type d'une population à partir d'un échantillon, et donc pour un intervalle de confiance, il faut diviser par N − 1, ce qu'on demande par ddof=1. La bibliothèque pandas fait le choix inverse : serie.std() divise par N − 1. Le même calcul donne donc 13,26 d'un côté et 13,85 de l'autre, et l'écart grandit à mesure que l'effectif diminue. Écrire ddof explicitement lève l'ambiguïté.
np.percentile interpole entre deux rangs : il trouve Q₁ = 17,25 et Q₃ = 31,5, là où la règle des deux moitiés donne 16,5 et 32. Aucun des deux n'est faux, ce sont deux conventions, et elles se rejoignent dès que la série est longue. Mais un résultat vérifié à la main doit l'être avec la même règle : la petite fonction quartiles_du_cours ci-dessus l'applique, et c'est aussi celle des boîtes à moustaches tracées dans les chapitres du site. Les boîtes de plt.boxplot, elles, suivent np.percentile.
Trier, compter, cumuler
Le tableau d'effectifs du chapitre de statistiques, avec ses fréquences et ses cumuls, se construit en trois appels.
np.unique rend les modalités distinctes, triées, et avec return_counts=True le nombre de fois où chacune apparaît. np.cumsum fait la somme cumulée : sa dernière case vaut l'effectif total, le contrôle qui prend trois secondes. Et le mode se lit en combinant les deux : la modalité placée au même rang que le plus grand effectif.
np.sort rend une copie triée et laisse l'original intact, comme sorted sur une liste. Pour regrouper une variable continue en classes, np.histogram(x, bins=5) rend les effectifs et les bornes des cinq classes : c'est le calcul que plt.hist fait avant de dessiner.
Tout accepte axis.
mean, median, std, min, argmax, percentile, sort et cumsum acceptent tous le paramètre axis, présenté plus haut avec les tableaux à deux dimensions : la même ligne résume un capteur ou tous les capteurs d'un coup.
1.Un tableau de temps de réponse contient une valeur de 4 000 ms parmi des valeurs voisines de 45 ms. Quelle commande donne la valeur typique ?
2.Pour un intervalle de confiance sur la moyenne d'un échantillon, quel écart-type calculer ?
Tirer au hasard
Tester un traitement demande des données, et les vraies ne sont pas toujours là. NumPy sait en fabriquer : des relevés bruités, des dés, des tirages dans une liste. L'exercice qui suit, le chapitre Matplotlib et le parcours d'intelligence artificielle s'en servent tous, toujours de la même façon.
| Appel | Ce qu'il tire |
|---|---|
rng.random(n) | n réels uniformes dans |
rng.integers(a, b, size=n) | n entiers de a à b exclu, comme range : un dé s'écrit integers(1, 7) |
rng.normal(mu, sigma, size=n) | n valeurs d'une loi normale de moyenne mu et d'écart-type sigma |
rng.choice(liste, size=n, p=...) | n éléments de la liste, avec les probabilités p si on les donne |
Le nombre passé à default_rng, la graine, fixe toute la suite des tirages : relancer le bloc redonne exactement les mêmes nombres. C'est ce qui rend un résultat vérifiable, et c'est pour cela que tous les exemples du site en posent une. Sans graine, np.random.default_rng() change de tirage à chaque exécution, ce qui est voulu pour une simulation et gênant pour un exercice corrigé.
np.random.seed(7) suivie de np.random.rand() ou np.random.randint(). Elle fonctionne, mais elle repose sur un générateur unique et partagé par tout le programme. La documentation de NumPy recommande le générateur explicite, rng = np.random.default_rng(graine), et c'est lui qu'emploie ce parcours.
Mettre en pratique
Le contrôle qualité. Un capteur relève une pression toutes les minutes. Écrire le code qui compte les valeurs hors de l'intervalle [980, 1040], en donne la proportion, et affiche la plus grande dérive observée.
Afficher la solution
np.where rend les indices où le masque est vrai, ce qui permet de remonter à l'instant du relevé et non seulement à sa valeur. C'est presque toujours cette information-là qu'on cherche ensuite.
Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?
Dans la bibliothèque d'exercices
Calculer sans boucle, sélectionner par une condition, et l'erreur de forme qui arrête tout.
- Débogage : deux tableaux de tailles différentes3 · Confirmé
- Débogage : la moyenne rend nan3 · Confirmé
- Débogage : l'original a changé3 · Confirmé
- Calculer sans boucle2 · Fonctions
- Un axe de valeurs régulières2 · Fonctions
- Sélectionner par une condition3 · Confirmé
- Une moyenne par ligne3 · Confirmé
- Centrer chaque capteur sur sa moyenne4 · Avancé