9 Introduction à Numpy

Ce chapitre est consacré à une librairie importante pour les calculs numérique : NumPy (abréviation de Numerical Python).

Il est coutume d’importer NumPy en lui attribuant l’alias np :

import numpy as np

9.1 Tableaux

NumPy propose une structure de données populaire, les tableaux (de type array), sur lesquels il est possible d’effectuer de manière efficace des calculs. Les tableaux sont une structure notamment utile pour effectuer des opérations statistiques basiques ainsi que de la génération pseudo-aléatoire de nombres.

La stucture des tableaux ressemble à celle des listes, mais ces dernières sont moins rapides à être traitées et utilisent davantage de mémoire. Le gain de vitesse de traitement des tableaux en NumPy vient du fait que les données sont stockées dans des blocs contigus de mémoire, facilitant ainsi les accès en lecture.

Pour s’en convaincre, on peut reprendre l’exemple de Pierre Navaro donné dans son notebook sur NumPy. Créons deux listes de longueur 1000 chacune, avec des nombres tirés aléatoirement à l’aide de la fonction random() du module random. Divisons chaque élément de la première liste par l’élément à la même position dans la seconde ligne, puis calculons la somme de ces 1000 divisions. Regardons ensuite le temps d’exécution à l’aide de la fonction magique %timeit :

from random import random
from operator import truediv
l1 = [random() for i in range(1000)]
l2 = [random() for i in range(1000)]
# %timeit s = sum(map(truediv,l1,l2))

(décommenter la dernière ligne et tester sur un Jupyter Notebook)

À présent, transformons les deux listes en tableaux NumPy avec la méthode array(), et effectuons le même calcul à l’aide d’une méthode NumPy :

a1 = np.array(l1)
a2 = np.array(l2)
# %timeit s = np.sum(a1/a2)

Comme on peut le constater en exécutant ces codes dans un environnement IPython, le temps d’exécution est bien plus rapide avec les méthodes de NumPy pour ce calcul.

9.1.1 Création

La création d’un tableau peut s’effectuer avec la méthode array(), à partir d’une liste, comme nous venon de le faire :

liste = [1,2,4]
tableau = np.array(liste)
print(tableau)
## [1 2 4]
print(type(tableau))
## <class 'numpy.ndarray'>

Si on fournit à array() une liste de listes imbriquées de même longueur, un tableau multidimensionnel sera créé :

liste_2 = [ [1,2,3], [4,5,6] ]
tableau_2 = np.array(liste_2)
print(tableau_2)
## [[1 2 3]
##  [4 5 6]]
print(type(tableau_2))
## <class 'numpy.ndarray'>

Les tableaux peuvent aussi être créés à partir de n-uplets :

nuplet = (1, 2, 3)
tableau = np.array(nuplet)
print(tableau)
## [1 2 3]
print(type(tableau))
## <class 'numpy.ndarray'>

Un tableau en dimension 1 peut être changé en tableau en dimension 2 (si possible), en modifiant son attribut shape :

tableau = np.array([3, 2, 5, 1, 6, 5])
tableau.shape = (3,2)
print(tableau)
## [[3 2]
##  [5 1]
##  [6 5]]

9.1.1.1 Quelques fonctions générant des array

Certaines fonctions de NumPy produisent des tableaux pré-remplis. C’est le cas de la fonction zeros(). Quand on lui fournit une valeur entière \(n\), la fonction zeros() créé un tableau à une dimension, avec \(n\) 0 :

print( np.zeros(4) )
## [0. 0. 0. 0.]

On peut préciser le type des zéros (par exemple int, int32, int64, float, float32, float64, etc.), à l’aide du paramètre dtype :

print( np.zeros(4, dtype = "int") )
## [0 0 0 0]

D’avantage d’explications sur les types de données avec NumPy sont disponibles sur la documentation en ligne.

Le type des éléments d’un tableau est indiqué dans l’attribut dtype :

x = np.zeros(4, dtype = "int")
print(x, x.dtype)
## [0 0 0 0] int64

Il est par ailleurs possible de convertir le type des éléments dans un un autre type, à l’aide de la méthode astype() :

y = x.astype("float")
print(x, x.dtype)
## [0 0 0 0] int64
print(y, y.dtype)
## [0. 0. 0. 0.] float64

Quand on lui fournit un n-uplet de longueur supérieure à 1, zeros() créé un tableau à plusieurs dimensions :

print( np.zeros((2, 3)) )
## [[0. 0. 0.]
##  [0. 0. 0.]]
print( np.zeros((2, 3, 4)) )
## [[[0. 0. 0. 0.]
##   [0. 0. 0. 0.]
##   [0. 0. 0. 0.]]
## 
##  [[0. 0. 0. 0.]
##   [0. 0. 0. 0.]
##   [0. 0. 0. 0.]]]

La fonction empty() de Numpy retourne également un tableau sur le même principe que zeros(), mais sans initialiser les valeurs à l’intérieur.

print( np.empty((2, 3), dtype = "int") )
## [[0 0 0]
##  [0 0 0]]

La fonction ones() de Numpy retourne le même genre de tableaux, avec des 1 en valeurs initialisées :

