Adloun

Travaux pratiques avec Python

Cours complet · mathématiques approfondies (ECG 2e année), chapitre 13 · prépa ECG, 2e année

Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre

Les douze chapitres précédents ont construit des objets et démontré des théorèmes. Ce dernier chapitre les met en machine. Non pour vérifier ce qui est déjà démontré — une simulation ne démontre rien — mais pour voir ce que les énoncés disent, et pour traiter les cas où le calcul exact n'aboutit pas.

Le programme découpe ces travaux en quatre thèmes, que ce chapitre suit dans l'ordre : statistiques descriptives bivariées, fonctions de plusieurs variables, simulation de lois, estimation.

AttentionCe qu'un TP n'est pas

Le programme est explicite : « L'objectif de ces travaux pratiques n'est pas l'écriture de longs programmes mais l'assimilation de savoir-faire ». On n'attend donc de vous ni une bibliothèque, ni une interface : quinze lignes qui répondent à une question précise, et une phrase qui interprète le résultat.

Autre point de règlement, souvent mal connu : « On rappellera dans les sujets toutes les syntaxes des commandes non exigibles. » Les seules commandes à savoir par cœur sont celles de première année. Tout le reste — et ce chapitre en emploie — vous sera donné le jour de l'épreuve.

13.1 Thème 1 : statistiques descriptives bivariées

13.1.1 Le nuage et son point moyen

Définition 13.1Série double, nuage, point moyen

Une série statistique double est une liste de couples relevée sur individus. Le nuage de points est l'ensemble de ces couples dans le plan.

Le point moyen du nuage est , où et .

Définition 13.2Covariance et corrélation empiriques

La covariance empirique de la série est

et le coefficient de corrélation empirique est , où et sont les écarts types des deux séries simples.

ImportantCe sont les formules du chapitre 8, appliquées à des données

La covariance empirique est la covariance du couple pour la loi uniforme sur les observations. La formule de Huygens et l'encadrement ne sont donc pas des résultats nouveaux : ce sont ceux du cours, lus sur cette loi-là. En particulier si et seulement si les points sont exactement alignés, ce qui est le cas d'égalité de Cauchy-Schwarz du chapitre 4.

Prenons huit entreprises d'un même secteur, dont on relève le budget publicitaire et le chiffre d'affaires , tous deux en milliers d'euros.

(budget)1218243036424854
(chiffre)4652616473778689

Python : Le nuage, la covariance, la droite


import numpy as np
import matplotlib.pyplot as plt

x = np.array([12, 18, 24, 30, 36, 42, 48, 54])
y = np.array([46, 52, 61, 64, 73, 77, 86, 89])

xb, yb = x.mean(), y.mean()
cov = ((x - xb)*(y - yb)).mean()
r = cov/(x.std()*y.std())
a = cov/x.var()
b = yb - a*xb
print(round(cov, 3), round(r, 4), round(a, 4), round(b, 4))
# 198.0  0.9956  1.0476  33.9286

plt.scatter(x, y)
plt.plot(x, a*x + b)
plt.plot(xb, yb, "o")        # le point moyen
plt.show()

Trois lignes de calcul, une ligne de tracé. Le .std() et le .var() de numpy divisent par , ce qui est bien la convention de la définition ci-dessus.

13.1.2 Il y a deux droites de régression

◆Théorème 13.3Les deux droites, et ce que leur écart mesure

La droite de régression de en est avec et ; celle de en est avec et .

Ces deux droites passent par le point moyen , et

Démonstration

Les deux droites passent par : c'est immédiat sur , et sur . Le produit est celui des deux quotients, soit .

ImportantChoisir laquelle, c'est choisir ce qu'on explique

Les deux droites ne coïncident que si . Ajuster en , c'est minimiser les écarts verticaux : on suppose que est connu et que est à expliquer. Ajuster en minimise les écarts horizontaux et inverse les rôles.

Si l'on veut prédire le chiffre d'affaires à partir du budget, c'est la première qu'il faut. Prendre l'autre parce qu'elle est déjà tracée est une faute de modèle, pas une approximation.

