NumPy: The Complete Guide to Numerical Computing in Python
A complete guide to NumPy: master ndarray arrays, broadcasting, vectorized operations, and linear algebra in Python with practical examples.
InSkillCoach
NumPy: The Complete Guide to Numerical Computing in Python
NumPy (Numerical Python) is the foundational library for numerical computing in Python. It provides a central object, the multidimensional ndarray, along with a vast set of optimized mathematical functions written in C. Practically the entire Python scientific ecosystem (Pandas, SciPy, scikit-learn, Matplotlib) is built on NumPy. This guide introduces the essential concepts for working efficiently with numerical arrays: creation, indexing, broadcasting, vectorization, and linear algebra.
Why NumPy Instead of Python Lists?
Python lists are flexible, but they are slow for numerical computing. Each element of a list is a full Python object with its own memory header, whereas a NumPy array stores its values in a contiguous, homogeneous block of memory. Operations are then delegated to compiled C code, which eliminates the interpreter overhead.
import numpy as np
import time
n = 10_000_000
liste = list(range(n))
tableau = np.arange(n)
# Multiplication par 2 avec une liste
debut = time.perf_counter()
resultat_liste = [x * 2 for x in liste]
print(f"Liste : {time.perf_counter() - debut:.3f} s") # Liste : 0.520 s (ordre de grandeur)
# Multiplication par 2 avec NumPy
debut = time.perf_counter()
resultat_np = tableau * 2
print(f"NumPy : {time.perf_counter() - debut:.3f} s") # NumPy : 0.015 s (ordre de grandeur)
The speedup is typically a factor of 10 to 100, depending on the operation.
| Criterion | Python list | NumPy array |
|---|---|---|
| Element types | Heterogeneous | Homogeneous (a single dtype) |
| Memory storage | Pointers to scattered objects | Compact contiguous block |
| Element-wise operations | Explicit Python loop | Vectorized in C |
| Memory consumption | High | Low |
| Dimensions | Manual nesting | Native N dimensions |
Creating Arrays
From Existing Data
import numpy as np
# Depuis une liste
a = np.array([1, 2, 3, 4])
print(a) # [1 2 3 4]
# Tableau 2D depuis une liste de listes
m = np.array([[1, 2, 3], [4, 5, 6]])
print(m)
# [[1 2 3]
# [4 5 6]]
Generation Functions
# Tableau de zéros (3 lignes, 4 colonnes)
zeros = np.zeros((3, 4))
# Tableau de uns
uns = np.ones((2, 3))
# Tableau non initialisé (plus rapide, contenu arbitraire)
vide = np.empty((2, 2))
# Valeur constante
sept = np.full((2, 2), 7)
# Matrice identité
identite = np.eye(3)
# Séquence avec un pas (comme range, mais renvoie un ndarray)
seq = np.arange(0, 10, 2)
print(seq) # [0 2 4 6 8]
# Nombre fixe de points également espacés entre deux bornes (bornes incluses)
lin = np.linspace(0, 1, 5)
print(lin) # [0. 0.25 0.5 0.75 1. ]
Prefer linspace over arange for floating-point values: the number of points is guaranteed, whereas the floating-point step of arange can produce rounding surprises.
dtype and shape: An Array’s ID Card
Every array has an element type (dtype) and a shape (shape).
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.shape) # (2, 3) -> 2 lignes, 3 colonnes
print(a.ndim) # 2 -> nombre de dimensions
print(a.size) # 6 -> nombre total d'éléments
print(a.dtype) # int64 (int32 sous Windows)
# Imposer un type à la création
b = np.array([1, 2, 3], dtype=np.float64)
print(b.dtype) # float64
# Conversion de type (crée un nouveau tableau)
c = b.astype(np.int32)
print(c) # [1 2 3]
# Changer la forme sans copier les données
d = np.arange(12).reshape(3, 4)
print(d.shape) # (3, 4)
# Aplatir en 1D
print(d.ravel()) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
Beware of overflow: an int8 can only hold values between -128 and 127. For data science, float64 and int64 are the safe default choices; reduce precision only if memory becomes a constraint.
Indexing and Slicing
NumPy indexing generalizes Python list indexing to multidimensional arrays.
a = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(a[0]) # 0
print(a[-1]) # 9
print(a[2:7]) # [2 3 4 5 6]
print(a[::2]) # [0 2 4 6 8]
print(a[::-1]) # [9 8 7 6 5 4 3 2 1 0]
m = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(m[1, 2]) # 6 -> ligne 1, colonne 2
print(m[0]) # [0 1 2 3] -> première ligne
print(m[:, 1]) # [1 5 9] -> deuxième colonne
print(m[1:, 2:]) # [[ 6 7]
# [10 11]]
An important point: a NumPy slice returns a view, not a copy. Modifying the view modifies the original array.
v = m[0, :]
v[0] = 99
print(m[0, 0]) # 99 -> le tableau original a été modifié
# Pour obtenir une copie indépendante
c = m[0, :].copy()
Boolean Masks
Filtering by condition is one of NumPy’s most powerful idioms.
notes = np.array([12, 8, 15, 17, 9, 14])
masque = notes >= 10
print(masque) # [ True False True True False True]
# Sélection des éléments qui vérifient la condition
print(notes[masque]) # [12 15 17 14]
# Conditions combinées : & (et), | (ou), ~ (non), avec parenthèses obligatoires
print(notes[(notes >= 10) & (notes < 16)]) # [12 15 14]
# Modification conditionnelle
notes[notes < 10] = 10
print(notes) # [12 10 15 17 10 14]
# np.where : choisir entre deux valeurs selon une condition
mention = np.where(notes >= 14, "admis avec mention", "admis")
print(mention)
# ['admis' 'admis' 'admis avec mention' 'admis avec mention' 'admis' 'admis avec mention']
Broadcasting Explained Step by Step
Broadcasting is the mechanism that lets NumPy combine arrays of different shapes without copying data. The rules are as follows:
- Shapes are aligned from the right.
- Two dimensions are compatible if they are equal, or if one of them is 1.
- A dimension of size 1 is virtually stretched to match the other.
Case 1: Array and Scalar
a = np.array([1, 2, 3])
print(a + 10) # [11 12 13]
# Le scalaire 10 est traité comme un tableau de forme (1,) étiré en (3,)
Case 2: Matrix and Row Vector
m = np.arange(6).reshape(2, 3) # forme (2, 3)
v = np.array([10, 20, 30]) # forme (3,)
# Alignement par la droite : (2, 3) et ( 3) -> compatible
print(m + v)
# [[10 21 32]
# [13 24 35]]
# v est ajouté à chaque ligne de m
Case 3: Column Vector and Row Vector
col = np.array([[1], [2], [3]]) # forme (3, 1)
lig = np.array([10, 20, 30]) # forme (3,)
# (3, 1) et (3,) -> alignées en (3, 1) et (1, 3) -> résultat (3, 3)
print(col + lig)
# [[11 21 31]
# [12 22 32]
# [13 23 33]]
Incompatible Case
a = np.ones((2, 3))
b = np.ones((2, 2))
# a + b -> ValueError: operands could not be broadcast together
# (2, 3) et (2, 2) : la dernière dimension diffère et aucune ne vaut 1
To center the columns of a matrix, broadcasting makes the code very concise:
donnees = np.array([[1.0, 200.0], [2.0, 300.0], [3.0, 400.0]])
centrees = donnees - donnees.mean(axis=0)
print(centrees)
# [[-1. -100.]
# [ 0. 0.]
# [ 1. 100.]]
Vectorized Operations and Universal Functions
Universal functions (ufuncs) apply element by element, without a Python loop.
a = np.array([1.0, 4.0, 9.0])
print(np.sqrt(a)) # [1. 2. 3.]
print(np.exp(a)) # [2.71828183e+00 5.45981500e+01 8.10308393e+03]
print(np.log(a)) # [0. 1.38629436 2.19722458]
angles = np.linspace(0, np.pi, 3)
print(np.sin(angles)) # [0.0000000e+00 1.0000000e+00 1.2246468e-16]
# Opérateurs arithmétiques : tous vectorisés
b = np.array([10.0, 20.0, 30.0])
print(a + b) # [11. 24. 39.]
print(a * b) # [ 10. 80. 270.] -> produit élément par élément
print(b / a) # [10. 5. 3.33333333]
print(a ** 2) # [ 1. 16. 81.]
# Comparaisons vectorisées
print(a > 3) # [False True True]
# Arrondis
x = np.array([1.234, 5.678])
print(np.round(x, 1)) # [1.2 5.7]
print(np.floor(x)) # [1. 5.]
print(np.ceil(x)) # [2. 6.]
The golden rule: if you write a for loop over the elements of a NumPy array, there is almost always a faster, more readable vectorized formulation.
Aggregations: sum, mean, and the axis Parameter
m = np.array([[1, 2, 3],
[4, 5, 6]])
# Agrégation globale
print(m.sum()) # 21
print(m.mean()) # 3.5
print(m.min(), m.max()) # 1 6
print(m.std()) # 1.707825127659933
# axis=0 : on réduit le long des lignes -> un résultat par colonne
print(m.sum(axis=0)) # [5 7 9]
# axis=1 : on réduit le long des colonnes -> un résultat par ligne
print(m.sum(axis=1)) # [ 6 15]
# Indice du maximum
print(m.argmax()) # 5 (indice dans le tableau aplati)
print(m.argmax(axis=0)) # [1 1 1]
# Sommes cumulées
print(np.cumsum([1, 2, 3, 4])) # [ 1 3 6 10]
A handy mnemonic: axis designates the dimension that disappears. With axis=0 on a (2, 3) shape, the result has shape (3,).
Linear Algebra
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
# Produit matriciel : opérateur @ (recommandé) ou np.dot
print(A @ B)
# [[19 22]
# [43 50]]
print(np.dot(A, B)) # résultat identique
# Attention : * est le produit élément par élément, pas matriciel
print(A * B)
# [[ 5 12]
# [21 32]]
# Transposée
print(A.T)
# [[1 3]
# [2 4]]
# Déterminant et inverse
print(np.linalg.det(A)) # -2.0000000000000004
print(np.linalg.inv(A))
# [[-2. 1. ]
# [ 1.5 -0.5]]
# Résolution du système linéaire A x = b (préférable à inv(A) @ b)
b = np.array([5, 11])
x = np.linalg.solve(A, b)
print(x) # [1. 2.]
# Valeurs propres et vecteurs propres
valeurs, vecteurs = np.linalg.eig(A)
print(valeurs) # [-0.37228132 5.37228132]
# Norme d'un vecteur
v = np.array([3.0, 4.0])
print(np.linalg.norm(v)) # 5.0
Generating Random Numbers with default_rng
Since NumPy 1.17, the recommended API goes through an explicit generator created with np.random.default_rng, which is faster and better designed than the older np.random.rand family of functions.
# Générateur avec graine pour des résultats reproductibles
rng = np.random.default_rng(seed=42)
# Flottants uniformes dans [0, 1)
print(rng.random(3)) # [0.77395605 0.43887844 0.85859792]
# Entiers uniformes dans [0, 10)
print(rng.integers(0, 10, size=5)) # [8 6 5 2 3]
# Loi normale (moyenne 0, écart-type 1)
print(rng.normal(loc=0, scale=1, size=3)) # [ 0.7611397 -0.86157352 0.36371758]
# Tirage sans remise dans un ensemble
print(rng.choice([10, 20, 30, 40], size=2, replace=False)) # [20 40]
# Mélange en place
a = np.arange(5)
rng.shuffle(a)
print(a) # [4 0 3 1 2] (par exemple)
Reading and Writing Arrays
a = np.arange(12).reshape(3, 4)
# Format binaire natif .npy : rapide, préserve dtype et shape
np.save("tableau.npy", a)
b = np.load("tableau.npy")
print(np.array_equal(a, b)) # True
# Plusieurs tableaux dans une archive .npz
np.savez("archive.npz", donnees=a, labels=np.array([0, 1, 2]))
archive = np.load("archive.npz")
print(archive["labels"]) # [0 1 2]
# Format texte : lisible mais plus lent et moins précis
np.savetxt("tableau.csv", a, delimiter=",", fmt="%d")
c = np.loadtxt("tableau.csv", delimiter=",")
print(c.dtype) # float64 (loadtxt lit en float par défaut)
For CSV files with headers, heterogeneous columns, or missing values, prefer pandas.read_csv, which is far more robust than loadtxt.
Best Practices
- Vectorize systematically: replace
forloops with operations on whole arrays; it’s faster and more readable. - Beware of views: slices share memory with the original array; use
.copy()when you need an independent array. - Fix the random seed with
np.random.default_rng(seed)to make your experiments reproducible. - Use
@for matrix multiplication and reserve*for element-wise multiplication to avoid confusion. - Prefer
np.linalg.solveover matrix inversion to solve a linear system: it is more numerically stable. - Check
shapeanddtypewhenever a result surprises you: most NumPy bugs come from unintended broadcasting or an unexpected type. - Choose the
.npy/.npzformat to save arrays between Python sessions, and use CSV only for exchanging data with other tools.
Conclusion
NumPy is the foundation on which all data analysis in Python is built. By mastering the ndarray, indexing, boolean masks, broadcasting, and vectorized operations, you have the tools you need to write numerical code that is both concise and performant. These concepts carry over directly to Pandas (whose columns are NumPy arrays), to SciPy (which extends NumPy to advanced scientific computing), and to machine learning libraries. The natural next step is to explore Pandas for tabular data manipulation, then Matplotlib for visualization.
About InSkillCoach
Training and technology expert
Coach specializing in advanced technologies and AI, backed by GNeurone Inc.
Certifications:
- AWS Certified Solutions Architect – Professional
- Google Cloud certifications
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comments
Comments are powered by GitHub Discussions
Sign in with GitHub to join the discussion