Les fonctions de Numpy

Créer des tableaux Numpy

De nombreuses fonctions de Numpy sont dédiées à la construction de tableaux multidimensionnels. La liste complète des fonctions pour la création de tableaux peut se trouver dans la documentation Numpy. Attention à bien choisir la documentation appropriée à votre version de Numpy, accessible avec :

[1]:
# Importation du module Numpy
import numpy as np

print(np.__version__)
1.21.2

La fonction arrayInfo

Nous allons créer une fonction pour afficher les informations relatives aux tableaux Numpy, même si nous verrons plus tard la syntaxe de création d’une fonction personnelle.

[2]:
def arrayInfo(array_name):
    """prints out the content, shape, dimensions and data type of an array"""
    print("-----")
    print(array_name)
    print("shape: ", array_name.shape)
    print("number of dimensions :", array_name.ndim)
    print("datatype: ", array_name.dtype)
    print("-----")
    return

Nous pouvons alors utiliser cette fonction comme une fonction Python pour obtenir des informations sur un tableau:

[3]:
# Création d'un tableau Numpy
A = np.array([[3, 5, 9],
            [4, 6, -2]])

# Utilisation de la fonction arrayInfo
arrayInfo(A)
-----
[[ 3  5  9]
 [ 4  6 -2]]
shape:  (2, 3)
number of dimensions : 2
datatype:  int64
-----

Uns et zeros

Voici quelques fonctions utiles pour créer des tableaux préremplis :

fonction description
empty(shape) retourne un tableau de forme donnée
ones(shape) retourne un tableau rempli de 1 de forme donnée
zeros(shape) retourne un tableau rempli de 0 de forme donnée

Par exemple, créons un tableau A avec 3 lignes et 4 colonnes rempli de 1 :

[4]:
A = np.ones((3, 4))
arrayInfo(A)
-----
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]
shape:  (3, 4)
number of dimensions : 2
datatype:  float64
-----

On rappelle que la forme du tableau (shape) s’écrit sous la forme d’un tuple (voir Le module Numpy). Ici le tuple définit le nombre d’éléments dans chacune des dimensions du tableau : 3 éléments dans la dimension 0 (lignes), et 4 éléments dans la dimension 1 (colonnes), c’est-à-dire un tableau avec 3 lignes et 4 colonnes.

On remarque que le type des éléments du tableau est par défaut float64, c’est-à-dire des réels. Si nous voulons un type différent, il faut ajouter l’argument dtype à la fonction :

[5]:
A = np.ones((3, 4), dtype='int64')
arrayInfo(A)
-----
[[1 1 1 1]
 [1 1 1 1]
 [1 1 1 1]]
shape:  (3, 4)
number of dimensions : 2
datatype:  int64
-----

Maintenant, créons un tableau à une dimension avec 5 éléments tous égaux à zéro. Pour cela, il faut se souvenir comment écrire un tuple avec 1 seul élément :

[6]:
A = np.zeros((5,))
arrayInfo(A)
-----
[0. 0. 0. 0. 0.]
shape:  (5,)
number of dimensions : 1
datatype:  float64
-----

La fonction empty() permet de créer un tableau rapidement. Mais attention, ce tableau n’est pas vide, contrairement à ce que le nom de la fonction laisse entendre :

[7]:
A = np.empty((3, 3))
arrayInfo(A)
-----
[[4.65302764e-310 6.93601410e-310 6.93600704e-310]
 [6.93601390e-310 6.93601385e-310 6.93597543e-310]
 [6.93597543e-310 6.93597543e-310 3.95252517e-322]]
shape:  (3, 3)
number of dimensions : 2
datatype:  float64
-----

On peut tester la vitesse d’exécution des différentes fonctions avec la commande %timeit :

[8]:
# %timeit np.zeros((1000, 1000))
# %timeit np.empty((1000, 1000))

Exercice

  1. Créer un tableau A de dimension 1, contenant 7 éléments tous égaux à \(4.2\) avec la fonction ones(). Afficher les attributs du tableau avec la fonction arrayInfo()
  2. Créer un tableau vide B avec la fonction empty(), de même forme que A, sans écrire explicitement de tuple en argument de la fonction
[9]:
# 1
A = np.ones((7,)) * 4.2
arrayInfo(A)

# 2
B = np.empty(A.shape)
arrayInfo(B)
-----
[4.2 4.2 4.2 4.2 4.2 4.2 4.2]
shape:  (7,)
number of dimensions : 1
datatype:  float64
-----
-----
[4.2 4.2 4.2 4.2 4.2 4.2 4.2]
shape:  (7,)
number of dimensions : 1
datatype:  float64
-----

Intervalles et pas

Voici quelques fonctions utiles pour créer des tableaux dans un intervalle donné :