print( np.ones((2, 3), dtype = "float") )
## [[1. 1. 1.]
##  [1. 1. 1.]]

Pour choisir une valeur spécifique pour l’initialisation, on peut utiliser la fonction full() de Numpy :

print( np.full((2, 3), 10, dtype = "float") )
## [[10. 10. 10.]
##  [10. 10. 10.]]
print( np.full((2, 3), np.inf) )
## [[inf inf inf]
##  [inf inf inf]]

La fonction eye() de Numpy créé un tableau à deux dimensions dans laquelle tous les éléments sont initalisés à zéro, sauf ceux de la diagonale initialisés à 1 :

print( np.eye(2, dtype="int64") )
## [[1 0]
##  [0 1]]

En modifiant le paramètre mot-clé k, on peut décaler la diagonale :

print( np.eye(3, k=-1) )
## [[0. 0. 0.]
##  [1. 0. 0.]
##  [0. 1. 0.]]

La fonction identity() de Numpy créé quant à elle une matrice identité sous la forme d’un tableau :

print( np.identity(3, dtype = "int") )
## [[1 0 0]
##  [0 1 0]
##  [0 0 1]]

La fonction arange() de Numpy permet de générer une séquence de nombres séparés par un interval fixe, le tout stocké dans un tableau. La syntaxe est la suivante :

np.arange( start, stop, step, dtype )

avec start la valeur de départ, stop celle d’arrivée, step le pas, l’espacement entre les nombres de la séquence et dtype le type des nombres :

print( np.arange(5) )
## [0 1 2 3 4]
print( np.arange(2, 5) )
## [2 3 4]
print( np.arange(2, 10, 2) )
## [2 4 6 8]

9.1.2 Dimensions

Pour connaître la dimension d’un tableau, on peut afficher la valeur de l’attribut ndim :

print("ndim tableau : ", tableau.ndim)
## ndim tableau :  2
print("ndim tableau_2 : ", tableau_2.ndim)
## ndim tableau_2 :  2

Le nombre d’éléments dans le tableau peut s’obtenir par l’attribut size ou par la fonction size() de Numpy :

print("size tableau : ", tableau.size)
## size tableau :  6
print("size tableau_2 : ", tableau_2.size)
## size tableau_2 :  6
print("np.size(tableau) :", np.size(tableau))
## np.size(tableau) : 6

L’attribut shape retourne un n-uplet indiquant la longueur pour chaque dimension du tableau :

print("size tableau : ", tableau.shape)
## size tableau :  (3, 2)
print("size tableau_2 : ", tableau_2.shape)
## size tableau_2 :  (2, 3)

9.1.3 Extraction des éléments d’un tableau

L’accès aux éléments d’un tableau se fait de la même manière que pour les listes (c.f. Section 3.1.1), grâce à l’indiçage. La syntaxe est la suivante :

tableau[lower:upper:step]

avec lower la borne inférieur de la plage d’indices, upper la plage supérieur, et step l’espacement entre les valeurs.

  • Lorsque lower n’est pas précisé, le premier élément (indicé 0) est considéré comme la valeur attribuée à lower.
  • Lorsque upper n’est pas précisé, le dernier élément est considéré comme la valeur attribuée à upper.
  • Lorsque step n’est pas précisé, un pas de 1 est attribué par défaut.

Reprenons rapidement quelques exemples, en s’appuyant sur deux objets : un tableau de dimension 1, et un second de dimension 2.

tableau_1 = np.arange(1,13)
tableau_2 = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
tableau_2 = np.array(tableau_2)

L’accès au premier élément :

message = "tableau_{}[0] : {} (type : {})"
print(message.format(0, tableau_1[0], type(tableau_1[0])))
## tableau_0[0] : 1 (type : <class 'numpy.int64'>)
print(message.format(1, tableau_2[0], type(tableau_2[0])))
## tableau_1[0] : [1 2 3] (type : <class 'numpy.ndarray'>)

L’accès aux éléments peut se faire en partant par la fin :

print("tableau_1[-1] : ", tableau_1[-1]) # dernier élément
## tableau_1[-1] :  12
print("tableau_2[-1] : ", tableau_2[-1]) # dernier élément
## tableau_2[-1] :  [10 11 12]

Le découpage est possible :

# les éléments du 2e (non inclus) au 4e
print("Slice Tableau 1 : \n", tableau_1[2:4])
## Slice Tableau 1 : 
##  [3 4]
print("Sclie Tableau 2 : \n", tableau_2[2:4])
## Sclie Tableau 2 : 
##  [[ 7  8  9]
##  [10 11 12]]

Pour les tableaux à deux dimensions, on peut accéder aux éléments de la manière suivante, de manière équivalente :

# Dans le 3e élément, accéder au 1er élément
print(tableau_2[2][0])
## 7
print(tableau_2[2,0])
## 7

Pour extraire des colonnes d’un tableau à deux entrées :

print("Deuxième colonne : \n", tableau_2[:, [1]])
## Deuxième colonne : 
##  [[ 2]
##  [ 5]
##  [ 8]
##  [11]]
print("Deuxièmes et troisièmes colonnes : \n", tableau_2[:, [1,2]])
## Deuxièmes et troisièmes colonnes : 
##  [[ 2  3]
##  [ 5  6]
##  [ 8  9]
##  [11 12]]

