3. Un exemple introductif#

3.1. Vue d’ensemble#

Nous sommes maintenant prêts à commencer à apprendre le langage Python lui-même.

Dans ce cours, nous allons écrire puis décortiquer de petits programmes Python.

L’objectif est de vous présenter la syntaxe de base de Python et ses structures de données.

Des concepts plus approfondis seront abordés dans les cours ultérieurs.

Vous devriez avoir lu le cours sur la prise en main de Python avant de commencer celui-ci.

3.2. La tâche : tracer un processus de bruit blanc#

Supposons que nous voulions simuler et tracer le processus de bruit blanc \(\epsilon_0, \epsilon_1, \ldots, \epsilon_T\), où chaque tirage \(\epsilon_t\) est un tirage indépendant d’une loi normale centrée réduite.

En d’autres termes, nous voulons générer des figures qui ressemblent à ceci :

_images/test_program_1_updated.png

(Ici \(t\) est sur l’axe horizontal et \(\epsilon_t\) sur l’axe vertical.)

Nous ferons cela de plusieurs façons différentes, en apprenant à chaque fois quelque chose de plus sur Python.

3.3. Version 1#

Voici quelques lignes de code qui accomplissent la tâche que nous nous sommes fixée

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng()
ϵ_values = rng.standard_normal(100)
plt.plot(ϵ_values)
plt.show()
_images/4f7f23e327c2d566c4aaae97fb6e6bc54ebda16553ecf17893f5f5c82885e321.png

Décomposons ce programme et voyons comment il fonctionne.

3.3.1. Importations#

Les deux premières lignes du programme importent des fonctionnalités provenant de bibliothèques de code externes.

La première ligne importe NumPy, un package Python privilégié pour des tâches telles que

  • le travail avec des tableaux (vecteurs et matrices)

  • les fonctions mathématiques courantes comme cos et sqrt

  • la génération de nombres aléatoires

  • l’algèbre linéaire, etc.

Après import numpy as np, nous avons accès à ces attributs via la syntaxe np.attribute.

Voici deux autres exemples

np.sqrt(4)
np.float64(2.0)
np.log(4)
np.float64(1.3862943611198906)

3.3.1.1. Pourquoi autant d’importations ?#

Les programmes Python nécessitent généralement plusieurs instructions d’importation.

La raison en est que le cœur du langage est délibérément maintenu petit, afin qu’il soit facile à apprendre, à maintenir et à améliorer.

Lorsque vous voulez faire quelque chose d’intéressant avec Python, vous avez presque toujours besoin d’importer des fonctionnalités supplémentaires.

3.3.1.2. Packages#

Comme indiqué ci-dessus, NumPy est un package Python.

Les packages sont utilisés par les développeurs pour organiser le code qu’ils souhaitent partager.

En fait, un package est simplement un répertoire contenant

  1. des fichiers avec du code Python — appelés modules dans le jargon Python

  2. éventuellement du code compilé accessible par Python (par exemple, des fonctions compilées à partir de code C ou FORTRAN)

  3. un fichier appelé __init__.py qui spécifie ce qui sera exécuté lorsque nous tapons import package_name

Vous pouvez vérifier l’emplacement de votre __init__.py pour NumPy en Python en exécutant le code :

import numpy as np

print(np.__file__)

3.3.1.3. Sous-packages#

Considérons la ligne rng = np.random.default_rng().

Ici np fait référence au package NumPy, tandis que random est un sous-package de NumPy.

Les sous-packages sont simplement des packages qui sont des sous-répertoires d’un autre package.

Par exemple, vous pouvez trouver le dossier random sous le répertoire de NumPy.

3.3.2. Importer directement des noms#

Rappelons le code que nous avons vu ci-dessus

import numpy as np

np.sqrt(4)
np.float64(2.0)

Voici une autre façon d’accéder à la fonction racine carrée de NumPy

from numpy import sqrt