fonction description
arange() valeurs régulièrement espacées pour un pas donné
linspace() valeurs régulièrement espacées pour un nombre d’éléments donné
logspace() valeurs régulièrement espacées sur une échelle logarithmique pour un nombre d’éléments donné

La fonction linspace() est utile pour générer des tableaux avec des valeurs définies sur un intervalle donné. Par exemple, pour générer un tableau avec 15 valeurs dans l’intervalle [−4, 4] :

[10]:
A = np.linspace(-4., 4, 15)
arrayInfo(A)
-----
[-4.         -3.42857143 -2.85714286 -2.28571429 -1.71428571 -1.14285714
 -0.57142857  0.          0.57142857  1.14285714  1.71428571  2.28571429
  2.85714286  3.42857143  4.        ]
shape:  (15,)
number of dimensions : 1
datatype:  float64
-----

Les deux premiers arguments de la fonction sont les bornes inférieure et supérieure, et le troisième argument est le nombre d’éléments du tableau. Si le nombre de points est omis, la fonction linspace() utilisera la valeur par défaut de 50 points, ce que l’on peut vérifier dans l’aide de python :

[11]:
# help(np.linspace)

Il arrive souvent que l’on veuille créer un tableau avec non pas un nombre d’éléments donné à l’avance, mais un pas donné à l’avance. Le pas est l’intervalle entre 2 éléments. Si l’intervalle entre n’importe quels 2 éléments consécutifs reste le même, on parle alors de pas fixe ou de pas constant.

Voici une petite routine qui permet de créer un tableau d’éléments compris entre 2 bornes connues à l’avance et un pas fixe :

[12]:
# Paramètres
start = -1.0   # borne inférieure
end = 2.0       # borne supérieure
step = 0.1      # pas

# Création du tableau
interval = end - start                     # intervalle
num_points = int(interval / step) + 1      # nombre d'éléments
g = np.linspace(start, end, num_points)    # taleau

# Attributs du tableau
arrayInfo(g)
-----
[-1.  -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1  0.   0.1  0.2  0.3
  0.4  0.5  0.6  0.7  0.8  0.9  1.   1.1  1.2  1.3  1.4  1.5  1.6  1.7
  1.8  1.9  2. ]
shape:  (31,)
number of dimensions : 1
datatype:  float64
-----

Exercice

Pourquoi dans la routine précédente le nombre de points est donné par int(interval / step) + 1 ?

La fonction arange() génère des valeurs dans l’intervalle semi-ouvert [début, fin[ avec un pas donné. Cependant, il est conseillé de ne pas l’utiliser avec un pas non entier, car le résultat n’est pas toujours cohérent avec la définition. Par exemple :

[13]:
print(np.arange(7.8, 8.4, 0.05))
[7.8  7.85 7.9  7.95 8.   8.05 8.1  8.15 8.2  8.25 8.3  8.35 8.4 ]

inclut l’élément 8.4 au lieu de l’exclure.

Par défaut la fonction arange() va générer des valeurs entières si tous les arguments d’entrée sont des nombres entiers. De plus, si le pas est omis, la fonction arange() utilisera le pas par de défaut de 1.

On peut donc l’utiliser à la place de la fonction range() pour générer un tableau Numpy d’entier :

[14]:
A = np.arange(-3, 5)

# est équivalent à :
B = np.array(list(range(-3, 5)))

arrayInfo(A)
arrayInfo(B)
-----
[-3 -2 -1  0  1  2  3  4]
shape:  (8,)
number of dimensions : 1
datatype:  int64
-----
-----
[-3 -2 -1  0  1  2  3  4]
shape:  (8,)
number of dimensions : 1
datatype:  int64
-----

Exercice

En vous aidant de l’aide de la fonction logspace(), créer le tableau Numpy contenant toutes les puissances de \(10\) depuis \(10^{-4}\) à \(10^{5}\), puis affichez les attributs du tableau avec arrayInfo.

[15]:
# help(np.logspace)
A = np.logspace(-4, 5, 10)
arrayInfo(A)
-----
[1.e-04 1.e-03 1.e-02 1.e-01 1.e+00 1.e+01 1.e+02 1.e+03 1.e+04 1.e+05]
shape:  (10,)
number of dimensions : 1
datatype:  float64
-----

Manipulation de tableau

Voici quelques fonction utiles pour manipuler les tableaux Numpy. La liste complète est dans la documentation Numpy.

fonction description
reshape() donne une forme différente au tableau sans changer ses éléments
t.flatten() retourne une copie 1D du tableau
t.T() transposée du tableau
concatenate() joindre plusieurs tableaux
append() ajoute des valeurs à la fin du tableau
unique() trouve les éléments uniques du tableau
flip() inverse l’ordre des éléments

Voici quelques exemples d’utilisation de ces fonctions :

[16]:
# tableau de forme (2, 4)
A = np.array([[2, 7, 9, 1],
            [-3, 4, 0, 2]])
arrayInfo(A)
-----
[[ 2  7  9  1]
 [-3  4  0  2]]
shape:  (2, 4)
number of dimensions : 2
datatype:  int64
-----
[17]:
# reformer en tableau de forme (4, 2)
B = np.reshape(A, (4, 2))
arrayInfo(B)
-----
[[ 2  7]
 [ 9  1]
 [-3  4]
 [ 0  2]]
shape:  (4, 2)
number of dimensions : 2
datatype:  int64
-----
[18]:
# Attention, on voit que c'est différent de la transposée
C = A.T
arrayInfo(C)
-----
[[ 2 -3]
 [ 7  4]
 [ 9  0]
 [ 1  2]]
shape:  (4, 2)
number of dimensions : 2
datatype:  int64
-----

On peut joindre 2 tableaux suivant un axe donné, ici la première dimension, ou dimension 0 (lignes), avec la fonction append() :

[19]:
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6]])
D = np.append(A, B, axis = 0)

