Le module NumPy#

Vidéo: Introduction#

Le tableau NumPy#

L’objet de base du module NumPy est un tableau à \(N\) dimensions (ndarray), que l’on appelera simplement tableau par la suite. Le plus simple pour créer un tableau est de convertir une liste en tableau. Pour cela, il faut utiliser la fonction array() du module NumPy :

# Import du module NumPy avec un alias np
import numpy as np

# Création de la liste
ma_liste = [2.2, 6.1, 9.6, 1.3, 56.8, 8.0]

# Création du tableau NumPy
mon_tableau = np.array(ma_liste)

# Affichage du tableau et de son type
print(mon_tableau)
print(type(mon_tableau))
[ 2.2  6.1  9.6  1.3 56.8  8. ]
<class 'numpy.ndarray'>

Nous avons une nouvelle classe (ou type d’objet, c’est équivalent) : le numpy.ndarray, ou tableau NumPy. Comme toute classe, il a des fonctions (appelées « méthodes ») et des opérateurs associés.

En Python, un objet a des attributs (des variables internes) auxquels on peut accéder en écrivant le nom de l’objet, suivi d’un point et du nom de l’attribut. Voici la liste des attributs d’un tableau NumPy :

attribut

description

t.ndim

nombre de dimensions

t.shape

taille du tableau dans chaque dimension

t.size

nombre total d’éléments

t.dtype

type des éléments du tableau

Regardons quelques attributs de mon_tableau :

# Attribut nombre de dimensions (ndim)
mon_tableau.ndim
1
# Attribut taille (size)
mon_tableau.size
6
# Attribut forme (shape) -> résultat sous la forme d'un tuple
mon_tableau.shape
(6,)

tuple

Un tuple est un type de données séquentiel comme les listes. Cependant, alors que les listes sont muables (modifiables), les tuples sont immuables (non modifiables). On peut créer un tuple comme une liste, mais en utilisant une paire de parenthèses plutôt que des crochets, par exemple a = (2, 4, 3). Pour différencier un tuple à un élément d’une simple paire de parenthèses, on ajoute une virgule après le chiffre, par exemple a = (2,).

# Attribut type des éléments (dtype)
mon_tableau.dtype
dtype('float64')

dtype

Numpy contient un nombre de types numériques beaucoup plus important que les types natifs de Python. De plus, il est possible de créer facilement de nouveaux types de données structurées. C’est pourquoi NumPy est adpaté au calcul scientifique. Le type par défaut d’un réel lorsque l’on crée un tableau est float64. Ce type est équivalent (en précision) au type float natif de Python. 64 bits correspond à la place que prend un scalaire en mémoire. On peut trouver une liste des types natifs de NumPy.

Exercice#

La liste suivante contient le nombre d’habitants des 8 villes de France les plus peuplées : Paris, Marseille, Lyon, Toulouse, Nice, Nantes, Strasbourg et Montpellier (chiffres INSEE 2012)

nombre_habitants = [2240621, 852516, 496343, 453317, 343629, 291604, 274394, 268456]
  1. Importer le module numpy sans alias.

  2. Créer un tableau NumPy np_nombre_habitants à partir de la liste nombre_habitants.

  3. Afficher le tableau créé et son attribut dtype.

Hide code cell source
# Import du module NumPy
import numpy

# Création du tableau NumPy
np_nombre_habitants = numpy.array(nombre_habitants)

# Afficher le tableau et son attribut dtype
print(np_nombre_habitants)
print(np_nombre_habitants.dtype)
[2240621  852516  496343  453317  343629  291604  274394  268456]
int64

Opérations sur les tableaux#

Un grand intérêt des tableaux NumPy par rapport aux listes Python est la possibilité de les utiliser dans des expressions avec des opérateurs arithmétiques.

Les opérateurs arithmétiques que l’on a vus pour les types scalaires en Python (addition, soustraction, multiplication, etc) s’appliquent terme à terme sur les tableaux.

Par exemple, si on multiplie un tableau par un scalaire, chaque élément du tableau est multiplié :

# Création du tableau
mon_tableau = np.array([2.2, 6.0, 9.6])

# Multiplication par un scalaire
print(0.5 * mon_tableau)
[1.1 3.  4.8]

