NumPy#

NumPy (Numerical Python) is “the fundamental package for scientific computing with Python”.

Le site officiel est : https://numpy.org/

Il faut tout d’abord installer la librairie. Si vous travaillez avec Anaconda, c’est très facile car elle est déjà installée. Sinon il est possible de l’installer via pip install numpy. Ensuite, il faut l’importer. Il est coutume d’utiliser l’alias np pour la NumPy.

import numpy as np

Le type de base est un “N-dimensional array”. Pour créer un ndarray qui contient les chiffres de 1 à 5, nous pouvons écrire:

x = np.array([1, 2, 3, 4, 5])
print(x)
[1 2 3 4 5]
type(x)
numpy.ndarray

A première vue, rien ne le distingue d’une liste. Le code ci-dessous va nous permettre d’y voir plus clair:

my_list = [1, 2, 3, 4, 5]
double_list = my_list * 2
print(double_list)

my_array = np.array([1, 2, 3, 4, 5])
double_array = my_array * 2 
print(double_array)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
[ 2  4  6  8 10]

Comme on peut le constater, lorsqu’on multiplie une liste par 2, les différents éléments de la liste sont dupliqués. Les chiffres de 1 à 5 apparaissent donc deux fois. Par contre, lorsqu’on multiplie un ndarray par 2, chaque élément au sein est multiplié par 2. Pour rappel, il est toutefois possible de multiplier chaque élément d’une liste par 2 à l’aide de:

my_list = [1, 2, 3, 4, 5]
double_list = [2*x for x in my_list]
print(double_list)
[2, 4, 6, 8, 10]

Rapidité d’exécution#

Un autre avantage important de NumPy est sa rapidité. Prenons l’exemple ci-dessous dans lequel nous créons une liste ou un ndarray de 10,000,000 d’éléments. Ensuite, nous multiplions chaque élément par 2.

Note 1: pour comparer uniquement la vitesse de création de la liste de valeurs, nous n’imprimons pas cette liste (car cela nécessite également du temps).

Note 2: %%timeit fonctionne dans un environement IPython (Interactive Python). Pour plus d’informations, vous pouvez consulter :

Le premier script ci-dessous initialise une liste de 10 millions d’éléments ainsi qu’une liste vide. Ensuite, le script parcourt l’entièreté de la première liste, multiplie chaque élément par 2 et insère cette valeur dans la deuxième liste (celle qui était vide au départ).

%%timeit

my_list = list(range(10_000_000))
double_list = []
for item in my_list:
    double_list.append(2*item)
    
# print(double_list[:10])
1.44 s ± 276 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Une possibilité pour améliorer la rapidité d’exécution du code est de le vectoriser, c’est-à-dire que l’opération “multiplier par 2” est appliqué sur l’entièreté du vecteur. Les compréhensions de liste permettent d’arriver à ce résultat.

%%timeit

my_list = list(range(10_000_000))
double_list = [2*x for x in my_list]

# print(double_list[:10])
764 ms ± 16.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Les deux exemples précédents utilisent les listes. Le script ci-dessous utilise le ndarray de NumPy. La diminution du temps nécessaire à accomplir la tâche est significative.

%%timeit

my_array = np.arange(10_000_000)
double_array = my_array * 2

#print(double_array[:10])
30.1 ms ± 785 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Fonctions statistiques#

NumPy contient également une liste importante de fonctions statistiques très utiles. Pour la plupart des fonctions ci-dessous, le nom est sufisamment explicite pour comprendre l’utilité de la fonction:

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

print(np.sum(x))
print(np.mean(x))
print(np.min(x))
print(np.max(x))
print(np.median(x))
print(np.std(x))            # standard deviation = ecart-type 
print(np.std(x, ddof=1))    # standard deviation with 1 delta degree of freedom
print(np.var(x))            # variance 
print(np.var(x, ddof=1))    # variance with 1 delta degree of freedom 
print(np.quantile(x, 0.25)) # first quartile = premier quartile 
print(np.quantile(x, 0.75)) # third quartile = troisieme quartile 
55
5.5
1
10
5.5
2.8722813232690143
3.0276503540974917
8.25
9.166666666666666
3.25
7.75

math vs NumPy#

Si nous prenons le cas de la fonction exponentielle, \(f(x) = e^x\), les modules math et NumPy permettent d’obtenir la valeur. La différence principale réside dans le fait que la fonction math.exp() ne fonctionne qu’avec un scalaire alors que np.exp() peut être appliqué soit sur un scalaire, soit sur un vecteur.

import math
%%timeit

x = list(range(1, 100))
y = [math.exp(x) for x in x]
22.8 µs ± 426 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
%%timeit

x = np.arange(1, 100)
y = np.exp(x)
3.65 µs ± 98.5 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Il en est de même pour la fonction racine carré, \(f(x) = \sqrt{x}\).

%%timeit 

x = list(range(1, 10000))
y = [math.sqrt(x) for x in x]
2.77 ms ± 74.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit

x = np.arange(1, 10000)
y = np.sqrt(x)
24.2 µs ± 2.22 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Array multidimensionnel#

Jusqu’à présent, nous avons principalement considéré des vecteurs. NumPy permet également de travailler avec des array multidimensionnel, notamment des array de dimension 2, ce qui correspond d’un point de vue mathématique à une matrice.

matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

Tout ce que nous venons de découvrir sur les vecteurs peut donc également s’appliquer aux matrices. Prenons l’exemple de la fonction somme:

np.sum(matrix)
45

En spécifiant en paramètre axis=0, la fonction somme s’appliquera colonne par colonne.

np.sum(matrix, axis=0)
array([12, 15, 18])

De même, en spécifiant axis=1, la fonction somme s’appliquera ligne par ligne.

np.sum(matrix, axis=1)
array([ 6, 15, 24])

Fonctions spécifiques aux matrices#

Le module NumPy contient un sous-module spécifiquement dédié à l’algèbre linéaire. Ce sous-module s’appelle linalg. Pour accéder à une fonction de ce sous-module, par exemple det qui calcule le déterminant d’une matrice, il faut écrire:

matrix = np.array([[1, 2],
                   [3, 4]])

np.linalg.det(matrix)
-2.0000000000000004

Une autre façon d’avoir accès à la fonction det est d’utiliser:

from numpy.linalg import det 

matrix = np.array([[1, 2],
                   [3, 4]])

det(matrix)
-2.0000000000000004