Tu écris une boucle for pour additionner les carrés d'un million de valeurs, tu lances, et tu attends. La même somme s'écrit en une ligne avec numpy, pour un résultat identique, et sur notre machine elle va environ 170 fois plus vite. Maîtriser numpy, c'est apprendre à penser en tableaux plutôt qu'en boucles, et ce tutoriel t'y amène pas à pas.
On part de zéro côté numpy : créer des tableaux, les indexer, calculer sans boucle, filtrer avec des masques booléens, résumer un tableau en quelques nombres, puis le final, remplacer une boucle lente par du calcul vectorisé, chronomètre en main. Chaque exemple se copie et se lance tel quel, avec Python 3.12 et numpy 2.5.1. Seul prérequis : les bases du langage (variables, boucles, listes), qu'on a posées dans notre article sur les bases de Python.
Installer et importer numpy
numpy est une bibliothèque : elle ne vient pas avec Python, il faut l'installer une fois. Si Python lui-même n'est pas encore sur ta machine, commence par notre guide d'installation de Python, puis reviens ici. L'installation de numpy tient en une commande dans le terminal :
python -m pip install numpy
Ensuite, en tête de chaque programme qui l'utilise, on importe la bibliothèque sous son alias habituel :
import numpy as np
L'alias np est la convention universelle : tous les exemples que tu croiseras l'utilisent, et tout le code de cet article la suit. Si cette ligne passe sans message d'erreur, numpy est prêt. Pour aller plus loin qu'ici, la documentation officielle vit sur numpy.org.
Créer ses premiers tableaux
L'objet central de numpy est le tableau (le type ndarray) : une suite de valeurs qui ont toutes le même type, rangées d'un seul bloc en mémoire. C'est cette uniformité qui rendra les calculs de la fin de l'article aussi rapides. La façon la plus simple d'en créer un : convertir une liste avec np.array.
import numpy as np
a = np.array([2, 5, 8, 11])
print(a) # [ 2 5 8 11]
print(type(a)) # <class 'numpy.ndarray'>
print(a.dtype) # int64
Trois choses à remarquer.
L'affichage n'a pas de virgules et aligne les nombres avec des espaces : c'est la marque d'un tableau numpy, pas d'une liste.
L'attribut dtype donne le type commun de tous les éléments, ici int64, des entiers stockés sur 64 bits.
Le type ne peut plus changer en cours de route, contrairement à une liste.
Un tableau peut avoir deux dimensions : on le crée à partir d'une liste de listes, et l'attribut shape donne ses dimensions sous la forme d'un couple (nombre de lignes, nombre de colonnes).
m = np.array([[1, 2, 3], [4, 5, 6]])
print(m.shape) # (2, 3) deux lignes, trois colonnes
print(m.ndim) # 2 nombre de dimensions
print(m.size) # 6 nombre total d'éléments
Enfin, on ne construit pas toujours un tableau valeur par valeur. Quatre fonctions couvrent les besoins courants : np.zeros remplit de zéros, np.ones remplit de uns, np.arange fonctionne comme range (début, fin exclue, pas), et np.linspace découpe un intervalle en un nombre donné de valeurs régulièrement espacées, bornes comprises.
print(np.zeros(4)) # [0. 0. 0. 0.]
print(np.ones((2, 3)))
# [[1. 1. 1.]
# [1. 1. 1.]]
print(np.arange(0, 10, 2)) # [0 2 4 6 8]
print(np.linspace(0, 1, 5)) # [0. 0.25 0.5 0.75 1. ]
arange ou linspace ? np.arange quand tu connais le pas entre deux valeurs, np.linspace quand tu connais le nombre de valeurs que tu veux. Le point après chaque nombre dans [0. 0. 0. 0.] indique des flottants : zeros, ones et linspace produisent des flottants par défaut.
Cours particuliers
Un accompagnement personnalisé pour réussir
Des professeurs d'excellence pour vous accompagner tout au long de l'année.
Indexer et découper
Bonne nouvelle : tout ce que tu sais des listes s'applique. On compte à partir de 0, les indices négatifs partent de la fin, et les tranches [début:fin] excluent la borne haute.
t = np.arange(10, 20)
print(t) # [10 11 12 13 14 15 16 17 18 19]
print(t[0], t[-1]) # 10 19
print(t[2:5]) # [12 13 14]
print(t[::2]) # [10 12 14 16 18]
La nouveauté arrive avec deux dimensions : on donne la ligne et la colonne entre les mêmes crochets, séparées par une virgule. Et le deux-points seul veut dire « tout » dans cette direction, ce qui permet d'extraire une colonne entière, chose impossible en une instruction avec des listes de listes.
m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(m[1, 2]) # 6 ligne 1, colonne 2
print(m[0]) # [1 2 3] la première ligne
print(m[:, 1]) # [2 5 8] toute la colonne 1
À savoir : une tranche de tableau numpy n'est pas une copie, c'est une vue sur le tableau d'origine. Modifier t[2:5] modifie t. Quand tu veux une copie indépendante, écris t[2:5].copy().
Opérer sans boucle
Voici le cœur de numpy. Une opération entre deux tableaux de même taille s'applique terme à terme, en une seule instruction, sans écrire de boucle : c'est ce qu'on appelle vectoriser un calcul. Là où + collerait deux listes bout à bout, il additionne les tableaux élément par élément.
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
print(a + b) # [11 22 33 44]
print(a * b) # [ 10 40 90 160]
print(a ** 2) # [ 1 4 9 16]
print(np.sqrt(b)) # [3.16227766 4.47213595 5.47722558 6.32455532]
La fonction np.sqrt illustre la même idée : elle prend la racine carrée de chaque élément d'un coup. numpy fournit ainsi toutes les fonctions usuelles (np.cos, np.exp, np.log...) en version tableau. Et l'opération marche aussi entre un tableau et un simple nombre, appliqué alors à chaque élément :
notes = np.array([8.0, 12.5, 15.0, 10.5])
print(notes + 1) # [ 9. 13.5 16. 11.5]
print(notes * 2) # [16. 25. 30. 21.]
Le réflexe à prendre : dès que tu t'apprêtes à écrire un for sur un tableau numpy, arrête-toi et demande-toi si l'opération ne s'écrit pas déjà en une ligne. La réponse est presque toujours oui, et la version en une ligne est à la fois plus courte, plus lisible et plus rapide.
Besoin d'un accompagnement ?
Échangez avec un conseiller pour définir la méthode de travail adaptée à votre profil.
Filtrer avec les masques booléens
Les comparaisons se vectorisent aussi. Comparer un tableau à une valeur renvoie un masque booléen : un tableau de True et de False, un par élément, qui dit lesquels vérifient la condition. Et ce masque, placé entre les crochets du tableau, sélectionne exactement ces éléments.
notes = np.array([8.0, 12.5, 15.0, 10.5, 6.0])
print(notes >= 10) # [False True True True False]
print(notes[notes >= 10]) # [12.5 15. 10.5]
print(np.sum(notes >= 10)) # 3
La troisième ligne mérite une explication : dans une somme, True vaut 1 et False vaut 0, donc np.sum sur un masque compte les éléments qui vérifient la condition. Trois notes sur cinq atteignent la moyenne. Filtrer, sélectionner, compter : trois opérations qui demandaient chacune une boucle avec des listes, et qui tiennent ici en une ligne chacune.
Résumer un tableau en un nombre
Dernière brique avant le final : les agrégations, ces méthodes qui résument tout un tableau en un seul nombre. Les quatre plus utiles s'appellent directement sur le tableau :
notes = np.array([8.0, 12.5, 15.0, 10.5, 6.0])
print(notes.mean()) # 10.4
print(notes.max()) # 15.0
print(notes.min()) # 6.0
print(notes.std()) # 3.184336665618132
mean donne la moyenne, max et min les valeurs extrêmes, et std l'écart type, qui mesure à quel point les notes s'étalent autour de la moyenne. Sur un tableau à deux dimensions, on précise en plus la direction du calcul avec un axe : axis=0 descend le long des lignes et produit un résultat par colonne, axis=1 parcourt chaque ligne et produit un résultat par ligne.
m = np.array([[10, 12, 14], [8, 16, 12]])
print(m.mean(axis=0)) # [ 9. 14. 13.] la moyenne de chaque colonne
print(m.mean(axis=1)) # [12. 12.] la moyenne de chaque ligne
Imagine m comme les notes de deux élèves (les lignes) sur trois devoirs (les colonnes) : axis=0 donne la moyenne de la classe par devoir, axis=1 la moyenne de chaque élève. Moyen mnémotechnique : l'axe indiqué est celui qui disparaît du résultat.
Le final : remplacer la boucle par la vectorisation
On y est. Reprenons la promesse de l'introduction : la somme des carrés d'un million de valeurs. On fabrique d'abord le tableau avec np.linspace, un million de valeurs régulièrement espacées entre 0 et 2π, puis on écrit les deux versions du calcul, la boucle et la ligne vectorisée.
x = np.linspace(0, 2 * np.pi, 1000000)
def somme_boucle(x):
total = 0.0
for v in x:
total += v * v
return total
print(round(somme_boucle(x), 6) == round(np.sum(x * x), 6)) # True
print(round(np.sum(x * x), 2)) # 13159479.11
Premier constat : les deux versions donnent le même résultat, l'égalité vérifiée après arrondi à six décimales renvoie True. On ne perd rien en remplaçant la boucle. Reste à mesurer le temps de chacune, avec le module timeit de la bibliothèque standard et une seule exécution de chaque version :
from timeit import timeit
duree_boucle = timeit(lambda: somme_boucle(x), number=1)
duree_numpy = timeit(lambda: np.sum(x * x), number=1)
Voici ce que cette mesure donne sur notre machine. Les valeurs exactes changent d'un ordinateur à l'autre, mais l'ordre de grandeur, lui, reste.
| Version | Temps mesuré | Rapport |
|---|---|---|
Boucle Python somme_boucle(x) | 0.049 s | référence |
Vectorisé np.sum(x * x) | 0.0003 s | environ 170 fois plus rapide |
Le nombre d'opérations mathématiques est pourtant le même : un million de multiplications et autant d'additions dans les deux cas. Ce qui change, c'est qui exécute la boucle. Dans la version for, Python traite chaque valeur une par une, avec tout le travail d'interprétation que chaque tour de boucle lui coûte. Dans la version vectorisée, numpy fait la même boucle en interne, d'un bloc, sur ce tableau aux valeurs toutes du même type rangées côte à côte en mémoire. Ce raisonnement sur le nombre d'opérations et leur coût, tu le retrouveras au centre de l'algorithmique de prépa, avec le tri, la recherche et la complexité.
À retenir
- Un tableau numpy contient des valeurs toutes du même type (son
dtype), etshapedonne ses dimensions. On le crée avecnp.array,np.zeros,np.ones,np.arangeounp.linspace. - L'indexation suit les listes (à partir de 0, borne haute exclue), avec deux nouveautés :
m[ligne, colonne]et la colonne entièrem[:, 1]. Une tranche est une vue, pas une copie. - Les opérations sont terme à terme :
a + b,a ** 2,np.sqrt(b), ou avec un simple nombre commenotes + 1. Pas de boucle à écrire. - Un masque booléen filtre et compte :
notes[notes >= 10]sélectionne,np.sum(notes >= 10)compte, carTruevaut 1. - Les agrégations résument :
mean,max,min,std, et sur deux dimensionsaxis=0par colonne,axis=1par ligne. - Vectoriser, c'est remplacer la boucle : même résultat, et sur un million de valeurs notre mesure passe de 0.049 s à 0.0003 s, environ 170 fois plus vite.
numpy est une brique parmi d'autres dans la préparation informatique : pour situer l'ensemble (langage, algorithmique, pratique), parcours notre guide pour coder avant la prépa.