Pour cette dernière instruction, on indique avec le premier paramètre non renseigné (avant les deux points) que l’on désire tous les éléments de la première dimension, puis, avec la virgule, on indique qu’on regarde à l’intérieur de chaque élément de la première dimension, et qu’on veut les valeurs aux positions 1 et 2 (donc les éléments des colonnes 2 et 3).

Pour extraire seulement certains éléments d’un tableau à 1 dimension, on peut indiquer les indices des éléments à récupérer :

print("2e et 4e éléments : \n", tableau_2[[1,3]])
## 2e et 4e éléments : 
##  [[ 4  5  6]
##  [10 11 12]]

9.1.3.1 Extraction à l’aide de booléens

Pour extraire ou non des éléments d’un tableu, on peut utiliser des tableaux de booléens en tant que masques. L’idée est de fournir un tableau de booléens (un masque) de même dimension que celui pour lequel on désire extraire des éléments sous certaines conditions. Lorsque la valeur du booléen dans le masque vaut True, l’élément correspondant du tableau est retourné ; sinon, il ne l’est pas.

tableau = np.array([0, 3, 2, 5, 1, 4])
res = tableau[[True, False, True, False, True, True]]
print(res)
## [0 2 1 4]

Seuls les éléments en position 1, 3, 5 et 6 on été retournés.

En pratique, le masque n’est que très rarement créé par l’utilisateur, il est plutôt issu d’une instruction logique appliquée au tableau d’intérêt. Par exemple, dans notre tableau, nous pouvons dans un premier temps créer un masque de manière à identifier les éléments pairs :

masque = tableau % 2 == 0
print(masque)
## [ True False  True False False  True]
print(type(masque))
## <class 'numpy.ndarray'>

Une fois ce masque créé, on peut l’appliquer au tableau pour extraire uniquement les éléments pour lesquels la valeur correspondante dans le masque vaut True :

print(tableau[masque])
## [0 2 4]

9.1.4 Modification

Pour remplacer les valeurs d’un tableau, on utilise le signe égal (=) :

