{"id":1409,"date":"2024-01-21T21:11:13","date_gmt":"2024-01-21T20:11:13","guid":{"rendered":"https:\/\/lorentzen.ch\/?p=1409"},"modified":"2024-01-21T21:11:13","modified_gmt":"2024-01-21T20:11:13","slug":"ml-xai-strong-glm","status":"publish","type":"post","link":"https:\/\/lorentzen.ch\/index.php\/2024\/01\/21\/ml-xai-strong-glm\/","title":{"rendered":"ML + XAI -> Strong GLM"},"content":{"rendered":"\n<p>My <a href=\"https:\/\/lorentzen.ch\/index.php\/2024\/01\/07\/explain-that-tidymodels-blackbox\/\">last post<\/a> was using {hstats}, {kernelshap} and {shapviz} to explain a binary classification random forest. Here, we use the same package combo to improve a Poisson GLM with insights from a boosted trees model.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Insurance pricing data<\/h2>\n\n\n\n<p>This time, we work with a synthetic, but quite realistic dataset. It describes 1 Mio insurance policies and their corresponding claim counts. A reference for the data is:<\/p>\n\n\n\n<p>Mayer, M., Meier, D. and Wuthrich, M.V. (2023), <br>SHAP for Actuaries: Explain any Model.<br>http:\/\/dx.doi.org\/10.2139\/ssrn.4389797<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">library(OpenML)\nlibrary(lightgbm)\nlibrary(splines)\nlibrary(ggplot2)\nlibrary(patchwork)\nlibrary(hstats)\nlibrary(kernelshap)\nlibrary(shapviz)\n\n#===================================================================\n# Load and describe data\n#===================================================================\n\ndf &lt;- getOMLDataSet(data.id = 45106)$data\n\ndim(df)  # 1000000       7\nhead(df)\n\n# year town driver_age car_weight car_power car_age claim_nb\n# 2018    1         51       1760       173       3        0\n# 2019    1         41       1760       248       2        0\n# 2018    1         25       1240       111       2        0\n# 2019    0         40       1010        83       9        0\n# 2018    0         43       2180       169       5        0\n# 2018    1         45       1170       149       1        1\n\nsummary(df)\n\n# Response\nggplot(df, aes(claim_nb)) +\n  geom_bar(fill = &quot;chartreuse4&quot;) +\n  ggtitle(&quot;Distribution of the response&quot;)\n\n# Features\nxvars &lt;- c(&quot;year&quot;, &quot;town&quot;, &quot;driver_age&quot;, &quot;car_weight&quot;, &quot;car_power&quot;, &quot;car_age&quot;)\n\ndf[xvars] |&gt; \n  stack() |&gt; \nggplot(aes(values)) +\n  geom_histogram(fill = &quot;chartreuse4&quot;, bins = 19) +\n  facet_wrap(~ind, scales = &quot;free&quot;, ncol = 2) +\n  ggtitle(&quot;Distribution of the features&quot;)\n\n# car_power and car_weight are correlated 0.68, car_age and driver_age 0.28\ndf[xvars] |&gt; \n  cor() |&gt; \n  round(2)\n#            year  town driver_age car_weight car_power car_age\n# year          1  0.00       0.00       0.00      0.00    0.00\n# town          0  1.00      -0.16       0.00      0.00    0.00\n# driver_age    0 -0.16       1.00       0.09      0.10    0.28\n# car_weight    0  0.00       0.09       1.00      0.68    0.00\n# car_power     0  0.00       0.10       0.68      1.00    0.09\n# car_age       0  0.00       0.28       0.00      0.09    1.00\n\n<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"553\" height=\"483\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-16.png\" alt=\"\" class=\"wp-image-1411\" style=\"aspect-ratio:1.144927536231884;width:344px;height:auto\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-16.png 553w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-16-300x262.png 300w\" sizes=\"auto, (max-width: 553px) 100vw, 553px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"972\" height=\"736\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-15.png\" alt=\"\" class=\"wp-image-1410\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-15.png 972w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-15-300x227.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-15-768x582.png 768w\" sizes=\"auto, (max-width: 972px) 100vw, 972px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Modeling<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>We fit a naive additive linear GLM and a tuned Boosted Trees model.<\/li>\n\n\n\n<li>We combine the models and specify their predict function.<\/li>\n<\/ol>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\"># Train\/test split\nset.seed(8300)\nix &lt;- sample(nrow(df), 0.9 * nrow(df))\ntrain &lt;- df[ix, ]\nvalid &lt;- df[-ix, ]\n\n# Naive additive linear Poisson regression model\n(fit_glm &lt;- glm(claim_nb ~ ., data = train, family = poisson()))\n\n# Boosted trees with LightGBM. The parameters (incl. number of rounds) have been \n# by combining early-stopping with random search CV (not shown here)\n\ndtrain &lt;- lgb.Dataset(data.matrix(train[xvars]), label = train$claim_nb)\n\nparams &lt;- list(\n  learning_rate = 0.05, \n  objective = &quot;poisson&quot;, \n  num_leaves = 7, \n  min_data_in_leaf = 50, \n  min_sum_hessian_in_leaf = 0.001, \n  colsample_bynode = 0.8, \n  bagging_fraction = 0.8, \n  lambda_l1 = 3, \n  lambda_l2 = 5\n)\n\nfit_lgb &lt;- lgb.train(params = params, data = dtrain, nrounds = 300)  \n\n# {hstats} works for multi-output predictions,\n# so we can combine all models to a list, which simplifies the XAI part.\nmodels &lt;- list(GLM = fit_glm, LGB = fit_lgb)\n\n# Custom predictions on response scale\npf &lt;- function(m, X) {\n  cbind(\n    GLM = predict(m$GLM, X, type = &quot;response&quot;),\n    LGB = predict(m$LGB, data.matrix(X[xvars]))\n  )\n}\npf(models, head(valid, 2))\n#       GLM        LGB\n# 0.1082285 0.08580529\n# 0.1071895 0.09181466\n\n# And on log scale\npf_log &lt;- function(m, X) {\n  log(pf(m = m, X = X))\n}\npf_log(models, head(valid, 2))\n#       GLM       LGB\n# -2.223510 -2.455675\n# -2.233157 -2.387983 -2.346350<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Traditional XAI<\/h2>\n\n\n\n<h4 class=\"wp-block-heading\">Performance<\/h4>\n\n\n\n<p>Comparing average Poisson deviance on the validation data shows that the LGB model is clearly better than the naively built GLM, so there is room for improvent!<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">perf &lt;- average_loss(\n  models, X = valid, y = &quot;claim_nb&quot;, loss = &quot;poisson&quot;, pred_fun = pf\n)\nperf\n#       GLM       LGB \n# 0.4362407 0.4331857\n<\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\">Feature importance<\/h4>\n\n\n\n<p>Next, we calculate permutation importance on the validation data with respect to mean Poisson deviance loss. The results make sense, and we note that <code>year<\/code> and <code>car_weight<\/code> seem to be negligile.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">imp &lt;- perm_importance(\n  models, v = xvars, X = valid, y = &quot;claim_nb&quot;, loss = &quot;poisson&quot;, pred_fun = pf\n)\nplot(imp)<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"568\" height=\"486\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-25.png\" alt=\"\" class=\"wp-image-1425\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-25.png 568w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-25-300x257.png 300w\" sizes=\"auto, (max-width: 568px) 100vw, 568px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Main effects<\/h4>\n\n\n\n<p>Next, we visualize estimated main effects by partial dependence plots on log link scale. The differences between the models are quite small, with one big exception: Investing more parameters into <code>driver_age<\/code> via spline will greatly improve the performance and usefulness of the GLM.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">partial_dep(models, v = &quot;driver_age&quot;, train, pred_fun = pf_log) |&gt; \n  plot(show_points = FALSE)\n\npdp &lt;- function(v) {\n  partial_dep(models, v = v, X = train, pred_fun = pf_log) |&gt; \n    plot(show_points = FALSE)\n}\nwrap_plots(lapply(xvars, pdp), guides = &quot;collect&quot;) &amp;\n  ylim(-2.8, -1.7)<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"553\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-26-1024x553.png\" alt=\"\" class=\"wp-image-1426\" style=\"aspect-ratio:1.8517179023508137;width:946px;height:auto\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-26-1024x553.png 1024w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-26-300x162.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-26-768x414.png 768w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-26.png 1486w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Interaction effects<\/h4>\n\n\n\n<p>Friedman&#8217;s H-squared (per feature and feature pair) and on log link scale shows that &#8211; unsurprisingly &#8211; our GLM does not contain interactions, and that the strongest relative interaction happens between <code>town<\/code> and <code>car_power<\/code>. The stratified PDP visualizes this interaction. Let&#8217;s add a corresponding interaction effect to our GLM later.<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">system.time(  # 5 sec\n  H &lt;- hstats(models, v = xvars, X = train, pred_fun = pf_log)\n)\nH\nplot(H)\n\n# Visualize strongest interaction by stratified PDP\npartial_dep(models, v = &quot;car_power&quot;, X = train, pred_fun = pf_log, BY = &quot;town&quot;) |&gt; \n  plot(show_points = FALSE)<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"580\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-27-1024x580.png\" alt=\"\" class=\"wp-image-1427\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-27-1024x580.png 1024w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-27-300x170.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-27-768x435.png 768w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-27.png 1054w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"574\" height=\"480\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-28.png\" alt=\"\" class=\"wp-image-1428\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-28.png 574w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-28-300x251.png 300w\" sizes=\"auto, (max-width: 574px) 100vw, 574px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">SHAP<\/h2>\n\n\n\n<p>As an elegant alternative to studying feature importance, PDPs and Friedman&#8217;s H, we can simply run a SHAP analysis on the LGB model.  <\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">set.seed(22)\nX_explain &lt;- train[sample(nrow(train), 1000), xvars]\n \nshap_values_lgb &lt;- shapviz(fit_lgb, data.matrix(X_explain))\nsv_importance(shap_values_lgb)\nsv_dependence(shap_values_lgb, v = xvars) &amp;\n  ylim(-0.35, 0.8)<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"586\" height=\"478\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-31.png\" alt=\"\" class=\"wp-image-1431\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-31.png 586w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-31-300x245.png 300w\" sizes=\"auto, (max-width: 586px) 100vw, 586px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"505\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-30-1024x505.png\" alt=\"\" class=\"wp-image-1430\" style=\"aspect-ratio:2.0277227722772277;width:1002px;height:auto\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-30-1024x505.png 1024w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-30-300x148.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-30-768x379.png 768w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-30-1536x757.png 1536w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-30.png 1888w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Here, we would come to the same conclusions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>car_weight<\/code> and <code>year<\/code> might be dropped.<\/li>\n\n\n\n<li>Add a regression spline for <code>driver_age<\/code><\/li>\n\n\n\n<li>Add an interaction between <code>car_power<\/code> and <code>town<\/code>.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Pimp the GLM<\/h2>\n\n\n\n<p>In the final section, we apply the three insights from above with very good results. <\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">fit_glm2 &lt;- glm(\n  claim_nb ~ car_power * town + ns(driver_age, df = 7) + car_age, \n  data = train, \n  family = poisson()\n  \n# Performance now as good as LGB\nperf_glm2 &lt;- average_loss(\n  fit_glm2, X = valid, y = &quot;claim_nb&quot;, loss = &quot;poisson&quot;, type = &quot;response&quot;\n)\nperf_glm2  # 0.432962\n\n# Effects similar as LGB, and smooth\npartial_dep(fit_glm2, v = &quot;driver_age&quot;, X = train) |&gt; \n  plot(show_points = FALSE)\n\npartial_dep(fit_glm2, v = &quot;car_power&quot;, X = train, BY = &quot;town&quot;) |&gt; \n  plot(show_points = FALSE)<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"600\" height=\"478\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-33.png\" alt=\"\" class=\"wp-image-1433\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-33.png 600w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-33-300x239.png 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"583\" height=\"487\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-32.png\" alt=\"\" class=\"wp-image-1432\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-32.png 583w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-32-300x251.png 300w\" sizes=\"auto, (max-width: 583px) 100vw, 583px\" \/><\/figure>\n\n\n\n<p>Or even via permutation or kernel SHAP:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;r&quot;,&quot;mime&quot;:&quot;text\/x-rsrc&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;&quot;,&quot;language&quot;:&quot;R&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;r&quot;}\">set.seed(1)\nbg &lt;- train[sample(nrow(train), 200), ]\nxvars2 &lt;- setdiff(xvars, c(&quot;year&quot;, &quot;car_weight&quot;))\n\nsystem.time(  # 4 sec\n  ks_glm2 &lt;- permshap(fit_glm2, X = X_explain[xvars2], bg_X = bg)\n)\nshap_values_glm2 &lt;- shapviz(ks_glm2)\nsv_dependence(shap_values_glm2, v = xvars2) &amp;\n  ylim(-0.3, 0.8)<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"694\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-34-1024x694.png\" alt=\"\" class=\"wp-image-1434\" style=\"aspect-ratio:1.4755043227665705;width:760px;height:auto\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-34-1024x694.png 1024w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-34-300x203.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-34-768x520.png 768w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2024\/01\/image-34.png 1086w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Final words<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Improving naive GLMs with insights from ML + XAI is fun. <\/li>\n\n\n\n<li>In practice, the gap between GLM and a boosted trees model can&#8217;t be closed that easily. (The true model behind our synthetic dataset contains a single interaction, unlike real data\/models that typically have much more interactions.)<\/li>\n\n\n\n<li>{hstats} can work with multiple regression models in parallel. This helps to keep the workflow smooth. Similar for {kernelshap}.<\/li>\n\n\n\n<li>A SHAP analysis often brings the same qualitative insights as multiple other XAI tools together.<\/li>\n<\/ul>\n\n\n\n<p><a href=\"https:\/\/github.com\/lorentzenchr\/notebooks\/blob\/master\/blogposts\/2024-01-21%20car_claims.R\">The full R script<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, we improve a simple GLM by insights from a boosted trees model. <\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16,17,9],"tags":[5],"class_list":["post-1409","post","type-post","status-publish","format-standard","hentry","category-machine-learning","category-programming","category-statistics","tag-r"],"featured_image_src":null,"author_info":{"display_name":"Michael Mayer","author_link":"https:\/\/lorentzen.ch\/index.php\/author\/michael\/"},"_links":{"self":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/1409","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/comments?post=1409"}],"version-history":[{"count":7,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/1409\/revisions"}],"predecessor-version":[{"id":1438,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/1409\/revisions\/1438"}],"wp:attachment":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/media?parent=1409"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/categories?post=1409"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/tags?post=1409"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}