sqrt(4)
np.float64(2.0)

C’est également correct.

L’avantage est qu’il y a moins à taper si nous utilisons sqrt souvent dans notre code.

L’inconvénient est que, dans un long programme, ces deux lignes pourraient être séparées par de nombreuses autres lignes.

Il est alors plus difficile pour les lecteurs de savoir d’où vient sqrt, s’ils le souhaitent.

3.3.3. Tirages aléatoires#

Pour revenir à notre programme qui trace le bruit blanc, les trois lignes restantes après les instructions d’importation sont

ϵ_values = rng.standard_normal(100)
plt.plot(ϵ_values)
plt.show()
_images/e87a77a977d24233ba9372579b7a9bea0fe25931860af293edf84a4fdcf1bf46.png

La première ligne génère 100 tirages (quasi) indépendants d’une loi normale centrée réduite et les stocke dans ϵ_values.

Les deux lignes suivantes génèrent le graphique.

Nous pouvons et allons examiner ci-dessous diverses façons de configurer et d’améliorer ce graphique.

3.4. Implémentations alternatives#

Essayons d’écrire quelques versions alternatives de notre premier programme, qui traçait des tirages IID de la loi normale centrée réduite.

Les programmes ci-dessous sont moins efficaces que l’original, et donc quelque peu artificiels.

Mais ils nous aident à illustrer une syntaxe et une sémantique Python importantes dans un cadre familier.

3.4.1. Une version avec une boucle for#

Voici une version qui illustre les boucles for et les listes Python.

ts_length = 100
ϵ_values = []   # liste vide

for i in range(ts_length):
    e = rng.standard_normal()
    ϵ_values.append(e)

plt.plot(ϵ_values)
plt.show()
_images/a972483187295f6b9388b2b4c844ace171899d98fcdc1782bffa250fb78e9967.png

En bref,

  • La première ligne définit la longueur souhaitée de la série temporelle.

  • La ligne suivante crée une liste vide appelée ϵ_values qui stockera les valeurs \(\epsilon_t\) au fur et à mesure que nous les générons.

  • L’instruction # liste vide est un commentaire, et est ignorée par l’interpréteur de Python.

  • Les trois lignes suivantes constituent la boucle for, qui tire de manière répétée un nouveau nombre aléatoire \(\epsilon_t\) et l’ajoute à la fin de la liste ϵ_values.

  • Les deux dernières lignes génèrent le graphique et l’affichent à l’utilisateur.

Étudions certaines parties de ce programme plus en détail.

3.4.2. Listes#

Considérons l’instruction ϵ_values = [], qui crée une liste vide.

Les listes sont une structure de données native de Python utilisée pour regrouper une collection d’objets.

Les éléments des listes sont ordonnés, et les doublons sont autorisés dans les listes.

Par exemple, essayez

x = [10, 'foo', False]
type(x)
list

Le premier élément de x est un entier, le suivant est une chaîne de caractères, et le troisième est une valeur booléenne.

Lors de l’ajout d’une valeur à une liste, nous pouvons utiliser la syntaxe list_name.append(some_value)

x
[10, 'foo', False]
x.append(2.5)
x
[10, 'foo', False, 2.5]

Ici append() est ce qu’on appelle une méthode, c’est-à-dire une fonction « attachée à » un objet — dans ce cas, la liste x.

Nous apprendrons tout sur les méthodes plus tard, mais juste pour vous donner une idée,

  • Les objets Python tels que les listes, les chaînes de caractères, etc. ont tous des méthodes utilisées pour manipuler les données contenues dans l’objet.

  • Les objets chaînes de caractères ont des méthodes de chaîne, les objets listes ont des méthodes de liste, etc.

Une autre méthode de liste utile est pop()

x
[10, 'foo', False, 2.5]
x.pop()
2.5
x
[10, 'foo', False]

Les listes en Python sont indexées à partir de zéro (comme en C, Java ou Go), donc le premier élément est référencé par x[0]

