Une introduction à Numpy¶
- Tableaux Numpy
- Création de tableaux
- Opérations basiques
- Indexation et slicing
- Algèbre linéaire
- Méthodes sur les tableaux
Contenu sous licence CC BY-SA 4.0, largement inspiré de https://github.com/pnavaro/python-notebooks
Numpy¶
Le Python pur est peu performant pour le calcul. Les listes ne sont pas des objets efficaces pour représenter les tableaux numériques de grande taille. Numpy a été créé à l'initiative de développeurs qui souhaitaient combiner la souplesse du langage python et des outils de calcul algébrique performants.
Numpy se base sur :
- le
ndarray
: tableau multidimensionnel - des objets dérivés comme les masked arrays et les matrices
- les
ufunc
s : opérations mathématiques optimisées pour les tableaux - des méthodes pour réaliser des opération rapides sur les tableaux :
- manipulation des shapes
- tri
- entrées/sorties
- FFT
- algébre linéaire
- statistiques
- calcul aléatoire
- et bien plus !
Numpy permet de calculer à la Matlab en Python. Il est un élément de base de l'écosystème SciPy
Démarrer avec Numpy¶
import numpy as np
print(np.__version__)
1.26.2
Utilisez l'auto-complétion pour lister les objets numpy :
#np.nd<TAB>
La rubrique d'aide est également précieuse
?np.ndarray
- Les listes Python sont trop lentes pour le calcul et utilisent beaucoup de mémoire
- Représenter des tableaux multidimensionnels avec des listes de listes devient vite brouillon à programmer
from random import random
from operator import truediv
L1 = [random() for i in range(100000)]
L2 = [random() for i in range(100000)]
%timeit s = sum(map(truediv, L1, L2))
3.33 ms ± 66.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
a1 = np.array(L1)
a2 = np.array(L2)
%timeit s = np.sum(a1/a2)
124 µs ± 485 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
La différence avec les listes¶
Les différences entre tableaux Numpy et listes Python :
- un
ndarray
a une taille fixée à la création - un
ndarray
est composé d'éléments du même type - les opérations sur les tableaux sont réalisées par des routines C pré-compilées et optimisées (éventuellement parallèles)
a = np.array([0, 1, 2, 3]) # list
b = np.array((4, 5, 6, 7)) # tuple
c = np.matrix('8 9 0 1') # string (syntaxe matlab)
print(a, b, c)
[0 1 2 3] [4 5 6 7] [[8 9 0 1]]
Propriétés¶
a = np.array([1, 2, 3, 4, 5]) # On crée un tableau
type(a) # On vérifie son type
numpy.ndarray
a.dtype # On affiche le type de ses éléments
dtype('int64')
a.itemsize # On affiche la taille mémoire (en octets) de chaque élément
8
a.shape # On retourne un tuple indiquant la longueur de chaque dimension
(5,)
a.size # On retourne le nombre total d'éléments
5
a.ndim # On retourne le nombre de dimensions
1
a.nbytes # On retourne l'occupation mémoire
40
- Toujours utiliser
shape
ousize
pour les tableaux numpy plutôt quelen
len
est réservé aux tableaux 1D
x = np.zeros((5, 3)) # On ne précise pas le type : on crée des flottants
print(x)
print(x.dtype)
[[0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] float64
x = np.zeros((5, 3), dtype=int) # On explicite le type
print(x)
print(x.dtype)
[[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] int64
On dispose d'une panoplie de fonctions pour allouer des tableaux avec des valeurs constantes ou non initialisées (empty
) :
empty, empty_like, ones, ones_like, zeros, zeros_like, full, full_like
np.arange(5) # entiers de 0 à 4
array([0, 1, 2, 3, 4])
np.arange(5, dtype=np.double) # flottants de 0. à 4.
array([0., 1., 2., 3., 4.])
np.arange(2, 7) # entiers de 2 à 6.
array([2, 3, 4, 5, 6])
np.arange(2, 7, 0.5) # flottants avec incrément de 0.5.
array([2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5])
linspace
et logspace
¶
# 5 éléments régulièrement espacés entre 1 et 4, 1 et 4 inclus
np.linspace(1, 4, 5)
array([1. , 1.75, 2.5 , 3.25, 4. ])
# 5 éléments régulièrement espacés selon une progression géométrique entre 10^1 et 10^4
np.logspace(1, 4, 5)
array([ 10. , 56.23413252, 316.22776602, 1778.27941004, 10000. ])
Consulter l'aide contextuelle pour plus de fonctionnalités
?np.logspace
Exercice : créer les tableaux suivants¶
[100 101 102 103 104 105 106 107 108 109]
Astuce :
np.arange()
# Votre code ici
# Solution
np.arange(100, 110)
array([100, 101, 102, 103, 104, 105, 106, 107, 108, 109])
[-2. -1.8 -1.6 -1.4 -1.2 -1. -0.8 -0.6 -0.4 -0.2 0.
0.2 0.4 0.6 0.8 1. 1.2 1.4 1.6 1.8]
Astuce :
np.linspace()
# Votre code ici
# Solution
np.linspace(-2., 2., num=20, endpoint=False)
array([-2. , -1.8, -1.6, -1.4, -1.2, -1. , -0.8, -0.6, -0.4, -0.2, 0. , 0.2, 0.4, 0.6, 0.8, 1. , 1.2, 1.4, 1.6, 1.8])
[ 0.001 0.00129155 0.0016681 0.00215443 0.00278256
0.00359381 0.00464159 0.00599484 0.00774264 0.01]
Astuce :
np.logspace()
# Votre code ici
# Solution
np.logspace(-3, -2, num=10)
array([0.001 , 0.00129155, 0.0016681 , 0.00215443, 0.00278256, 0.00359381, 0.00464159, 0.00599484, 0.00774264, 0.01 ])
[[ 0. 0. -1. -1. -1.]
[ 0. 0. 0. -1. -1.]
[ 0. 0. 0. 0. -1.]
[ 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0.]]
Astuce :
np.tri()
,np.ones()
# Votre code ici
# Solution
np.tri(7, 5, k=1) - np.ones((7, 5))
array([[ 0., 0., -1., -1., -1.], [ 0., 0., 0., -1., -1.], [ 0., 0., 0., 0., -1.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0.]])
Création de tableaux à partir de fichiers¶
Afin d'illustrer la création d'un tableau numpy à partir de données lues dans un fichier texte, on commence par sauvegarder un tableau dans un fichier.
x = np.arange(0.0, 5.0, 1.0)
y = x*10.
z = x*100.
np.savetxt('test.out', (x, y, z))
%pycat test.out
np.savetxt('test.out', (x, y, z), fmt='%1.4e') # Notation exponentielle
%pycat test.out
np.loadtxt('test.out')
array([[ 0., 1., 2., 3., 4.], [ 0., 10., 20., 30., 40.], [ 0., 100., 200., 300., 400.]])
Format HDF5 avec H5py¶
Le format .npy
n'est lisible que par Numpy.
À l'inverse, le format HDF5 est partagé par un grand nombre d'applications.
De plus, il permet de structurer des données binaires :
- en les nommants
- en ajoutant des métadonnées
- en assurant une portabilité indépendante de la plateforme
voir le manuel utilisateur
H5py est une interface Python pour HDF5.
On écrit dans le fichier test.h5
import h5py as h5
with h5.File('test.h5', 'w') as f:
f['x'] = x
f['y'] = y
f['z'] = z
On lit le fichier test.h5
(qui pourrait provenir d'une autre application)
with h5.File('test.h5', 'r') as f:
for field in f.keys():
print(f"{field}: {f[field][()]}")
x: [0. 1. 2. 3. 4.] y: [ 0. 10. 20. 30. 40.] z: [ 0. 100. 200. 300. 400.]
Opérations basiques sur les tableaux¶
Par défaut, Numpy réalise les opérations arithmétiques éléments par éléments
a = np.array([0, 1, 2, 3])
b = np.array((4, 5, 6, 7))
print(a * b) # Produit éléments par éléments : pas le produit matriciel !
print(a + b)
[ 0 5 12 21] [ 4 6 8 10]
print(a**2)
[0 1 4 9]
print(5 * a)
print(5 + a)
[ 0 5 10 15] [5 6 7 8]
print(a < 2)
[ True True False False]
print(np.cos(a*np.pi)) # Utilisation de ufunc
[ 1. -1. 1. -1.]
De nombreuses ufunc dans la doc officielle.
# Indexation d'un tableau numpy
a = np.arange(9).reshape(3, 3) # Notez l'effet de la méthode reshape()
print(a)
print(a[1, 2])
[[0 1 2] [3 4 5] [6 7 8]] 5
# Indexation de la liste équivalente
liste = a.tolist()
print(liste)
print(liste[1][2])
[[0, 1, 2], [3, 4, 5], [6, 7, 8]] 5
Slicing¶
Pour les tableaux unidimensionnels, les règles de slicing sont les mêmes que pour les séquences. Pour les tableaux multidimensionnels numpy, le slicing permet d'extraire des séquences dans n'importe quelle direction.
print(a)
print(a[2, :]) # Retourne la 3ème ligne
print(a[:, 1]) # Retourne la deuxième colonne
[[0 1 2] [3 4 5] [6 7 8]] [6 7 8] [1 4 7]
Attention : contrairement aux listes, le slicing de tableaux ne renvoie pas une copie mais constitue une vue du tableau.
b = a[:, 1]
b[0] = -1
print(a)
[[ 0 -1 2] [ 3 4 5] [ 6 7 8]]
a
est aussi une vue du tableau np.arange(9)
obtenue avec la méthode reshape()
donc a
et b
sont deux vues différentes du même tableau :
b.base is a.base # b.base retourne le tableau dont b est la vue
True
Si on veut réaliser une copie d'un tableau, il faut utiliser la fonction copy()
b = a[:, 1].copy()
ici b
n'est pas une vue mais une copie donc b.base
vaut None
et a
n'est pas modifié.
print(b.base)
b[0] = 200
print(a)
None [[ 0 -1 2] [ 3 4 5] [ 6 7 8]]
Exercice¶
Approcher la dérivée de $f(x) = \sin(x)$ par la méthode des différences finies :
$$\frac{\partial f}{\partial x} \approx \frac{f(x+\Delta x)-f(x)}{\Delta x}$$
Les valeurs seront calculées au milieu de deux abscisses discrètes successives.
# On crée un tableau de 40 points d'abscisse
x, dx = np.linspace(0., 4.*np.pi, 40, retstep=True)
y = np.sin(x)
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(x, np.cos(x)); # la dérivée analytique de sin() est cos()
# Votre code ici
# Décommentez la dernière ligne pour tracer la dérivée numérique dy_dx
# en fonction du milieu de 2 abscisses x_mil
#plt.plot(x_mil, dy_dx, 'rx');
# Solution
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(x, np.cos(x)) # la dérivée analytique de sin() est cos()
x_mil = 0.5*(x[1:] + x[:-1])
dy = y[1:] - y[:-1]
dy_dx = dy/dx
plt.plot(x_mil, dy_dx, 'rx') # x_mil est le milieu de deux abscisses
plt.title(r"$\rm{Derivative\ of}\ \sin(x)$");
Quelques opérations d'algèbre linéaire¶
A = np.array([[1, 1],
[0, 1]])
B = np.array([[2, 0],
[3, 4]])
print(A*B) # produit élément par élément
print(A.dot(B)) # produit matriciel
print(np.dot(A, B)) # une autre syntaxe de produit matriciel
print(A@B) # encore un autre produit matriciel
[[2 0] [0 4]] [[5 4] [3 4]] [[5 4] [3 4]] [[5 4] [3 4]]
a = np.array([[1.0, 2.0],
[3.0, 4.0]])
print(a)
[[1. 2.] [3. 4.]]
# Deux syntaxes équivalentes pour la transposition
print(a.transpose())
print(a.T)
[[1. 3.] [2. 4.]] [[1. 3.] [2. 4.]]
print(np.linalg.inv(a)) # inversion de matrice
[[-2. 1. ] [ 1.5 -0.5]]
print(np.trace(a)) # trace
5.0
print(np.eye(2)) # "eye" représente "I", la matrice identité
[[1. 0.] [0. 1.]]
y = np.array([[5.], [7.]]) # Résoudre A*x = y
x = np.linalg.solve(a, y)
print(x)
print(a@x == y)
[[-3.] [ 4.]] [[ True] [ True]]
j = np.array([[0.0, -1.0], [1.0, 0.0]])
print(np.dot(j, j)) # produit matriciel
print(np.linalg.eig(j)) # Extraction des valeurs propres
[[-1. 0.] [ 0. -1.]] EigResult(eigenvalues=array([0.+1.j, 0.-1.j]), eigenvectors=array([[0.70710678+0.j , 0.70710678-0.j ], [0. -0.70710678j, 0. +0.70710678j]]))
Les méthodes associées aux tableaux¶
Elles sont très nombreuses : impossible de toutes les lister dans le cadre de ce cours. Citons brièvement :
min
,max
,sum
sort
,argmin
,argmax
- statistiques basiques :
cov
,mean
,std
,var
À chercher dans la doc officielle.
Programmation avec Numpy¶
- Les opérations sur les tableaux sont rapides, les boucles python sont lentes => Éviter les boucles !
- C'est une gymnastique qui nécessite de l'entraînement
- Le résultat peut devenir difficile à lire et à débugguer, par exemple dans le cas de boucles contenant de multiples conditions
- D'autres options sont alors envisageables (Cython, Pythran, Numba, etc.)