On peut aussi utiliser des opérateurs arithmétiques dans des expressions avec des tableaux de même forme. Les opérations sont alors effectuées terme à terme : le 1er élément du 1er tableau avec le 1er élément du 2ème tableau, etc.

# Création de 2 tableaux
A = np.array([0, 5, 3])
B = np.array([2, 5, 1])

# Addition terme à terme
print(A + B)
[ 2 10  4]

On peut former des expressions avec des tableaux de types numériques différents. Le résultat correspond alors au type le plus général ou le plus précis (propriété appelée upcasting).

Par exemple, si on multiplie un tableau d’entiers avec un tableau de réels :

# Création d'un tableau de réels
A = np.array([0.0, 5.0, 3.0], dtype = 'float64')

# Création d'un tableau d'entiers
B = np.array([2, 5, 1], dtype = 'int64')

# Multiplication terme à terme
C = A * B

# Type des éléments du tableau résultant
print(C.dtype)
float64

Exercice#

Reprenons le tableau np_nombre_habitants, donnant le nombre d’habitants des 8 villes de France les plus peuplées (Paris, Marseille, Lyon, Toulouse, Nice, Nantes, Strasbourg et Montpellier). On définit un tableau donnant la superficie de ces mêmes villes (en \(\mathrm{km}^2\)), dans le même ordre :

np_superficie = np.array([105.40, 240.62, 47.87, 118.30, 71.92, 65.19, 78.26, 56.88]) # km**2
  1. Calculer la densité de population de chaque ville en nombre d’habitants par \(\mathrm{km}^2\). Affecter le résultat à une variable np_densite et afficher le résultat. Note : une seule expression sur une seule ligne est nécessaire pour effectuer le calcul.

  2. Quel est le type des éléments du tableau np_densité (attribut dtype) ?

Hide code cell source
# Calcul de la densité de population (hab/km**2)
np_densité = np_nombre_habitants / np_superficie

# Afficher le résultat
print(np_densité)

# Attribut dtype du résultat
print(np_densité.dtype)
[21258.26375712  3542.99725709 10368.56068519  3831.92730347
  4777.93381535  4473.14005216  3506.18451316  4719.69057665]
float64

Indexation et tranche#

Comme pour les listes, on peut utiliser les opérateurs indexation [] et tranche [m:n] avec les tableaux Numpy.

Rappelons les règles d’indexation :

  • toute expression entière peut être utilisée comme indice

  • si un indice a une valeur négative, il compte en sens inverse, à partir de la fin de la liste

  • si vous essayez de lire ou d’écrire un élément qui n’existe pas, vous obtenez une erreur

# Création du tableau
A = np.array([4, 6, 1, 23, 3, 8, 9])

# Sélection de l'élément d'indice 3
print(A[3])
23

Attention

Comme pour les listes, le premier élément de la liste est numéroté avec l’indice 0, le deuxième élément avec l’indice 1, et ainsi de suite.

# Sélection d'une tranche du tableau
tranche = A[2:5]

# Affichage
print(tranche)
[ 1 23  3]

Attention

Comme pour les listes, l’élément de fin de la tranche est exclu.

Avec l’opérateur tranche, on peut décider de sauter des éléments, en écrivant [m:n:step]. Par exemple, si on ne veut prendre qu’un élément sur deux :

# Création du tableau
A = np.array([4, 6, 1, 23, 3, 8, 9])

# Sélection d'un élément sur deux
tranche = A[0:7:2]

# Affichage
print(tranche)
[4 1 3 9]

Ou de manière équivalente :

# Sélection d'un élément sur deux
tranche = A[::2]

# Affichage
print(tranche)
[4 1 3 9]

En effet, si on ne spécifie pas de valeur pour m et n dans l’opérateur tranche [m:n:step], par défaut m est l’indice du premier élément du tableau (0), et n est l’indice du dernier élément du tableau plus 1 (A.size).

Vidéo: Tableaux à 2 dimensions#

Tableau à 2 dimensions#

Un tableau Numpy peut avoir autant de dimensions que l’on veut. Nous avons manipulé jusqu’ici des tableaux à une dimension, généralement utilisés pour contenir un type d’information. Nous avons introduit 2 tableaux Numpy :

  • np_nombre_habitants qui contient le nombre d’habitant·es des 8 villes de France les plus peuplées

  • np_superficie qui contient les superficies de ces 8 villes

