import numpy as np # np is the convention
À la différence des listes, les tableaux ont toutes leurs valeurs du même type.
Création d'un tableau¶
a = np.array([[1,2,3], [4,5,7]]) # 2D array built from a list
print(a)
print("shape: ",a.shape)
print("type: ",type(a[0,0]))
[[1 2 3] [4 5 7]] shape: (2, 3) type: <class 'numpy.int64'>
dtype : le choix du type des éléments¶
Lorsqu'on fait du calcul scientifique il est important de choisir le type des tableaux afin d'optimiser la mémoire, les erreurs, la vitesse.
NumPy propose les types suivants :
Data type | Description |
---|---|
bool | Boolean (True or False) stored as a byte |
int | Platform integer (normally either int32 or int64) |
int8 | Byte (-128 to 127) |
int16 | Integer (-32768 to 32767) |
int32 | Integer (-2147483648 to 2147483647) |
int64 | Integer (-9223372036854775808 to 9223372036854775807) |
uint8 | Unsigned integer (0 to 255) |
uint16 | Unsigned integer (0 to 65535) |
uint32 | Unsigned integer (0 to 4294967295) |
uint64 | Unsigned integer (0 to 18446744073709551615) |
float | Shorthand for float64. |
float16 | Half precision float: sign bit, 5 bits exponent, 10 bits mantissa |
float32 | Single precision float: sign bit, 8 bits exponent, 23 bits mantissa |
float64 | Double precision float: sign bit, 11 bits exponent, 52 bits mantissa |
complex | Shorthand for complex128. |
complex64 | Complex number, represented by two 32-bit floats (real and imaginary components) |
complex128 | Complex number, represented by two 64-bit floats (real and imaginary components) |
x = np.arange(4, dtype= np.uint8) # arange is the numpy version of range to produce an array
print("x =", x, x.dtype)
x[0] = -2 # 0 - 2 = max -1 for unsigned int
print("x =", x, x.dtype)
y = x.astype('float32') # conversion
print("y =", y, y.dtype)
x = [0 1 2 3] uint8 x = [254 1 2 3] uint8 y = [254. 1. 2. 3.] float32
On peut connaitre la taille mémoire (en octets) qu'occupe un élément du tableau et l'occupation de tout le tableau :
print(x[0].itemsize)
print(y[0].itemsize)
y.nbytes
1 4
16
Méthodes prédéfinies¶
On a déjà vu la méthode arange
pour créer un tableau, il en existe aussi pour
créer un tableau vide ou de la dimension de son choix avec que des 0 ou que des 1 ou ce qu'on veut.
En fait il existe tellement de méthodes qu'on en présente qu'une petite partie ici, pour les autres voir la
liste des méthodes de création prédéfinies.
a = np.empty((2,2), dtype=float) # empty do not set any value, it is faster
print("Empty float:\n", a)
print("Float zeros:\n", np.zeros((2,2), dtype=float)) # matrix filled with 0
print("Complex ones:\n", np.ones((2,3), dtype=complex)) # matrix filled with 1
print("Full of 3.2:\n", np.full((2,2), 3.2))
print("La matrice suivante est affichée partiellement car trop grande : ")
print(np.identity(1000)) # identity matrix
Empty float: [[2.42350504e-316 0.00000000e+000] [4.94065646e-324 nan]] Float zeros: [[0. 0.] [0. 0.]] Complex ones: [[1.+0.j 1.+0.j 1.+0.j] [1.+0.j 1.+0.j 1.+0.j]] Full of 3.2: [[3.2 3.2] [3.2 3.2]] La matrice suivante est affichée partiellement car trop grande : [[1. 0. 0. ... 0. 0. 0.] [0. 1. 0. ... 0. 0. 0.] [0. 0. 1. ... 0. 0. 0.] ... [0. 0. 0. ... 1. 0. 0.] [0. 0. 0. ... 0. 1. 0.] [0. 0. 0. ... 0. 0. 1.]]
print("Random integers < 10:\n", np.random.randint(10, size=(3,4))) # can also choose a min
print("Random reals between 0 and 1 :\n", np.random.random(size=(3,4)))
Random integers < 10: [[7 5 7 2] [6 7 0 8] [6 3 8 3]] Random reals between 0 and 1 : [[0.45228105 0.39674551 0.02142443 0.14342572] [0.95223097 0.85798264 0.85777533 0.28784271] [0.5142878 0.36823048 0.87704756 0.44096786]]
On peut choisir la loi de distribution (loi uniforme par défaut).
loc = 3
scale = 1.5
np.random.normal(loc, scale, size=(2,3)) # Gauss distribution
array([[1.44216124, 2.5586318 , 4.28770816], [2.16814785, 1.82332334, 4.53031302]])
En redéfinissant sa forme¶
Un cas classique pour faire des tests est de créer un petit tableau multidimensionnelle avec des valeurs différentes.
Pour cela le plus simple est de mettre 0,1,2,...,N dans les cases de notre tableau de forme (3,4) par exemple.
Cela se fait avec arange
qu'on a déjà vu pour générer les valeurs et reshape
pour avoir la forme voulue :
arr = np.arange(3*4).reshape((3,4))
arr
array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
arr[1, 0]
4
Attention, la numérotation des cases d'un tableau est effectuée avec des boucles imbriquées aussi c'est toujours la dernière dimension qui varie le plus vite. En 3D cela veut dire que passer d'un élément au suivant fait varier le dernier indice (suivant z). Cela ne correspond pas à la manière humaine de remplir un cube (on a tendance à faire des empilements de tableaux 2D). À l'usage cela ne pose pas de problème, c'est seulement bizarre lorsqu'on affiche des tests pour voir.
humain Numpy
┌─┬─┐ ┌─┬─┐
┌─┬─┐│5│ ┌─┬─┐│3│
│0│1│┼─┤ │0│2│┼─┤
├─┼─┤│7│ ├─┼─┤│7│
│2│3│┴─┘ │4│6│┴─┘
└─┴─┘ └─┴─┘
A = np.arange(8).reshape(2,2,2) # a[0,1,0] is 2 and a[0,1,1] is 3
A
array([[[0, 1], [2, 3]], [[4, 5], [6, 7]]])
L'inverse de reshape est flatten()
qui transforme un tableau à plusieurs dimension en un tableau à 1 dimension. On peut aussi utiliser flat
pour avoir une vue 1D sur le tableau sans le transformer.
print(A.flat[5])
print(A.shape)
5 (2, 2, 2)
Mélanger les valeurs¶
Si on veut travailler avec les valeurs d'un tableau prises dans un ordre aléatoire alors on peut mélanger
le tableau avec np.random.permutation()
. Attention la permutation s'effectue sur les éléments du premier niveau du tableau, A[:]
, à savoir des tableaux
si le tableau est à plusieurs dimensions (un tableau de tableaux).
data = np.arange(12).reshape(3,4)
np.random.permutation(data) # permutes lines only
array([[ 4, 5, 6, 7], [ 8, 9, 10, 11], [ 0, 1, 2, 3]])
Si on veut mélanger toutes les valeurs il faut applatir le tableau et lui redonner sa forme :
data = np.random.permutation(data.flatten()).reshape(data.shape) # it works because flatten returns a copy
data
array([[10, 3, 11, 8], [ 4, 1, 2, 7], [ 0, 9, 6, 5]])
Avec une fonction de son choix¶
On peut aussi créer un tableau avec une fonction qui donne la valeur du tableau pour chaque indice (i,j) :
def f(i,j):
return 2*i - j
np.fromfunction(f, shape=(3,4), dtype=int)
array([[ 0, -1, -2, -3], [ 2, 1, 0, -1], [ 4, 3, 2, 1]])
Opérations de base¶
Numpy permet d'appliquer les opérations mathématiques usuelles à tous les éléments des tableaux :
A = np.array([[1,2], [3,4]])
print("A + 1:\n", A + 1, '\n')
print("2 A + Id:\n", 2 * A + np.identity(2), '\n')
print(u"A * A (element-wise product):\n", A * A, '\n') # or np.square(A)
print(u"A @ A (matrix or dot product):\n", A @ A) # or A.dot(A)
A + 1: [[2 3] [4 5]] 2 A + Id: [[3. 4.] [6. 9.]] A * A (element-wise product): [[ 1 4] [ 9 16]] A @ A (matrix or dot product): [[ 7 10] [15 22]]
On peut transposer un tableau avec .T
. Cela ne fait rien avec un tableau en 1D, pour faire la différence entre vecteur horizontal et un vecteur vertical, il faut l'écrire en 2D :
v = np.array([[1,3,5]])
print(v, '\n\n', v.T, '\n')
print("Guess what v + v.T means:\n", v + v.T)
[[1 3 5]] [[1] [3] [5]] Guess what v + v.T means: [[ 2 4 6] [ 4 6 8] [ 6 8 10]]
On dispose aussi des fonctions trigonométriques, hyperboliques, exposant, logarithme...
np.set_printoptions(precision=3) # set printing precision for reals
np.sin(A)
array([[ 0.841, 0.909], [ 0.141, -0.757]])
Enfin Numpy offre un ensemble de méthodes pour faire des calculs sur les élements du tableau :
sum()
pour additionner tous les élementsmean()
pour avoir la moyenne des éléments etaverage()
pour avoir la moyenne pondérée,prod()
pour multiplier tous les élements,min()
etmax()
pour avoir la valeur minimale et la valeur maximale,argmin()
etargmax()
pour avoir les indices des valeurs minimales et maximales du tableaucumsum()
etcumprod()
pour les additions et multiplication cumulatives,diff()
pour avoir l'écart avec l'élément suivant (utile pour calculer une dérivée).
Chaque méthode A.sum()
existe aussi en fonction np.sum(A)
.
A.argmax()
3
np.diff(A.flatten())
array([1, 1, 1])
Parcourir un tableau¶
Le facon naturelle pour parcourir tous les éléments d'un tableau à plusieur dimension est de faire une boucle pour chaque dimension :
a = np.arange(6).reshape(3,2)
for ligne in a:
for element in ligne:
print(" ", element, end="") # end="" avoid the return after each print
print()
0 1 2 3 4 5
On a vu dans la manipulation de la forme d'un tableau qu'on peut l'applatir, mais plutôt que d'utiliser flatten()
qui fabrique un tableau en 1 dimension, on préfère utiliser flat
qui donne itérateur pour parcourir tous les éléments du tableau.
for v in a.flat:
print(v)
0 1 2 3 4 5
Il est aussi possible de faire une boucle sur les indices mais c'est nettement moins performant.
for i in range(len(a)):
for j in range(len(a[i])):
print(a[i,j])
0 1 2 3 4 5
Travailler en vectoriel¶
Faire des boucles correspond souvent à la facon de penser de ceux qui programment depuis longtemps mais ce n'est pas efficace en Python. Il est préférable de travailler directement sur le tableau. Ainsi plutôt que de faire la boucle
for i in range(len(x)):
z[i] = x[i] + y[i]
on fera
z = x + y
Non seulement c'est plus lisible mais c'est aussi plus rapide.
def double_loop(a):
for i in range(a.shape[0]):
for j in range(a.shape[1]):
a[i,j] = np.sqrt(a[i,j]) # change in-place
def iterate(a):
for x in a.flat:
x = np.sqrt(x) # modification not saved in a, x is a local var
b = np.random.random(size=(200,200))
a = b.copy() # we need a copy to be sure to use the same data each time
%timeit double_loop(a)
a = b.copy()
%timeit iterate(a)
a = b.copy()
%timeit np.sqrt(a) # vectorial operation, 1000 times faster
45.5 ms ± 494 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 35.3 ms ± 139 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 36.9 µs ± 156 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)