x[0]   # premier élément de x
10
x[1]   # deuxième élément de x
'foo'

3.4.3. La boucle for#

Considérons maintenant la boucle for du programme ci-dessus, qui était

for i in range(ts_length):
    e = rng.standard_normal()
    ϵ_values.append(e)

Python exécute les deux lignes indentées ts_length fois avant de passer à la suite.

Ces deux lignes sont appelées un bloc de code, car elles constituent le « bloc » de code sur lequel nous bouclons.

Contrairement à la plupart des autres langages, Python connaît l’étendue du bloc de code uniquement grâce à l’indentation.

Dans notre programme, l’indentation diminue après la ligne ϵ_values.append(e), indiquant à Python que cette ligne marque la limite inférieure du bloc de code.

Plus de détails sur l’indentation ci-dessous — pour l’instant, regardons un autre exemple de boucle for

animals = ['dog', 'cat', 'bird']
for animal in animals:
    print("The plural of " + animal + " is " + animal + "s")
The plural of dog is dogs
The plural of cat is cats
The plural of bird is birds

Cet exemple aide à clarifier comment fonctionne la boucle for : lorsque nous exécutons une boucle de la forme

for variable_name in sequence:
    <code block>

L’interpréteur Python effectue ce qui suit :

  • Pour chaque élément de la sequence, il « lie » le nom variable_name à cet élément puis exécute le bloc de code.

3.4.4. Une remarque sur l’indentation#

En discutant de la boucle for, nous avons expliqué que les blocs de code sur lesquels on boucle sont délimités par l’indentation.

En fait, en Python, tous les blocs de code (c’est-à-dire ceux qui se trouvent dans les boucles, les clauses if, les définitions de fonctions, etc.) sont délimités par l’indentation.

Ainsi, contrairement à la plupart des autres langages, les espaces blancs dans le code Python affectent la sortie du programme.

Une fois que vous vous y êtes habitué, c’est une bonne chose : cela

  • force une indentation propre et cohérente, améliorant la lisibilité

  • supprime l’encombrement, comme les accolades ou les instructions de fin utilisées dans d’autres langages

D’un autre côté, cela demande un peu de soin pour être fait correctement, alors veuillez retenir :

  • La ligne précédant le début d’un bloc de code se termine toujours par deux points

    • for i in range(10):

    • if x > y:

    • while x < 100:

    • etc.

  • Toutes les lignes d’un bloc de code doivent avoir la même quantité d’indentation.

  • La norme Python est de 4 espaces, et c’est ce que vous devriez utiliser.

3.4.5. Boucles while#

La boucle for est la technique la plus courante d’itération en Python.

Mais, à des fins d’illustration, modifions le programme ci-dessus pour utiliser une boucle while à la place.

ts_length = 100
ϵ_values = []
i = 0
while i < ts_length:
    e = rng.standard_normal()
    ϵ_values.append(e)
    i = i + 1
plt.plot(ϵ_values)
plt.show()
_images/3638a10a5218fc3eb8ce54192679539a2442ab2e94e1ed5bd733874b661e1a28.png

Une boucle while continuera d’exécuter le bloc de code délimité par l’indentation jusqu’à ce que la condition (i < ts_length) soit satisfaite.

Dans ce cas, le programme continuera d’ajouter des valeurs à la liste ϵ_values jusqu’à ce que i soit égal à ts_length :

i == ts_length #la condition de fin de la boucle while
True

Notez que

  • le bloc de code de la boucle while est à nouveau délimité uniquement par l’indentation.

  • l’instruction i = i + 1 peut être remplacée par i += 1.

3.5. Une autre application#

Faisons encore une application avant de passer aux exercices.

Dans cette application, nous traçons le solde d’un compte bancaire au fil du temps.

Il n’y a pas de retraits au cours de la période, dont la dernière date est notée par \(T\).

Le solde initial est \(b_0\) et le taux d’intérêt est \(r\).