13.1.3 Redresser avant d'ajuster

Une droite ne résume bien qu'un nuage droit. Quand la croissance est multiplicative, on pré-transforme les données pour se ramener au cas linéaire. Voici le nombre d'abonnés d'un service, en milliers, sur huit trimestres.

01234567
1219314778121195310
ImportantLe passage au logarithme change le modèle, pas les données

Si , alors : la série est exactement alignée. Ajuster une droite sur les logarithmes revient donc à ajuster une exponentielle sur les données.

Le coefficient calculé après transformation ne mesure plus la même chose : il juge la qualité du modèle exponentiel, pas celle d'un lien linéaire. Ici il passe de à — et c'est le second chiffre qui dit que le modèle est le bon.

Python : Redresser un nuage par le logarithme


import numpy as np

t = np.array([0, 1, 2, 3, 4, 5, 6, 7])
y = np.array([12, 19, 31, 47, 78, 121, 195, 310])
L = np.log(y)

beta = ((t - t.mean())*(L - L.mean())).mean()/t.var()
alpha = np.exp(L.mean() - beta*t.mean())
print(round(alpha, 3), round(beta, 4))       # 11.996  0.4642
print(round(np.log(2)/beta, 3))  # 1.493 : doublement en 1,5 trimestre
print(round(alpha*np.exp(8*beta)))   # 492 : prevision au 9e trimestre

Le modèle s'écrit . Le temps de doublement se lit directement : trimestre.

AttentionUne corrélation forte ne désigne aucune cause

dit que les deux séries varient ensemble. Il ne dit pas que la publicité fait le chiffre d'affaires : les entreprises qui vendent le plus sont aussi celles qui peuvent dépenser le plus. Un troisième facteur — la taille de l'entreprise — explique peut-être les deux.

Le programme demande explicitement un regard critique sur ces méthodes. C'est ici qu'il s'exerce : la statistique descriptive décrit, elle n'explique pas.

13.2 Thème 2 : fonctions de plusieurs variables

13.2.1 Graphe, lignes de niveau, gradient

Python : Les trois représentations d'une même fonction


import numpy as np
import matplotlib.pyplot as plt

f = lambda x, y: x**2 + 2*y**2
X, Y = np.meshgrid(np.linspace(-3, 3, 200), np.linspace(-3, 3, 200))
Z = f(X, Y)

fig = plt.figure()
ax = fig.add_subplot(1, 2, 1, projection="3d")
ax.plot_surface(X, Y, Z)                    # le graphe
ax2 = fig.add_subplot(1, 2, 2)
ax2.contour(X, Y, Z, levels=[2, 6, 12])     # les lignes de niveau
ax2.quiver(2, 1, 4, 4)                      # le gradient en (2, 1)
ax2.set_aspect("equal")
plt.show()

meshgrid fabrique la grille des couples ; contour trace les lignes de niveau demandées ; quiver pose une flèche. Ces trois syntaxes ne sont pas exigibles et vous seraient rappelées.

◆Théorème 13.4Le gradient est orthogonal à la ligne de niveau

Soit de classe sur un ouvert de et un point où . Si est un arc de classe tracé sur la ligne de niveau de passant par , avec , alors

Démonstration

La fonction est constante sur la ligne de niveau. Sa dérivée en est donc nulle ; or la règle de dérivation composée du chapitre 5 donne .

13.2.2 Le graphe et son plan tangent

Le développement limité d'ordre du chapitre 10 dit que, près d'un point critique ,

donc que la position du graphe par rapport à son plan tangent est gouvernée par le signe de la forme quadratique hessienne — c'est-à-dire par les signes des valeurs propres de la matrice hessienne, qui est symétrique réelle et donc diagonalisable en base orthonormée (chapitre 9).

Python : Classer un point critique par les valeurs propres


import numpy as np

def hessienne(x, y):
    # pour f = x^3 - 3xy + y^3
    return np.array([[6*x, -3.0], [-3.0, 6*y]])

