NumPy : calculer sur des milliers de mesures
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. 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.
Ce qu'on va apprendre
- 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.
- Résumer une série de mesures, et traiter les valeurs manquantes.
- Reconnaître les deux erreurs qui font perdre le plus de temps : la forme et le
nan.
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.
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.
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 pour nous.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é.
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.
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.
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.
À 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.
Vérification
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 ?
3.(a > 1) and (b < 2) sur des tableaux produit quoi ?
4.Sur un masque de booléens, que donne .sum() ?
5.mesures.mean() sur un tableau contenant un nan rend quoi ?
6.Sur un tableau de forme (4, 5), que rend mean(axis=1) ?
7.Additionner un tableau de 1000 valeurs et un de 999 donne quoi ?
Synthèse
- Un tableau a un seul type et une forme fixe ; une liste accepte tout et grandit. Le premier calcule vite, la seconde est souple.
- Une opération écrite sur le tableau s'applique à tous les éléments. La boucle disparaît du code, et le temps de calcul avec elle.
*et+ne veulent pas dire la même chose sur une liste et sur un tableau. Convertir tôt.- Une condition sur un tableau rend un masque de booléens.
tab[masque]sélectionne,masque.sum()compte,masque.mean()donne la proportion. - Combiner avec
&et|, jamaisandetor, et parenthéser chaque comparaison. nancontamine les calculs exprès.nanmeanet ses variantes écartent les trous, et l'employer est une décision qui se documente.axisdésigne la dimension qui disparaît. Vérifier la forme du résultat.- Devant un message d'erreur NumPy, afficher les formes avant toute chose.
np.where(masque)rend les indices, c'est-à-dire les instants, et non seulement les valeurs.