Le solde se met à jour de la période \(t\) à \(t+1\) selon \(b_{t+1} = (1 + r) b_t\).

Dans le code ci-dessous, nous générons et traçons la séquence \(b_0, b_1, \ldots, b_T\).

Au lieu d’utiliser une liste Python pour stocker cette séquence, nous utiliserons un tableau NumPy.

r = 0.025         # taux d'intérêt
T = 50            # date de fin
b = np.empty(T+1) # un tableau NumPy vide, pour stocker tous les b_t
b[0] = 10         # solde initial

for t in range(T):
    b[t+1] = (1 + r) * b[t]

plt.plot(b, label='solde bancaire')
plt.legend()
plt.show()
_images/e241a4e2d6da709c4a16ef9769065ce29fc5ad6f44a246eb19a3b78b0e3b6bed.png

L’instruction b = np.empty(T+1) alloue de l’espace mémoire pour T+1 nombres (à virgule flottante).

Ces nombres sont remplis par la boucle for.

Allouer la mémoire au départ est plus efficace que d’utiliser une liste Python et append, car cette dernière doit demander de manière répétée de l’espace de stockage au système d’exploitation.

Remarquez que nous avons ajouté une légende au graphique — une fonctionnalité que l’on vous demandera d’utiliser dans les exercices.

3.6. Exercices#

Passons maintenant aux exercices. Il est important que vous les complétiez avant de continuer, car ils présentent de nouveaux concepts dont nous aurons besoin.

Exercice 3.1

Votre première tâche est de simuler et de tracer la série temporelle corrélée

\[ x_{t+1} = \alpha \, x_t + \epsilon_{t+1} \quad \text{où} \quad x_0 = 0 \quad \text{et} \quad t = 0,\ldots,T \]

La séquence de chocs \(\{\epsilon_t\}\) est supposée être IID et suivre une loi normale centrée réduite.

Dans votre solution, limitez vos instructions d’importation à

import numpy as np
import matplotlib.pyplot as plt

Posez \(T=200\) et \(\alpha = 0.9\).

Exercice 3.2

En partant de votre solution à l’exercice 1, tracez trois séries temporelles simulées, une pour chacun des cas \(\alpha=0\), \(\alpha=0.8\) et \(\alpha=0.98\).

Utilisez une boucle for pour parcourir les valeurs de \(\alpha\).

Si vous le pouvez, ajoutez une légende, pour aider à distinguer les trois séries temporelles.

Exercice 3.3

De manière similaire aux exercices précédents, tracez la série temporelle

\[ x_{t+1} = \alpha \, |x_t| + \epsilon_{t+1} \quad \text{où} \quad x_0 = 0 \quad \text{et} \quad t = 0,\ldots,T \]

Utilisez \(T=200\), \(\alpha = 0.9\) et \(\{\epsilon_t\}\) comme précédemment.

Recherchez en ligne une fonction qui peut être utilisée pour calculer la valeur absolue \(|x_t|\).

Exercice 3.4

Un aspect important de pratiquement tous les langages de programmation est le branchement et les conditions.

En Python, les conditions sont généralement implémentées avec la syntaxe if–else.

Voici un exemple, qui affiche -1 pour chaque nombre négatif d’un tableau et 1 pour chaque nombre non négatif

numbers = [-9, 2.3, -11, 0]
for x in numbers:
    if x < 0:
        print(-1)
    else:
        print(1)
-1
1
-1
1

Maintenant, écrivez une nouvelle solution à l’exercice 3 qui n’utilise pas de fonction existante pour calculer la valeur absolue.

Remplacez cette fonction existante par une condition if–else.

Exercice 3.5

Voici un exercice plus difficile, qui demande de la réflexion et de la planification.

La tâche consiste à calculer une approximation de \(\pi\) en utilisant Monte-Carlo.

N’utilisez aucune importation en dehors de

import numpy as np