for (x, y) in [(0, 0), (1, 1)]:
    vp = np.linalg.eigvalsh(hessienne(x, y))      # matrice symetrique
    print((x, y), vp.round(4), "selle" if vp[0]*vp[-1] < 0
          else ("minimum" if vp[0] > 0 else "maximum"))
# (0, 0) [-3.  3.] selle
# (1, 1) [ 3.  9.] minimum

eigvalsh est réservé aux matrices symétriques et rend les valeurs propres triées. Le test de signe suffit alors : deux signes opposés donnent un point selle, deux valeurs propres strictement positives un minimum local.

ImportantLe programme dit « lien avec les valeurs propres », et rien d'autre

On classe un point critique par les signes des valeurs propres de la hessienne. Aucune autre quantité associée à la matrice n'intervient, et il n'est pas question d'en calculer une pour obtenir ces signes : ici on les lit, sur machine, avec eigvalsh ; à la main, on cherche un polynôme annulateur comme au chapitre 2.

Le point mérite qu'on regarde ce qui s'y passe. La fonction y vaut et son gradient y est nul : le plan tangent est le plan . Deux coupes suffisent alors à conclure.

13.2.3 Extrema sous contrainte linéaire

ImportantUne contrainte linéaire se substitue

Sous une contrainte affine, on exprime une variable en fonction des autres et l'on reporte : le problème contraint à variables devient un problème libre à variables, que l'on traite avec les outils du chapitre 10. C'est la seule méthode au programme, et elle suffit.

Cherchons le minimum de sous la contrainte . En posant , on étudie , dont la dérivée s'annule en . Comme , c'est un minimum, et il vaut , atteint en .

Python : Vérifier le minimum contraint par balayage


import numpy as np

f = lambda x, y: x**2 + 2*y**2
xs = np.linspace(-2, 6, 100001)
vals = f(xs, 3 - xs)                 # la contrainte est deja injectee
i = vals.argmin()
print(round(xs[i], 5), round(3 - xs[i], 5), round(vals[i], 6))
# 2.0  1.0  6.0

Le balayage corrobore le calcul, il ne le remplace pas : il ne visite qu'une grille, et rien ne garantit qu'il n'existe pas ailleurs un point plus bas. Ici c'est le calcul de dérivée, lui, qui le garantit.

13.3 Thème 3 : simulation de lois

13.3.1 La méthode d'inversion

◆Théorème 13.5Méthode d'inversion

Soit la fonction de répartition d'une variable à densité, continue et strictement croissante sur un intervalle où elle prend ses valeurs dans . Si suit la loi uniforme sur , alors admet pour fonction de répartition.

Démonstration

Pour , l'événement est égal à par croissance stricte de et de sa réciproque. Or , donc . La fonction de répartition de est donc .

Pour la loi exponentielle de paramètre , sur , d'où .

Python : Simuler une loi exponentielle sans numpy


import random, math

random.seed(4)
lam, N = 1.5, 200000
ech = [-math.log(1 - random.random())/lam for _ in range(N)]
print(round(sum(ech)/N, 4))                          # 0.6671    1/lam
# 0.7763    1 - e^{-1.5}
print(round(sum(1 for v in ech if v <= 1)/N, 4))

La moyenne empirique approche et la fréquence de approche . Deux contrôles valent mieux qu'un : le premier vérifie l'échelle, le second la forme.

13.3.2 Une loi sans espérance : Cauchy

La loi de Cauchy a pour densité sur , donc pour fonction de répartition et pour réciproque .

ImportantPourquoi elle n'a pas d'espérance

L'intégrale vaut , qui diverge. La condition d'existence de l'espérance n'est pas remplie ; il n'y a donc aucun nombre vers lequel la moyenne empirique aurait à converger. La loi faible des grands nombres ne s'applique pas : son hypothèse manque.

AttentionCe que cette figure prouve, et ce qu'elle ne prouve pas

Elle ne démontre pas que l'espérance n'existe pas — une simulation ne démontre rien. C'est le calcul de l'intégrale divergente qui le démontre.