Nous allons maintenant structurer ces informations en utilisant un tableau à 2 dimensions :

  • la première dimension (lignes) donne pour une ville donnée son nombre d’habitants et sa superficie

  • la deuxième dimension (colonnes) donne pour toutes les villes une information donnée (son nombre d’habitants ou sa superficie)

Nous allons créer le tableau Numpy à deux dimensions à partir d’une liste de listes :

# Création de liste contenant les informations : nombre d'habitant·es et superficie (km**2)
villes = [[2240621, 105.40],    # Paris
        [852516, 240.62],       # Marseille
        [496343, 47.87],        # Lyon
        [453317, 118.30],       # Toulouse
        [343629, 71.92],        # Nice
        [291604, 65.19],        # Nantes
        [274394, 78.26],        # Strasbourg
        [268456, 56.88]]        # Montpellier

# Création du tableau Numpy à 2 dimensions
np_villes = np.array(villes)

Exercice#

Afficher les quatres attributs du tableau Numpy np_villes : nombre de dimensions, taille du tableau dans chaque dimension, nombre total d’éléments, type des éléments du tableau.

Hide code cell source
# Affichage des attributs du tableau
print(np_villes.ndim)   # Nombre de dimensions
print(np_villes.shape)  # Taille du tableau dans chaque dimension
print(np_villes.size)   # Nombre total d'éléments
print(np_villes.dtype)  # Type des éléments du tableau
2
(8, 2)
16
float64

Le tableau Numpy est de dimension 2, contient 8 lignes et 2 colonnes, pour 16 éléments.

Nous remarquons qu’il est de type float64, alors que le nombre d’habitants est un entier. En effet, nous avons vu qu’un tableau Numpy ne peut contenir qu’un seul type de données. Par défaut, il transforme tous les nombres en réel float64, selon le principe de l’upcasting vu plus haut.

Indexation et tranche 2D#

Les opérateurs d’indexation et de tranche s’utilisent de la même façon que pour un tableau à une dimension, mais il faut les spécifier pour chaque dimension :

  • le premier opérateur agit sur la première dimension (lignes)

  • le deuxième opérateur agit sur la deuxième dimension (colonnes)

L’avantage du tableau Numpy 2D sur une liste de listes est qu’il devient très facile d’extraire une information donnée pour toutes les villes. Par exemple :

# Extraire les superficies pour toutes les villes à partir du tableau Numpy 2D
print(np_villes[:, 1])

# Extraire les superficies pour toutes les villes à partir de la liste de listes
print([villes[0][1], villes[1][1], villes[2][1], villes[3][1], villes[4][1], villes[5][1], villes[6][1], villes[7][1]])
[105.4  240.62  47.87 118.3   71.92  65.19  78.26  56.88]
[105.4, 240.62, 47.87, 118.3, 71.92, 65.19, 78.26, 56.88]

Exercice#

À partir du tableau Numpy np_villes :

  • Afficher le nombre d’habitants de la ville de Toulouse

  • Afficher les superficies des 4 dernières villes du tableau

Hide code cell source
# Nombre d'habitants de la ville de Toulouse
print(np_villes[3, 0])

# Superficies des 4 dernières villes du tableau
print(np_villes[-4:, 1])
453317.0
[71.92 65.19 78.26 56.88]

Opérations sur tableaux 2D#

Comme pour les tableaux 1D, il est possible d’utiliser les tableaux 2D dans des expressions avec des opérateurs arithmétiques. Les opérateurs arithmétiques s’appliquent terme à terme sur des tableaux de même forme (shape).

Cependant, il est possible de former des expressions avec des tableaux de formes différentes. Il faut alors que le tableau 1D soit de la même taille que les tableaux à l’intérieur du tableau 2D (deuxième dimension, ou nombre de colonnes) :

A = np.array([[-1, 6, 3],
            [4, 2, 8]])

B = np.array([3, 6, 0])

print('A * B = ', A * B)
print('A + B = ', A + B)
A * B =  [[-3 36  0]
 [12 12  0]]
A + B =  [[ 2 12  3]
 [ 7  8  8]]

Il est aussi possible de former des expressions à l’aide des opérateurs de tranche et d’indexation :

print(A[0, :] + B)
print(A[:, 0] + B[0])
[ 2 12  3]
[2 7]