arrayInfo(D)
-----
[[1 2]
 [3 4]
 [5 6]]
shape:  (3, 2)
number of dimensions : 2
datatype:  int64
-----

Si on veut joindre plusieurs tableaux ensemble, il faut les concaténer avec la fonction concatenate() :

[20]:
C = np.array([[7, 8], [9, 10], [11, 12]])
D = np.concatenate((A, B, C), axis = 0)

arrayInfo(D)
-----
[[ 1  2]
 [ 3  4]
 [ 5  6]
 [ 7  8]
 [ 9 10]
 [11 12]]
shape:  (6, 2)
number of dimensions : 2
datatype:  int64
-----

Un peu de tri

Il peut être utile de trier un tableau Numpy. Reprenons les tableaux de la leçon précédente donnant les nombres d’habitants et les superficies des 8 villes les plus peuplées de France en 2017 :

[21]:
noms = np.array(['Paris', 'Marseille', 'Lyon', 'Toulouse', 'Nice', 'Nantes', 'Montpellier', 'Strasbourg'])
nombre_habitants = np.array([2187526, 863310, 516092, 479553, 340017, 309346, 285121, 280966])
superficie = np.array([105.40, 240.62, 47.87, 118.30, 71.92, 65.19, 56.88, 78.26]) # km**2

Nous voyons que les tableaux sont triés suivant la donnée du nombre d’habitants, du plus grand au plus petit. On aimerait trier les données des villes suivant la donnée de la superficie en ordre décroissant. On peut utiliser les fonctions sort() et flip() :

[22]:
superficie_sort = np.sort(superficie)         # tri par défaut dans l'ordre croissant
superficie_sort = np.flip(superficie_sort)    # inverse l'ordre du tri
print(superficie_sort)
[240.62 118.3  105.4   78.26  71.92  65.19  56.88  47.87]

Si on obtient bien un tableau trié dans le bon ordre pour les superficies, les tableaux contenant les nombres d’habitants et les noms de ville n’ont pas été triés, et on ne sait pas dans quel ordre les trier pour qu’ils correspondent au nouvel ordre du tableau donnant les superficies.

Pour résoudre ce problème, on va utiliser un masque d’indices grâce à la fonction argsort(). Cette fonction retourne non pas le tableau trié, mais le tableau d’indices qui permet de trier le tableau. On peut alors utiliser ce masque d’indices en indice pour trier les autres tableaux :

[23]:
mask = np.argsort(superficie)   # retourne le masque d'indices pour trier le tableau dans l'ordre croissant
mask = np.flip(mask)            # inverse l'ordre du masque pour obtenir un ordre décroissant

# Application du masque sur les 3 tableaux
noms_sort = noms[mask]
nombre_habitants_sort = nombre_habitants[mask]
superficie_sort = superficie[mask]

# Affichage des tableaux triés
print(noms_sort)
print(nombre_habitants_sort)
print(superficie_sort)
['Marseille' 'Toulouse' 'Paris' 'Strasbourg' 'Nice' 'Nantes' 'Montpellier'
 'Lyon']
[ 863310  479553 2187526  280966  340017  309346  285121  516092]
[240.62 118.3  105.4   78.26  71.92  65.19  56.88  47.87]

Quelle est la date ?

Afin de manipuler simplement des dates, Numpy a un type de données datetime64. On peut alors utiliser les fonctions vues plus haut sur ce nouveau type de données.

Pour créer une date on utilise alors la fonction datetime() :

[24]:
date = np.datetime64('2020-09-21')
print("La date est :", date)
print("L'année est :", np.datetime64(date, 'Y'))
La date est : 2020-09-21
L'année est : 2020