Ce qu'elle montre, en revanche, est précieux : le symptôme de cette absence, qu'on saura reconnaître ailleurs. Une moyenne empirique qui refuse de se stabiliser quand est déjà grand doit faire soupçonner une loi à queue lourde, et non un manque de tirages.

13.3.3 Trois façons de simuler une loi géométrique

Python : Bernoulli et while, exponentielle et floor, numpy


import numpy as np
import random, math

p, N = 0.3, 200000
rng = np.random.default_rng(5)

def par_bernoulli(p):               # methode 1 : on compte les essais
    k = 1
    while random.random() >= p:
        k += 1
    return k

ech1 = [par_bernoulli(p) for _ in range(N)]
# methode 2
ech2 = np.floor(rng.exponential(1/(-math.log(1 - p)), N)) + 1
ech3 = rng.geometric(p, N)                                 # methode 3

for e in (ech1, ech2, ech3):
    e = np.array(e)
    print(round(float(e.mean()), 3), round(float(np.mean(e == 1)), 3))
#  3.333 et  0.300 pour les trois : moyenne 1/p, et P(X = 1) = p

La méthode 2 s'explique : si suit avec , alors , qui est exactement pour géométrique. C'est la même loi.

ImportantTrois méthodes, trois coûts

La première ne demande qu'une loi de Bernoulli, mais sa durée est aléatoire : en moyenne tours de boucle, et beaucoup plus quand est petit. La deuxième et la troisième rendent un tirage en temps constant. Comparer des méthodes de simulation — pas seulement les écrire — fait partie des compétences attendues.

13.3.4 Une loi normale par le théorème limite central

Si sont indépendantes de loi uniforme sur , chacune a pour espérance et pour variance . La somme vérifie donc et : la variable est centrée réduite, et le théorème limite central du chapitre 11 suggère qu'elle est presque normale.

AttentionLa méthode des douze uniformes ment sur les extrêmes

exactement, alors qu'une vraie normale centrée réduite dépasse avec probabilité . Sur tirages, le plus extrême observé vaut .

Ce n'est négligeable que si l'on s'intéresse au centre. Pour un calcul de risque, qui vit précisément dans la queue, la méthode est fausse — et numpy.random.standard_normal, qui n'a pas cette borne, est alors le bon outil. Choisir sa méthode de simulation en fonction de la question posée est, là encore, une compétence du programme.

13.4 Thème 4 : Monte-Carlo, estimation, intervalles

13.4.1 Le principe et sa garantie

Définition 13.6Méthode de Monte-Carlo

Estimer une quantité en simulant un -échantillon de la loi de et en retenant

c'est appliquer la méthode de Monte-Carlo. Le cas où est la fonction indicatrice d'un événement donne l'estimation d'une probabilité par une fréquence.

◆Théorème 13.7Garantie d'approximation

Si admet une variance , alors est un estimateur sans biais de , il est convergent (loi faible des grands nombres), et le théorème limite central donne, pour grand,

ImportantL'erreur décroît en , et cela coûte cher

Le du dénominateur est la loi d'airain de la méthode : pour diviser l'erreur par , il faut multiplier le nombre de tirages par . Aucune astuce de programmation n'y change rien — seule une réduction de le peut.

C'est aussi ce qui rend la méthode utilisable en grande dimension, là où les méthodes de quadrature s'effondrent : le ne dépend pas du nombre de variables.

Python : Une intégrale, et la taille d'échantillon qu'elle exige


import numpy as np

rng = np.random.default_rng(11)
ech = np.exp(-rng.random(1000000)**2)
m, s = ech.mean(), ech.std(ddof=1)
print(round(m, 6), round(1.96*s/np.sqrt(len(ech)), 6))
# 0.746516  0.000394        (valeur exacte : 0.746824)

sigma = 0.20098
print(int(np.ceil((1.96*sigma/0.005)**2)))     # 6207

La dernière ligne répond à la question utile : pour que la demi-largeur tombe sous — deux décimales sûres — il faut environ tirages. On dimensionne l'échantillon avant de lancer le calcul, à partir de .

