knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
Les données simulées sont disponibles à même le package. D'autres packages doivent aussi être importés.
library(xgbmr) library(tidyverse) library(xgboost) library(mlr) library(iml) library(xtable) # pour des fins de présentation library(gridExtra) dt <- SimulatedIndClaims
Avant de pouvoir utiliser les données dans l'algorithme XGBoost, on doit d'abord effectuer un certain traitement des données.
Dans la pratique, nous ne connaissons pas encore le vrai montant à l'ultime pour les réclamations qui sont plus récentes. Cet aspect a été tenu en compte, et une fonction du package permet de censurer les réclamations pour lesquelles nous ne sommes pas censé connaître l'information
dat <- dt %>% censoreDataset(evalYr = 2005)
set.seed(20191024) # Même seed que dans presentation-data.Rnw dat %>% select(ClNr, AY, starts_with('Pay'), real_ultimate) %>% sample_n(5) %>% knitr::kable()
set.seed(20191023) index <- makeResampleInstance( desc = makeResampleDesc('Holdout', stratify.cols = 'AY', split = 0.70), # il faut absolument donner une tâche et un target bidon task = makeRegrTask(data = dt, target = 'Pay00') ) traindt <- dat[index$train.inds[[1]],] testdt <- dat[index$test.inds[[1]],]
mack_bs_tri <- traindt %>% getAggrTriangle() %>% ChainLadder::BootChainLadder(R = 1000) ldf_bs <- mack_bs_tri$simClaims %>% apply(3, function(triangle){ triangle %>% incr2cum() %>% as.triangle() %>% getLDF() }) %>% t() %>% apply(2, quantile, probs = 0.8) data.frame(LDF = ldf_bs) %>% t() %>% knitr::kable()
train_a <- traindt %>% widetolong() %>% filter(complete.cases(.)) %>% # garder les années connues filter(!(Open == 1 & Paid == 0)) %>% # enlever masse à 0 select(-AY, -TotalPaid, -Pay, -still, -max_dev_yr) %>% rename(Ultimate = real_ultimate) %>% # Arranger le dataset final pour le modèle A select(ClNr:RepDel, devYear, Paid, Open, Ultimate) %>% as_tibble() train_b <- traindt %>% widetolong() %>% filter(complete.cases(.)) %>% mutate(Ultimate = TotalPaid) %>% filter(!(Open == 1 & Paid == 0)) %>% # enlever masse à 0 filter(Open == 0) %>% select(-AY, -TotalPaid, -Pay, -still, -max_dev_yr, -real_ultimate) %>% as_tibble() train_c <- traindt %>% widetolong() %>% filter(complete.cases(.)) %>% # garder les années connues mutate(Ultimate = case_when( still == 1 ~ TotalPaid * ldf_bs[max_dev_yr + 1], TRUE ~ TotalPaid)) %>% filter(!(Open == 1 & Paid == 0)) %>% # enlever masse à 0 select(-AY, -TotalPaid, -Pay, -still, -max_dev_yr, -real_ultimate) %>% as_tibble() # pour enlever le tag grouped df
Idem pour le test set :
test <- testdt %>% widetolong() %>% filter(complete.cases(.)) %>% # garder les années connues rename(Ultimate = real_ultimate) %>% # Arranger le dataset final pour le modèle A select(ClNr:RepDel, devYear, Paid, Open, Ultimate) %>% as_tibble()
Étant donné que les modèles xgboost peuvent être long à rouler, des modèles déjà entrainés sur les données d'exemple sont inclus avec le package. Les hyperparamètres des modèles sont disponibles dans la table suivante :
fit_a$learner$par.vals %>% data.frame(Modèle = 'A', .) %>% bind_rows(fit_b$learner$par.vals %>% data.frame(Modèle = 'B', .), fit_c$learner$par.vals %>% data.frame(Modèle = 'C', .)) %>% knitr::kable()
Note : la vignette entrainement_modeles permet d'avoir un peu plus de détail sur la procédure.
test_dt <- test %>% select(-ClNr) test_tsk <- makeRegrTask(data = test_dt, target = 'Ultimate')
Les modèles sont déjà inclus dans le package pour l'exemple
pred_a <- predict(fit_a, test_tsk) pred_b <- predict(fit_b, test_tsk) pred_c <- predict(fit_c, test_tsk)
Mettre ensemble les variables explicatives et les prédictions
test_pred_a <- test_dt %>% rownames_to_column('id') %>% mutate_at(vars(id), as.integer) %>% left_join(pred_a$data, by = "id") test_pred_b <- test_dt %>% rownames_to_column('id') %>% mutate_at(vars(id), as.integer) %>% left_join(pred_b$data, by = "id") test_pred_c <- test_dt %>% rownames_to_column('id') %>% mutate_at(vars(id), as.integer) %>% left_join(pred_c$data, by = "id")
## Vraie réserve (selon données simulées du test set) vraie_reserve <- test_dt %>% summarise(paye = sum(Paid), total = sum(Ultimate), R = total - paye) ## Calcul des réserves totales selon chaque modèle aa <- apply(bs_pred_a, 2, sum) bs_res_a <- (aa - sum(test_dt$Paid)) %>% data.frame(A = .) bb <- apply(bs_pred_b, 2, sum) bs_res_b <- (bb - sum(test_dt$Paid)) %>% data.frame(B = .) cc <- apply(bs_pred_c, 2, sum) bs_res_c <- (cc - sum(test_dt$Paid)) %>% data.frame(C = .)
Et on obtient le tableau suivant :
bind_cols(bs_res_a, bs_res_b, bs_res_c) %>% gather('Modèle', 'Réserve') %>% group_by(Modèle) %>% nest() %>% mutate( moyenne = map_dbl(data,function(x) unlist(x) %>% mean), sd = map_dbl(data,function(x) unlist(x) %>% sd), VaR95 = map_dbl(data,function(x) unlist(x) %>% quantile(., probs = .95)), VaR99 = map_dbl(data,function(x) unlist(x) %>% quantile(., probs = .99)) ) %>% select(-data) %>% data.frame() %>% format(big.mark = ' ', justify = 'centre') %>% knitr::kable()
Et le graphique suivant :
bind_cols(bs_res_a, bs_res_b, bs_res_c) %>% ggplot() + geom_density(aes(x = A, y = ..density.., fill = 'A'), n = 2^12, alpha = 0.4) + geom_density(aes(x = B, y = ..density.., fill = 'B'), n = 2^12, alpha = 0.4) + geom_density(aes(x = C, y = ..density.., fill = 'C'), n = 2^12, alpha = 0.4) + theme_classic() + theme(legend.position = 'bottom') + labs(fill = 'Modèles', x = 'Réserve totale', y = 'Probabilité') + geom_vline(xintercept = vraie_reserve$R, linetype = 'dashed')
zoom_out_pred <- pred_c$data %>% # filter(response > 0) %>% ggplot(aes(x = truth, y = response)) + geom_point() + annotate('rect', xmin = 0, xmax = 200000, ymin = 0, ymax = 200000, alpha = 0.3, col = 'blue') + # geom_vline(xintercept = qq, col = 'red', linetype = 'dashed') + geom_abline(intercept = 0, slope = 1, col = 'blue', size = 1, linetype = 'dashed') + # coord_cartesian(xlim = c(0, 25000), ylim = c(0,25000)) + labs(y = 'Valeur prédite') zoom_in_pred <- pred_c$data %>% # filter(response > 0) %>% ggplot(aes(x = truth, y = response)) + geom_point() + # geom_vline(xintercept = qq, col = 'red', linetype = 'dashed') + geom_abline(intercept = 0, slope = 1, col = 'blue', size = 1, linetype = 'dashed') + coord_cartesian(xlim = c(0, 200000), ylim = c(0,200000)) + labs(y = 'Valeur prédite') zoom <- arrangeGrob(zoom_out_pred, zoom_in_pred) plot(zoom)
pred_a$data %>% rename(A = response) %>% mutate( B = pred_b$data$response, C = pred_c$data$response ) %>% gather('Modèle', 'response', A,B,C) %>% group_by(Modèle) %>% nest() %>% mutate(quantile = map(data, function(dt){ dt %>% transmute(quantile = ntile(response, n = 5)) })) %>% unnest(cols = c(data, quantile)) %>% select(-id) %>% group_by(Modèle, quantile) %>% summarise( reel_moyen = mean(truth), rmse_moyen = sqrt(mean((truth - response)^2)) ) %>% mutate(quantile = case_when( quantile == 1 ~ "(0, 20)", quantile == 2 ~ "(20, 40)", quantile == 3 ~ "(40, 60)", quantile == 4 ~ "(60, 80)", quantile == 5 ~ "(80, 100)" )) %>% ggplot(aes(x = quantile)) + geom_bar(aes(y = reel_moyen),stat = 'identity', fill = 'green', alpha = 0.4, col = 'black') + geom_line(aes(y = rmse_moyen, col = 'RMSE moyen par quantile'), linetype = 'dashed', group =1) + geom_point(aes(y = rmse_moyen), col = 'red') + theme(legend.position = 'bottom', axis.text = element_text(angle = 90, hjust = 1, vjust = 0.5)) + coord_cartesian(ylim = c(0, 15000)) + labs( y = "Valeur réelle moyenne", x = "Regroupement des quantiles de la variable réponse", col = NULL) + facet_grid(. ~ Modèle)
rmse_selon_variable(test_pred_c, devYear) + scale_x_discrete(limits = 0:11)
set.seed(2019) pred_a$data %>% sample_frac(.1) %>% mutate(erreur = truth - response) %>% ggplot() + geom_histogram(aes(x = erreur), binwidth = 100) + coord_cartesian(xlim = c(-2000, 2000)) + labs(x = expression(epsilon = y[i] - hat(y)[i]), y = 'Fréquence', caption = 'Échantillon aléatoire de 10% des prédictions sur les données de validation') + scale_y_discrete(limits = seq(0, 2000, 100))
set.seed(2019) predictor_a <- test_dt %>% # On sélectionne 30 observations par année de développement group_by(devYear) %>% sample_n(30) %>% # on rammène en tibble car iml n'accepte pas un 'grouped_df' de dplyr as_tibble() %>% Predictor$new(model = fit_a, data = select(., -Ultimate), y= select(., Ultimate)) predictor_b <- test_dt %>% # On sélectionne 30 observations par année de développement group_by(devYear) %>% sample_n(30) %>% # on rammène en tibble car iml n'accepte pas un 'grouped_df' de dplyr as_tibble() %>% Predictor$new(model = fit_b, data = select(., -Ultimate), y= select(., Ultimate)) predictor_c <- test_dt %>% # On sélectionne 30 observations par année de développement group_by(devYear) %>% sample_n(30) %>% # on rammène en tibble car iml n'accepte pas un 'grouped_df' de dplyr as_tibble() %>% Predictor$new(model = fit_a, data = select(., -Ultimate), y= select(., Ultimate))
varimp_a <- FeatureImp$new(predictor_a, loss = "mse") %>% plot() + theme_classic() varimp_b <- FeatureImp$new(predictor_b, loss = "mse") %>% plot() + theme_classic() varimp_c <- FeatureImp$new(predictor_c, loss = "mse") %>% plot() + theme_classic() # grid.arrange(varimp_a, varimp_b, varimp_c, nrow = 3) varimp_c
var2check <- c('Paid', 'Open') pdp_ice <- lapply(var2check, function(var){ FeatureEffect$new(predictor_a, feature = var, method = 'pdp+ice', center.at = 0) %>% plot() + theme_classic() + theme(axis.text = element_text(angle = 90, hjust = 1, vjust = 0.5)) }) # Produire les 4 graphiques dans une seule figure. do.call('grid.arrange', pdp_ice)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.