On peut créer simplement des tableaux de dates avec la fonction arange() :

[25]:
dates = np.arange('2020-09', '2020-10', dtype = 'datetime64[D]')
print("Dates de septembre 2020 :\n", dates)
print("La date est en septembre :", date in dates)
Dates de septembre 2020 :
 ['2020-09-01' '2020-09-02' '2020-09-03' '2020-09-04' '2020-09-05'
 '2020-09-06' '2020-09-07' '2020-09-08' '2020-09-09' '2020-09-10'
 '2020-09-11' '2020-09-12' '2020-09-13' '2020-09-14' '2020-09-15'
 '2020-09-16' '2020-09-17' '2020-09-18' '2020-09-19' '2020-09-20'
 '2020-09-21' '2020-09-22' '2020-09-23' '2020-09-24' '2020-09-25'
 '2020-09-26' '2020-09-27' '2020-09-28' '2020-09-29' '2020-09-30']
La date est en septembre : True

Ou encore calculer des durées :

[26]:
dur = np.datetime64('2021-01-09') - np.datetime64('2020-09-21')
print("No. de jours :", dur)
print("No. de semaines :", np.timedelta64(dur, 'W'))
No. de jours : 110 days
No. de semaines : 15 weeks

Ou encore trier un tableau de dates :

[27]:
a = np.array(['2018-01-19', '2017-05-06', '2021-11-25'], dtype = 'datetime64')
print("Dates triées :", np.sort(a))
Dates triées : ['2017-05-06' '2018-01-19' '2021-11-25']

Pour aller plus loin

Le type datetime64 de Numpy est similaire au type datetime de Python. On peut trouver les références pour Numpy ici, et pour Python ici.

Les fonctions mathématiques

La force de Numpy est d’avoir de nombreuses fonctions mathématiques prédéfinies que l’on peut utiliser avec comme argument des tableaux Numpy. La plupart de ces fonctions sont vectorisées, c’est-à-dire qu’elles fonctionnent avec des tableaux multidimensionnels, de la même manière que lorsque l’on a utilisé les opérateurs arithmétiques.

Voici une liste non exhaustive de fonctions classées par thème. La liste complète est dans l’aide de Numpy.

fonctions Numpy
Trigonométrique sin(), cos(), tan(), degrees(), radians()
Somme, produit prod(), sum()
Exponentielle et logarithme exp(), log(), log10
Autres sqrt(), fabs(), maximum(), minimum()

Ces fonctions appartiennent au module Numpy, il ne faut donc pas oublier d’écrire np.func() pour les appeler.

Exercice

Un oscillateur amorti en régime libre à une dimension, lorsque le taux d’amortissement \(\xi\) est faible, se déplace suivant l’équation

\[ \begin{align}\begin{aligned}x(t) = C e^{-\xi\omega_0 t} \sin(\omega_d t+\phi)\\avec :math:`x` la position, :math:`t` le temps, et :math:`\omega_d=\omega_0\sqrt{1-\xi^2}`. Vous prendrez comme valeurs : :math:`C=1~\mathrm{m}`, :math:`\xi=0.1`, :math:`\omega_0=1~\mathrm{s}^{-1}` et :math:`\phi=0.2~\mathrm{rad}`.\end{aligned}\end{align} \]
  1. Créer le tableau Numpy t contenant 50 valeurs de \(t\) entre 0 et 10.
  2. Calculer alors le tableau Numpy x contenant les valeurs de \(x\) pour les valeurs correspondantes du tableau t, grâce à une unique expression
  3. Afficher le tableau des positions
[28]:
# Paramètres
C = 1       # m
xi = 0.1    # sans unités
w0 = 1      # s**-1
phi = 0.2   # radians

# Tableau des temps
t = np.linspace(0, 10, 50) # s

# Tableau correspondant des positions
x = C * np.exp(-xi * w0 * t) * np.sin(w0 * np.sqrt(1 - xi ** 2) * t + phi)

# Affichage des positions
print(x)
[ 0.19866933  0.38431009  0.54689659  0.6807386   0.78154248  0.84652994
  0.87449045  0.86576775  0.82218372  0.74690515  0.64426118  0.51952075
  0.37864069  0.227996    0.07410384 -0.07664723 -0.21825188 -0.34531696
 -0.45325632 -0.53844506 -0.59832852 -0.63148334 -0.63763015 -0.61759912
 -0.57325189 -0.50736454 -0.42347814 -0.32572417 -0.21863285 -0.10693294
  0.00464846  0.11157817  0.20969347  0.29535659  0.3655817   0.41813022
  0.45157202  0.46531119  0.45957698  0.43538141  0.39444687  0.3391076
  0.27219031  0.19687956  0.11657387  0.03473899 -0.04523576 -0.12017224
 -0.18722434 -0.24398044]