knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
insurancerating provides actuarial building blocks for insurance pricing in R.
A common GLM-based pricing exercise often combines several tasks:
This vignette illustrates one way to combine the main building blocks:
factor_analysis()glm()rating_table()model_performance() and bootstrap_performance()The focus is on the transition from portfolio data to an interpretable tariff structure.
We use the example dataset MTPL2, which contains a motor portfolio with:
nclaims),exposure),premium),amount),library(insurancerating) library(dplyr) head(MTPL2)
A pricing analysis often starts with an analysis of the portfolio.
Before fitting a model, it is necessary to understand:
This is done with factor_analysis().
We start by analysing a single risk factor.
fa <- factor_analysis( MTPL, risk_factors = "zip", claim_count = "nclaims", exposure = "exposure", claim_amount = "amount" ) fa
The output provides commonly used portfolio metrics such as:
autoplot(fa, metrics = c("exposure", "frequency", "risk_premium"))
This provides a direct view of:
At this stage, the purpose is not yet to fit a model, but to understand whether the factor behaves in a way that is suitable for pricing.
Continuous variables are typically not used directly in a tariff. In pricing practice, they are usually:
This ensures that the final tariff remains interpretable and implementable.
age_freq <- risk_factor_gam( data = MTPL, risk_factor = "age_policyholder", claim_count = "nclaims", exposure = "exposure" ) autoplot(age_freq, show_observations = TRUE)
This step is used to inspect:
age_segments <- derive_tariff_segments(age_freq) autoplot(age_segments)
This converts the continuous variable into risk-homogeneous tariff segments.
The resulting segments should reflect differences in risk, while remaining suitable for use in a tariff.
dat <- MTPL |> add_tariff_segments(age_segments, name = "age_cat") |> mutate(across(where(is.character), as.factor)) |> mutate(across(where(is.factor), ~ set_reference_level(., exposure)))
set_reference_level() sets the reference level to the level with the highest
exposure. In pricing models, this is often the most stable and interpretable
baseline.
Generalized linear models are widely used in insurance pricing because they:
A common decomposition is:
mod_freq <- glm( nclaims ~ age_cat, offset = log(exposure), family = poisson(), data = dat )
mod_sev <- glm( amount ~ age_cat, weights = nclaims, family = Gamma(link = "log"), data = dat |> filter(amount > 0) )
Frequency and severity are modelled separately because they capture different aspects of the loss process.
premium_df <- dat |> add_prediction(mod_freq, mod_sev) |> mutate(premium = pred_nclaims_mod_freq * pred_amount_mod_sev) head(premium_df)
This produces a pure premium estimate, i.e. expected loss per unit of exposure.
burn_unrestricted <- glm( premium ~ age_cat + zip, weights = exposure, family = Gamma(link = "log"), data = premium_df )
This model combines the rating factors into a single premium structure.
In practice, this is often the model that is closest to the final tariff logic, because it reflects the premium level rather than only individual model components such as frequency or severity.
rt <- rating_table(burn_unrestricted) rt
rating_table() expresses fitted coefficients in terms of the original factor
levels, including the reference level.
This output is commonly used to inspect tariff relativities.
rating_table(burn_unrestricted) |> autoplot()
This plot is typically used to assess:
At this stage, the relevant questions are:
model_performance(mod_freq)
This provides summary measures of model fit, such as RMSE.
bp <- bootstrap_performance(mod_freq, dat, n_resamples = 50, show_progress = FALSE) autoplot(bp)
This provides a view of predictive stability by evaluating how performance changes across bootstrap samples.
A single fit statistic is usually not sufficient. In pricing practice, it is also relevant to assess whether the model behaves consistently under small data perturbations.
At this point, the example has produced:
In many cases, a further step is required before the model output can be used as a tariff.
Typical reasons include:
This can be handled with the refinement tools described in Refinement building blocks.
A possible sequence in insurancerating is:
factor_analysis() # analyse portfolio behaviour risk_factor_gam() # analyse continuous variables derive_tariff_segments() # derive tariff segments glm() # estimate pricing models rating_table() # interpret fitted coefficients bootstrap_performance() # assess stability prepare_refinement() # refine tariff structure if needed
The aim is to move from raw portfolio data to a tariff structure that is:
The following vignette covers the refinement step in more detail:
For the conceptual background to exposure, risk premium, and tariff design, see:
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.