1 Données

On commence par charger les données utilisées. Il s’agit du même jeu de données que celui utilisé dans le premier bloc-notes, auquel on renvoie pour davantage d’explications sur les données.

load(url("https://irma.math.unistra.fr/~jberard/donnees_swautoins_ext_sim"))

Les données ainsi récupérées se trouvent dans une variable appelée donnees.

str(donnees)
## 'data.frame':    239444 obs. of  7 variables:
##  $ Kilometres: Factor w/ 5 levels "1","2","3","4",..: 1 2 3 4 5 1 2 3 4 5 ...
##  $ Zone      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 2 2 2 2 2 ...
##  $ Bonus     : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Make      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Claims    : int  0 1 3 3 6 1 1 1 2 2 ...
##  $ Insured   : num  6.36 7.37 6.81 6.56 13.53 ...
##  $ Payment   : num  0 5475 21866 15230 31354 ...

2 Modèle log-Gamma pour le coût d’un sinistre individuel

2.1 Préparation du jeu de données

On va ici s’intéresser à la variable Payment, qui représente le montant total (en couronnes suédoises de 1977) de sinistres associé à chaque observation (ici, une observation est représentée par une ligne du jeu de données, et correspond à une ou plusieurs polices d’assurance).

Plus précisément, nous allons ajuster un modèle log-Gamma pour le coût individuel d’un sinistre, en fonction des variables explicatives Kilometres, Bonus, Zone et Make.

On commence par extraire du jeu de données les lignes correspondant effectivement à des polices sinistrées, c’est-à-dire comportant un nombre de sinistres non-nul. L’extraction est effectuée au moyen de la commande subset, les lignes sélectionnées étant caractérisées par une valeur strictement positive de la variable Claims. On constate que le jeu de données ainsi obtenu est fortement réduit par rapport au jeu de données intial, puisque l’on ne conserve que 83980 lignes sur les 239444 initialement présentes.

donnees <- subset(donnees, subset = (Claims > 0))
str(donnees)
## 'data.frame':    83980 obs. of  7 variables:
##  $ Kilometres: Factor w/ 5 levels "1","2","3","4",..: 2 3 4 5 1 2 3 4 5 2 ...
##  $ Zone      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 2 2 2 2 2 3 ...
##  $ Bonus     : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Make      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Claims    : int  1 3 3 6 1 1 1 2 2 3 ...
##  $ Insured   : num  7.37 6.81 6.56 13.53 8.2 ...
##  $ Payment   : num  5475 21866 15230 31354 7508 ...

Chaque ligne du jeu de données ainsi sélectionné correspond à un nombre de sinistres individuels variable d’une ligne à l’autre, dont la variable Payment indique le coût total, c’est-à-dire la somme des coûts des sinistres individuels correspondants. Notre but étant de modéliser le coût d’un sinistre individuel, on travaillera donc avec les coûts de sinistres normalisés par ce nombre, en utilisant le nombre de sinistres comme un poids dans l’ajustement du modèle GLM correspondant. On ajoute à cette fin une variable nommée Payments_n dans le jeu de données, qui contenant les coûts ainsi normalisés.

donnees[["Payment_n"]] <- donnees[["Payment"]] / donnees[["Claims"]]
str(donnees)
## 'data.frame':    83980 obs. of  8 variables:
##  $ Kilometres: Factor w/ 5 levels "1","2","3","4",..: 2 3 4 5 1 2 3 4 5 2 ...
##  $ Zone      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 2 2 2 2 2 3 ...
##  $ Bonus     : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Make      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Claims    : int  1 3 3 6 1 1 1 2 2 3 ...
##  $ Insured   : num  7.37 6.81 6.56 13.53 8.2 ...
##  $ Payment   : num  5475 21866 15230 31354 7508 ...
##  $ Payment_n : num  5475 7289 5077 5226 7508 ...

2.2 Ajustement du modèle GLM

