portfolio <- data.frame(
policy_id = 1:10,
sector = rep(c("Industry", "Retail"), each = 5),
region = rep(c("North", "South"), 5),
underwriting_year = rep(c(2024, 2025), each = 5),
earned_exposure = c(1, 0.8, 1, 0.5, 1, 1, 0.7, 1, 0.9, 1),
claim_count = c(0, 1, 2, 0, 1, 0, 1, 0, 2, 1),
claim_amount = c(0, 2500, 18000, 0, 6000, 0, 4500, 0, 22000, 9000)
)
# Aggregate policy records into observed combinations of sector and region.
# The resulting exposure is the total earned exposure in each combination.
rating_grid(
portfolio,
group_by = c("sector", "region"),
exposure = "earned_exposure"
)
# Split earned exposure by underwriting year. This is useful when reviewing
# whether the portfolio mix within each rating combination changes over time.
rating_grid(
portfolio,
group_by = c("sector", "region"),
exposure = "earned_exposure",
exposure_by = "underwriting_year"
)
# Claim count and claim amount remain additive totals in the rating grid.
# Frequency and average severity can subsequently be derived from them.
claims_grid <- rating_grid(
portfolio,
group_by = c("sector", "region"),
exposure = "earned_exposure",
aggregate_cols = c("claim_count", "claim_amount")
)
claims_grid$frequency <-
claims_grid$claim_count / claims_grid$earned_exposure
claims_grid$average_severity <- ifelse(
claims_grid$claim_count > 0,
claims_grid$claim_amount / claims_grid$claim_count,
NA_real_
)
claims_grid
# Fit a severity model to grouped average claim amounts. Grid rows without
# claims are excluded because average severity is undefined for those rows.
severity_model_grid <- glm(
average_severity ~ sector + region,
weights = claim_count,
family = Gamma(link = "log"),
data = subset(claims_grid, claim_count > 0)
)
coef(severity_model_grid)
# For a fitted GLM, extract_model_data() retains the model variables and
# exposure information required to construct the observed rating grid.
mtpl_portfolio <- MTPL
mtpl_portfolio$zip <- factor(mtpl_portfolio$zip)
frequency_model <- glm(
nclaims ~ bm + zip + offset(log(exposure)),
family = poisson(link = "log"),
data = mtpl_portfolio
)
frequency_model |>
extract_model_data() |>
rating_grid()
# For this Poisson frequency model, fitting on the corresponding aggregated
# grid gives the same coefficient estimates as fitting on the policy rows.
frequency_grid <- rating_grid(
mtpl_portfolio,
group_by = c("bm", "zip"),
exposure = "exposure",
aggregate_cols = "nclaims"
)
frequency_model_grid <- glm(
nclaims ~ bm + zip + offset(log(exposure)),
family = poisson(link = "log"),
data = frequency_grid
)
isTRUE(all.equal(
unname(coef(frequency_model)),
unname(coef(frequency_model_grid)),
tolerance = 1e-8
))
Run the code above in your browser using DataLab