Python : Une probabilité qu'on saurait calculer, et une qu'on ne saurait pas


import numpy as np

rng = np.random.default_rng(13)
X = rng.exponential(1, 1000000)
Y = rng.exponential(1, 1000000)
print(round(float(np.mean(X + Y <= 1)), 6))    # 0.264411
print(round(1 - 2/np.e, 6))                 # 0.264241  (calcul exact)

# pas de forme simple a comparer
print(round(float(np.mean(X*Y <= 1)), 6))

La première ligne se vérifie : suit une loi de paramètre (chapitre 8), dont on sait intégrer la densité. La seconde ne se vérifie pas, et c'est justement pour ces cas-là que la méthode existe. Toujours l'essayer d'abord sur un cas où l'on connaît la réponse : c'est ainsi qu'on teste le code.

13.4.2 Comparer deux estimateurs

Soit de loi uniforme sur , inconnu. Deux estimateurs se présentent : , et où .

Exemple 13.8Les deux sont sans biais, l'un est bien meilleur

Comme , on a et .

Pour , l'indépendance donne sur , donc une densité , puis et . On en tire et .

Le rapport des variances vaut : pour , l'estimateur par le maximum est quatre fois moins dispersé.

Python : Comparer par histogrammes


import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(17)
theta, n, N = 3.0, 10, 100000
ech = rng.random((N, n))*theta
T1 = 2*ech.mean(axis=1)
T2 = (n + 1)/n*ech.max(axis=1)

for T, th in ((T1, theta**2/(3*n)), (T2, theta**2/(n*(n + 2)))):
    print(round(T.mean(), 4), round(T.var(), 5), round(th, 5))
# 2.9961  0.29974  0.3      -> T1
# 2.9994  0.07548  0.075    -> T2

plt.hist(T1, bins=60, alpha=0.5)
plt.hist(T2, bins=60, alpha=0.5)
plt.show()

Les deux histogrammes sont centrés au même endroit — les deux estimateurs sont sans biais — mais le second est nettement plus étroit. C'est la variance, et elle seule, qui les départage : le programme exclut explicitement toute comparaison par une quantité qui mélangerait biais et variance.

13.4.3 Comparer des intervalles de confiance

Exemple 13.9L'espérance d'une loi normale d'écart type connu

Soit un -échantillon de loi avec connu. La moyenne suit , ce qui donne un intervalle de confiance exact au niveau :

L'inégalité de Bienaymé-Tchebychev en donne un autre, valable sans hypothèse de loi : .

Pour et , les demi-largeurs valent et .

Python : Demi-largeur moyenne et taux de couverture


import numpy as np

rng = np.random.default_rng(23)
m, sigma, n, N = 20.0, 4.0, 25, 200000
Xb = m + sigma/np.sqrt(n)*rng.standard_normal(N)

d_exact = 1.96*sigma/np.sqrt(n)
d_tcheb = sigma/np.sqrt(0.05*n)
for d in (d_exact, d_tcheb):
    print(round(d, 4), round(float(np.mean(np.abs(Xb - m) <= d)), 4))
# 1.568   0.9495    -> exact : tient sa promesse au plus juste
# 3.5777  1.0000  -> Tchebychev : couvre toujours, 2x plus large

Les deux intervalles sont honnêtes — aucun ne couvre moins de — mais le second est fois plus large. Tenir sa promesse exactement vaut mieux que la dépasser : un intervalle trop large ne dit plus rien.

ImportantÀ partir de quel rang l'intervalle asymptotique est-il honnête ?

Pour une loi de Bernoulli, l'intervalle n'est justifié qu'à la limite. En simulant, on mesure sa couverture réelle :

Le rang à partir duquel l'approximation vaut dépend de : suffit pour , il en faut environ pour . Un intervalle qui annonce et n'en couvre que n'est pas approximatif, il est faux — et seule la simulation le révèle.

Continuer sur Adloun : animation, QCM, fiches, exercices