Le module NumPy

Le tableau NumPy

L’objet de base du module NumPy est un tableau à N-dimension (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:

[1]:
# importation 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 nouvel objet (ou type, c’est équivalent): le numpy.ndarray, ou tableau NumPy. Comme tout objet, il a des opérateurs, des fonctions et des méthodes associés.

En Python, un objet a des attributs 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:

[2]:
# attribut nombre de dimensions (ndim)
mon_tableau.ndim
[2]:
1
[3]:
# attribut taille (size)
mon_tableau.size
[3]:
6
[4]:
# attribut forme (shape) -> résultat sous la forme d'un tuple
mon_tableau.shape
[4]:
(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èse plutôt que des crochets. Pour différencier un tuple à un élément d’une simple paire de parenthèse, on ajoute une virgule après le chiffre, par exemple a = (2,).

[5]:
# attribut type des éléments (dtype)
mon_tableau.dtype
[5]:
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)

[6]:
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.
[7]:
# importer le 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 vu 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é:

[8]:
# 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 tableaux, etc..

[9]:
# 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:

[10]:
# 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 ville (en km**2), dans le même ordre:

[11]:
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/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.
  2. Quel est le type des éléments du tableau np_densité (attribut dtype)
[12]:
# 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
[13]:
# 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 élement avec l’indice 1, et ainsi de suite.

[14]:
# 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:

[15]:
# 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:

[16]:
# 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, et n est l’indice du dernier élément du tableau.

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’habitants 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’habitant et sa superficie - la deuxième dimension (colonnes) donne pour toutes les villes une information donnée (son nombre d’habitant ou sa superficie)

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

[17]:
# Création de liste contenant les informations: nombre d'habitants 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.

[18]:
# 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 liste est qu’il devient très facile d’extraire une information donnée pour toutes les villes. Par exemple:

[19]:
# 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 liste
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

A 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

[20]:
# 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 la taille du tableau 1D soit de la même taille que les tableaux à l’intérieur du tableau 2D (deuxième dimension, ou nombre de colonnes) :

[21]:
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’indexage:

[22]:
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:

[23]:
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. Ajouter le tableau evolution à la première colonne du tableau np_villes_2017.
  4. Afficher les populations des villes en 2012 et en 2017: le classement des villes les plus peuplée de France a-t-il changé?
[24]:
# Création du tableau Numpy
np_evolution = np.array(evolution)

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

# Evolution 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.]

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
correlate() coefficient de correlation

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\).

[25]:
# Moyenne
mean_villes = np.mean(np_villes, axis=0) # axis=0 signifie 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
med_villes = np.median(np_villes, axis=0) # axis=0 signifie 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])

# Ecart-type
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
shape_ville = np_villes.shape
N = shape_ville[0] # nombre de 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)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_397/3544981860.py in <module>
     20 shape_ville = np_villes.shape
     21 N = shape_ville[0] # nombre de villes
---> 22 sigma_XY = 1/N * (np_villes[:,0] - mean_hab) * (np_villes[:,1] - mean_sup)
     23 cor_villes = np.sum(sigma_XY) / std_villes[0] / std_villes[1]
     24

NameError: name 'mean_sup' is not defined