On procède maintenant à l’ajustement du modèle GLM, en prenant soin de choisir les coûts normalisés (Payment_n) comme variable réponse, et les nombres de sinistres (Claims) comme poids.

ajustement <- glm(formula = Payment_n ~ Kilometres + Zone + Bonus + Make, 
                  family = Gamma(link = "log"),
                  weights = Claims,
                  data = donnees)
summary(ajustement)
## 
## Call:
## glm(formula = Payment_n ~ Kilometres + Zone + Bonus + Make, family = Gamma(link = "log"), 
##     data = donnees, weights = Claims)
## 
## Coefficients:
##              Estimate Std. Error  t value Pr(>|t|)    
## (Intercept)  8.487525   0.007525 1127.849  < 2e-16 ***
## Kilometres2  0.004559   0.004124    1.106  0.26891    
## Kilometres3  0.001646   0.004738    0.347  0.72830    
## Kilometres4  0.003231   0.006589    0.490  0.62392    
## Kilometres5 -0.004860   0.006979   -0.696  0.48617    
## Zone2        0.025130   0.005209    4.824 1.41e-06 ***
## Zone3        0.044600   0.005309    8.401  < 2e-16 ***
## Zone4        0.135446   0.004748   28.528  < 2e-16 ***
## Zone5        0.056600   0.007942    7.127 1.03e-12 ***
## Zone6        0.151309   0.006526   23.187  < 2e-16 ***
## Zone7        0.045258   0.022049    2.053  0.04011 *  
## Bonus2       0.008902   0.006631    1.343  0.17939    
## Bonus3      -0.001263   0.007398   -0.171  0.86446    
## Bonus4       0.014629   0.007959    1.838  0.06604 .  
## Bonus5       0.001818   0.007710    0.236  0.81353    
## Bonus6      -0.002833   0.006374   -0.444  0.65674    
## Bonus7       0.003935   0.004758    0.827  0.40818    
## Make2       -0.030249   0.011623   -2.603  0.00926 ** 
## Make3        0.079690   0.013809    5.771 7.92e-09 ***
## Make4       -0.211314   0.012994  -16.263  < 2e-16 ***
## Make5       -0.088844   0.011153   -7.966 1.66e-15 ***
## Make6       -0.057513   0.009525   -6.038 1.56e-09 ***
## Make7-9     -0.140698   0.005414  -25.989  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for Gamma family taken to be 0.2993475)
## 
##     Null deviance: 26953  on 83979  degrees of freedom
## Residual deviance: 26217  on 83957  degrees of freedom
## AIC: 2039224
## 
## Number of Fisher Scoring iterations: 4

On constate que le paramètre de dispersion \(\phi\) estimé est égal à 0.2993475.

On peut extraire cette valeur estimée à l’aide de la commande ci-dessous.

summary(ajustement)[["dispersion"]]
## [1] 0.2993475

2.3 Estimation du paramètre de dispersion

On examine maintenant de plus près l’estimation du paramètre de dispersion \(\phi\).

2.3.1 Estimateur basé sur la statistique de Pearson

L’estimation précédente est obtenue à l’aide de l’estimateur classique \[\hat{\phi} = \frac{1}{N-(d+1)}\sum_{i=1}^N w_i \cdot \frac{(Y_i -\hat{\mu}_i)^2}{v(\hat{\mu}_i)}.\]

On peut retrouver cette valeur “à la main”, en extrayant directement les résidus de Pearson \(\frac{(Y_i -\hat{\mu}_i)}{\sqrt{v(\hat{\mu}_i)/w_i}}\) du modèle ajusté à l’aide de la fonction residuals, et en calculant la somme de leurs carrés.

stat_Pearson <- sum(residuals(ajustement, type = "pearson")^2)
stat_Pearson # Statistique de Pearson
## [1] 25132.32

On accède ensuite à la quantité \(N-(d+1)\), appelée “nombre de degrés de liberté” du modèle ajusté, en faisant appel à la fonction df.residual.

