Stages de Pré-Rentrée — Inscriptions ouvertes, places très limitées ! S'inscrire

Aller au contenu principal
Maîtriser numpy pas à pas : des tableaux à la vectorisation
Prépa
10 min

Maîtriser numpy pas à pas : des tableaux à la vectorisation

Équipe Hadamard

Équipe Hadamard

Rédacteurs Hadamard, polytechniciens, centraliens et normaliens, orientation, méthode et concours de prépa

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.

Cours en visio ou présentielProfs X/Centrale/ENS
Découvrir nos cours

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.

RDV gratuit de 15 min

Besoin d'un accompagnement ?

Échangez avec un conseiller pour définir la méthode de travail adaptée à votre profil.

Diagnostic de vos points forts/faibles
Plan de travail personnalisé
Conseils méthodologiques

Sans engagement • Réponse sous 24h

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.

VersionTemps mesuréRapport
Boucle Python somme_boucle(x)0.049 sréférence
Vectorisé np.sum(x * x)0.0003 senviron 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), et shape donne ses dimensions. On le crée avec np.array, np.zeros, np.ones, np.arange ou np.linspace.
  • L'indexation suit les listes (à partir de 0, borne haute exclue), avec deux nouveautés : m[ligne, colonne] et la colonne entière m[:, 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 comme notes + 1. Pas de boucle à écrire.
  • Un masque booléen filtre et compte : notes[notes >= 10] sélectionne, np.sum(notes >= 10) compte, car True vaut 1.
  • Les agrégations résument : mean, max, min, std, et sur deux dimensions axis=0 par colonne, axis=1 par 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.

Partager

FAQ

Questions fréquentes

Accompagnement personnalisé

Besoin d'aide pour réussir votre prépa ?

Nos professeurs, issus de Polytechnique et Centrale, vous accompagnent dans votre réussite avec un suivi sur-mesure.