DecPyMC-08 : Capstone — la sous-série Actuariat

Open In Colab

Navigation : << DecPyMC-7 (MDPs, bandits, POMDPs) | Sous-série Actuariat | Index DecisionTheory

Série : DecPyMC — ce capstone est l’escalier de la série vers sa sous-série Actuariat (T1-T5). À l’image du capstone Lean-37 — Serre 100 pour la série Lean, il ne remplace pas la sous-série : il présente son geste, mesure sa surface, et laisse le lecteur décider s’il entre.

Ce que ce notebook suppose : avoir lu DecPyMC-1 à 7 (utilité espérée, valeur de l’information, décision séquentielle) — ou, au minimum, DecPyMC-2 (prime de risque) et DecPyMC-7 (décision dans le temps). Aucune connaissance actuarielle préalable : les trois primes y sont distinguées avant d’être calculées.

1. Le geste : la décision bayésienne devient un métier

Les notebooks 1 à 7 ont traité la décision en général : une utilité, une information, une séquence. L’assurance est le métier où ces abstractions deviennent des nombres encadrés par la loi et le marché :

  • tarifer — quel prix demander pour couvrir un risque ? (prime pure, chargement, prime commerciale)
  • mutualiser — comment pondérer l’expérience d’un contrat contre celle du portefeuille ? (hiérarchie, crédibilité)
  • capitaliser — quelle réserve pour ne pas faire faillite ? (processus de ruine)
  • souscrire — un test supplémentaire vaut-il son prix ? (valeur de l’information)

Chaque question a son carnet dans la sous-série. L’ancienne « jambe actuarielle » de la série (notebooks 8 à 12) est descendue dans le dossier Actuariat/ et régraduée T1-T5 : le numéro dit désormais la place dans l’arc de métier, pas l’ordre d’écriture.

2. L’escalier T1-T5 — où entrer

# Carnet Durée Ce qu’il apporte
T1 Actuariat-01 — Prime pure, chargement 45 min Du risque à la prime : trois régimes (fréquentiste, bayésien, commercial)
T2 Actuariat-02 — Fréquence × sévérité ~50 min La structure hiérarchique qui rend le tarif estimable (partial pooling)
T3 Actuariat-03 — Crédibilité 20-30 min Bühlmann–Straub : pondérer l’individuel contre le collectif
T4 Actuariat-04 — Ruine 45 min Cramér–Lundberg : la prime comme garde-fou contre la faillite
T5 Actuariat-05 — Valeur d’info en souscription ~45 min Quand un test de risque vaut son prix (EVPI/EVSI au métier)

