| jupytext |
|
||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kernelspec |
|
||||||||||||||||||||||||||||||||||||||
| translation |
|
(python_by_example)=
<div id="qe-notebook-header" align="right" style="text-align:right;">
<a href="https://quantecon.org/" title="quantecon.org">
<img style="width:250px;display:inline;" width="250px" src="https://assets.quantecon.org/img/qe-menubar-logo.svg" alt="QuantEcon">
</a>
</div>
Nous sommes maintenant prêts à aborder le langage Python proprement dit.
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.
Les notions plus avancées seront abordées dans les chapitres suivants.
Vous devriez avoir lu le {doc}cours <getting_started> sur la prise en main de Python avant de commencer celui-ci.
Supposons que nous voulions simuler et tracer le processus de bruit blanc
En d'autres termes, nous voulons générer des figures qui ressemblent à ceci :
:scale: 120
(Ici
Nous ferons cela de plusieurs façons différentes, en apprenant à chaque fois quelque chose de plus sur Python.
(ourfirstprog)= 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()
Décomposons ce programme et voyons comment il fonctionne.
(import)=
Les deux premières lignes du programme importent des fonctionnalités provenant de bibliothèques de code externes.
La première ligne importe {doc}NumPy <numpy>, une bibliothèque Python très utilisée pour des tâches telles que
- le travail avec des tableaux (vecteurs et matrices)
- les fonctions mathématiques courantes comme
cosetsqrt - 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.log(4)
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.
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
- des fichiers avec du code Python --- appelés modules dans le jargon Python
- éventuellement du code compilé accessible par Python (par exemple, des fonctions compilées à partir de code C ou FORTRAN)
- un fichier appelé
__init__.pyqui spécifie ce qui sera exécuté lorsque nous taponsimport package_name
Vous pouvez vérifier l'emplacement de votre __init__.py pour NumPy en Python en exécutant le code :
:class: no-execute
import numpy as np
print(np.__file__)
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.
Rappelons le code que nous avons vu ci-dessus
import numpy as np
np.sqrt(4)
Voici une autre façon d'accéder à la fonction racine carrée de NumPy
from numpy import sqrt
sqrt(4)
C'est également correct.
Cette écriture permet d'alléger le code lorsque la fonction sqrt est utilisée fréquemment.
L'inconvénient est que, dans un long programme, ces deux lignes pourraient être séparées par de nombreuses autres lignes.
En revanche, dans un programme long, il peut devenir plus difficile de déterminer de quelle bibliothèque provient sqrt
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()
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 verrons plus loin différentes façons de configurer et d'améliorer ce graphique.
Essayons d'écrire quelques versions alternatives de {ref}notre premier programme <ourfirstprog>, 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.
Ils permettent néanmoins d'illustrer, dans un contexte familier, plusieurs éléments importants de la syntaxe et du fonctionnement de Python.
Voici une version qui illustre les boucles for et les listes Python.
(firstloopprog)=
ts_length = 100
ϵ_values = [] # liste vide
for i in range(ts_length):
e = rng.standard_normal()
ϵ_values.append(e)
plt.plot(ϵ_values)
plt.show()
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
ϵ_valuesqui stockera les valeurs$\epsilon_t$ au fur et à mesure que nous les générons. - L'instruction
# liste videest 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.
(lists_ref)=
Considérons l'instruction ϵ_values = [], qui crée une liste vide.
Une liste est une structure de données native de Python qui permet de regrouper plusieurs 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)
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
x.append(2.5)
x
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 étudierons les méthodes plus en détail dans un {doc}chapitre ultérieur <oop_intro>. Pour le moment, retenons simplement que :
- 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
x.pop()
x
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
x[1] # deuxième élément de x
Considérons maintenant la boucle for du {ref}programme ci-dessus <firstloopprog>, 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 constituent un bloc de code, c'est-à-dire l'ensemble des instructions répétées à chaque itération.
Contrairement à la plupart des autres langages, Python détermine les limites du bloc de code uniquement à partir de 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")
Cet exemple aide à clarifier comment fonctionne la boucle for : lorsque nous exécutons une
boucle de la forme
:class: no-execute
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 nomvariable_nameà cet élément puis exécute le bloc de code.
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é
- évite certains éléments syntaxiques supplémentaires, comme les accolades ou les mots-clés de fin utilisés 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.
La boucle for est la technique la plus courante d'itération en Python.
Mais, à des fins d'illustration, modifions {ref}le programme ci-dessus <firstloopprog> pour utiliser une boucle while à la place.
(whileloopprog)=
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()
Une boucle while exécute le bloc de code délimité par l'indentation tant que la condition (i < ts_length) est vraie.
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
Notez que
- le bloc de code de la boucle
whileest à nouveau délimité uniquement par l'indentation. - l'instruction
i = i + 1peut être remplacée pari += 1.
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
Le solde initial est
Le solde se met à jour de la période
Dans le code ci-dessous, nous générons et traçons la séquence
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()
L'instruction b = np.empty(T+1) alloue de l'espace mémoire pour T+1
nombres (à virgule flottante).
Ces nombres sont remplis au moyen de la boucle for.
Allouer la mémoire au départ est plus efficace que d'utiliser une liste Python et
append, car l'utilisation répétée de cette dernière peut nécessiter de nouvelles allocations de mémoire.
Remarquez que nous avons ajouté une légende au graphique --- une fonctionnalité que l'on vous demandera d'utiliser dans les 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.
:label: pbe_ex1
Votre première tâche est de simuler et de tracer la série temporelle corrélée
La séquence de chocs
Dans votre solution, limitez vos instructions d'importation à
import numpy as np
import matplotlib.pyplot as plt
On pose
:class: dropdown
Voici une solution.
α = 0.9
T = 200
x = np.empty(T+1)
x[0] = 0
rng = np.random.default_rng()
for t in range(T):
x[t+1] = α * x[t] + rng.standard_normal()
plt.plot(x)
plt.show()
:label: pbe_ex2
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.
```{hint}
:class: dropdown
* Si vous appelez la fonction `plot()` plusieurs fois avant d'appeler `show()`, toutes les lignes que vous produisez se retrouveront sur la même figure.
* Pour la légende, notez que si `var = 42`, l'expression `f'foo{var}'` s'évalue en `'foo42'`.
:class: dropdown
α_values = [0.0, 0.8, 0.98]
T = 200
x = np.empty(T+1)
rng = np.random.default_rng()
for α in α_values:
x[0] = 0
for t in range(T):
x[t+1] = α * x[t] + rng.standard_normal()
plt.plot(x, label=f'$\\alpha = {α}$')
plt.legend()
plt.show()
`f'$\\alpha = {α}$'` dans la solution est une application de la [f-string](https://docs.python.org/3/tutorial/inputoutput.html#tut-f-strings), qui vous permet d'utiliser `{}` pour contenir une expression.
L'expression contenue sera évaluée, et le résultat sera placé dans la chaîne de caractères.
:label: pbe_ex3
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|$.
:class: dropdown
Voici une solution :
α = 0.9
T = 200
x = np.empty(T+1)
x[0] = 0
rng = np.random.default_rng()
for t in range(T):
x[t+1] = α * np.abs(x[t]) + rng.standard_normal()
plt.plot(x)
plt.show()
:label: pbe_ex4
Les branchements conditionnels constituent un élément essentiel de presque tous les langages de programmation.
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)
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.
:class: dropdown
Voici une façon de faire :
α = 0.9
T = 200
x = np.empty(T+1)
x[0] = 0
rng = np.random.default_rng()
for t in range(T):
if x[t] < 0:
abs_x = - x[t]
else:
abs_x = x[t]
x[t+1] = α * abs_x + rng.standard_normal()
plt.plot(x)
plt.show()
Voici une façon plus courte d'écrire la même chose :
α = 0.9
T = 200
x = np.empty(T+1)
x[0] = 0
rng = np.random.default_rng()
for t in range(T):
abs_x = - x[t] if x[t] < 0 else x[t]
x[t+1] = α * abs_x + rng.standard_normal()
plt.plot(x)
plt.show()
:label: pbe_ex5
Voici un exercice plus difficile, qui demande de la réflexion et de la planification.
La tâche consiste à calculer une approximation de
N'utilisez aucune importation en dehors de
import numpy as np
:class: dropdown
Vos indices sont les suivants :
* Si $U$ est une variable aléatoire uniforme bivariée sur le carré unité $(0, 1)^2$, alors la probabilité que $U$ se trouve dans un sous-ensemble $B$ de $(0,1)^2$ est égale à l'aire de $B$.
* Si $U_1,\ldots,U_n$ sont des copies IID de $U$, alors, lorsque $n$ devient suffisamment grand, la fraction qui tombe dans $B$ converge vers la probabilité que U appartienne à $B$.
* Pour un cercle, $aire = \pi * rayon^2$.
:class: dropdown
Considérons le cercle de diamètre 1 inscrit dans le carré unité.
Soit
Si nous connaissons
Mais ici le but est de calculer
Résumé : si nous pouvons estimer l'aire d'un cercle de diamètre 1, alors diviser
par
Nous estimons l'aire en générant des observations d'une loi uniforme bidimensionnelle et en regardant la fraction qui tombe dans le cercle.
n = 1000000 # taille d'échantillon pour la simulation de Monte-Carlo
rng = np.random.default_rng()
count = 0
for i in range(n):
# tirage de positions aléatoires sur le carré
u, v = rng.uniform(), rng.uniform()
# vérifier si le point tombe à l'intérieur de la limite
# du cercle unité centré en (0.5,0.5)
d = np.sqrt((u - 0.5)**2 + (v - 0.5)**2)
# s'il tombe à l'intérieur du cercle inscrit,
# l'ajouter au compteur
if d < 0.5:
count += 1
area_estimate = count / n
print(area_estimate * 4) # division par rayon**2