tableau = np.array([ [1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
tableau[0] = [11, 22, 33]
print(tableau)
## [[11 22 33]
##  [ 4  5  6]
##  [ 7  8  9]
##  [10 11 12]]

Si on fournit un scalaire lors du remplacement, la valeur sera répétée pour tous les éléments de la dimension :

tableau[0] = 100
print(tableau)
## [[100 100 100]
##  [  4   5   6]
##  [  7   8   9]
##  [ 10  11  12]]

Idem avec un découpage :

tableau[0:2] = 100
print(tableau)
## [[100 100 100]
##  [100 100 100]
##  [  7   8   9]
##  [ 10  11  12]]

D’ailleurs, un découpage avec juste les deux points sans préciser les paramètres de début et de fin du découpage suivi d’un signe égal et d’un nombre remplace toutes les valeurs du tableau par ce nombre :

tableau[:] = 0
print(tableau)
## [[0 0 0]
##  [0 0 0]
##  [0 0 0]
##  [0 0 0]]

9.1.4.1 Ajout d’éléments

Pour ajouter des éléments, on utilise la fonction append() de NumPy. Il faut noter que l’appel à cette fonction ne modifie pas l’objet auquel on ajoute les valeurs. Si on désire que les modifications sont apportées à cet objet, il faut l’écraser :

t_1 = np.array([1,3,5])
print("t_1 : ", t_1)
## t_1 :  [1 3 5]
t_1 = np.append(t_1, 1)
print("t_1 après l'ajout : ", t_1)
## t_1 après l'ajout :  [1 3 5 1]

Pour ajouter une colonne à un tableau à deux dimensions :

t_2 = np.array([[1,2,3], [5,6,7]])
print("t_2 : \n", t_2)
## t_2 : 
##  [[1 2 3]
##  [5 6 7]]
ajout_col_t_2 = np.array([[4], [8]])
t_2 = np.append(t_2,ajout_col_t_2, axis = 1)
print("t_2 après ajout colonne : \n", t_2)
## t_2 après ajout colonne : 
##  [[1 2 3 4]
##  [5 6 7 8]]

Pour ajouter une ligne, on utilise la fonction vstack() de Numpy :

ajout_ligne_t_2 = np.array([10, 11, 12, 13])
t_2 = np.vstack([t_2,ajout_ligne_t_2])
print("t_2 après ajout ligne : \n", t_2)
## t_2 après ajout ligne : 
##  [[ 1  2  3  4]
##  [ 5  6  7  8]
##  [10 11 12 13]]

9.1.4.2 Suppression d’éléments

Pour supprimer des éléments, on utilise la fonction delete() de NumPy :

print("t_1 : ", t_1)
# Supprimer le dernier élément
## t_1 :  [1 3 5 1]
np.delete(t_1, (-1))

Note : pour que la suppression soit effective, on assigne le résultat de np.delete() à l’objet.

Pour supprimer plusieurs éléments :

print("t_1 : ", t_1)
# Supprimer les 1er et 2e éléments
## t_1 :  [1 3 5 1]
t_1 = np.delete(t_1, ([0, 2]))
print(t_1)
## [3 1]

Pour supprimer une colonne d’un tableau à deux dimensions :

print("t_2 : ", t_2)
# Supprimer la première colonne :
## t_2 :  [[ 1  2  3  4]
##  [ 5  6  7  8]
##  [10 11 12 13]]
np.delete(t_2, (0), axis=1)

Supprimer plusieurs colonnes :

print("t_2 : ", t_2)
# Supprimer la 1ère et la 3e colonne :
## t_2 :  [[ 1  2  3  4]
##  [ 5  6  7  8]
##  [10 11 12 13]]
np.delete(t_2, ([0,2]), axis=1)

Et pour supprimer une ligne :

print("t_2 : ", t_2)
# Supprimer la première ligne :
## t_2 :  [[ 1  2  3  4]
##  [ 5  6  7  8]
##  [10 11 12 13]]
np.delete(t_2, (0), axis=0)

Supprimer plusieurs lignes :

print("t_2 : ", t_2)
# Supprimer la 1ère et la 3e ligne
## t_2 :  [[ 1  2  3  4]
##  [ 5  6  7  8]
##  [10 11 12 13]]
np.delete(t_2, ([0,2]), axis=0)

9.1.5 Copie de tableau

La copie d’un tableau, comme pour les listes (c.f. Section 3.1.4), ne doit pas se faire avec le symbole égal (=).

tableau_1 = np.array([1, 2, 3])
tableau_2 = tableau_1

Modifions le premier élément de tableau_2, et observons le contenu de tableau_2 et de tableau_1 :

tableau_2[0] = 0
print("Tableau 1 : \n", tableau_1)
## Tableau 1 : 
##  [0 2 3]
print("Tableau 2 : \n", tableau_2)
## Tableau 2 : 
##  [0 2 3]

Comme on peut le constater, le fait d’avoir utilisé le signe égal a simplement créé une référence et non pas une copie.

Pour effectuer une copie de tableaux, plusieurs façons existent. Parmi elles, l’utilisation de la fonction np.array() :

tableau_1 = np.array([1, 2, 3])
tableau_2 = np.array(tableau_1)
tableau_2[0] = 0
print("tableau_1 : ", tableau_1)
## tableau_1 :  [1 2 3]
print("tableau_2 : ", tableau_2)
## tableau_2 :  [0 2 3]

On peut également utiliser la méthode copy() :

tableau_1 = np.array([1, 2, 3])
tableau_2 = tableau_1.copy()
tableau_2[0] = 0
print("tableau_1 : ", tableau_1)
## tableau_1 :  [1 2 3]
print("tableau_2 : ", tableau_2)
## tableau_2 :  [0 2 3]

On peut noter que lorsque l’on fait un découpement, un nouvel objet est créé, pas une référence :

tableau_1 = np.array([1, 2, 3, 4])
tableau_2 = tableau_1[:2]
tableau_2[0] = 0
print("tableau_1 : ", tableau_1)
## tableau_1 :  [0 2 3 4]
print("tableau_2 : ", tableau_2)
## tableau_2 :  [0 2]

9.1.6 Tri

La librairie NumPy fournit une fonction pour trier les tableaux : sort().

tableau = np.array([3, 2, 5, 1, 6, 5])
print("Tableau trié : ", np.sort(tableau))
## Tableau trié :  [1 2 3 5 5 6]
print("Tableau : ", tableau)
## Tableau :  [3 2 5 1 6 5]

Comme on peut le constater, la fonction sort() de NumPy propose une vue : le tableau n’est pas modifié, ce qui n’est pas le cas si on utilise la méthode sort() :

tableau = np.array([3, 2, 5, 1, 6, 5])
tableau.sort()
print("Le tableau a été modifié : ", tableau)
## Le tableau a été modifié :  [1 2 3 5 5 6]

9.1.7 Transposition

Pour obtenir la transposée d’un tableau, on fait appel à l’attribut T. Il faut noter que l’on obtient une vue de l’objet, que cela ne le modifie pas.

tableau = np.array([3, 2, 5, 1, 6, 5])
tableau.shape = (3,2)
print("Tableau : \n", tableau)
## Tableau : 
##  [[3 2]
##  [5 1]
##  [6 5]]
print("Tableau transposé : \n", tableau.T)
## Tableau transposé : 
##  [[3 5 6]
##  [2 1 5]]

On peut également utiliser la fonction transpose() de NumPy :

print(np.transpose(tableau))
## [[3 5 6]
##  [2 1 5]]

Attention, si on assigne un nom à la transposée, que ce soit en utilisant l’attribut T ou la méthode np.transpose(), cela créé une référence, pas une copie d’élément…

tableau_transpose = np.transpose(tableau)
tableau_transpose[0,0] = 99
print("tableau : \n", tableau)
## tableau : 
##  [[99  2]
##  [ 5  1]
##  [ 6  5]]
print("tableau_transpose : \n", tableau_transpose)
## tableau_transpose : 
##  [[99  5  6]
##  [ 2  1  5]]

Pour savoir si un tableau est une vue ou non, on peut afficher l’attribut base, qui retourne None si ce n’est pas le cas :

print("tableau : ", tableau.base)
## tableau :  None
print("tableau_transpose : ", tableau_transpose.base)
## tableau_transpose :  [[99  2]
##  [ 5  1]
##  [ 6  5]]

9.1.8 Opérations sur les tableaux

Il est possible d’utiliser des opérateurs sur les tableaux. Leur effet nécessite quelques explications.

9.1.8.1 Opérateurs + et -

Lorsque l’opérateur + (-) est utilisé entre deux tableaux de même dimension, une addition (soustraction) terme à terme est effectuée :

t_1 = np.array([1, 2, 3, 4])
t_2 = np.array([5, 6, 7, 8])
t_3 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
t_4 = np.array([[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]])
t_1 + t_2
t_3 + t_4
t_1 - t_2

Lorsque l’opérateur + (-) est utilisé entre un scalaire et un tableau, le scalaire est ajouté (soustrait) à tous les éléments du tableau :

print("t_1 + 3 : \n", t_1 + 3)
## t_1 + 3 : 
##  [4 5 6 7]
print("t_1 + 3. : \n", t_1 + 3.)
## t_1 + 3. : 
##  [4. 5. 6. 7.]
print("t_3 + 3 : \n", t_3 + 3)
## t_3 + 3 : 
##  [[ 4  5  6  7]
##  [ 8  9 10 11]
##  [12 13 14 15]]
print("t_3 - 3 : \n", t_3 - 3)
## t_3 - 3 : 
##  [[-2 -1  0  1]
##  [ 2  3  4  5]
##  [ 6  7  8  9]]

9.1.8.2 Opérateurs * et /

Lorsque l’opérateur * (/) est utilisé entre deux tableaux de même dimension, une multiplication (division) terme à terme est effectuée :

t_1 * t_2
t_3 * t_4
t_3 / t_4

Lorsque l’opérateur * (/) est utilisé entre un scalaire et un tableau, tous les éléments du tableau sont multipliés (divisés) par ce scalaire :

print("t_1 * 3 : \n", t_1 * 3)
## t_1 * 3 : 
##  [ 3  6  9 12]
print("t_1 / 3 : \n", t_1 / 3)
## t_1 / 3 : 
##  [0.33333333 0.66666667 1.         1.33333333]

9.1.8.3 Puissance

Il est également possible d’élever chaque nombre d’un tableau à une puissance donnée :

print("t_1 ** 3 : \n", t_1 ** 3)
## t_1 ** 3 : 
##  [ 1  8 27 64]

9.1.8.4 Opérations sur des matrices

En plus des opérations/soustraction/multiplication/division terme à terme ou par un scalaire, il est possible d’effectuer certains calculs sur des tableaux à deux dimension.

Nous avons déjà vu la tranposée en Section 9.1.7.

Pour effectuer un produit matriciel, NumPy fournit la fonction dot() :

np.dot(t_3, t_4.T)

Il faut bien s’assurer d’avoir des matrices compatibles, sinon, une erreur sera retournée :

np.dot(t_3, t_4)
## ValueError: shapes (3,4) and (3,4) not aligned: 4 (dim 1) != 3 (dim 0)
## 
## Detailed traceback: 
##   File "<string>", line 1, in <module>

Le produit matriciel peut également s’obtenir à l’aide de l’opérateur @ :

t_3 @ t_4.T

Le produit d’un vecteur avec une matrice est également possible :

np.dot(t_1, t_3.T)

9.1.9 Opérateurs logiques

Pour effectuer des tests logiques sur les éléments d’un tableau, NumPy propose des fonctions, répertoriées dans le Tableau 9.1. Le résultat retourné par l’application de ces fonctions est un tableau de booléens.

Table 9.1: Fonctions logiques
Code Description
greater() Supérieur à
greater_equal() Supérieur ou égal à
less() Inférieur à
less_equal() Inférieur ou égal à
equal() Égal à
not_equal() Différent de
logical_and() Et logique
logical_or() Ou logique
logical_xor() XOR logique

Par exemple, pour obtenir les éléments de t compris entre 10 et 20 (inclus) :

t = np.array([[1, 10, 3, 24], [9, 12, 40, 2], [0, 7, 2, 14]])
masque = np.logical_and(t <= 20, t >= 10)
print("masque : \n", masque)
## masque : 
##  [[False  True False False]
##  [False  True False False]
##  [False False False  True]]
print("les éléments de t compris entre 10 et 20 : \n",
      t[masque])
## les éléments de t compris entre 10 et 20 : 
##  [10 12 14]

9.1.10 Quelques constantes

NumPy propose quelques constantes, dont certaines sont reportées dans le Tableau 9.2.

Table 9.2: Codes de formatages
Code Description
np.inf Infini (on obtient \(-\infty\) en écrivant -np.inf ou np.NINF)
np.nan Représentation en tant que nombre à virgule flottante de Not a Number
np.e Constante d’Euler (\(e\))
np.euler_gamma Constante d’Euler-Mascheroni (\(\gamma\))
np.pi Nombre Pi (\(\pi\))

On peut noter la présence de la valeur NaN, qui est une valeur spéciale parmi les nombres à virgule flottante. Le comportement de cette constante est spécial.

Quand on additionne, soustrait, multiplie ou divise un nombre par cette valeur NaN, on obtient NaN :

print("Addition : ", np.nan + 1)
## Addition :  nan
print("Soustraction : ", np.nan - 1)
## Soustraction :  nan
print("Multiplication : ", np.nan + 1)
## Multiplication :  nan
print("AddDivisiontion : ", np.nan / 1)
## AddDivisiontion :  nan

9.1.11 Fonctions universelles

Les fonctions universelles (ufunc pour universal functions) sont des fonctions qui peuvent être appliquées terme à terme aux éléments d’un tableau. On distingue deux types de fonctions universelles : les fonctions unaires, qui effectuent une opération sur une seule, et les fonctions binaires qui effectuent une opération sur deux opérandes.

Parmi les ufuncs, on retrouve des opérations arithmétiques (addition, multiplication, puissance, valeur absolue, etc.) et des fonctions mathématiques usuelles (fonctions trigonométriques, exponentielle, logarithme, etc.). Le Tableau 9.3 répertorie quelques fonctions universelles unaires, tandis que le Tableau 9.4 répertories quelques fonctions universelles binaires.

Table 9.3: Fonctions universelles unaires
Code Description
negative(x) Opposés des éléments de x
absolute(x) Valeurs absolues des éléments de x
sign(x) Signes des éléments de x (0, 1 ou -1)
rint(x) Arrondi de x à l’entier
floor(x) Troncature de x à l’entier inférieur
ceil(x) Troncature de x à l’entier supérieur
sqrt(x) Racine carrée de x
square(x) Carré de x
sin(x), cos(x), tan(x) Sinus (cosinus, et tangente) de x
sinh(x), cosh(x), tanh(x) Sinus (cosinus, et tangente) hyperbolique de x
arcsin(x), arccos(x), arctan(x) Arc-sinus (arc-cosinus, et arc-tangente) de x | | `arcsinh(x)`, `arccosh(x)`, `arctanh(x)` | Arc-sinus (arc-cosinus, et arc-tangente) hyperbolique dex
hypoth(x,y) Hypoténuse \(\sqrt{x^2+y^2}\)
degrees(x) Conversion des angles x de radians en degrés
radians(x) Conversion des angles x de degrés en radians
exp(x) Exponentielle de x
expm1(x) \(e^x-1\)
log(x) Logarithme népérien des éléments de x
log10(x) Logatithme des éléments de x en base 10
log2(x) Logarithme des éléments de x en base 2
log1p(x) \(ln(1+x\)
exp2(x) \(2^x\)
isnan(x) Tableau de booléens indiquant True pour les éléments NaN
isfinite(x) Tableau de booléens indiquant True pour les éléments non infinis et non-NaN
isinf(x) Tableau de booléens indiquant True pour les éléments infinis
Table 9.4: Fonctions universelles binaires
Code Description
add(x,y) Addition terme à terme de x et y
subtract(x,y) Soustraction terme à terme de x et y
multiply(x,y) Multiplication terme à terme de x et y
divide(x,y) Division terme à terme de x et y
floor_divide(x,y) Quotients entiers des divisions terme à terme de x et y
power(x,y) Élévation des éléments de x à la puissance des éléments de y
mod(x,y) Restes des divisions eucliennes des éléments de x par ceux de y
round(x,n) Arrondi de x à \(n\) décimales
arctan2(x,y) Angles polaires de x et y

Pour utiliser ses fonctions, procéder comme dans l’exemple suivant :

t_1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
t_2 = np.array([[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]])
np.log(t_1) # Logarithme népérien
np.subtract(t_1, t_2) # Soustraction des éléments de t_1 par ceux de t_2

9.1.12 Méthodes et fonctions mathématiques et statistiques

NumPy fournit de nombreuses méthodes pour calculer des statistiques sur l’ensemble des valeurs des tableaux, ou sur un des axes des tableaux (par exemple sur l’équivalent de lignes ou des colonnes dans les tableaux à deux dimensions). Certaines sont reportées dans le Tableau 9.5.

Table 9.5: Méthodes mathématiques et statistiques
Code Description
sum() Retourne la somme des éléments
prod() Retourne le produit des éléments
cumsum() Retourne la somme cumulée des éléments
cumprod() Retourne le produit cumulé des éléments
mean() Retourne la moyenne
var() Retourne la variance
std() Retourne l’écart-type
min() Retourne la valeur minimale
max() Retourne la valeur maximale
argmin() Retourne l’indice du premier élément à la plus petite valeur
argmax() Retourne l’indice du premier élément à la plus grande valeur

Donnons un exemple de l’utilisation de ces méthodes :

t_1 = np.array([[1, 2, 3, 4], [-1, 6, 7, 8], [9, -1, 11, 12]])
print("t_1 : \n", t_1)
## t_1 : 
##  [[ 1  2  3  4]
##  [-1  6  7  8]
##  [ 9 -1 11 12]]
print("Somme des éléments : ", t_1.sum())
## Somme des éléments :  61
print("Covariance des éléments : ", t_1.var())
## Covariance des éléments :  18.07638888888889

Pour appliquer ces fonctions sur un axe donné, on modifie la valeur du paramètre axis :

print("Somme par colonne: ", t_1.sum(axis=0))
## Somme par colonne:  [ 9  7 21 24]
print("Somme par ligne: ", t_1.sum(axis=1))
## Somme par ligne:  [10 20 31]

NumPy offre aussi certaines fonctions spécifiques aux statistiques, dont certaines sont répertoriées dans le Tableau 9.6.

Table 9.6: Fonctions statistiques
Code Description
sum(x), nansum(x) Somme de x (nansum(x) ne tient pas compte des valeurs NaN)
mean(x), nanmean() Moyenne de x
median(x), nanmedian() Médiane de x
average(x) Moyenne de x (possibilité d’utiliser des poids à l’aide du paramètre weight)
min(x), nanmin() Minimum de x
max(x), nanmax() Maximum de x
percentile(x,p), nanpercentile(n,p) P-ème percentile de x
var(x), nanvar(x) Variance de x
std(x), nanstd() Écart-type de x
cov(x) Covariance de x
corrcoef(x) Coefficients de corrélation

Pour utiliser les fonctions statistiques :

t_1 = np.array([[1, 2, 3, 4], [-1, 6, 7, 8], [9, -1, 11, 12]])
print("t_1 : \n", t_1)
## t_1 : 
##  [[ 1  2  3  4]
##  [-1  6  7  8]
##  [ 9 -1 11 12]]
print("Variance: ", np.var(t_1))
## Variance:  18.07638888888889

Si le tableau comporte des valeurs NaN, pour calculer la somme par exempe, si on utilise sum(), le résultat sera NaN. Pour ignorer les valeurs NaN, on utilise une fonction spécifique (ici, nansum()) :

t_1 = np.array([[1, 2, np.NaN, 4], [-1, 6, 7, 8], [9, -1, 11, 12]])
print("somme : ", np.sum(t_1))
## somme :  nan
print("somme en ignorant les NaN : ", np.nansum(t_1))
## somme en ignorant les NaN :  58.0

Pour calculer une moyenne pondérée (prenons un vecteur) :

v_1 = np.array([1, 1, 4, 2])
w = np.array([1, 1, .5, 1])
print("Moyenne pondérée : ", np.average(v_1, weights=w))
## Moyenne pondérée :  1.7142857142857142

9.2 Génération de nombres pseudo-aléatoires

La génération de nombres pseudo-aléatoires est permise par le module random de Numpy. Le lecteur intéressé par les aspects plus statistiques pourra trouver davantage de notions abordées dans le sous-module stats de SciPy.

from numpy import random

Le Tableau 9.7 répertorie quelques fonctions permettant de tirer de manière pseudo-aléatoire des nombres avec le module random de Numpy (en évaluant ??random, on obtient une liste exhaustive).

Table 9.7: Quelques fonctions de génération de nombres pseudo-aléatoires
Code Description
rand(size) Tirage de size valeurs selon une Uniforme \([0,1]\)
uniform(a,b,size) Tirage de size valeurs selon une Uniforme \([a ; b]\)
randint(a,b,size) Tirage de size valeurs selon une Uniforme \([a ; b[\)
randn(size) Tirage de size valeurs selon une Normale centrée réduite
normal(mu, std, size) Tirage de size valeurs selon une Normale d’espérance mu et d’écart-type std
binomial(size) Tirage de size valeurs selon une \(\mathcal{B}in(n,p)\)
beta(alpha, beta, size) Tirage de size valeurs selon une loi bêta de paramètres alpha et beta
poisson(lambda, size) Tirage de size valeurs selon une loi de Poisson de paramètre lambda
f(size) Tirage de size valeurs selon une
standard_t(df, size) Tirage de size valeurs selon une loi de Student à df degrés de liberté

Voici un exemple de génération de nombres pseudo aléatoires selon une distribution Gaussienne :

x = np.random.normal(size=10)
print(x)
## [-0.21608437 -0.04763351 -0.91065944 -0.8920658  -1.20739158  0.6140933
##  -0.79615403 -0.10350956 -0.7288215   1.46985647]

On peut générer un tableau à plusieurs dimensions. Par exemple, un tableau à deux dimensions, dans lequel la première dimension contient 10 éléments, contenant chacun 4 tirages aléatoires selon une \(\mathcal{N}(0,1)\) :

x = np.random.randn(10, 4)
print(x)
## [[ 0.0145722  -1.87532297  0.26389604  0.74479245]
##  [ 0.21507124  0.37340081 -2.30198978  0.80505913]
##  [ 0.47294127 -0.41094583  0.32768293  0.44436743]
##  [ 0.78187322 -0.58665232 -0.5309845   0.04994529]
##  [-0.18898402 -1.09015923  0.23203975  1.43007593]
##  [-0.48374179 -0.62116834  1.92289637  1.04752284]
##  [-1.03791008  0.57730921  0.57319431  0.9893094 ]
##  [-1.83270058  0.49668076 -0.10631126 -0.74305415]
##  [-1.34024884 -0.77796387 -0.50054115  1.77357994]
##  [-0.20097655 -1.43614827  0.74681482  0.83931251]]

La génération des nombres s’effectue en fonction d’une graine (seed), c’est-à-dire un nombre initiant le générateur de nombres pseudo aléatoires. Il est possible de fixer cette graine, pour pouvoir avoir des résultats reproductibles par exemple. Pour ce faire, on peut faire appel à la méthode seed(), à qui on indique une valeur en paramètre :

np.random.seed(1234)
x = np.random.normal(size=10)
print(x)
## [ 0.47143516 -1.19097569  1.43270697 -0.3126519  -0.72058873  0.88716294
##   0.85958841 -0.6365235   0.01569637 -2.24268495]

En fixant à nouveau la graîne, on obtiendra exactement le même tirage :

np.random.seed(1234)
x = np.random.normal(size=10)
print(x)
## [ 0.47143516 -1.19097569  1.43270697 -0.3126519  -0.72058873  0.88716294
##   0.85958841 -0.6365235   0.01569637 -2.24268495]

Pour éviter d’affecter l’environnement global par la graine aléatoire, on peut utiliser la méthode RandomStatedu sous-module random de NumPy :

from numpy.random import RandomState
rs = RandomState(123)
x = rs.normal(10)
print(x)
## 8.914369396699438

Par ailleurs, la fonction permutation() du sous-module random permet d’effectuer une permutation aléatoire :

x = np.arange(10)
y = np.random.permutation(x)
print("x : ", x)
## x :  [0 1 2 3 4 5 6 7 8 9]
print("y : ", y)
## y :  [9 7 4 3 8 2 6 1 0 5]

La fonction shuffle() du sous-module random permet quant à elle d’effectuer une permutation aléatoire des éléments :

x = np.arange(10)
print("x avant permutation : ", x)
## x avant permutation :  [0 1 2 3 4 5 6 7 8 9]
np.random.permutation(x)
print("x après permutation : ", x)
## x après permutation :  [0 1 2 3 4 5 6 7 8 9]

9.3 Exercice

Premier exercice

Considérons le vecteur suivant : \(x = \begin{bmatrix}1 & 2 & 3 & 4 & 5\end{bmatrix}\)

  1. Créer ce vecteur à l’aide d’un tableau que l’on appellera x.
  2. Afficher le type de x puis sa longueur.
  3. Extraire le premier élément, puis en faire de même avec le dernier.
  4. Extraire les trois premiers éléments et les stocker dans un vecteur que l’on nommera a.
  5. Extraire les 1er, 2e et 5e éléments du vecteur (attention aux positions) ; les stocker dans un vecteur que l’on nommera b.
  6. Additionner le nombre 10 au vecteur x, puis multiplier le résultat par 2.
  7. Effectuer l’addition de a et b, commenter le résultat.
  8. Effectuer l’addition suivante : x+a ; commenter le résultat, puis regarder le résultat de a+x.
  9. Multiplier le vecteur par le scalaire c que l’on fixera à 2.
  10. Effectuer la multiplication de a et b ; commenter le résultat.
  11. Effectier la multiplication suivante : x*a ; commenter le résultats.
  12. Récupérer les positions des multiples de 2 et les stocker dans un vecteur que l’on nommera ind, piuis conserver uniquement les multiples de 2 de x dans un vecteur que l’on nommera mult_2.
  13. Afficher les éléments de x qui sont multiples de 3 et multiples de 2.
  14. Afficher les éléments de x qui sont multiples de 3 ou multiples de 2.
  15. Calculer la somme des éléments de x.
  16. Remplacer le premier élément de x par un 4.
  17. Remplacer le premier élément de x par la valeur NaN, puis calculer la somme des éléments de x. 18 Supprimer le vecteur x.

Deuxième exercice

  1. Créer la matrice suivante : \(A = \begin{bmatrix} -3 & 5 & 6 \\ -1 & 2 & 2 \\ 1 & -1 & -1 \end{bmatrix}\).
  2. Afficher la dimension de A, son nombre de colonnes, son nombre de lignes et sa longueur.
  3. Extraire la seconde colonne de A, puis la première ligne. 4.Extraire l’élément en troisième position à la première ligne.
  4. Extraire la sous-matrice de dimension \(2\times 2\) du coin inférieur de A, c’est-à-dire \(\begin{bmatrix} 2 & 2 \\ -1 & -1 \end{bmatrix}\).
  5. Calculer la somme des colonnes puis des lignes de A.
  6. Afficher la diagonale de A.
  7. Rajouter le vecteur \(\begin{bmatrix} 1 & 2 & 3\end{bmatrix}^\top\) à droite de la matrice A et stocker le résultat dans un objet appelé B.
  8. Retirer le quatrième vecteur de B.
  9. Retirer la première et la troisième ligne de B.
  10. Ajouter le scalaire 10 à A.
  11. Ajouter le vecteur \(\begin{bmatrix} 1 & 2 & 3\end{bmatrix}^\top\) à A.
  12. Ajouter la matrice identité \(I_3\) à A.
  13. Diviser tous les éléments de la matrice A par 2.
  14. Multiplier la matrice A par le vecteur ligne \(\begin{bmatrix} 1 & 2 & 3\end{bmatrix}^\top\).
  15. Afficher la transposée de A.
  16. Effectuer le produit avec transposition \(A^\top A\).