deg_lib <- df.residual(ajustement)
deg_lib # Nombre de degrés de liberté
## [1] 83957

On peut ainsi recalculer l’estimation du paramètre de dispersion, et constater que l’on obtient la même valeur que celle constatée plus haut.

phi_est <- stat_Pearson / deg_lib
phi_est # Valeur estimée de phi
## [1] 0.2993475

On peut aller un cran plus loin dans l’extraction “manuelle” des quantités précédentes, comme illustré maintenant. L’intérêt de ce qui suit est de montrer comment accéder individuellement aux éléments intervenant dans ce type de calculs.

On commence par extraire la fonction de variance \(v\) associée à la loi du modèle que l’on a ajusté. (Dans notre cas précis, nous savons que, pour la loi Gamma, la fonction de variance est \(v(\mu)=\mu^2\)), mais il est plus commode d’obtenir celle-ci automatiquement.)

fonc_variance <- family(ajustement)[["variance"]]

On vérifie que la fonction ainsi extraite est bien celle attendue.

fonc_variance
## function (mu) 
## mu^2
## <bytecode: 0x7f8205f7e0c0>
## <environment: 0x7f8205f83048>

On extrait maintenant les valeurs modélisées pour l’espérance des réponses normalisées (puisque c’est sur des réponses normalisées que le modèle a été ajusté) sur les données utilisées pour l’ajustement.

esp_mod_n <- fitted(ajustement)

On récupère également les poids de normalisation utilisés (ici, les nombres de sinistres).

poids <- donnees[["Claims"]]

Enfin, on récupère les valeurs des réponses normalisées dans les données d’ajustement.

reponse_n <- donnees[["Payment_n"]]

On peut maintenant calculer la statistique de Pearson en appliquant élément par élément la formule qui la définit.

sum(poids * (reponse_n - esp_mod_n)^2 / fonc_variance(esp_mod_n))
## [1] 25132.32

Si l’on y tient, on peut encore extraire “à la main” le nombre de coefficients (\(d+1\)) et le nombre de lignes (\(N\)) du jeu de données utilisé pour l’ajustement.

N <- nrow(donnees)
d_plus_un <- length(coef(ajustement))
N-d_plus_un
## [1] 83957

2.3.2 Estimateur du maximum de vraisemblance

Une autre approche possible pour estimer le paramètre de dispersion est d’estimer celui-ci par maximum de vraisemblance, ce qui est par exemple possible pour un GLM gamma en utilisant la fonction gamma.shape du paquet MASS, qui fournit une estimation du paramètre de forme \(k\), appelé ici alpha (attention : on a \(\phi=1/k\)), avec non seulement une estimation ponctuelle mais également un écart-type estimé permettant, si on le souhaite, de construire des intervalles de confiance en utilisant l’approximation normale. La composante alpha de l’objet renvoyé par la fonction donne la valeur estimée de \(k\), tandis que la composante SE donne la valeur estimée de l’écart-type de l’estimateur de \(k\) fournissant cette valeur estimée.

library(MASS)
forme_est <- gamma.shape(ajustement)
str(forme_est)
## List of 2
##  $ alpha: num 3.34
##  $ SE   : num 0.0156
##  - attr(*, "class")= chr "gamma.shape"
phi_est_bis <- 1 / forme_est[["alpha"]]
phi_est_bis # Valeur estimée de phi
## [1] 0.2994553

On constate que la valeur ainsi estimée pour \(\phi\) est voisine de celle obtenue avec l’approche basée sur la statistique de Pearson.

2.3.3 Estimateur basé sur la déviance

Une troisième approche non-conseillée utilise la déviance, en estimant \(\phi\) par le rapport \(D/(N-d+1)\). Ce calcul est effectué ci-après pour mémoire.

phi_est_ter <- deviance(ajustement)/df.residual(ajustement)
phi_est_ter # Valeur estimée de phi
## [1] 0.3122628

Dans le cas présent, la valeur obtenue n’est pas très éloignée de celle obtenue par les deux autres méthodes.

