Nous disposons de deux jeux de données initialement. freMTPLfreq de longueur 413169 qui répertorie :
ClaimNb)PolicyID)Expposure)Power)CarAge)DriverAge)Brand)Gas)Region)Density)summary(Contrats)
## PolicyID ClaimNb Exposure Power
## 1 : 1 Min. :0.00000 Min. :0.002732 f :95718
## 2 : 1 1st Qu.:0.00000 1st Qu.:0.200000 g :91198
## 3 : 1 Median :0.00000 Median :0.540000 e :77022
## 4 : 1 Mean :0.03916 Mean :0.561088 d :68014
## 5 : 1 3rd Qu.:0.00000 3rd Qu.:1.000000 h :26698
## 6 : 1 Max. :4.00000 Max. :1.990000 j :18038
## (Other):413163 (Other):36481
## CarAge DriverAge
## Min. : 0.000 Min. :18.00
## 1st Qu.: 3.000 1st Qu.:34.00
## Median : 7.000 Median :44.00
## Mean : 7.532 Mean :45.32
## 3rd Qu.: 12.000 3rd Qu.:54.00
## Max. :100.000 Max. :99.00
##
## Brand Gas
## Fiat : 16723 Diesel :205945
## Japanese (except Nissan) or Korean: 79060 Regular:207224
## Mercedes, Chrysler or BMW : 19280
## Opel, General Motors or Ford : 37402
## other : 9866
## Renault, Nissan or Citroen :218200
## Volkswagen, Audi, Skoda or Seat : 32638
## Region Density
## R24 :160601 Min. : 2
## R11 : 69791 1st Qu.: 67
## R53 : 42122 Median : 287
## R52 : 38751 Mean : 1985
## R72 : 31329 3rd Qu.: 1410
## R31 : 27285 Max. :27000
## (Other): 43290
L’autre jeu de données freMTPLsev de longueur 16181récapitule les montants de sinistres ClaimAmount pour chaque PolicyID.
summary(Sinistres)
## PolicyID ClaimAmount
## 18652 : 4 Min. : 2
## 226856 : 4 1st Qu.: 698
## 311457 : 4 Median : 1156
## 15608 : 3 Mean : 2130
## 25363 : 3 3rd Qu.: 1243
## 25580 : 3 Max. :2036833
## (Other):16160
L’on constate que plusieurs lignes correspondent au même PolicyID. En effet, dans ce jeu de données, les montants des sinistres sont répertoriés un par un et non aggrégés par numéro de police. Nous allons donc regrouper les montants de sinistres par numéro de police. Nous utiliserons les librairies dplyr et tidyr pour une gestion des données plus efficace.
Sinistres = summarise(group_by(Sinistres,PolicyID),ClaimAmount =sum(ClaimAmount))
summary(Sinistres)
## PolicyID ClaimAmount
## 33 : 1 Min. : 2
## 41 : 1 1st Qu.: 769
## 92 : 1 Median : 1163
## 96 : 1 Mean : 2239
## 142 : 1 3rd Qu.: 1279
## 182 : 1 Max. :2036833
## (Other):15384
Aprés avoir éliminé les doublons, nous avons maintenant 15390 lignes qui correspond au nombres d’assurés sinistrés.
Nous allons maintenant fusionner Sinistres et COntrats puis reconstituer deux nouveaux de jeux données, mydata.amount et mydata.freq ayant comme réponses respectives les montants de sinistres et les nombres de sinitres. Ces tables contiennent aussi toutes les autres variables explicatives à notre disposition.
### Fusion des deux jeux de données
mydata = merge(Contrats,Sinistres,by.x = "PolicyID", by.y = "PolicyID", all.x = TRUE)
mydata = replace(mydata, is.na(mydata), 0)
###Séparation en une base pour les montants de sinistres et une autre pour le nombre
mydata.amount =mydata %>%
filter(ClaimAmount != 0) %>%
select(- c(ClaimNb,PolicyID))
#summary(mydata.amount)
mydata.freq = mydata %>%
select(-c(ClaimAmount))
#summary(mydata.freq)
Nous procédons par la suite à la séparation des sinistres en graves et attritionnels. En effectuant un Mean Excess Plot, nous avons décidé de fixer un seuil à 20000. De ce fait il y a 112 sinistres qui seront considèrés comme étant graves et 99,31 % des sinistres sont considérés comme atritionnels.
### Choix d'un seuil pour les sinistres graves et séparation des données
meplot(mydata$ClaimAmount, col = "steelblue")
abline(v = 20000,col = "red")
#findthresh(mydata.amount$ClaimAmount, n = 133)# 133 sinistres considérés graves
#print(quantile(mydata.amount$ClaimAmount, probs = 0.99161))# proba sinistres attritionnels
seuil = 20000
Une fois notre seuil trouvé, nous séparons donc nos données. Nous précisons que cette séparation concerne que notre jeu de données mydata.amount qui nous servira à modéliser le coût des sinistres mais aussi mydata.freq.
policy.grave = mydata[mydata$ClaimAmount >= seuil,1]
mydata.amount.attritionnel = mydata.amount %>%
filter(ClaimAmount <= seuil)
mydata.amount.grave = mydata.amount %>%
filter(ClaimAmount > seuil)
mydata.freq = mydata.freq %>%
filter(!(PolicyID %in% policy.grave)) %>%
select(-PolicyID)
Avant de commencer la modélisation du coût et de la fréquence, nous allons d’abord verifier les corrélations entre les variables quantitatives pour éviter toute redondance entre celles-ci.
### Corrélation entre nos variables explicatives quantitatives
mydata.numeric = mydata %>%
select(c(Exposure,CarAge,DriverAge,Density))
mat.cor = cor(mydata.numeric)
col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))
corrplot(mat.cor, method="color", col=col(200), type="upper", addCoef.col = "black",
tl.col="black", tl.srt=45,diag=FALSE )
Les corrélations entre les variables explicactives quantitatives sont faibles, on les garde toutes donc. Nous allons commencer maintenant le calcul de la prime pure par le modèle fréquence-coût.
DAns cette partie, nous détaillerons entièrement le procédé qu’on utilise pour modéliser la fréquence et le montant des sinistres. Les autres parties seront donc plus succinctes. ### I.1 Modélisation de la fréquence Le jeu de données correspondant est mydata.freq. Nous le décomposerons en deux pour avoir un échantillon d’apprentissage et un autre de test.
set.seed(1234)
N = nrow(mydata.freq)
e = sort(sample(N, floor(N/2)))
e.1 = sort(sample(N, floor(N/5)))
freq.train = mydata.freq[e,]
freq.test = mydata.freq[-e,]
freq.train.1 = mydata.freq[e.1,]
freq.test.1 = mydata.freq[-e.1,]
summary(freq.train.1)
## ClaimNb Exposure Power CarAge
## Min. :0.00000 Min. :0.002732 f :19174 Min. : 0.000
## 1st Qu.:0.00000 1st Qu.:0.200000 g :18418 1st Qu.: 3.000
## Median :0.00000 Median :0.540000 e :15235 Median : 7.000
## Mean :0.04013 Mean :0.563072 d :13447 Mean : 7.525
## 3rd Qu.:0.00000 3rd Qu.:1.000000 h : 5263 3rd Qu.: 12.000
## Max. :4.00000 Max. :1.750000 j : 3658 Max. :100.000
## (Other): 7412
## DriverAge Brand
## Min. :18.00 Fiat : 3278
## 1st Qu.:34.00 Japanese (except Nissan) or Korean:15779
## Median :44.00 Mercedes, Chrysler or BMW : 3908
## Mean :45.37 Opel, General Motors or Ford : 7494
## 3rd Qu.:54.00 other : 1982
## Max. :99.00 Renault, Nissan or Citroen :43718
## Volkswagen, Audi, Skoda or Seat : 6448
## Gas Region Density
## Diesel :41115 R24 :32077 Min. : 2
## Regular:41492 R11 :13766 1st Qu.: 67
## R53 : 8440 Median : 287
## R52 : 7811 Mean : 1969
## R72 : 6285 3rd Qu.: 1414
## R31 : 5515 Max. :27000
## (Other): 8713
Nous vérifions ensuite que nos échantillons reprennent bien les caractéristiques essentielles des données initiales.
A = matrix(c(mean(mydata.freq$ClaimNb), mean(freq.train$ClaimNb),mean(freq.test$ClaimNb),
min(mydata.freq$ClaimNb),min(freq.train$ClaimNb),min(freq.test$ClaimNb),
max(mydata.freq$ClaimNb),max(freq.train$ClaimNb),max(freq.test$ClaimNb)), nrow = 3)
rownames(A) = c("données initiales", "apprentissage", "test")
colnames(A) = c("moyenne", "min", "max")
A
## moyenne min max
## données initiales 0.03879816 0 4
## apprentissage 0.03881036 0 4
## test 0.03878597 0 4
A.1 = matrix(c(mean(mydata.freq$ClaimNb), mean(freq.train.1$ClaimNb),mean(freq.test.1$ClaimNb),
min(mydata.freq$ClaimNb),min(freq.train.1$ClaimNb),min(freq.test.1$ClaimNb),
max(mydata.freq$ClaimNb),max(freq.train.1$ClaimNb),max(freq.test.1$ClaimNb)), nrow = 3)
rownames(A.1) = c("données initiales", "apprentissage", "test")
colnames(A.1) = c("moyenne", "min", "max")
A.1
## moyenne min max
## données initiales 0.03879816 0 4
## apprentissage 0.04012977 0 4
## test 0.03846526 0 4
On peut procéder maintenant à l’implémentation de l’algorithme CART grâce à rpart().
set.seed(12345)
freq.cart = rpart(ClaimNb ~ . ,data = freq.train, minbucket = 1000, cp = 0, weights = Exposure , method = "anova")
#summary(freq.cart)
fancyRpartPlot(freq.cart)
## Warning: labs do not fit even at cex 0.15, there may be some overplotting
#plot(nb.cart, uniform = TRUE)
Nous procédons à l’élagage de l’arbre pour obtenir l’arbre optimal.
freq.cart.cp.min = freq.cart.cp[freq.cart.cp[,4] == min(freq.cart.cp[,4]),1]
plot(freq.cart.cp[,1], freq.cart.cp[,4], type = "l")
La complexité cp optimal 1.563520910^{-4} est . L’élagage de l’arbre donne :
freq.cart.pruned = prune(freq.cart, cp = freq.cart.cp.min)
fancyRpartPlot(freq.cart.pruned)
#plot(nb.cart.pruned, uniform = TRUE)
Nous pouvons maintenant tester notre arbre sur l’échantillon de test.
freq.cart.test = predict(freq.cart.pruned, newdata = freq.test, type ="vector" )
Nous mesurons la qualité de la prédiction par la least square error :
lse = mean((freq.cart.test-freq.test$ClaimNb)^2)
lse
## [1] 0.04097941
mean(freq.cart.test)
## [1] 0.04055999
La modélisation avec les autres algorithmes nous permettra de mieux apprécier les mse obtenues dans ce chapitre.
Nous essayons à présent de modéliser le montant des sinistres.Nous séparons notre jeu de données mydata.amount.attritionnel en échantillons d’apprentissage amount.train (2/3 des données) et de test amount.test.
### Séparation apprentissage et test
L = nrow(mydata.amount.attritionnel)
set.seed(12345)
d = sort(sample(L, L/2))
amount.train = mydata.amount.attritionnel[d,]
amount.test = mydata.amount.attritionnel[-d,]
Nous vérifions ensuite que nos échantillons reprennent bien les caractéristiques essentielles des données initiales.
#Test sur les échantillons
B = matrix(c(mean(mydata.amount.attritionnel$ClaimAmount), mean(amount.train$ClaimAmount),mean(amount.test$ClaimAmount),
min(mydata.amount.attritionnel$ClaimAmount),min(amount.train$ClaimAmount),min(amount.test$ClaimAmount),
max(mydata.amount.attritionnel$ClaimAmount),max(amount.train$ClaimAmount),max(amount.test$ClaimAmount)), nrow = 3)
rownames(B) = c("données initiales", "apprentissage", "test")
colnames(B) = c("moyenne", "min", "max")
B
## moyenne min max
## données initiales 1455.986 2 19912
## apprentissage 1458.601 2 19912
## test 1453.370 2 19618
Arbre de régression pour le montant des sinistres.
set.seed(12345)
amount.cart = rpart(ClaimAmount ~. , data = amount.train , minbucket = 200, cp = 0, weights = Exposure )
fancyRpartPlot(amount.cart)
## Warning: labs do not fit even at cex 0.15, there may be some overplotting
Comme pour la fréquence nous allons essayer d’optimiser l’arbre:
amount.cart.cp = printcp(amount.cart)
##
## Regression tree:
## rpart(formula = ClaimAmount ~ ., data = amount.train, weights = Exposure,
## minbucket = 200, cp = 0)
##
## Variables actually used in tree construction:
## [1] Brand CarAge Density DriverAge Exposure Power Region
##
## Root node error: 1.353e+10/7628 = 1773668
##
## n= 7628
##
## CP nsplit rel error xerror xstd
## 1 0.00706769 0 1.00000 1.00098 0.086154
## 2 0.00369175 1 0.99293 0.99529 0.085600
## 3 0.00191862 2 0.98924 0.99345 0.085554
## 4 0.00176082 3 0.98732 0.99538 0.085433
## 5 0.00145701 4 0.98556 0.99648 0.085491
## 6 0.00137702 7 0.98119 0.99703 0.085456
## 7 0.00097413 8 0.97981 0.99950 0.085580
## 8 0.00094985 9 0.97884 1.00039 0.085414
## 9 0.00094805 10 0.97789 1.00042 0.085397
## 10 0.00092832 12 0.97599 1.00042 0.085397
## 11 0.00087747 13 0.97506 1.00096 0.085351
## 12 0.00084374 14 0.97419 1.00127 0.085285
## 13 0.00053099 15 0.97334 1.00123 0.085332
## 14 0.00042017 16 0.97281 1.00394 0.085479
## 15 0.00030606 17 0.97239 1.00399 0.085470
## 16 0.00027176 18 0.97209 1.00465 0.085579
## 17 0.00021782 19 0.97181 1.00471 0.085597
## 18 0.00000000 20 0.97160 1.00449 0.085563
amount.cart.cp.min = amount.cart.cp[amount.cart.cp[,4] == min(amount.cart.cp[,4]),1]
plot(amount.cart.cp[,1], amount.cart.cp[,4], type = "l")
En essayant d’élaguer l’abre, nous obtenons trop peu de feuilles. Nous allons donc considérer l’arbre optimal. De plus, nous verrons que la mse est meilleure avec l’abre maximal.
amount.cart.pruned = prune(amount.cart, cp = amount.cart.cp.min)
Prédiction et analyse des résidus sur l’échantillon de test.
amount.cart.pred = predict(amount.cart,amount.test)
amount.cart.pred.pruned = predict(amount.cart.pruned, amount.test)
summary(amount.cart.pred)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 991.6 1303.0 1360.0 1458.0 1677.0 2017.0
summary(amount.cart.pred.pruned)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1119 1367 1562 1428 1562 1562
Nous mesurons la qualité de la prédiction par la least square error :
lse.amount.cart = mean((amount.cart.pred-amount.test$ClaimAmount)^2)
lse.amount.cart.pruned = mean((amount.cart.pred.pruned-amount.test$ClaimAmount)^2)
lse.amount.cart
## [1] 3124853
lse.amount.cart.pruned
## [1] 3111924
Prime pure sans graves
sum(predict(freq.cart,mydata)*predict(amount.cart,mydata))
## [1] 23658419
Nous utilserons dans cette partie les échantillons d’apprentissage et de test créés dans le chapitre sur les arbres de régression et de classification.
Pour les fores aléatoires, nous utiliserons le package éponyme randomForest.
Apprentissage
set.seed(1234)
mydata.amount.1 = mydata.amount %>%
mutate(Exposure.1 = mydata.amount$ClaimAmount*mydata.amount$Exposure/(mydata.amount$ClaimAmount%*%mydata.amount$Exposure))
#%>% select(-Exposure)
L.1 = nrow(mydata.amount.1)
d.1 = sort(sample(L.1, floor(L.1/2)))
amount.train.1 = mydata.amount.1[d.1,]
amount.test.1 = mydata.amount.1[-d.1,]
#glimpse(amount.train.1)
amount.rf = randomForest(ClaimAmount ~. , data = amount.train.1, ntree = 1000,nodesize = 500)
Prédiction sur l’échantillon test
amount.rf.test = predict(amount.rf, newdata = amount.test.1)
summary(amount.rf.test)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1147 1354 1574 2243 1990 513900
Erreur de prédiction
mse.amount.rf = mean((amount.rf.test-amount.test.1$ClaimAmount)^2)
mse.amount.rf
## [1] 120602938
Apprentissage
Prédiction sur l’échantillon de test
freq.nnet.test = predict(freq.nnet, freq.test.1)
summary(freq.nnet.test)
## V1
## Min. :0.007229
## 1st Qu.:0.030274
## Median :0.040704
## Mean :0.044742
## 3rd Qu.:0.054472
## Max. :0.317264
#print(freq.nnet.test)
#for(i in 1:length(freq.nnet.test)){if(freq.nnet.test[i] <= 0){freq.nnet.test[i]=0}}
Erreur de prédiction
mse.freq.nnet = mean((freq.nnet.test - freq.test.1$ClaimNb )^2)
mse.freq.nnet
## [1] 0.04052797
Apprentissage
Prédiction sur l’échantillon de test
amount.nnet.test =max(amount.train$ClaimAmount)* predict(amount.nnet,amount.test )
summary(amount.nnet.test)
## V1
## Min. :-111.1
## 1st Qu.:1263.8
## Median :1433.7
## Mean :1463.2
## 3rd Qu.:1642.4
## Max. :3331.2
#max(amount.train$ClaimAmount)*
Erreur de prédiction
mse.amount.nnet = mean((amount.nnet.test-amount.test$ClaimAmount)^2)
mse.amount.nnet
## [1] 3116405
Nous utiliserons la fonction gbm du package éponyme. Apprentissage
set.seed(1234)
e1 = sort(sample(N, floor(N/5)))
freq.train.1 = mydata.freq[e1,]
freq.test.1 = mydata.freq[-e1,]
freq.gbm = gbm(ClaimNb~., data = freq.train.1,
distribution = "poisson",n.trees = 2000,
shrinkage = 0.1,n.minobsinnode = 1000)
summary(freq.gbm)
## var rel.inf
## Power Power 20.0712666
## Exposure Exposure 17.7463596
## DriverAge DriverAge 17.1290939
## Density Density 15.7034882
## Region Region 15.1152414
## Brand Brand 9.2556291
## CarAge CarAge 4.5049579
## Gas Gas 0.4739633
Nous utilisons la fonction gbm.perf() pour estimer un nombre d’itérations optimal.
print(best.iter)
## [1] 54
Nous utiliserons donc 54, 54, 54, 54, 54, 54 itérations pour nos prédictions. Prédiction sur l’échantillon de test
freq.gbm.test = predict(freq.gbm,freq.test, n.trees = best.iter)
summary(exp(freq.gbm.test))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.008284 0.024640 0.042170 0.038910 0.050520 0.128100
Erreur de prédiction
lse.freq.gbm = mean((exp(freq.gbm.test)- freq.test$ClaimNb)^2)
lse.freq.gbm
## [1] 0.04092621
Apprentissage
#amount.train$ClaimAmount = amount.train$ClaimAmount
amount.gbm = gbm(ClaimAmount~., data = amount.train,
distribution = "gaussian",n.trees = 2000,
shrinkage = 0.1,n.minobsinnode = 500, weights = Exposure)
summary(amount.gbm)
## var rel.inf
## Power Power 31.0463764
## Region Region 25.9018404
## Brand Brand 10.9749917
## Density Density 10.6672954
## Exposure Exposure 7.4978601
## DriverAge DriverAge 7.4162482
## CarAge CarAge 5.9020136
## Gas Gas 0.5933741
best.iter.amount = gbm.perf(amount.gbm,plot.it = TRUE,oobag.curve = TRUE,overlay = TRUE, method="OOB")
## Warning in gbm.perf(amount.gbm, plot.it = TRUE, oobag.curve = TRUE, overlay
## = TRUE, : OOB generally underestimates the optimal number of iterations
## although predictive performance is reasonably competitive. Using cv.folds>0
## when calling gbm usually results in improved predictive performance.
#best.iter = gbm.perf(freq.gbm,plot.it = TRUE,oobag.curve = TRUE,overlay = TRUE,method="cv")
summary(best.iter.amount)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 29 29 29 29 29 29
Prédiction sur l’échantillon de test
amount.gbm.test = predict(amount.gbm,amount.test, n.trees = best.iter.amount)
summary(amount.gbm.test)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1156 1319 1425 1429 1535 1827
Erreur de prédiction
lse.amount.gbm = mean((amount.gbm.test- amount.test$ClaimAmount)^2)
lse.amount.gbm
## [1] 3099919
Nous avons donc mis en oeuvre 4 algorithmes différents de Machine learning. Nous allons maintenant comparer leurs qualités de prédiction et en choisir un seul.