Pourquoi cet ordre : on apprend à tarifer (T1), puis à modéliser le portefeuille (T2), puis à mélanger individu et collectif (T3, consommé par T2), puis à capitaliser (T4, qui juge le tarif), puis à décider d’acheter de l’information (T5, qui boucle sur DecPyMC-5). L’ordre d’origine plaçait la crédibilité avant la structure qui la motive ; la régraduation corrige l’arc (EPIC #12904).

# Mesurer la surface de la sous-série : on lit les carnets, on ne suppose rien.
import json
from pathlib import Path

CANDIDATS = [Path("Actuariat"), Path("../Actuariat")]
RACINE = next((p for p in CANDIDATS if p.is_dir()), None)
assert RACINE is not None, "dossier Actuariat introuvable depuis ce carnet"

def decrire(chemin):
    nb = json.loads(chemin.read_text(encoding="utf-8"))
    c0 = "".join(x if isinstance(x, str) else x[0] for x in nb["cells"][0]["source"])
    titre = c0.splitlines()[0].lstrip("# ").strip()
    n_code = sum(1 for c in nb["cells"] if c["cell_type"] == "code")
    n_md = sum(1 for c in nb["cells"] if c["cell_type"] == "markdown")
    kernel = nb.get("metadata", {}).get("kernelspec", {}).get("name", "?")
    source = "\n".join(
        "".join(x if isinstance(x, str) else x[0] for x in c["source"])
        for c in nb["cells"] if c["cell_type"] == "code"
    )
    return titre, n_md, n_code, kernel, source
def imports_libs(source):
    """Detecte les imports de pymc et arviz dans une source agregee, via AST.
    Gere les imports groupes (ex. 'import numpy as np, pymc as pm, arviz as az')
    et les formes 'from pymc import ...'. Renvoie (imports_pymc, imports_arviz).
    """
    import ast
    pymc = False
    arviz = False
    try:
        tree = ast.parse(source)
    except SyntaxError:
        return pymc, arviz
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                top = alias.name.split('.')[0]
                if top == 'pymc':
                    pymc = True
                elif top == 'arviz':
                    arviz = True
        elif isinstance(node, ast.ImportFrom):
            if node.module is None:
                continue
            top = node.module.split('.')[0]
            if top == 'pymc':
                pymc = True
            elif top == 'arviz':
                arviz = True
    return pymc, arviz


carnets = sorted(RACINE.glob("Actuariat-*.ipynb"))
print(f"Sous-série Actuariat : {len(carnets)} carnets dans {RACINE}/")
print()
print("| # | Titre | Cellules (md/code) | Kernel | PyMC | ArviZ |")
print("|---|-------|--------------------|--------|------|-------|")
for chemin in carnets:
    numero = int(chemin.stem.split("-")[1])
    titre, n_md, n_code, kernel, source = decrire(chemin)
    imports_pymc, imports_arviz = imports_libs(source)
    print(f"| T{numero} | {titre[:52]} | {n_md}/{n_code} | `{kernel}` | "
          f"{'oui' if imports_pymc else 'non'} | {'oui' if imports_arviz else 'non'} |")

# L'escalier doit etre continu : 01..05 sans trou
numeros = [p.stem.split("-")[1] for p in carnets]
assert numeros == ["01", "02", "03", "04", "05"], f"escalier discontinu : {numeros}"
tot_md = sum(decrire(p)[1] for p in carnets)
tot_code = sum(decrire(p)[2] for p in carnets)
print()
print(f"Escalier T1-T5 continu. Surface totale : {tot_md} cellules markdown, {tot_code} cellules code.")
Sous-série Actuariat : 5 carnets dans ..\Actuariat/

| # | Titre | Cellules (md/code) | Kernel | PyMC | ArviZ |
|---|-------|--------------------|--------|------|-------|
| T1 | Actuariat-01 : Du risque à la prime — prime pure, ch | 22/15 | `python3` | oui | oui |
| T2 | Actuariat-02 — Fréquence × sévérité hiérarchique : l | 20/14 | `python3` | oui | oui |
| T3 | Actuariat-03 — Crédibilité actuarielle de Bühlmann–S | 19/8 | `python3` | oui | oui |
| T4 | Actuariat-04 : Ruine et capital — le processus de Cr | 21/12 | `python3` | oui | oui |
| T5 | Actuariat-05 — Valeur de l'Information en Souscripti | 24/13 | `python3` | oui | oui |

Escalier T1-T5 continu. Surface totale : 106 cellules markdown, 62 cellules code.

Lecture de la mesure

Cinq carnets, un escalier continu, la même stack que la série mère (python3, PyMC, ArviZ) : entrer dans la sous-série ne demande aucun outillage nouveau — seulement le socle théorique de DecPyMC-1 à 7. La surface (~3 h 30 – 4 h de lecture déclarée) est celle d’un arc de métier, pas d’un corpus : chaque carnet tient en une séance.

3. La charnière décisionnelle : du posterior à la prime

Pour goûter le geste de la sous-série sans y entrer, un seul calcul suffit — celui que T1 déroule en entier. Un contrat d’assurance est une loterie : une fréquence de sinistres (Poisson) et une sévérité (montant). La théorie de la décision dit quel prix rendre acceptable ; l’actuariat dit quel prix demander pour que le portefeuille survive :

\[\text{prime commerciale} = \underbrace{\mathbb{E}[S]}_{\text{prime pure}} + \underbrace{\theta\,\mathbb{E}[S]}_{\text{chargement proportionnel}} + \underbrace{\alpha\sqrt{\mathrm{Var}(S)}}_{\text{chargement de variance}}\]

La prime pure est l’espérance mathématique du coût — c’est un nombre fréquentiste si les paramètres sont connus, une distribution si on les infère (c’est tout le geste bayésien de Actuariat-01). Le chargement transforme l’espérance en prix de marché ; le terme de variance rémunère la variabilité du coût. En trois nombres, la décision « couvrir ou non, à quel prix » est devenue un calcul.

# Exemple guide : la charniere decisionnelle, en trois nombres.
# Risque discretise : frequence de Poisson lambda, severite discrete — calcul ferme, pas de sampling.
import math

lam = 0.3                                  # frequence moyenne de sinistre par contrat et par an
severites = [200, 500, 1000, 2500, 6000]   # montants possibles d'un sinistre
probas =    [0.50, 0.25, 0.15, 0.08, 0.02] # loi de severite (somme = 1)

E_X  = sum(s * p for s, p in zip(severites, probas))    # severite moyenne
E_X2 = sum(s * s * p for s, p in zip(severites, probas))

E_S   = lam * E_X            # prime pure : E[S] = lambda * E[X] (modele collectif)
Var_S = lam * E_X2           # Var[S] = lambda * E[X^2] (variance composee)

theta = 0.15                 # chargement proportionnel : frais commerciaux + marge
alpha = 0.10                 # coefficient du chargement de securite (variance)

chargement_prop = theta * E_S
chargement_var  = alpha * math.sqrt(Var_S)
prime_commerciale = E_S + chargement_prop + chargement_var

print(f"Prime pure            E[S]        = {E_S:9.2f}")
print(f"Chargement prop.      0.15 x E[S] = {chargement_prop:9.2f}")
print(f"Chargement de var.  0.10.sqrt(Var) = {chargement_var:9.2f}")
print(f"---------------------------------------------")
print(f"Prime commerciale                  = {prime_commerciale:9.2f}")
print()
print(f"Ecart-type de S : {math.sqrt(Var_S):.2f}  ({math.sqrt(Var_S)/E_S:.0%} de la prime pure)")
print(f"La prime commerciale couvre E[S] a hauteur de {prime_commerciale / E_S:.1%}")
Prime pure            E[S]        =    208.50
Chargement prop.      0.15 x E[S] =     31.27
Chargement de var.  0.10.sqrt(Var) =     66.01
---------------------------------------------
Prime commerciale                  =    305.79

Ecart-type de S : 660.11  (317% de la prime pure)
La prime commerciale couvre E[S] a hauteur de 146.7%

Lecture du résultat

La prime pure est le coût espéré ; la prime commerciale est le prix. L’écart entre les deux — ici ~47 % — n’est pas un profit arbitraire : c’est ce qui paie les frais et rémunère la variabilité du coût ; le terme de variance, dans le modèle collectif de Poisson sous-jacent, réduit la probabilité de ruine conditionnelle au capital et aux paramètres du modèle, sans l’annuler (l’exercice 2 de T4 Lundberg en exhibe une probabilité résiduelle strictement positive sous ce chargement). Détail instructif : le terme de variance domine le chargement proportionnel (66 contre 31) — signature d’un contrat à fréquence faible, où une seule grosse sévérité fait basculer l’année. Ce calcul est volontairement fréquentiste (paramètres connus) : tout l’apport de Actuariat-01 est de refaire ce même calcul avec un posterior PyMC sur les paramètres — la prime pure devient alors une distribution, et le chargement une décision sur ses quantiles.


Exercice 1 : la réassurance stop-loss — combien céder à la tranche M ?

Contexte : l’assureur peut céder à un réassureur ce qui dépasse une tranche M — il ne retient que min(S, M). Le réassureur facture la charge cédée E[(S − M)+] (plus son propre chargement).

Objectifs : 1. Simuler 20000 années de sinistres pour le contrat ci-dessus — tirer d’abord N ~ Poisson(0.3), puis N sévérités selon la loi discrète (numpy, default_rng(42)) 2. Pour chaque tranche M ∈ {500, 1000, 2000}, estimer E[(S − M)+] et le taux de cession E[(S − M)+] / E[S]

Indice : np.add.outer ou une simple boucle sur M suffisent — la simulation ne coûte rien. Étape 1 : construire le vecteur des S simulés. Étape 2 : appliquer np.maximum(S - M, 0).mean() par tranche.

# Exercice 1 : reassurance stop-loss
# Etape 1 : simuler 20000 annees (frequence Poisson puis severites, seed 42)
# Etape 2 : pour M dans [500, 1000, 2000], calculer E[(S - M)+] et le taux de cession
import numpy as np

rng = np.random.default_rng(42)
# ... simulation ...
resultat = None  # TODO etudiant : liste des (M, E[(S - M)+], taux de cession)
print("Exercice a completer : reinsurance stop-loss")
Exercice a completer : reinsurance stop-loss

Exercice 2 : la probabilité de ruine sur un an

Contexte : l’assureur part avec un capital initial u et encaisse la prime commerciale calculée plus haut. Il fait ruine si S > u + prime_commerciale. C’est la version un an, discrète, du processus de T4.

Objectifs : 1. Réutiliser la simulation de l’exercice 1 (même seed) 2. Pour u ∈ {0, 1000, 2000}, estimer P(S > u + prime_commerciale) 3. Vérifier l’ordre de grandeur : la prime couvre ~147 % de E[S] — la ruine doit être rare mais pas impossible

Indice : (S_sim > u + prime_commerciale).mean() ; comparer les trois probabilités entre elles dit comment le capital réduit le risque de queue.

# Exercice 2 : probabilite de ruine sur un an
# Etape 1 : reutiliser S_sim (meme seed que l'exercice 1)
# Etape 2 : pour u dans [0, 1000, 2000], estimer P(S > u + prime_commerciale)
proba_ruine = None  # TODO etudiant : liste des (u, P(ruine))
print("Exercice a completer : probabilite de ruine")
Exercice a completer : probabilite de ruine

Exercice 3 : régénérer l’escalier depuis les carnets

Contexte : la table T1-T5 de la section 2 est écrite à la main. Une documentation qui se régénère depuis l’artefact ne peut pas mentir sur l’artefact — même geste que la mesure de la section 1.

Objectifs : 1. Relire les carnets de Actuariat/ et extraire le titre (première ligne de la cellule 0) de chacun 2. Produire les lignes markdown | T1 | <titre> | … | T5 | <titre> | dans l’ordre des numéros

Indice : la fonction decrire de la section 1 fait déjà la moitié du travail. Étape 1 : collecter (numéro, titre). Étape 2 : trier et formater.

# Exercice 3 : regenerer la table T1-T5 depuis les titres des carnets
# Etape 1 : relire les carnets de RACINE, extraire le titre de chaque cellule 0
# Etape 2 : produire les lignes "| T1 | <titre> |" dans l'ordre 01..05
lignes = None  # TODO etudiant : liste des lignes markdown
print("Exercice a completer : table des matieres de la sous-serie")
Exercice a completer : table des matieres de la sous-serie

4. Conclusion — ce que ce capstone laisse au lecteur

Trois choses, dans cet ordre :

  1. Un point d’entrée : la sous-série Actuariat est un arc de métier en cinq carnets (T1 tarifer, T2 modéliser, T3 mutualiser, T4 capitaliser, T5 souscrire), ~3 h 30 – 4 h, même stack que la série mère.
  2. Un geste goûté : la charnière décisionnelle — espérance, chargement, prix — calculée ici en fréquentiste, déroulée en bayésien dans Actuariat-01.
  3. Une mesure honnête : la surface de la sous-série a été mesurée depuis les carnets eux-mêmes, pas déclarée — la documentation y est régénérable (exercice 3), pas seulement lisible.

La série DecPyMC, elle, s’arrête ici : le slot 08 était la dernière marche, et cette marche ouvre sur un autre escalier.


References

Actuariat

  • Bühlmann, H. & Gisler, A. A Course in Credibility Theory and its Applications. Springer. (crédibilité Bühlmann–Straub, T3)
  • Kaas, R., Goovaerts, M., Dhaene, J. & Denuit, M. Modern Actuarial Risk Theory. Springer. (modèle collectif, prime pure, ruine)
  • Asmussen, S. & Albrecher, H. Ruin Probabilities. World Scientific. (processus de Cramér–Lundberg, T4)

Théorie de la décision (série mère)

  • Von Neumann, J. & Morgenstern, O. Theory of Games and Economic Behavior. Princeton University Press. (utilité espérée, DecPyMC-1)
  • Howard, R. A. Information Value Theory. IEEE Trans. SSC, 1966. (EVSI, DecPyMC-5 et T5)
Retour au sommet