2.4 Déviance

La déviance associée au modèle ajusté (appelée “residual deviance”) peut être extraite facilement à l’aide de la fonction deviance.

D <- deviance(ajustement)
D
## [1] 26216.65

On rappelle que celle-ci est définie par la formule \(D=\sum_{i=1}^N w_i \cdot D\left(y_i, \mu_i^{\mbox{est.}} \right)\), où \(D\) est la fonction de déviance associée à la loi utilisée par le modèle. On recalcule maintenant cette valeur “à la main”.

On commence par extraire la fonction de déviance dont on a besoin. (On sait qu’il s’agit de la fonction de déviance associée à la loi Gamma, soit \(D(y,\mu)=-2 \left[\log(y/\mu) - \frac{y-\mu}{\mu} \right]\), mais on l’extrait ici de manière systématique.)

fonc_deviance <- family(ajustement)[["dev.resids"]]
fonc_deviance
## function (y, mu, wt) 
## -2 * wt * (log(ifelse(y == 0, 1, y/mu)) - (y - mu)/mu)
## <bytecode: 0x7f8205f7d950>
## <environment: 0x7f8205f83048>

La fonction ainsi extraite inclut comme argument non-seulement la valeur de la réponse (y) et de l’espérance modélisée (mu), mais également le poids (wt), de manière à renvoyer directement la valeur \(w_i \cdot D\left(y_i, \mu_i^{\mbox{est.}} \right)\) lorsque y et mu sont les valeurs normalisées de la réponse et de l’espérance modélisée, et wt le poids utilisé. On peut donc re-calculer la déviance en sommant comme ci-dessous.

sum(fonc_deviance(y = reponse_n, mu = esp_mod_n, wt = poids))
## [1] 26216.65

On obtient bien une valeur identique à celle obtenue précédemment.

La déviance \(D_0\) (“null deviance”) est associée à un modèle ajusté en utilisant uniquement un terme constant, c’est-à-dire un modèle dont l’espérance modélisée est toujours égale à la moyenne globale des réponses observées, indépendamment des valeurs des variables explicatives.

Il n’y a pas de fonction dédiée pour extraire celle-ci, mais on peut obtenir sa valeur en accédant au champ nommé null.deviance de l’objet renvoyé par la fonction glm.

D_0 <- ajustement[["null.deviance"]]
D_0
## [1] 26953.47

(Remarque : dans ce qui précède, nous avons utilisé diverses fonctions d’extraction : coef, family, df.resid, deviance, fitted, etc. pour accéder à des valeurs associées au modèle ajusté. Dans chacun de ces cas, il était également possible d’accéder à ces valeurs à l’aide du champ correspondant de l’objet renvoyé par la fonction glm : ajustement[["coefficients"]], ajustement[["family"]], ajustement[["df.residual"]], ajustement[["deviance"]], ajustement[["fitted.values"]], etc. On peut néanmoins considérer qu’il est préférable de passer par des fonctions d’extraction dédiées, lorsqu’elles existent, plutôt que de manipuler la structure interne des objets.)

Pour retrouver la valeur de \(D_0\), on calcule la moyenne globale des réponses.

rep_moyenne <- sum(reponse_n * poids) / sum(poids)

On calcule ensuite la somme correspondante, et l’on vérifie que la valeur obtenue est bien identique à celle obtenue précédemment.

sum(fonc_deviance(y = reponse_n, mu = rep_moyenne, wt = poids))
## [1] 26953.47

On peut maintenant calculer le pseudo-\(R^2\) donné par la formule \(1-D_0/D\).

r_D_sq <- (1 - D / D_0)
r_D_sq
## [1] 0.02733705

La valeur obtenue (0.0273371) ne semble pas très élevée si on la compare à des références couramment mentionnées dans des contextes de modélisation différents. Cependant, lorsque le phénomène étudié présente un caractère fortement aléatoire à l’échelle d’une unité d’observation (ici, une ligne du jeu de données), il n’est pas surprenant que le modèle utilisé ne parvienne à rendre compte que d’une faible part de la variabilité de la réponse. En conservant le même modèle, mais en utilisant des unités d’observation associées à des expositions plus importantes, les valeurs obtenues peuvent changer considérablement.