Exercice#

Le tableau suivant donne l’évolution de la population entre 2012 et 2017 pour chacune des villes du tableau np_villes utilisé précédemment :

evolution = [-53095, 10794, 19749, 26236, -3612, 17742, 6572, 16665]
  1. Créer un tableau Numpy np_evolution à partir du tableau evolution.

  2. Copier le tableau np_villes dans un tableau np_villes_2017 avec la fonction Numpy copy() (pour éviter l’aliasing, voir cours sur les listes).

  3. Additionner les valeurs du tableau evolution aux valeurs de la première colonne du tableau np_villes_2017, qui représentent le nombre d’habitant·es en 2012, afin d’obtenir un tableau avec les nombre d’habitant·es en 2017 en première colonne

  4. Afficher les populations des villes en 2012 et en 2017 : le classement des villes les plus peuplées de France a-t-il changé ?

Hide code cell source
# Création du tableau Numpy
np_evolution = np.array(evolution)

# Copie du tableau en évitant l'aliasing
np_villes_2017 = np.copy(np_villes)

# Évolution de la population
np_villes_2017[:, 0] = np_villes_2017[:, 0] + np_evolution

# Comparaison des populations
print(np_villes[:, 0])
print(np_villes_2017[:, 0])
[2240621.  852516.  496343.  453317.  343629.  291604.  274394.  268456.]
[2187526.  863310.  516092.  479553.  340017.  309346.  280966.  285121.]

Vidéo: Statistiques#

Statistiques#

Il est très facile de faire des statistiques simples avec Numpy. Voyons quelques fonctions :

fonction

description

median()

médiane

mean()

moyenne arithmétique

sum()

somme des éléments du tableau

std()

écart-type

corrcoef()

matrice de correlation de Pearson

La liste complète des fonctions et de leurs arguments optionnels est dans l’aide Numpy.

Exercice#

  1. Comparer la moyenne et la médiane du nombre d’habitants pour les villes du tableau np_villes

  2. Calculer l’écart-type des superficies des villes du tableau np_villes

  3. Calculer le coefficient de corrélation entre le nombre d’habitants et la superficie. On rappele que :

\[\hat{r}_p = \dfrac{\hat{\sigma}_{XY}}{\hat{\sigma}_X \hat{\sigma}_Y}\]

avec

\[\hat{\sigma}_{XY} =\frac{1}{N}{\sum_{i=1}^N (x_i - \bar x)\cdot(y_i - \bar y)}\]

et \(\hat{\sigma}_X\) et \(\hat{\sigma}_Y\) sont les écart-types des variables \(X\) et \(Y\).

Hide code cell source
# Moyenne du nombre d'habitants
mean_villes = np.mean(np_villes, axis = 0) # axis = 0 permet de faire la moyenne sur la 1ère dimension (lignes)
mean_hab = mean_villes[0]
# ou bien
mean_hab = np.mean(np_villes[:, 0])

# Médiane du nombre d'habitants
med_villes = np.median(np_villes, axis = 0) # axis = 0 permet de faire la médiane sur la 1ère dimension (lignes)
med_hab = med_villes[0]
# ou bien
med_hab = np.median(np_villes[:, 0])

# Écart-type des superficies
std_villes = np.std(np_villes, axis = 0)
std_sup = std_villes[1]
# ou bien
std_sup = np.std(np_villes[:, 1])

# Coefficient de corrélation entre le nombre d'habitants et la superficie
shape_ville = np_villes.shape
N = shape_ville[0] # Nombre de villes
mean_sup = mean_villes[1] # Moyenne de la superficie des villes
sigma_XY = 1 / N * (np_villes[:, 0] - mean_hab) * (np_villes[:, 1] - mean_sup)
cor_villes = np.sum(sigma_XY) / std_villes[0] / std_villes[1]

# Affichage des résultats
print("Moyenne du nombre d'habitants =", mean_hab)
print("Médiane du nombre d'habitants =", med_hab)
print('Ecart-type des superficies =', std_sup, "km**2")
print('Coefficient de corrélation =', cor_villes)
Moyenne du nombre d'habitants = 652610.0
Médiane du nombre d'habitants = 398473.0
Ecart-type des superficies = 58.26393781748706 km**2
Coefficient de corrélation = 0.29855441699187646