Cours Langage R en Actuariat
Cours Langage R en Actuariat
Cours Langage R en Actuariat
Nicolas Baradel
3 mai 2023
2 Manipulation de données 13
2.1 La data.frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Le paquet data.table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 Les recherches REGEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.1 Recherche par motif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3.2 Capture d’un sous-motif . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3 R en finance 23
3.1 Simulation de processus stochastique . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2 Évaluation d’actif simple par Monte Carlo . . . . . . . . . . . . . . . . . . . . . 29
3.2.1 Approche générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.2 Introduction à la réduction de variance . . . . . . . . . . . . . . . . . . . 30
4 R en assurance dommage 32
4.1 Tarification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.1.1 Modèle linéaire avec R . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.1.2 Modèle linéaire généralisé avec R . . . . . . . . . . . . . . . . . . . . . . 35
4.2 Provisionnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2.1 Triangles de liquation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2.2 Chain Ladder - Mack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5 R en assurance vie 39
5.1 Rentes viagères et capital décès . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.2 Estimation de tables de mortalité . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2.1 Taux bruts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2.2 Lissage Whittaker-Henderson . . . . . . . . . . . . . . . . . . . . . . . . 43
1
6 Utiliser du code compilé C pour accélérer R 45
6.1 Initiation avec .C() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.2 Manipulation d’objet de R avec .Call() . . . . . . . . . . . . . . . . . . . . . . 49
Introduction
Le cours requiert une première initiation à R. Pour une introduction au langage, on pourra
se référer à [2], ou à [5] pour aller plus en profondeur. Les structures conditionnelles, de boucle
ou de fonction sont considérées comme connues. Nous revoyons les structures de conteneur de
R, comme les vecteurs, tableaux de données, qui sont approfondis afin d’apprendre à écrire un
code R aussi concis qu’efficace.
[1] 0 0 0
On peut accéder à un élément en utilisant [] :
x[1]
[1] 0
[1] 1 2 3 4 5
[1] 3
[1] 1 3 7
2
[1] 1 3 7 1 3 7
[1] 3.141593
length (x)
[1] 1
La fonction rep permet de créer un vecteur de taille n dont chaque composante est identique.
rep (5, 3)
[1] 5 5 5
La fonction seq (pour sequence) permet de créer une suite de nombre régulière.
seq (1, 2, 0.2)
Il est possible de donner un vecteur de booléens qui donne les indices à garder.
x ← c(1, 3, 5)
x[c(FALSE , TRUE , TRUE)]
[1] 3 5
[1] 1 5 5 3
Une autre possibilité est d’appeler tous les éléments sauf un. La syntaxe est x[−a] où a est un
indice entier.
x[−2]
[1] 1 5
[1] 1 4 7
3
x∗y
[1] 0 3 10
x^y
[1] 1 3 25
x == y
[1] 3 7 11
x^2
[1] 1 9 25
factorial (x) %% 2
[1] 1 0 0
x <= 2
Exercices
Les exercices sont à faire sans boucle.
• Ecrire une fonction f qui prend n en argument et qui renvoie un vecteur composé des
n + 1 premiers carrés de N. [
f :N→ Nn
n∈N
n 7→ (0 , 12 , . . . , n2 )
2
• Ecrire une fonction deriv1 qui prend un vecteur x, un pas h, et qui renvoie un vecteur
de taille length(x)− 1 l’approximation du nombre dérivé :
xi+1 − xi
∂[h] xi := .
h
Correction
f ← function (n)
•
return ((0:n)^2)
4
1.1.2 Fonctions vectorielles et programmation efficace
On dira qu’une fonction f est vectorielle si f prend un ou des vecteurs en argument (et
éventuellement d’autres arguments de taille 1) et renvoie un vecteur où une fonction a été
appliquée élément par élément. C’est le cas par exemple de la fonction sqrt ou exp.
Soit la boucle de la forme :
for(i in I)
x[i] ← f(i,x[i],z[i])
Si f est vectorielle alors cette boucle est toujours évitable, la solution est :
x ← f(1: length (x),x,z)
Il se peut que nous souhaitions modifier x uniquement sur une partie de ses indices. Par exemple,
quelque chose de la forme
for(i in I)
if(h(i,x[i],z[i]))
x[i] ← f(i,x[i],z[i])
où h est une fonction vectorielle qui renvoie TRUE ou FALSE, f n’est appliquée que sur un
sous-ensemble de I où h est vérifiée.
Si f et h sont vectorielles alors cette boucle est toujours évitable, la solution est :
ind ← h(1: length (x),x,z)
x[ind] ← f((1: length (x))[ind], x[ind], z[ind ])
Il ne faut pas être effrayé par le fait d’écrire x dans x. Ce qui est à l’intérieur n’est que le calcul
d’un vecteur de logical en fonction de x (s’il vaut NA ou non). Ensuite, nous effectuons une
opération dans les indices de x vérifiant cette condition. Par exemple
(x ← c(7, 2, NA , 3, −1, NA))
[1] 7 2 NA 3 -1 NA
x[ is.na(x)] ← 0
x
[1] 7 2 0 3 -1 0
5
ind ← x + y > z
x[ind] ← z[ind ]%%2
x[! ind] ← y[! ind ]%%2
Rappel : le point d’exclamation est le NON logique, il inverse les TRUE et FALSE. Nous affectons,
en fonction de x + y > z, à chaque élément, soit z%%2, y%%2.
Un dernier exemple intervient dans la classification d’une variable. Par exemple, si x est inférieur
à un seuil a fixé, nous le mettons dans la classe 0, s’il est supérieur à b, nous le mettons dans
la classe 2, et enfin s’il est entre les deux, dans la classe 1.
y ← rep (1, length (x))
y[x < a] ← 0
y[x > b] ← 2
Pour les conditions, les opérateurs && et || ne sont pas vectoriels. Les versions vectorielles
correspondantes sont & et |.
Pour tester si une condition est vérifiée sur tous les éléments d’un vecteur, on utilisera la
fonction all.
Pour tester si une condition est vérifiée sur au moins un élément d’un vecteur, on utilisera la
fonction any.
Exercices
Les exercices sont à faire sans boucle.
• Écrire une fonction gammaEuler qui approxime à l’ordre n ∈ N∗ la constante γ d’Euler
définie par la limite : !
n
X 1
γ = lim − log(n) .
n→+∞
k=1
k
On pourra vérifier avec −digamma(1) qui vaut γ.
• Soit X une variable aléatoire dont la densité est définie par la fonction f :
tan2 (x)
1
∀x ∈ R, f (x) = √ exp − 1]− π2 , π2 [ (x)
2π(1 + x2 ) 2
où 1 est la fonction indicatrice (elle vaut 1 si x est dans l’intervalle, 0 sinon). Écrire cette
fois-ci la fonction f de manière vectorielle sous R afin qu’elle puisse prendre un vecteur
x et renvoie un vecteur de même taille f (x) où f est appliquée élément par élément.
• Estimons π par méthode de Monte-Carlo. Pour ce faire, prenons le carré unité [−1; 1]2 et
le disque de rayon 1 et de centre 0 de ce carré. L’aire du carré est 4, l’aire du disque est
π. Si on tire uniformément dans le carré (ce qui revient à tirer deux lois uniformes dans
[-1,1], une étant l’axe des abscisses, l’autre l’axe des ordonnées), la probabilité d’être dans
le disque est π4 . Écrire une fonction qui prend en argument le nombre de simulations n et
qui renvoie une estimation π.
6
Correction
gammaEuler ← function (n)
•
return ( sum (1/(1: n)) − log(n) )
f ← function (x)
{
y ← numeric ( length (x))
ind ← x > −pi/2 & x < pi/2
•
z ← x[ind]
y[ind] ← exp(−0.5 ∗ tan(z)^2) /( sqrt (2∗ pi) ∗(1+ z^2))
return (y)
}
X[8]
[1] 8
Il est possible de remplir une matrice par ligne avec l’argument byrow = TRUE.
La fonction as.matrix permet de convertir un vecteur de taille n en une matrice à n lignes et
une colonne.
La fonction t permet d’obtenir la matrice tranposée.
Le produit matriciel se fait avec %∗% et non pas avec ∗, ce dernier est le produit des deux
matrices élément par élément.
Il est possible de construire une matrice diagonale avec diag :
7
diag (1:3)
La fonction cbind permet de combiner des vecteurs en une matrice en les plaçant par colonne :
cbind (rep (1 ,3) , rep (2 ,3) , rep (3 ,3))
On peut extraire une ligne ou une colonne sous la forme d’un vecteur en ne spécifiant que
l’indice la ligne ou de la colonne
X[1, ]
C1 C2 C3
1 4 7
X[, 1]
R1 R2 R3
1 2 3
C1 C2 C3
1 4 7
Il est possible de forcer la conservation du type matrix avec l’argument drop = FALSE
c( is.matrix (X[1, ]), is.matrix (X[1, , drop= FALSE ]))
[1] 1 3
8
[1] 9
On peut obtenir le nombre de lignes grâce à la fonction nrow et le nombre de colonnes grâce à
la fonction ncol
c(nrow(X), ncol(X))
[1] 3 3
Le langage vectoriel fonctionne également avec les matrices. On peut appeler X[a, b] où a
est un vecteur de TRUE et FALSE qui sélectionne les lignes, et b de même qui sélectionne les
colonnes. Enfin, il est possible d’appeler X[A] où A est une matrice de booléen qui sélectionne
les indices à conserver de la matrice.
Exercices
• On suppose avoir la matrice suivante :
X ← cbind (c(1 ,2 ,1 ,3 ,2) , c(121 , 256, 842, 510 , 82) , c(1, 2, 3,
4, 5), c(5, 11, 2, 7, 3))
Trier la matrice par ordre croissant selon la première colonne et, en cas d’égalité, selon la
deuxième colonne (toujours par ordre croissant). On utilisera la fonction order, celle-ci
renvoie les indices d’un vecteur de telle sorte que x[order(x)] renvoie sort(x).
• Écrire une fonction qui prend une matrice X = (xi,j ) de taille n × m en argument et
qui renvoie un vecteur de taille m où l’élément j est la moyenne sur i de (cos(xi,j )i )1≤i≤n
(attention, bien voir que le cosinus est élevé à la puissance i). On pourra utiliser la fonction
row qui renvoie une matrice de même dimension que celle donnée en argument, où chaque
indice est le numéro de ligne : (row(x))i,j = i, et la fonction col qui fait de même mais
avec les colonnes : (col(x))i,j = j.
• Écrire une fonction qui prend une matrice carrée X = (xi,j ) de taille n × n en argument
et renvoie la trace de la matrice X, sans utiliser la fonction diag. La trace de la matrice
X est la somme des éléments diagonaux définie par
n
X
Trace(X) = xk,k .
k=1
Correction
X ← cbind (c(1, 2, 1, 3, 1), c(121 , 256, 842 , 510, 82) , 1:5,
• c(5, 11, 2, 7, 3))
X ← X[order (X[, 1], X[, 2]) , ]
f ← function (X)
•
return ( colMeans (cos(X)^row(X)) )
9
1.2 Les listes
La liste est un regroupement d’objets arbitraires. L’exemple suivant illustre la création
d’une liste
li ← list(a = TRUE , b = 1:3)
$a
[1] TRUE
$b
[1] 1 2 3
[1] TRUE
li [["a"]]
[1] TRUE
[1] "s"
Il est possible de récupérer une sous liste en plaçant le vecteur des noms de la sous liste entre
[].
li[c("b", "c")]
$b
[1] 1 2 3
$c
[1] "s"
10
En informatique, les calculs (et le stockage) se font en base 2, ce qui se traduit par la représen-
tation :
x = s × m × 2e , s = ±1, 1 ≤ m < 2, e ∈ Z.
Les 64 bits de stockages se répartissent en :
• 1 bit représente le signe (+ ou -) ;
• 11 bits pour l’exposant ;
• 53 bits pour la mantisse.
On peut en déduire que
• La partie en exposant donnera le nombre le plus grand qui sera tout au plus 22
11−1
=
21024 ≈ 10308 , (ce nombre représente l’infini),
• Tandis que, sans entrer dans les détails, le nombre strictement positif minimum est
2−1074 ≈ 10−324 .
• La précision donnée par la mantisse sera tout au plus de 1/253 ≈ 1.110223 × 10−16 pour
la mantisse (à multiplier par la partie puissance).
En conséquence nous pouvons observer que le calcul n’est pas toujours associatif, et que l’ad-
dition de deux nombres, dont l’un est plus petit que l’autre d’un facteur d’environ 10−16 peut
en supprimer l’information, et rendre le calcul non associatif.
−1 + (1 + 1e−16)
[1] 0
(−1 + 1) + 1e−16
[1] 1e-16
Pour les dépassements de capacité (nombre trop petit ramené à 0, ou trop grand et ramené à
l’infini) en utilisant les bornes données, on observe :
c(2^(−1074), 2^(−1075))
En conséquence, des choses simples comme la composition de fonctions inverses peuvent ne plus
fonctionner, tant bien même que le résultat final est d’un ordre de grandeur habituel.
log(exp (1000) )
[1] Inf
Ici, il est facile de simplifier à l’avance le calcul, mais dans certaines situations où le résultat est
d’un ordre de grandeur habituel, mais s’exprime comme log(A) où A est une valeur de calcul
intermédiaire très grande, dépassant la capacité, résoudre ce problème paraît moins évident.
Comme par exemple pour le calcul de log(n!) dès n = 200.
log( factorial (200))
11
[1] Inf
Pour ces situations, R a prévu des fonctions qui utilisent un aglorithme spécifique et qui
permettent d’éviter un dépassement de capacité lors des calculs intermédiaires. Il s’agit ici de
la fonction lfactorial.
lfactorial (200)
[1] 863.232
Exercices
• Construire une fonction qui puisse évaluer
1
1− ,
1+x
puis
1
1−x− ,
1+x
en particulier, aux points x = 10−10 et x = 10−20 .
• Construire une fonction qui puisse évaluer
Γ(α)
,
Γ(β)
√
en particulier, au point (α = 200 + π, β = 200).
• Construire une fonction qui évalue le logarithme de la densité de la loi de student, (sans
utiliser la fonction dt). La densité de la loi de student de paramètre n est définie par :
f : R × R+ → R∗+
− n+1
1 Γ( n+1 x2
2
) 2
(x, n) 7→ √ n 1+ .
nπ Γ( 2 ) n
12
Correction
f ← function (x)
return (x/(1+x))
•
f(c(10^(−10), 10^(−20)))
[1] 1e-10 1e-20
f ← function (x)
return(−x ^2/(1+ x))
f(c(10^(−10), 10^(−20)))
[1] -1e-20 -1e-40
f ← function (x, n)
return ( −0.5 ∗ log(n∗ pi) + lgamma ((n+1) /2) − lgamma (n/2)
• −0.5 ∗(n+1) ∗ log1p (x^2/n) )
f(1000 , 1000)
[1] -3458.751
2 Manipulation de données
L’objet R pour la manipulation de données est la data.frame.
Le paquet data.table permet de manipuler des data.frame avec des outils supplémen-
taires. Il offre également de meilleures performances lorsque les données sont volumineuses. Il
est compatible avec les data.frame.
Le paquet dplyr (de la famille tidyverse) offre une alternative complètement différente en
terme de manipulation de données. Toutefois, dplyr n’est pas compatible avec les data.frame,
est moins performant que data.table, et sa syntaxe d’usage diffère complètement de celle de
R.
Plus précisément, data.frame a une approche qui s’appuie sur la structure vectorielle de
R, puis data.table est une extension naturelle. Tandis que dplyr s’inscrit en rupture, avec
une syntaxe complètement différente. Enfin, en terme de peformance, le paquet data.table
domine totalement dplyr, en ayant des temps d’exécution généralement deux fois plus rapide
pour l’ensemble des opérations.
2.1 La data.frame
La data.frame est une liste qui regroupe des vecteurs de même taille. Cette liste se présente
comme un tableau à deux dimensions, où chaque colonne est un élément de la liste : c’est un
13
vecteur. En conséquence, chaque colonne a un type, mais ce type peut varier d’une colonne à
une autre. En résumé, une data.frame est un regroupement de vecteurs de même taille sous
forme de liste, où chaque colonne est un vecteur qui a son propre type.
Cette particularité fait que la data.frame partage la syntaxe des matrices et des listes, mais est
bien stockée comme une liste.
is.list ( data.frame ())
[1] TRUE
L’objet peut également être vu comme un tableau à deux dimensions pour utiliser la syntaxe
matricielle. On préférera l’appel sous forme de liste, quand c’est possible.
Prenons la data.frame iris présente dans R. Pour appeler une colonne, par exemple Sepal.Length
, on utilise la syntaxe des listes :
iris$Sepal.Length
Si le nom de la colonne est une chaîne de caractère stockée dans une variable, nous passerons
par :
nomcol ← " Sepal.Length "
iris [[ nomcol ]]
Sepal.Length Sepal.Width
...
À noter que iris["Sepal.Length"] renvoie une data.frame d’une colonne tandis que iris
[["Sepal.Length"]] et iris$Sepal.Length renvoient un vecteur.
La notation matricielle équivalente est iris[, "Sepal.Length"] (renvoie un vecteur, sauf si
on ajoute drop = FALSE) et iris[, c("Sepal.Length", "Sepal.Width")].
Pour sélectionner (filtrer) les lignes, on utilisera la notation matricielle : on placera en premier
indice le vecteur des lignes à sélectionner (un vecteur d’entier, ou un vecteur de booléens du
nême nombre de lignes que la data.frame où les TRUE sont positionnés dans les lignes à
conserver).
iris[ iris$Species == " versicolor ", ]
14
iris[ iris$Species == " versicolor ", c(" Petal.Length "," Sepal.Width ")]
Comme pour les vecteurs, il est possible de combiner avec & (et logique vectoriel) et | (ou
logique vectoriel) les conditions.
iris[ iris$Species == " versicolor " | iris$Species == " virginica ", c(
" Petal.Length ", " Sepal.Width ")]
Cependant, la sélection de colonnes avec tiris["Sepal.Length"] (version des listes qui pro-
duit une sous data.frame) ne fonctionne plus avec les data.table, il faut utiliser la version
matricielle en rajoutant une virgule : tiris[, "Sepal.Length"].
Lors de la sélection de colonnes avec la notation matricielle, data.table n’évalue pas les
variables, mais prend la chaîne de caractères.
15
tiris [, Sepal.Length ]
Il faut voir cela comme utiliser tiris$Sepal.Length. Cela est compatible avec plusieurs co-
lonnes
tiris [, c( Sepal.Length , Sepal.Width )]
A noter que lors d’un appel de la forme x[, a], a n’est jamais évalué : on recherche la colonne
a. Pour évaluer a, on passe par la syntaxe de liste tiris[[a]].
Cependant cela pourrait être problématique : comment sélectionner plusieurs colonnes avec une
variable ? Il est possible de forcer l’évaluation en variable avec l’argument with = FALSE, si a
est une variable représentant une ou plusieurs colonnes :
tiris [, a, with= FALSE ]
Trier une data.frame se fait avec order. Pour trier par Sepal.Length puis par Species,
iris[order ( iris$Species , iris$Sepal.Length ), ]
Cela fonctionne avec data.table car order renvoie les numéros de lignes. Avec data.table il
est possible d’écrire
tiris [ order (Species , Sepal.Length )]
Il est parfois utile de passer d’un format à l’autre avec as.data.frame et as.data.table. Ces
fonctions passent par une copie. Pour modifier directement l’objet de manière très efficace nous
avons les fonctions setDF (set data.frame) et setDT (set data.table).
setDF ( tiris)
setDT ( tiris)
Les data.frame ont la fonction read.csv pour importer des données. Le paquet data.table
a la fonction fread. Cette fonction a les mêmes arguments que read.csv, mais pas les mêmes
valeurs par défaut pour tous. Elle est plus rapide.
16
2.3.1 Recherche par motif
Pour les recherches Regex, nous utilisons stri_extract_all_regex du paquet stringi
qu’on n’oublie pas de charger. Son premier argument str est le texte qui est l’objet de la
recherche tandis que pattern est le motif recherché dans str.
Par facilité d’usage, on définit la regex par :
regex ← function (texte , motif , ...)
return ( stri_extract_all_regex (texte , motif , ...)[[1]] )
L’argument pattern (le motif) de stri_extract_all_regex peut être un vecteur. Chaque
élément de ce vecteur est recherché, et la fonction retourne une liste où chaque élément de
la liste représente une recherche regex du vecteur de motifs. Dans la suite, nous n’utiliserons
qu’un motif de taille 1, c’est pourquoi on met [[1]] dans la fonction regex pour récupérer
directement le vecteur.
Remarque. Il est possible de passer directement par les fonctions de R sans le paquet stringi
. La fonction qui permet de trouver des expressions régulières est gregexpr(motif, texte).
L’argument motif est le motif recherché dans texte. La fonction renvoie les positions trouvées,
pour obtenir les chaînes de caractère, il faut utiliser regmatches(texte, matches).
L’appel regmatches(texte, gregexpr(motif, texte)) permet de récupérer le résultat d’une
recherche Regex.
Nous pourrions définir la fonction regex par :
regex ← function (motif , texte)
return ( regmatches (texte , gregexpr (motif , texte))[[1]] )
Une expression régulière est une chaîne de caractères qui décrit le motif recherché. Elle est
composée de caractères simples et de metacaractères qui ont une fonction spéciale.
Par exemple le caractère | signigie ou.
texte ← "Un oiseau , deux oies , une autre oie et une pintade. "
regex (texte , " oiseau |oie")
[1] "oiseau" "oie" "oie"
Un caractère suivi d’un signe ? est optionnel. Le ? ne s’applique qu’au caractère précédent.
regex (texte , " oiseaux ?| oies?")
[1] "oiseau" "oies" "oie"
Dans l’exemple suivant :
texte ← "Un chat blanc , un chat brun , un chat tachet é et un chat\
nnoir. "
regex (texte , "chat blanc|chat brun|chat noir")
[1] "chat blanc"
On échoue à capturer le chat brun et noir. Dans le cas du noir, le caractère \n représente le retour
à la ligne. On souhaite récupérer le motif, peu importe qu’il s’agisse d’un retour à la ligne ou d’un
espace. Pour cela, on utilise le caractère spécial \s qui symbolise un caractère d’espacement,
les tabulations sont aussi incluses. Rappelons que dans R, pour obtenir le caractère \, il faut
écrire \\.
17
regex (texte , "chat \\ sblanc |chat \\ sbrun|chat \\ snoir")
Le chat brun n’est pas capturé car il y deux espaces. Un caractère suivi d’un signe + doit être
présent une ou plusieurs fois. Le + ne s’applique qu’au caractère précédent.
regex (texte , "chat \\s+blanc|chat \\s+brun|chat \\s+noir")
Un caractère suivi d’un signe * peut être présent zéro, une ou plusieurs fois.
texte ← "Un chatblanc , un chat brun et un chat\ nnoir. "
regex (texte , "chat \\s∗( blanc|brun|noir)")
Les symboles ?, + et * sont des quantificateurs, ils s’appliquent directement au caractère (ou
groupe de caractères en cas de parenthèse) qui le précède.
Un caractère devant ? doit être présent zéro ou une fois. Un caractère devant + doit être présent
une ou plusieurs fois. Un caractère devant * doit être présent zéro, une ou plusieurs fois.
Il est possible de spécifier le nombre exact de fois avec {n} pour n ∈ N. Par exemple a{3}
demande à ce que a soit présent exactement 3 fois, c’est équivalent à aaa.
De manière plus générale, {n, m} précise que le caractère doit être présent entre n et m fois.
{n, } précise que le caractère doit être présent au moins n fois et {, m} doit être présent au
plus m fois (et au moins une fois, le minimum par défaut est fixé à 1).
Les symboles ?, + et * sont respectivement équivalents à {0, 1}, {1, } et {0, }.
Le symbole . représente tous les caractères. Par exemple, si on veut récupérer les mots en gras
dans une chaîne de caractères provenant d’une page internet :
texte ← "Un <strong > cheval blanc </ strong > et un <strong > cheval noir
</ strong >."
regex (texte , "<strong >.∗ </ strong >")
Cela fonctionne, mais on pourrait s’attendre à récupérer deux chaînes. Par défaut, les quanti-
ficateurs sont gourmands : ils essaient de prendre un maximum de caractères. Pour rendre le
quantificateur * non gourmand, on lui fait suivre ?.
regex (texte , "<strong >.∗? </ strong >")
18
Pour choisir un caractère qui appartient à une sélection, on utilise []. Pour demander à ce que
le caractère soit a, b ou c, on écrit simplement [abc].
Pour sélectionner une plage de caractères, par exemple entre a et g on écrit [a−g]. Les cas les
plus utilisés sont [a-z] (lettres minuscules), [A-Z] (lettres majuscules). La plage correspond
à celle du code ASCII, les caractères accentués ne sont pas pris en compte dans ces plages.
Il est possible de combiner avec d’autres choix ou plages. [a−z_] représentera une lettre mi-
nuscule ou le caractère souligné. Tandis que [A−Za−z] représente les lettres minuscules et
majuscules.
Il existe des groupes de caractères particuliers.
[[:lower:]] et [[:upper:]] considèrent tous les caractères minuscules ou majuscules, en
incluant les caractères accentués.
[[:digit:]] est équivalent à [0−9] lui même équivalent \\d.
[[:alpha:]] est équivalent à [[:lower:][:upper:]].
[[:alnum:]] est équivalent à [[:alpha:][:digit:]].
Il est possible d’exiger que la recherche commence sur le début de la chaîne et non pas n’importe
où. En faisant commencer le motif par ˆ, la recherche doit correspondre au début de la chaîne,
tandis qu’avec $ en fin de motif, celle-ci doit se terminer avec le motif recherché. En combinant
les deux, la chaîne passée en argument doit être le motif exact recherché.
texte ← " [email protected] "
regex (texte , "^[[: alnum :]]+@[[: alnum :]]+\\ .[a−z]{2 ,5}$")
[1] "[email protected]"
Une adresse email contient un . et comme ce caractère a une signification spéciale, on doit
l’échapper : cela se fait en rajouter un \ qui doit être doublé en R pour être interprété comme
tel.
L’email est renvoyé car cela correspond à la recherche. L’adresse passée a donc le format d’une
adresse email. Pour tester un motif et non le capturer, on utilise la fonction stri_detect_regex
.
stri_detect_regex (texte , "^[[: alnum :]]+@[[: alnum :]]+\\ .[a−z]{2 ,5}$"
)
[1] TRUE
Il existe une fonction équivalente dans R, la fonction grepl.
grepl ("^[[: alnum :]]+@[[: alnum :]]+\\ .[a−z]{2 ,5}$", texte)
[1] TRUE
Remarque : une adresse email peut contenir certains caractères spéciaux (comme un .), mais
ne peut pas commencer avec. Une bonne vérification d’adresse email peut se faire avec le motif :
^[[:alnum:]]([−_.]?[[:alnum:]])∗@[[:alnum:]]([−.]?[[:alnum:]])∗\\.([a−z]{2,5})
$.
19
Exercices
• Tester si une chaîne de caractères a le format d’une adresse web. Celle-ci peut commencer
(mais c’est facultatif) par http:// ou https://, doit être suivie d’une chaîne alphanu-
mérique non accentuée d’au moins 2 caractères et peut également contenir − ou . sans
qu’ils puissent se suivre, et doit terminer par {.extension} où l’extension est une chaîne
de caractères de taille 2 à 5. On testera avec les adresses candidates :
texte ← c("http :// domaine.com ", "https :// domaine −123.info", "
www.sous−domaine.domaine.org ", "1 and1.net ", "100% .info", "
http :// sousdomaine..domaine.info ", "ftp :// domaine.com ", "
.domaine.com ")
Les 4 premières sont des adresses valides tandis que les 4 dernières sont invalides.
Correction
stri_detect_regex (texte , "^( https ?://) ?[a−z0−9]{2}([−.]?[a−
•
z0−9])∗\\ .[a−z]{2 ,5}$")
[1] TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE
ou
grepl ("^( https ?://) ?[a−z0−9]{2}([−.]?[a−z0−9])∗\\ .[a−z
]{2 ,5}$", texte)
[1] TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE
Le texte est encadré par <strong> et </strong>, mais on souhaite ne récupérer que le texte
intérieur. Pour cela, nous faisons une capture dans le motif.
La fonction stri_match_all_regex du paquet stringi permet de capturer des sous motifs
et remplace stri_extract_all_regex . Nous définissons la fonction regexc qui permet de
faire une recherche regex avec capture directement et nous l’utiliserons par la suite.
regexc ← function (texte , motif , ...)
return ( stri_match_all_regex (texte , motif , ...)[[1]] )
Remarque. Il est possible de faire des recherches regex avec capture depuis les fonctions de R.
La fonction suivante le permet.
regexc ← function (motif , texte)
return ( regmatches (texte , regexec (motif , texte)) )
Toutefois, elle ne capture que la première occurence, ce qui rend son utilisation plus compliquée.
Elle est équivalente à stri_match_first_regex.
20
Toute zone entre parenthèses dans le motif sera capturée. Il est possible de capturer plusieurs
sous motifs.
texte ← "Un <strong > cheval blanc </ strong > et un <strong > cheval noir
</ strong >."
regexc (texte , ’<strong >(. ∗?) </strong >’)
[,1] [,2]
[1,] "<strong>cheval blanc</strong>" "cheval blanc"
[1,] "<strong>cheval noir</strong>" "cheval noir"
En premier elle renvoie le motif complet, puis chacun de sous motifs capturés. Parfois, les
parenthèses sont utilisées pour appliquer un quantificateur à un groupe, et non pour capturer.
Pour refuser la capture, on fait suivre la parenthèse ouvrante par ?:.
regexc (texte , ’<strong >(?:. ∗?) </strong >’)
[,1]
[1,] "<strong>un cheval blanc</strong>"
[2,] "<strong>cheval noir</strong>"
Pour une capture multiple, il est parfois plus simple d’isoler la zone de capture puis d’y effectuer
la capture.
Soit le texte suivant :
texte ← ’Du contenu pré alable...
<ul id =" autre">
<li >autre </li >
</ul >
<ul id =" planetes ">
<li >Vénus </li >
<li >Mars </li >
<li >Terre </li >
</ul >
Du contenu posté rieur... ’
Objectif : capturer la liste des planètes en un vecteur. On ne peut pas simplement rechercher
le motif "<li>(.∗?)</li>" car dans ce cas, on capturerait d’autres éléments d’autres listes.
Dans un premier temps, on extrait la liste complète avec les planètes. L’argument dotall=TRUE
permet d’inclure le retour à la ligne \n dans le . (pas le cas par défaut).
( global ← regex (texte , ’<ul id=" planetes ">.∗? </ul >’, dotall =TRUE))
[,1] [,2]
[1,] "<li>Vénus</li>" "Vénus"
[2,] "<li>Mars</li>" "Mars"
[3,] "<li>Terre</li>" "Terre"
21
Exercices
• Capturez la liste des paquets R actuellement disponibles sur le CRAN qui se trouvent
dans la page : https://cran.r-project.org/web/packages/available_packages_by_
name.html. On pourra importer la page dans R avec :
html ← paste( readLines ("https :// cran.r−project.org /web/ packages
/ available_packages_by_name.html ", encoding ="UTF−8"),
collapse = "\n")
Correction
• On importe dans un premier temps la page web
html ← paste( readLines ("https :// cran.r−project.org /web/ packages
/ available_packages_by_name.html ", encoding ="UTF−8"),
collapse = "\n")
On extrait le tableau qui contient les données (cela évite d’entrer en conflit avec un autre
éventuel tableau, par exemple), on s’assure qu’il n’y en a qu’un (car pas d’identifiant).
html_table ← regex (html , ’<table >.∗? </ table >’, dotall = TRUE)
On extrait le tableau qui contient les données (cela évite d’entrer en conflit avec un autre
éventuel tableau, par exemple) en repérant comment extraire le bon.
html_table ← regex (html , ’<table class ="c−table" data−table−
sorter >\\s∗<thead >.∗? </ thead >.∗? </ table >’, dotall = TRUE)
Pour chaque ligne, on capture les 6 éléments des colonnes en nettoyant des espaces (on
duplique la chaîne avec strrep car les quantificateurs, même constants, appliqués à des
captures sont interdits).
tableau ← regexc (html_table , paste0 (’<tr\\s∗ class ="c−table__row
">’, strrep (’\\s∗< td. ∗? >\\s∗(. ∗?) \\s∗ </td >’, 6), ’\\s∗ </tr >’
), dotall = TRUE)
22
3 R en finance
En modélisation financière, il est fréquent de rencontrer des processus X définis par une
équation différentielle stochastique (une dynamique) de la forme :
Z t Z t
Xt = x + µ(s, Xs )ds + σ(s, Xs )dWs , t ∈ [0, T ], (1)
0 0
avec
• x ∈ Rq où q ≥ 1 est la dimension du processus (Xt )0≤t≤T ,
• (Wt )t≥0 est un mouvement brownien en dimension d,
• µ : [0, T ] × Rq → Rq est une fonction lipschtiz à croissance sous linéaire qui représente
l’évolution moyenne locale,
• σ : [0, T ]×Rq → Mq,d (R) est une fonction lipschtiz à croissance sous linéaire qui représente
le bruit local.
La loi d’un portefeuille peut dépendre d’un tel processus et, afin d’évaluer des statistiques sur ce
portfeuille, comme des quantiles, ou évaluer des options, il est possible d’utiliser des méthodes
de type Monte Carlo, c’est-à-dire simuler massivement le processus X.
On pourra se référer à [3] pour un approfondissement de ces méthodes.
i
Wj∆t , 1 ≤ i ≤ n, 0 ≤ j ≤ m.
que nous allons représenter, dans R , par W[i,j] avec W une matrice de dimension n × (m + 1).
L’une des propriétés du mouvement brownien est :
23
Puis nous pouvons afficher sommairement quelques trajectoires du mouvement brownien simulé.
couleurs = c("# c0392b ", "#2980 b9", "#27 ae60", "#2 c3e50", "#8 e44ad",
"# f39c12 ")
t ← (0:m)∗ dt
nW_plot ← 6
plot(t, W[1, ], ylim = c(−1,1)∗ max(abs(W[1: nW_plot , ])), type = "l"
, col = couleurs [1], ylab = expression (W[t]))
for(i in 2: nW_plot )
lines (t, W[i, ], col = couleurs [i])
2
1
Wt
0
−1
−2
Remarque 1. Dans le code ci-dessus, la matrice W représente les simulations. Nous avons
choisi qu’une ligne représentait une simulation d’une trajectoire, et que l’écoulement du temps
se faisait sur les colonnes. Ceci implique d’écrire une boucle sur l’indice des colonnes. C’est
plus performant que la représentation transposée (où une simulation de trajectoire serait une
colonne). En effet, dans R, les données d’une matrice sont stockées de manière contiguë, par
colonne. Ainsi, extraire une colonne revient à extraire une zone contiguë de mémoire tandis
qu’extraire une ligne nécessite de récupérer chaque valeur de manière isolée et éloignée, ce qui
est plus coûteux. L’exemple ci-dessus est 25-30% plus lent en itérant par ligne plutôt que par
colonne. Nous utiliserons toujours la représentation où chaque ligne est la simulation d’une
trajectoire.
Le processus (Xt )0≤t≤T caractérisé par l’équation (1) est markovien : si nous connaissons
24
L(Xt+∆t | Xt ) alors il est possible de simuler X sur T∆t
m et, comme pour le mouvement brownien,
pour toutes les trajectoires en simultané.
Prenons l’exemple du processus de Vašíček. Il est caractérisé par l’équation différentielle sto-
chastique : Z t Z t
Xt = x + −a(Xs − µ)ds + σdWs ,
0 0
avec a, σ > 0 et µ ∈ R. Le processus se réécrit :
Z t
−at −at
Xt = µ + (x − µ)e + σe eas dWs .
0
Ceci implique
2
−a∆t σ −2a∆t
X(j+1)∆t | Xj∆t ∼ N µ + (Xj∆t − µ)e , (1 − e ) .
2a
n ← 10^4
m ← 10^3
T ← 1
dt ← T/m
x ← 0; mu ← 0
a ← 1; sigma ← 0.2
25
0.3
0.2
0.1
X[1, ]
0.0
−0.1
−0.2
−0.3
temps
m m m m
X(j+1)∆t = Xj∆t + µ(j∆t, Xj∆t )∆t + σ(j∆t, Xj∆t )(W(j+1)∆t − Wj∆t ).
On a donc en loi :
m m m m m 2
X(j+1)∆t | Xj∆t ∼ N Xj∆t + µ(j∆t, Xj∆t )∆t, σ(j∆t, Xj∆t ) ∆t .
(2)
p
dXt = −a(Xt − µ)dt + σ Xt (1 − Xt )dWt .
n ← 10^4
m ← 10^3
T ← 1
dt ← T/m
x ← 0.5; mu ← 0.5
a ← 0.5; sigma ← 0.5
26
X[, j+1] = rnorm (n, X[, j] − a∗(X[, j] − mu)∗dt , sigma ∗ sqrt(dt ∗
X[, j]∗(1−X[, j])))
Illustrons quelques trajectoires.
1.0
0.8
0.6
X[1, ]
0.4
0.2
0.0
temps
Le processus ci-dessus prend ses valeurs dans [0, 1] mais le schéma a une probabilité non nulle
de sortir de l’intervalle, qui devient négligeable avec ∆t suffisamment petit. Aux paramètres
ci-dessus, mettre σ = 0.75 amène à ce genre de problèmes.
Il existe un second schéma, celui de Milstein, qui améliore l’approximation de l’intégrale sto-
chastique et permet une convergence plus rapide de la trajectoire vers celle du processus, au
prix d’un terme supplémentaire qui suppose σ dérivable en espace. Le schéma est
m m m m
X(j+1)∆t = Xj∆t + µ(j∆t, Xj∆t )∆t + σ(j∆t, Xj∆t )(W(j+1)∆t − Wj∆t )
1 m m
) (W(j+1)∆t − Wj∆t )2 − ∆t) .
+ σ(j∆t, Xj∆t )∂x σ(j∆t, Xj∆t
2
Nous illustrons en appliquant le schéma de Milstein au processus (2), le calcul de la dérivée
donne
h p i0 1 − 2x
σ x(1 − x) = σ p .
2 x(1 − x)
On a la simplification du terme :
1 σ 2 (1 − 2x)
σ(t, x)∂x σ(t, x) = .
2 4
27
n ← 10^4
m ← 10^3
T ← 1
dt ← T/m; sdt ← sqrt(dt)
x ← 0.5; mu ← 0.5
a ← 0.5; sigma ← 0.5
0.4
0.2
0.0
temps
Le processus obtenu par le schéma ci-dessus ne sort pas de l’intervalle [0, 1] avec σ = 0.75
mais il pourra sortir avec σ = 1 (il serait obligatoire, dans ce cas, de réduire le pas).
28
3.2 Évaluation d’actif simple par Monte Carlo
Nous n’abordons pas les très importantes méthodes de réduction de variance, qui font partie
d’un cours de Monte Carlo, on pourra se référer à [3].
cat( paste0 ("mu = ", round(mu , 6), " +/− ", round(e, 6), "\n"))
mu = -0.576789 ± 0.00256
Soit (St )t≥0 un actif sous la probabilité risque-neure Q et (rt )t≥0 le taux d’intérêt sans risque
sous Q (pouvant être constant, selon le modèle).
Pour estimer le prix d’une option européenne, par exemple d’un Call de prix d’exercice K > 0,
on rappelle que le prix C(T ) a la représentation
h RT i
C(T ) = EQ e− 0 rt dt (ST − K)+ .
Pour estimer C(T ), nous pouvons appliquer des simulations de Monte Carlo : il faut simuler
ST et (rt )t≥0 .
Le modèle le plus simple (pour lequel il existe par ailleurs une formule explicite) est le
modèle de Black-Scholes. Si on se place dans ce dernier, le processus (rt )t≥0 est constant et vaut
r ∈ R, et (St )t≥0 est
σ2
St = S0 e(r− 2
)t+σWt
.
29
Comme le flux associé à une option européenne d’achat ne dépend que de la valeur terminale ST ,
on peut estimer le prix de cette option dans le modèle de Black Scholes plus simplement, sans
sauvegarder toutes les trajectoires. D’autres part, ST étant explicite, on peut écrire simplement :
n ← 10^6
S0 ← 1
ST ← S0 ∗ rlnorm (n, (r − sigma ^2/2) ∗T, sigma ∗ sqrt(T))
SmKp ← ST − K
SmKp[SmKp < 0] ← 0
C ← exp(−r∗T)∗ mean(SmKp)
e ← exp(−r∗T) ∗2∗ sd(SmKp)/sqrt(n)
cat( paste0 ("C = ", round(C, 6), " +/− ", round(e, 6), "\n"))
σ (f (X))2
f (Xk ) + f (a − Xk )
V ar ≤ .
2 2
30
Définition 2. Soit X une variable aléatoire telle et g une fonction telle que m := E [g(X)]
avec m connu. Soit a? ∈ R, on pose l’estimateur avec variable de contrôle :
n
1X
µ̂C
n := f (Xk ) − a? (g(Xk ) − m),
n k=1
Cov(f (X),g(X))
avec a? := 2
σg(X)
.
On remarque que celui-ci est sans biais et
Exercices
Appliquez la mvéthode de réduction de variance par
loi
• Variables antithétiques, en remarquant que WT = −WT ;
σ2
• Varible de contrôle en utilisant g(WT ) = S0 e(r− 2
)T +σWT
;
pour l’évaluation du prix d’une option d’achat dans Black Scholes, avec les paramètres de
l’exemple précédent.
Correction
On recalcule dans Black Scholes mais en simulant directement WT qui sera la variable
antithétique.
n ← 10∗∗6
T ← 1; K ← 1
r ← 0.02; sigma ← 0.2
X ← rnorm (n)
fX ← exp ((r − sigma ^2/2) ∗T + sigma ∗ sqrt(T)∗X)−K
fX[fX < 0] ← 0
mu ← exp(−r∗T)∗ mean(fX)
s ← exp(−r∗T)∗ sd(fX)
e ← 2∗s/sqrt(n)
cat( paste0 ("C = ", round(mu , 6), " +/− ", round(e, 6), "\n"))
31
cat( paste0 ("C = ", round(muA , 6), " +/− ", round(eA , 6), "\n",
" Facteur de gain de pré cision : ", round(s/sA , 2), ", simulation :
", round ((s/sA)∗∗2 , 2), "\n"))
cat( paste0 ("C = ", round(muC , 6), " +/− ", round(eC , 6), "\n",
" Facteur de gain de pré cision : ", round(s/sC , 2), ", simulation :
", round ((s/sC)∗∗2 , 2), "\n"))
4 R en assurance dommage
Cette section s’appuie sur [1], qu’on pourra utiliser pour approfondir.
4.1 Tarification
En assurance dommage, le coût des sinistres d’un assuré peut être représenté par une variable
aléatoire de la forme
XN
S= Yn ,
n=1
où
• N est le nombre de sinistres, il s’agit d’une variable aléatoire entière éventuellement nulle
(N = 0 ⇒ S = 0),
• (Yn )n≥1 est une suite de variables aléatoires positives et i.i.d. qui représente le coût des
sinistres,
• N et (Yn )n≥0 sont indépendantes.
Sous ces hypothèses, on peut démontrer que (voir [4, Première formule de Wald] )
On se placera toujours dans le cas où toutes les espérances sont finies. Cependant, il n’y a
pas de raison que la loi de N (nombre de sinistres) et des (Xn )n≥1 (coût des sinistres) soient
32
identiques pour tous les assurés. Si on note X i = xi ∈ Rd les caractéristiques observables d’un
assuré i, (3) devient
De plus, on suppose que la loi des N i appartient à une famille (Pθ ) où N i ∼ PµN i , c’est à dire
paramétrée (ou reparamétrée) par la moyenne. De manière plus générale, on peut introduire
un facteur de déviance (qui est relié à la dispersion) commun et d’avoir N i ∼ PφµN i où φ pourra
être à estimer (il est l’équivalent de la variance du bruit dans le modèle linéaire). La fonction
g est choisie, et tout ceci posé, il est possible d’estimer les paramètres du modèle : β0 , β (et φ
s’il y a lieu) par maximum de vraisemblance.
Nous commençons par voir sur R les modèles linéaires gaussiens, qui sont un cas particulier
des modèles linéaires généralises, avec g l’identité (x 7→ x), et Pφµi = N (µi , φ).
La fonction de R qui permet d’estimer les paramètres est la fonction glm.
Nous allons commencer par voir les modèles linéaires
Pour régresser la distance de freinage dist contre la vitesse speed, la formule est dist ~
speed.
lm(dist ~ speed , data = cars)
33
Call:
lm( formula = dist ~ speed , data = cars)
Coefficients :
( Intercept ) speed
−17.579 3.932
Call:
lm( formula = dist ~ speed + speed2 , data = cars)
Coefficients :
( Intercept ) speed speed2
2.47014 0.91329 0.09996
Lors qu’il y a beaucoup de variables dans la régression, et qu’on souhaite le faire sur toutes, on
utilise le point . pour les représenter.
cars$speed2 ← cars$speed ^2
lm(dist ~ . , data = cars)
Call:
lm( formula = dist ~ ., data = cars)
Coefficients :
( Intercept ) speed speed2
2.47014 0.91329 0.09996
Pour retirer des variables on utilise le signe moins −. La constante de régression s’appelle 1, il
est possible de la retirer.
lm(dist ~ . −1, data = cars)
Call:
lm( formula = dist ~ . − 1, data = cars)
Coefficients :
speed speed2
1.23903 0.09014
La fonction lm renvoie une liste. On peut connaître ses éléments en appliquant la fonction
names(lm(dist ~ speed, data = cars)). La fonction summary permet de récupérer les tests
statistiques des régresseurs et diverses information avec summary(lm(dist ~ speed, data =
cars)). C’est également une liste dont nous pouvons connaître les éléments avec names.
34
4.1.2 Modèle linéaire généralisé avec R
La fonction de R qui permet d’estimer les paramètres d’un modèle linéaire généralisé est la
fonction glm. Elle fonctionne de la même manière que la fonction lm, où il faut préciser en plus
• la fonction de lien g,
• la famille de loi (Pθ ).
Ces deux éléments sont donnés ensemble via un objet family. Pour retrouver le modèle
linéaire, le cas particulier avec une famille normale et une fonction de lien qui est l’identité, on
utilise gaussian(link="identity").
glm(dist ~ speed , family = gaussian (link=" identity "), data = cars)
Coefficients :
( Intercept ) speed
−17.579 3.932
Ci-dessous, nous listons des exemples de familles de loi avec leur fonction de lien canonique.
On pourra voir l’aide de aide avec help(family).
n ← 300
set.seed (0) #Pour f i x e r l ’ a l é a e t r e p r o d u i r e
X ← data.frame (X1 = rgamma (n, 5, 5), X2 = rbinom (n, 1, 0.2))
35
(R ← glm(freq ~ ., family = poisson ("log"), data = X))
Call: glm( formula = freq ~ ., family = poisson (" log "), data = X)
Coefficients :
( Intercept ) X1 X2
−2.1506 1.0345 −0.7048
Puis nous pouvons afficher les statistiques associées aux coefficients de la régression.
summary (R)
Call:
glm( formula = freq ~ ., family = poisson (" log "), data = X)
Deviance Residuals :
Min 1Q Median 3Q Max
−1.3036 −0.7712 −0.6377 0.3320 2.6235
Coefficients :
Estimate Std. Error z value Pr(>|z|)
( Intercept ) −2.1506 0.2865 −7.506 6.1e−14 ∗∗∗
X1 1.0345 0.2291 4.516 6.3e−06 ∗∗∗
X2 −0.7048 0.3098 −2.275 0.0229 ∗
−−−
Signif . codes: 0 ’∗∗∗ ’ 0.001 ’∗∗ ’ 0.01 ’∗’ 0.05 ’.’ 0.1 ’ ’ 1
[,1]
[1 ,] 0.4554772
36
1
0.4554772
Il faudrait faire de même sur les coûts et multiplier ensemble pour obtenir la prime pure. Pour
la loi binomiale négative, il y a la fonction dédiée glm.nb du paquet MASS qui est livré avec R .
4.2 Provisionnement
En assurance dommage, la prime est payée à la signature mais le paiement des sinistres
éventuels à lieu dans l’année qui suit. Parfois, un sinistre qui a eu lieu n’est pas connu de l’as-
sureur ou son montant n’est pas encore complètement déterminé, et le coût total du sinistre est
quelquefois connu que plusieurs années après. L’assureur est tenu d’estimer et de provisionner
ce coût probable.
Nous prendrons l’exemple du modèle de Chain Ladder - Mack.
i, j 1 ··· n
1 X1,1 · · · X1,n
.. .. .
. . ..
n Xn,1
où Xi,j est la variable aléatoire qui correspond au coût des sinistres survenus l’année i pour
l’année de développement j.
Nous aurons besoin du triangle de charges cumulées, on définit pour 1 ≤ i, j ≤ n,
j
X
Ci,j = Xi,k .
k=1
i, j 1 ··· n
1 C1,1 · · · C1,n
.. .. .
. . ..
n Cn,1
Nous pouvons passer de C à X via la multiplication d’une matrice simple, ce qui permettra
d’avoir un code de passage efficace. Si on multiplie X par la matrice
1 1 1 1
0 1 1 1
XtoC = 0 0 1 1 ,
0 0 0 1
on obtient C. Si X est le triangle incrémental dans R , le code de passage est simplement :
37
XtoC ← upper.tri (X, TRUE)
C ← X %∗% XtoC
où upper.tri est une fonction qui renvoie une matrice de booléens avec TRUE sur la partie
supérieure à la diagonale.
Pour passer du triangle des coûts cumulés C à X, il suffit de multiplier à droite par l’inverse
de XtoC. Son inverse est
solve (XtoC)
38
Grâce à ces facteurs de développement, nous pouvons ensuite développer le triangle des coûts
de sinistres cumulés. On utilise la relation Ĉi,j+1 = fˆj Ĉi,j en partant de la diagonale.
hatC_ ← function (C, f)
{
for(j in 1:(n−1))
C[(n−j+1):n, j+1] ← f[j]∗C[(n−j+1):n, j]
return (C)
}
C ← hat_C (C)
La provision associée à chaque année i est la différence entre le coût total estimé après tous les
développements Ĉi,n et le coût actuel Ci,n−i+1 , et la provision totale est la somme des provisions
associée à chaque année.
R_ ← function (C)
return (C[, n] − rev(C[row(C) + col(C) == n + 1]))
R ← R_(C)
Avec les données, la réserve totale est R̂ = 18680848. Calculer la réserve n’est pas suffisant : il
ne s’agit que de la moyenne estimée du coût des sinistres tardifs.
Le modèle de Mack suppose que V ar(Ci,j+1 | passé) = σj2 Ci,j , et les estimateurs sans biais
n−j 2
2 1 X Ci,j+1 ˆ
σ̂j := Ci,j − fj .
n − j − 1 i=1 Ci,j
En R , cela se traduit par
s2_ ← function (C, f)
{
s2 ← numeric (n−1)
for(j in 1:(n−2))
s2[j] ← sum( C[1:(n−j), j] ∗ (C[1:(n−j), j + 1]/C[1:(n−j),
j] − f[j])^2 )/(n − j − 1)
s2[n−1] ← min(s2[n−2]^2/s2[n−3], s2[n−3], s2[n−2])
return (s2)
}
s2 ← s2_(C, f)
De là, on peut en déduire la volatilité sur le montant des réserves, incluant l’incertitude d’esti-
mation des paramètres (voir [1, Section 4.2]) ou appliquer la méthode du boostrap pour estimer
la distribution des réserves (voir [1, Section 6]).
On peut également utiliser le paquet ChainLadder qui possède les fonctions pour calculer
directement les différentes quantités du modèle de Chain Ladder Mack.
5 R en assurance vie
5.1 Rentes viagères et capital décès
Le paquet qui permet de faire les calculs habituels en assurance-vie est lifecontingencies
. Avec celui-ci, il est possible de construire une table de mortalité, qui est un objet de R .
L’objet est un lifetable et se créé avec la fonction new.
39
(TM ← new(" lifetable ", x=0:120 , lx=round (100000∗ exp ( −25∗((0:120)
/120) ^9)), name=" exemple "))
x lx px ex
1 0 100000 1.0000000 78.9673600
...
110 109 3 0.3333333 0.3333333
Ci-dessus, lx qui est donné dans la création de la table est le nombre de survivant à l’âge x,
tandis que px est la probabilité de survivre à 1 an sachant que l’individu a l’âge x. Enfin, ex
est l’espérance de vie résiduelle à l’âge x. Nous pouvons récupérer les noms des éléments.
slotNames (TM)
TM@lx
x lx Dx Nx Cx Mx
Rx
1 0 100000 1.000000 e+05 4.029114 e+06 0.0000000 2.099777 e+04
1.629732 e+06
...
111 110 1 1.132351e−01 1.132351e−01 0.1110148 1.110148e−01
1.110148e−01
Toutes les quantités Dx, etc, sont les grandeurs actuarielles habituelles. Elles sont calculées à la
volée, seul le taux d’intérêt a été ajouté à l’objet.
40
slotNames (TM)
De cette table actuarielle, il est possible de calculer les primes pures unitaires des rentes asso-
ciées.
• äx = ∞ t px
P
t=0 (1+r)t = axn(TM, x)
• ax = ∞ t px
P
t=1 (1+r)t = axn(TM, x, payment = "immediate")
Sur la table actuarielle TM, nous pouvons calculer la prime pure unitaire du versement d’un
capital en cas de décès.
• Ax = ∞
P t px ×qx+t
t=0 (1+r)t = Axn(TM, x)
t px ×qx+t
• m|n Ax = m+n−1
P
t=m (1+r)t
= Axn(TM, x, m=m, n=n)
Si l’assuré paie une prime P annuelle pour une assurance décès unitaire sur la vie entière, qu’il
versera jusqu’au décès, alors la prime pure de ce qu’il versera à l’assureur est äx tandis que la
prime pure de sa garantie est Ax . La prime d’équilibre est Aäxx qui s’obtient avec Axn(TM, x)/axn
(TM, x). Nous pouvons en déduire aussi les provisions mathématiques. Par exemple, si l’assuré
signe une garantie vie entière en cas de décès à l’âge x pour un capital K, et verse une rente vie
entière d’un montant annuel P , on a P äx = KAx . Puis, s’il est toujours en vie après t années,
la provision mathématique est t Vx = KAx+t − P äx+t , c’est-à-dire K∗Axn(x+t)− P∗axn(x+t).
qx = P (T ∈]x, x + 1] | T > x) , x ≥ 0.
Dans un premier temps, nous les estimons indépendamment les uns des autres, ce que nous
appelons les taux bruts.
nous pouvons intégrer la troncature facilement (observation d’un assuré à partir d’un âge non
entier). Ainsi, si on note
41
• τi la durée de troncature pour l’observation (l’assurée est observé à partir de l’âge x + τi ),
• δi l’observation d’un décès,
• Yi la date de fin de l’observation (décès, sortie de l’intervalle, ou arrivée au bout de
l’intervalle, dans ce dernier cas Yi = 1),
dans ce cas, l’estimateur du maximum de vraisemblance de λx est
Pn
bx = Pn i=1 δi
λ
i=1 Yi − τi
Nous faisons un petit exemple où nous simulons la troncature, censure, le décès, et calculons
q̂x pour x fixé.
qx ← 0.1
lambdax ← −log(1−qx)
n ← 10^6
#Date de d é b u t de l ’ o b s e r v a t i o n dans l ’ â ge x
tau ← rbinom (n, 1, 0.2)∗ runif (n)
T ← tau + rexp(n, lambdax ) #Vé r i f i e l ’ h y p o t h è s e mu_x l o c a l e m e n t
constant
#S i Z v a u t 0 , l ’ o b s e r v a t i o n e s t c e n s u r é e a v a n t x+1
Z ← rbinom (n, 1, 0.8)
#Fin de l ’ o b s e r v a t i o n en x+1 ou a p p l i q u a t i o n d ’ une c e n s u r e
C ← Z + (1−Z)∗ runif (n, tau , 1)
Y ← pmin(T, C)
delta ← T <= C
( hat.qx ← 1−exp(−sum(delta)/sum(Y−tau)))
[1] 0.1000338
Nous pouvons aussi utiliser l’estimateur suivant, très proche, qui coïncide avec l’estimateur
naturel sans troncature / censure.
#Autre v e r s i o n , q u i co ï n c i d e a v e c l e c a s s a n s c e n s u r e ( qx = dx / nx )
Ybis ← Y
Ybis[T <= C] ← 1
( hat.qx ← sum(delta)/sum(Ybis−tau))
[1] 0.100055
42
5.2.2 Lissage Whittaker-Henderson
Nous savons que x 7→ qx est une fonction croissante et régulière. En revanche, bien que
l’estimateur qbx soit sans biais, la fonction x 7→ qbx n’est en général pas croissante et peut être
peu régulière là où il y a peu d’observations. Nous pouvons appliquer une méthode de lissage,
comme celle de Whittaker-Henderson.
Pour pouvoir appliquer la méthode, construisons-nous une table de taux bruts fictifs. Dans
un premier temps nous construisons des lx fictifs desquels nous en déduisons des qx .
lx ← 10000∗ exp ( −25∗((0:105) /120) ^9)
qx ← 1−lx[−1]/lx[−length (lx)]
m ← length (qx)
Nous simulons ensuite des observations de mortalité suivant cette table et en déduisons les taux
brut qbx .
nx ← numeric (m)
dx ← numeric (m)
set.seed (0)
nx [1] ← 10000
dx [1] ← rbinom (1, nx[1], qx [1])
for(i in 2:m)
{
nx[i] ← nx[i−1] − dx[i−1]
dx[i] ← rbinom (1, nx[i], qx[i])
}
hat.qx ← dx/nx
Le lissage de Whittaker-Henderson consiste à trouver x 7→ qex qui est un arbitrage entre la
fidélité aux taux bruts x 7→ qbx et la régularité. Soit m l’âge maximum, la fidélité aux taux bruts
est définie par la quantité :
m
X
F (e
qx ) = wx (e q − qb)0 W (e
qx − qbx )2 = (e q − qb),
x=0
où qe = (e
qx )0≤x≤m , qb = (b
qx )0≤x≤m et W est une matrice diagonale de diagonale (wx )0≤x≤m . Les
poids sont généralement l’inverse de la variance de l’estimateur brut, c’est à dire
Pn i
i=1 Ex
wx := ,
qbx (1 − qbx )
ou, s’il y a des problèmes numériques liés à qbx (s’annule ou très instable car peu de données),
n
X
wx := Exi .
i=1
W ← diag(nx)
La régularité d’ordre 2 est mesurée par
m−2
X
S(e
q ) := (e qx+1 + qex )2 = qe0 K 0 K qe,
qx+2 − 2e
x=0
43
avec K une matrice m − 2 × m définie par
1 −2 1 0 0 ··· 0
0 1 −2 1 0 · · · 0
K := .
.. ... ... ... ... ..
.
0 ··· 0 0 1 −2 1
k ← c(1, −2, 1)
K ← matrix (0, m−2, m)
for(i in 1:(m−2))
K[i, i:(i+2)] ← k
F (e
q ) + hS(e
q ),
où h est un paramètre à choisir : l’arbitrage entre fidélité et régularité. La solution est explicite
est
qe = (W + hK 0 K)−1 W qb.
h ← 10000
tilde.qx ← solve (W + h∗t(K) %∗%K, W %∗% hat.qx )
44
qx
^
q x
~
q
0.4
0.3 x
qx
0.2
0.1
0.0
50 60 70 80 90 100
Dans Windows, il faut l’ajouter au path. Pour accéder au menu correspondant, on écrit path ou
variables d’environnement dans le menu rechercher de Windows et on accède à une fenêtre où
nous cliquons sur Variables d’environnement.... On modifie PATH et on y ajoute le chemin du
dossier qui contient Rtools. Pour une version 4.2.x de R, on ajoute C:\rtools42\usr\bin.
Les préparatifs sont terminés, pour s’assurer que tout cela fonctionne, on ouvre un Terminal
dans RCode ou via Windows (cmd dans le menu recherché, ou depuis une fenêtre de l’explorateur
Windows via le champs du dossier). Puis on écrit R et on éxécute. Cela doit lancer R dans
le Terminal, il s’agit de celui dans le dossier renseigné dans le path, qu’on pourra changer
ultérieurement si besoin. Cela permettra d’exécuter R en ligne de commande pour compiler
une fonction ou un paquet.
45
6.1 Initiation avec .C()
Nous présentons la compilation d’une fonction C par un exemple. Dans un fichier fonction.c,
nous écrivons le code C suivant, détaillé ci-après.
void somme( double ∗ x, double ∗ y, double ∗ z)
{
∗z = ∗x + ∗y;
}
[1] 0
Nous sommes prêts à appeler notre fonction dans R. Cela se fait avec .C. Le premier argument
est le nom de la fonction, et ensuite on fournit, en les nommants, les autres arguments.
.C(" somme ", x=1, y=pi , z=0)
$x
[1] 1
$y
[1] 3.141593
$z
[1] 4.141593
La fonction renvoie une liste avec la (nouvelle) valeur de chacune des variables en argument de
la fonction C. Nous pouvons encapsuler l’appel de .C avec une fonction R.
somme ← function (x, y)
return (.C("somme", x=x, y=y, z=0) $z)
[1] 4.141593
46
En revanche, il faut être très prudent sur le type de l’objet envoyé. Si la fonction C attend un
double et qu’on lui envoie un autre type, cela peut faire planter la fonction et R tout entier qui
devra redémarrer, ou alors renvoyer un résultat aléatoire, comme le montre l’exemple suivant.
somme (1L, 0)
[1] 1.188318e−312
somme (1L, 0)
[1] 1
Expliquons la fonction somme écrite en C. Ici, le type de retour est void, cela veut dire qu’elle
ne renvoie rien (pas de return. Le type des variables doit être précisé en C, il s’agit dans notre
exemple de double. L’étoile qui suit est obligatoire dans la création d’une fonction C pour R
via cette méthode. Cela indique que la mémoire de la variable sera directement modifiée (mais
R fait une copie de la variable envoyée, la variable R sera inchangée). En conséquence, pour
modifier ou appeler la variable x dans le corps de la fonction, on l’appel avec ∗x.
Un exemple plus intéressant est de pouvoir par exemple simuler la suite i.i.d. de (Si )1≤i≤n
de variables aléatoires définies par
Ni
X
Si = Xki , 1 ≤ i ≤ n,
k=1
où (Ni )1≤i≤n est une suite i.i.d. de variables aléatoires à valeurs dans N et (Xki )k≥1,1≤i≤n sont
des variables aléatoires i.d.d. à valeurs dans R et indépendantes des (Ni ). Par exemple, si
i.i.d. i.i.d.
(Ni ) ∼ P(λ) et (Xki ) ∼ LN (µ, σ 2 ). Ce qui est passé en C via double ∗ x peut être un
vecteur. Dans ce cas, on accède aux éléments via [] comme en R, à la différence que l’indexation
commence à 0. Et ∗x est équivalent à x[0]. Il faut toutefois passer la taille des vecteurs en
argument, il n’est pas possible de la connaître dans le code C sinon.
Pour simuler les variables aléatoires en C, nous pouvons appeler les fonctions de simulation de
R codées en C, il suffit d’ajouter les bons #include en préambule.
# include <R.h >
# include <Rmath.h >
47
GetRNGstate ();
for(int i=0 ; i !=∗ n ; ++i)
{
N = rpois (∗ lambda );
for(int k=0; k!=N ; ++k)
S[i] += rlnorm (∗mu , ∗ sigma);
}
PutRNGstate ();
}
Ci-dessus, nous pouvons accéder aux fonctions C natives de R pour les simulations. Ce sont
les mêmes que celles de R, à la différence qu’elles ne demandent pas le nombre de simulations
(elles n’en renvoient qu’une). Toutes les fonctions de #include <Rmath.h> sont consultables
en téléchargeant le code source de R et en allant dans src/nmath. Dans le code ci-dessus, S
est un vecteur de taille n et doit être entré comme tel dans la fonction C via l’appel de R .
Enfin, GetRNGstate doit être appelé pour s’assurer que le générateur est sur la graine de R
et PutRNGstate à la fin pour transmettre l’état à R : sans cela, on sera confronté à des bugs
(simulations nulles ou identiques à chaque appel).
Ecrivons maintenant la fonction R associée.
rsum ← function (n, lambda , mu , sigma)
return (.C("rsum",
n = as.integer (n),
lambda = as.double ( lambda ),
mu = as.double (mu),
sigma = as.double (sigma),
S = numeric (n)
)$S)
rsum (6, 5, 1, 1)
Il faut ensuite tester sa fonction, on peut par exemple vérifier la moyenne et la variance.
Comparons le gain en temps de calcul par rapport à deux approches sans code C. Nous définis-
sons deux fonctions, rsumR qui est l’approche la plus simple qui comporte une unique boucle
sur les simulations.
rsumR ← function (n, lambda , mu , sigma)
{
S ← numeric (n)
for(i in 1:n)
S[i] ← sum( rlnorm ( rpois (1, lambda ), mu , sigma))
return (S)
}
48
N ← rpois (n, lambda )
X ← matrix (0, n, max(N))
X[col(X) <= N] ← rlnorm (sum(N), mu , sigma)
return ( rowSums (X))
}
Nous allons comparer le temps de calcul grâce au paquet microbenchmark. Il ajoute la fonction
microbenchmark, celle-ci prend un code R, par défaut l’execute 100 fois, et renvoie le temps
de calcul médian. Comparons les 3 fonctions.
microbenchmark (rsum (10^4 , 5, 1, 1))
Unit: milliseconds
expr min lq mean median uq max neval
rsum (10^4 , 5, 1, 1) 5.499 5.645 5.8364 5.7814 6.0081 6.770 100
Unit: milliseconds
expr min lq mean median uq max neval
rsumR (10^4 , 5, 1, 1) 36.23 37.54 40.175 40.829 41.40 60.28 100
Unit: milliseconds
expr min lq mean median uq max neval
rsumR2 (10^4 , 5, 1, 1) 7.465 8.076 8.3527 8.1723 8.298 13.03 100
Si nous comparons les temps de calcul médians dans cet exemple, la fonction C prend environ
5.8ms contre 40.8ms par la fonction R simple et 8.2ms la fonction R sans boucle. On remarque
qu’un code R très bien pensé permet de se rapprocher du temps de calcul de la fonction C,
sans l’atteindre mais en étant sous un facteur 2. Tandis que le code R simple est environ 7 fois
plus lent que la fonction C (et 5 fois plus que la fonction R sans boucle).
En revanche, dans cet exemple, la fonction R sans boucle consomme beaucoup plus de mémoire
que les autres pendant le calcul. Elle demande à construire une matrice n×max(N ) et si lambda
est élevé, celle-ci peut être gigantesque. La fonction C cumule l’avantage d’être la plus efficace
avec la consommation de mémoire la plus faible durant le calcul.
49
SEXP somme(SEXP x, SEXP y)
{
SEXP z;
PROTECT (z = allocVector (REALSXP , 1));
REAL(z)[0] = REAL(x)[0] + REAL(y)[0];
UNPROTECT (1);
return (z);
}
[1] 4.141593
SEXP S;
PROTECT (S = allocVector (REALSXP , _n));
double ∗ pS = REAL(S);
GetRNGstate ();
for(int i = 0; i != _n ; ++i)
{
N = rpois ( _lambda );
50
for(int k = 0; k != N ; k++)
pS[i] += rlnorm (_mu , _sigma );
}
PutRNGstate ();
UNPROTECT (1);
return (S);
}
Pour tous les vecteurs de taille 1, nous récupérons la valeur directement dans une variable de
la même manière que précédemment. Pour le vecteur S, nous récupérons le pointeur : c’est ce
qui permet d’accéder aux valeurs de S via un tableau après, avec pS[i]. Le reste du code est
identique.
La fonction d’appel dans R est simplement :
rsum ← function (n, lambda , mu , sigma)
return ( .Call ("rsum",
as.integer (n),
as.double ( lambda ),
as.double (mu),
as.double (sigma)
))
rsum (6, 5, 1, 1)
Références
[1] Nicolas Baradel. Assurance dommage. https://nicolasbaradel.fr/enseignement/
ressources/cours_assurance_dommage.pdf.
[2] Nicolas Baradel. Introduction au langage r. https://nicolasbaradel.fr/enseignement/
ressources/cours_r.pdf.
[3] Nicolas Baradel. Méthodes numériques en finance. https://nicolasbaradel.fr/
enseignement/ressources/cours_methodes_numeriques_finance.pdf.
[4] Nicolas Baradel. Théorie du risque. https://nicolasbaradel.fr/enseignement/
ressources/cours_theorie_du_risque.pdf.
[5] Nicolas Baradel. Langage R : Introduction à la Statistique, à l’Actuariat et à la Finance.
Economica, 2015.
51
Domaine Meilleur Eviter
Gestion des données data.table dplyr
Base de données SQL RODBC
Chaînes de caractères stringi stringr
Copules copula
52