On illustre cela ci-après, en regroupant les lignes du jeu de données possédant exactement la même combinaison de valeurs des variables explicatives.

donnees_groupe <- aggregate(x = cbind(Claims, Payment, Insured) ~ Kilometres + Zone + Bonus + Make,
                            data = donnees,
                            FUN = sum)
str(donnees_groupe)
## 'data.frame':    1428 obs. of  7 variables:
##  $ Kilometres: Factor w/ 5 levels "1","2","3","4",..: 1 2 3 4 5 1 2 3 4 5 ...
##  $ Zone      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 2 2 2 2 2 ...
##  $ Bonus     : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Make      : Factor w/ 7 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Claims    : num  73 94 46 10 12 69 109 50 8 7 ...
##  $ Payment   : num  336602 478366 201823 56473 67723 ...
##  $ Insured   : num  357.4 514.9 178.2 36.3 39.4 ...
donnees_groupe[["Payment_n"]] <- donnees_groupe[["Payment"]] / donnees_groupe[["Claims"]]
ajustement_groupe <- glm(formula = Payment_n ~ Kilometres + Zone + Bonus + Make, 
                         family = Gamma(link = "log"),
                         weights = Claims,
                         data = donnees_groupe)
summary(ajustement_groupe)
## 
## Call:
## glm(formula = Payment_n ~ Kilometres + Zone + Bonus + Make, family = Gamma(link = "log"), 
##     data = donnees_groupe, weights = Claims)
## 
## Coefficients:
##              Estimate Std. Error  t value Pr(>|t|)    
## (Intercept)  8.487525   0.007674 1105.978  < 2e-16 ***
## Kilometres2  0.004560   0.004206    1.084   0.2785    
## Kilometres3  0.001646   0.004831    0.341   0.7334    
## Kilometres4  0.003231   0.006719    0.481   0.6307    
## Kilometres5 -0.004860   0.007117   -0.683   0.4948    
## Zone2        0.025130   0.005312    4.730 2.47e-06 ***
## Zone3        0.044600   0.005414    8.238 3.96e-16 ***
## Zone4        0.135446   0.004842   27.975  < 2e-16 ***
## Zone5        0.056600   0.008099    6.989 4.27e-12 ***
## Zone6        0.151309   0.006655   22.737  < 2e-16 ***
## Zone7        0.045258   0.022485    2.013   0.0443 *  
## Bonus2       0.008902   0.006762    1.317   0.1882    
## Bonus3      -0.001263   0.007545   -0.167   0.8671    
## Bonus4       0.014629   0.008116    1.802   0.0717 .  
## Bonus5       0.001818   0.007862    0.231   0.8171    
## Bonus6      -0.002833   0.006500   -0.436   0.6630    
## Bonus7       0.003935   0.004852    0.811   0.4175    
## Make2       -0.030249   0.011853   -2.552   0.0108 *  
## Make3        0.079689   0.014082    5.659 1.85e-08 ***
## Make4       -0.211315   0.013251  -15.947  < 2e-16 ***
## Make5       -0.088844   0.011373   -7.812 1.10e-14 ***
## Make6       -0.057514   0.009713   -5.921 4.01e-09 ***
## Make7-9     -0.140698   0.005521  -25.485  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for Gamma family taken to be 0.3113038)
## 
##     Null deviance: 1176.14  on 1427  degrees of freedom
## Residual deviance:  439.31  on 1405  degrees of freedom
## AIC: 1599345
## 
## Number of Fisher Scoring iterations: 4
r_D_sq_groupe <- 1 - ajustement_groupe[["deviance"]] / ajustement_groupe[["null.deviance"]]
r_D_sq_groupe
## [1] 0.6264814