| Type: | Package |
| Title: | Actuarial Tools for Insurance Pricing Models |
| Version: | 0.8.2 |
| Author: | Martin Haringa [aut, cre] |
| Maintainer: | Martin Haringa <mtharinga@gmail.com> |
| BugReports: | https://github.com/MHaringa/insurancerating/issues |
| Description: | Provides actuarial tools and building blocks for analysing, modelling, refining, and validating insurance rating models. Designed to support common GLM-based pricing tasks and the translation of statistical model output into practical tariff structures. The package supports the construction of insurance tariff classes using a data-driven approach, based on the methodology of Antonio and Valdez (2012) <doi:10.1007/s10182-011-0152-7>. |
| License: | GPL-2 | GPL-3 [expanded from: GPL (≥ 2)] |
| URL: | https://mharinga.github.io/insurancerating/, https://github.com/MHaringa/insurancerating |
| Encoding: | UTF-8 |
| LazyData: | true |
| Imports: | ciTools, data.table, DHARMa, dplyr, evtree, fitdistrplus, ggplot2, lifecycle, lubridate, mgcv, patchwork, partykit, rlang, scales, scam, stringr, tibble |
| Depends: | R (≥ 4.1.0) |
| Suggests: | classInt, DBI, dbplyr, duckdb, ggbeeswarm, ggrepel, gt, spelling, knitr, rmarkdown, testthat |
| Language: | en-US |
| VignetteBuilder: | knitr |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-09-01 11:18:50 UTC; martin |
| Repository: | CRAN |
| Date/Publication: | 2026-09-01 11:50:02 UTC |
Backward-compatible access to rating-table contents
Description
A rating_table is a data frame. This method keeps the historical x$df
accessor available while allowing ordinary $ access to table columns.
Package metadata is returned only when the requested name is not a column.
Usage
## S3 method for class 'rating_table'
x$name
Arguments
x |
A |
name |
Column or legacy component name. |
Value
A table column, the underlying data frame for name = "df", or a
stored metadata component.
Motor Third Party Liability (MTPL) portfolio
Description
A dataset containing the characteristics of 30,000 policyholders in a Dutch Motor Third Party Liability (MTPL) insurance portfolio. Includes information on policyholder characteristics, vehicle attributes, and claims.
Usage
MTPL
Format
A data frame containing 30,000 rows and 7 variables:
- age_policyholder
Age of the policyholder (in years).
- nclaims
Number of claims.
- exposure
Exposure, expressed in years. For example, if a vehicle is insured from July 1, the exposure equals 0.5 for that year.
- amount
Total claim amount for the portfolio record (in euros).
- power
Engine power of the vehicle (in kilowatts).
- bm
Bonus-malus level (0–22). Higher levels indicate worse claim history.
- zip
Region indicator (0–3).
Author(s)
Martin Haringa
Motor Third Party Liability (MTPL) portfolio (3,000 policyholders)
Description
A dataset containing the characteristics of 3,000 policyholders in a Dutch Motor Third Party Liability (MTPL) insurance portfolio. Includes information on region, claims, exposure, and premium.
Usage
MTPL2
Format
A data frame containing 3,000 rows and 6 variables:
- customer_id
Unique customer identifier.
- area
Region where the customer lives (0–3).
- nclaims
Number of claims.
- amount
Total claim amount for the portfolio record (in euros).
- exposure
Exposure, expressed in years.
- premium
Earned premium.
Author(s)
Martin Haringa
Match event dates to active portfolio periods
Description
Match event dates, such as claim dates or inspection dates, to policy records that were active when the event occurred. The matched result contains the portfolio characteristics and coverage information applicable on each event date.
Usage
active_rows_by_date(
portfolio,
dates,
period_start,
period_end,
date,
by = NULL,
unmatched = c("drop", "keep"),
multiple_matches = c("all", "first", "last"),
nomatch = NULL,
mult = NULL
)
Arguments
portfolio |
A |
dates |
A |
period_start |
Character string. Name of the portfolio column with period start dates. |
period_end |
Character string. Name of the portfolio column with period end dates. |
date |
Character string. Name of the date column in |
by |
Character vector with additional columns used to match |
unmatched |
Character string. Use |
multiple_matches |
Character string controlling events that match
multiple active portfolio rows. Use |
nomatch, mult |
Deprecated technical argument names. Use |
Details
Claim and event files often contain an event date and policy identifier but
not the rating factors used at that point in time. The function performs an
interval match between those events and the portfolio history. Supplying a
policy identifier through by prevents an event from matching active periods
belonging to another policy.
This is a temporal matching operation rather than a portfolio reduction. See
merge_date_ranges() for consolidating connected coverage periods and
split_periods_to_months() for expanding periods into monthly records.
Multiple matches can be valid when one event relates to several concurrently
active coverages. They can also reveal overlapping or duplicated policy
periods. Use multiple_matches = "all" when every active record is relevant;
use "first" or "last" only when the source system defines which record
should take precedence.
With unmatched = "drop", events outside every applicable coverage period
are omitted. With unmatched = "keep", they remain visible with missing
portfolio fields. Retaining unmatched events is generally preferable during
data-quality review because it makes gaps in the policy history explicit.
The interval join is performed internally with data.table::foverlaps() on
local copies. Neither input is modified by reference. The output follows the
original order of dates and is always returned as a regular data.frame.
Value
A regular data.frame containing the event records and the portfolio
information active on their dates. Event order is preserved. Depending on
multiple_matches, one event can produce more than one output row.
Author(s)
Martin Haringa
See Also
split_periods_to_months(), merge_date_ranges(), rating_grid()
Examples
portfolio <- data.frame(
policy_id = c("P001", "P001", "P002"),
coverage_start = as.Date(c("2024-01-01", "2025-01-01", "2025-01-01")),
coverage_end = as.Date(c("2024-12-31", "2025-12-31", "2025-12-31")),
sector = c("Retail", "Industry", "Services"),
insured_amount = c(500000, 750000, 300000),
earned_premium = c(900, 1250, 650)
)
claims <- data.frame(
claim_id = c("C001", "C002", "C003"),
policy_id = c("P001", "P001", "P002"),
claim_date = as.Date(c("2024-06-15", "2025-08-10", "2026-01-10")),
claim_amount = c(12000, 45000, 8000)
)
# Attach the policy characteristics that applied on each claim date.
active_rows_by_date(
portfolio,
claims,
period_start = "coverage_start",
period_end = "coverage_end",
date = "claim_date",
by = "policy_id"
)
# Keep claims outside the available policy history for data-quality review.
active_rows_by_date(
portfolio,
claims,
period_start = "coverage_start",
period_end = "coverage_end",
date = "claim_date",
by = "policy_id",
unmatched = "keep"
)
Deprecated alias for add_portfolio_experience()
Description
add_observed_experience() is deprecated. Use
add_portfolio_experience() instead.
Usage
add_observed_experience(...)
Arguments
... |
Unused. |
Value
See add_portfolio_experience().
Author(s)
Martin Haringa
Add portfolio experience to a rating table
Description
add_portfolio_experience() enriches a rating_table() object with observed
portfolio experience. When data is supplied, observed experience is
calculated automatically for all risk factors in the rating table, unless
risk_factors is specified. Existing factor_analysis() results can also be
supplied through observed.
This makes it possible to compare fitted GLM relativities with observed
portfolio patterns in autoplot.rating_table(). The full observed output is
stored on the rating table, so autoplot.rating_table() can later switch
between metrics such as "frequency", "average_severity" and
"risk_premium" without recalculating the summaries.
The observed metric is scaled before plotting. With scale = "reference"
the metric is divided by the observed value of the model reference level. If
a clear reference level cannot be found, the metric is scaled to its mean.
With scale = "mean", the metric is always scaled to its mean.
Usage
add_portfolio_experience(x, ...)
## S3 method for class 'rating_table'
add_portfolio_experience(
x,
observed = NULL,
data = NULL,
risk_factors = NULL,
claim_count = NULL,
exposure = NULL,
claim_amount = NULL,
metric = NULL,
label = "Observed experience",
color = NULL,
scale = c("reference", "mean"),
experience = NULL,
...
)
Arguments
x |
A |
... |
Unused. |
observed |
Optional |
data |
Optional |
risk_factors |
Optional character vector. Risk factors for which
observed experience should be calculated. If |
claim_count |
Optional character string. Claim count column used by
|
exposure |
Optional character string. Exposure column used by
|
claim_amount |
Optional character string. Claim amount column used by
|
metric |
Optional character string. Default observed metric to plot.
Common choices are |
label |
Character; legend label for the observed experience line. |
color |
Optional line color. If |
scale |
Character; scaling applied before plotting. One of
|
experience |
Deprecated alias for |
Value
A rating_table object with observed portfolio experience attached.
Author(s)
Martin Haringa
Examples
df <- MTPL2
df$area <- as.factor(df$area)
model <- glm(
nclaims ~ area + offset(log(exposure)),
family = poisson(),
data = df
)
rating_table(model, model_data = df, exposure = "exposure") |>
add_portfolio_experience(
data = df,
claim_count = "nclaims",
exposure = "exposure"
) |>
autoplot(risk_factors = "area", metric = "frequency")
observed <- factor_analysis(
df,
risk_factors = "area",
claim_count = "nclaims",
exposure = "exposure"
)
rating_table(model, model_data = df, exposure = "exposure") |>
add_portfolio_experience(observed = observed) |>
autoplot(risk_factors = "area")
Add model predictions to a pricing data set
Description
add_prediction() adds predictions from one or more fitted glm models to
a data frame.
In pricing workflows, this is often used to bring count and severity model output together on the same portfolio. For example, an expected claim count can be normalised by exposure and multiplied by an expected average claim amount to calculate a risk premium per exposure unit.
The function is deliberately small: it does not refit models or decide how predictions should be combined. It only adds model predictions, and optionally confidence intervals, using clear output column names.
Usage
add_prediction(
data,
...,
predictions = NULL,
prefix = "pred",
confidence = FALSE,
interval_names = c("lower", "upper"),
alpha = 0.1,
var = NULL,
conf_int = NULL
)
Arguments
data |
A |
... |
One or more fitted model objects of class |
predictions |
Optional character vector giving names for the new
prediction columns. Must have the same length as the number of models
supplied. If |
prefix |
Character. Prefix used for automatically generated prediction
column names. Default is |
confidence |
Logical. If |
interval_names |
Character vector of length two. Names appended to the
prediction column name for lower and upper confidence interval bounds.
Default is |
alpha |
Numeric between 0 and 1. Controls the miscoverage level for
interval estimates. Default is |
var |
Deprecated. Use |
conf_int |
Deprecated. Use |
Details
Predictions are calculated on the response scale using
stats::predict(..., type = "response"). For GLMs with a log link, such as
Poisson count models or Gamma severity models, the added columns are already
on the original response scale. For a Poisson claim-count model containing
an exposure offset, this is the expected claim count for the supplied
exposure, not frequency per exposure unit. Divide by exposure when a rate is
required.
If confidence = TRUE, lower and upper confidence interval columns are added
next to each prediction column. The default interval suffixes are "lower"
and "upper".
Predictions containing missing values are retained. If one or more NA
predictions are produced, the function issues a warning with the affected
prediction columns and number of missing predictions. This is typically
caused by missing predictor values in data or by predictor values outside
the domain supported by the fitted model.
Value
A data.frame containing the original data and additional columns
for model predictions. If confidence = TRUE, confidence interval columns
are added as well.
Author(s)
Martin Haringa
Examples
mod1 <- glm(nclaims ~ age_policyholder,
data = MTPL,
offset = log(exposure),
family = poisson())
# Add the expected claim count for each record's exposure
mtpl_pred <- add_prediction(
MTPL,
mod1,
predictions = "expected_claim_count"
)
# Add predicted values with confidence bounds
mtpl_pred_ci <- add_prediction(
MTPL,
mod1,
predictions = "expected_claim_count",
confidence = TRUE
)
# Combine frequency and severity predictions into a risk premium
freq <- glm(nclaims ~ bm + zip,
data = MTPL,
offset = log(exposure),
family = poisson())
severity_data <- MTPL[MTPL$nclaims > 0 & MTPL$amount > 0, ]
severity_data$average_claim_amount <-
severity_data$amount / severity_data$nclaims
sev <- glm(average_claim_amount ~ bm + zip,
data = severity_data,
weights = nclaims,
family = Gamma(link = "log"))
pricing <- add_prediction(
MTPL,
freq,
sev,
predictions = c("expected_claim_count", "expected_average_severity")
)
pricing$claim_frequency <-
pricing$expected_claim_count / pricing$exposure
pricing$expected_loss <-
pricing$expected_claim_count * pricing$expected_average_severity
pricing$risk_premium <-
pricing$claim_frequency * pricing$expected_average_severity
Rebase categorical tariff relativities to a reference level
Description
Rescale the current relativities of one categorical risk factor so that a
selected level has relativity 1. add_rebasing() is an ordered refinement
step: it uses the relativities available at that point in the workflow and
retains all ratios between levels.
Usage
add_rebasing(model, model_variable, reference_level = NULL, weights = NULL)
Arguments
model |
A |
model_variable |
Character string naming the categorical risk factor to
rebase. This may identify an original GLM factor or a tariff factor created
by an earlier |
reference_level |
Optional single character value naming the level that
should receive relativity 1. When |
weights |
|
Details
Rebasing changes the numerical reference of a tariff factor, but does not
change its relative differentiation. If the current relativity of reference
level j is r_j, every level is transformed as
r_i^{new} = \frac{r_i}{r_j}.
The selected reference level therefore becomes 1, while the ratio between
any two levels remains unchanged. For example, relativities 0.8, 1.0 and 1.2
rebased to the first level become 1.0, 1.25 and 1.5. This is different from
add_shrinkage(), which deliberately reduces the spread between levels.
Selecting the reference level
Supply reference_level when the tariff has an established reference class
or when governance requires a particular level to remain at 1. If
reference_level = NULL, the level with the largest aggregated weight is
selected. Ties are resolved by the order of the current factor levels.
With weights = NULL, explicit GLM weights are used when available;
otherwise a single offset of the form log(column) is used. This commonly
selects claim count for a weighted severity GLM and exposure for a frequency
or risk-premium GLM. An explicit numeric column can be supplied when another
portfolio basis is required. weights is ignored when reference_level is
supplied because no automatic selection is then needed.
Position in the refinement workflow
Rebasing is generally applied after the step that creates the final tariff
levels. For example, add_relativities() may replace a broad level by
several sublevels; add_rebasing() can then select one of those resulting
sublevels as the new reference. It can also follow add_shrinkage() when the
shrunken relativities should be reported relative to an established level.
set_reference_level() serves a different purpose. It changes the contrast
reference of a factor before fitting a GLM. add_rebasing() rescales current
tariff relativities inside an existing refinement specification. The
refinement step and selected reference are retained for review by
summary() and audit_refinement().
Value
A rating_refinement object containing an ordered rebasing step.
The step stores the original and rebased relativities, the selected
reference level, its original relativity, the selection method and, when
applicable, the aggregated level weights. The GLM is fitted only when
refit() is called.
Author(s)
Martin Haringa
See Also
prepare_refinement(), set_reference_level(),
add_relativities(), add_shrinkage(), add_restriction(), refit(),
audit_refinement()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4, 1, 5),
exposure = c(1, 1, 1, 1, 2, 1, 1, 1),
sector = factor(rep(c("Industry", "Office", "Retail", "Transport"), 2))
)
model <- glm(
claims ~ sector + offset(log(exposure)),
family = poisson(),
data = portfolio
)
# Keep Office as the explicit tariff reference after shrinkage.
refinement <- prepare_refinement(model, data = portfolio) |>
add_shrinkage(
model_variable = "sector",
credibility = 0.9,
weights = "exposure"
) |>
add_rebasing(
model_variable = "sector",
reference_level = "Office"
)
summary(refinement)
refined_model <- refit(refinement)
rating_table(refined_model)
# Omitting reference_level selects the level with the largest exposure.
exposure_reference <- prepare_refinement(model, data = portfolio) |>
add_rebasing(
model_variable = "sector",
weights = "exposure"
)
Add sublevel relativities to a refinement workflow
Description
Divide one or more levels of an existing GLM risk factor into more detailed tariff levels using supplied relativities. This can be appropriate when the GLM is estimated on a coarser factor for statistical stability, while a documented actuarial segmentation is required within sufficiently homogeneous model levels.
Usage
add_relativities(
model,
model_variable,
split_variable,
relativities,
exposure,
normalize = TRUE,
output_variable = paste0(model_variable, "_refined")
)
Arguments
model |
Object of class |
model_variable |
Character string. Existing variable in the GLM, or a
restricted version created by an earlier |
split_variable |
Character string. More granular portfolio variable that
defines the detailed groups inside |
relativities |
Named list of data frames, usually created with
|
exposure |
Character string. Exposure column used for weighting and, when requested, normalisation. |
normalize |
Logical. If |
output_variable |
Character string naming the resulting hybrid tariff
factor. The default appends |
Details
add_relativities() stores a relativity step on a rating_refinement
object. It does not alter the fitted GLM immediately. The split is evaluated
in the recorded step order and applied when refit() is called.
model_variable is the variable already used in the GLM. split_variable is
the more detailed variable in the portfolio data that will be used to split
one or more levels of model_variable. The relativities argument should be
a named list describing those splits, usually built with relativities() and
split_level(). output_variable names the resulting hybrid tariff factor:
levels included in relativities are represented by their detailed
split_variable level, while all other levels retain their
model_variable level.
Levels of model_variable that are not included in relativities retain
their existing model coefficient. In rating_table(), exposure for these
retained levels is aggregated from model_variable, while exposure for the
newly split levels is aggregated from split_variable within the specified
parent model level. Omitting a model level from relativities therefore
means that the level remains unsplit; it is not treated as an incomplete
specification.
add_relativities() validates the supplied sublevel names against the
observed values of split_variable before storing the refinement step. A
misspelled or incorrectly spaced category or sublevel therefore produces an
immediate error, with a suggestion when a closely matching observed value is
available. It also verifies that each sublevel occurs within its specified
parent category of model_variable.
When normalize = TRUE, the supplied relativities are normalised using
exposure so that their exposure-weighted mean equals one within the split
model level. They then redistribute the existing model coefficient across
the sublevels without changing its exposure-weighted average. With
normalize = FALSE, the supplied relativities are applied directly.
Step order and restrictions
If model_variable was restricted in an earlier add_restriction() step,
the restricted coefficients are automatically used as the basis for the
derived relativities. The user can continue to supply the original model
variable; no additional argument is needed. Supplying the restricted
variable explicitly gives the same coefficient basis and does not apply the
restriction a second time. Refinement steps are order-dependent, so a
restriction added after add_relativities() does not affect an earlier
relativity step. Once the restricted coefficients have been used to derive
the final split, rating_table() reports output_variable as the tariff
factor and does not also show the intermediate restricted variable.
Conversely, add_restriction() can be called after add_relativities() to
adjust selected levels of the derived output_variable. The output variable
is then recognised as an existing refinement factor; users do not need to
set allow_new_risk_factors = TRUE. Levels omitted from the restriction
table are fixed at the relativities calculated by this step.
Appropriate use
add_relativities() is intended for refinement within an already reasonably
homogeneous GLM segment. It redistributes an existing coefficient across
sublevels using exposure-weighted relativities, while preserving the overall
level of the original coefficient when normalisation is used. Appropriate
applications include mild residual heterogeneity, monotonic tariff
differentiation and expert-based segmentation within a stable risk group
where the original GLM coefficient remains broadly representative.
Limitations
The method is not a substitute for creating a separate risk segment when the original GLM coefficient is itself distorted. For example, suppose a broad industry segment contains many relatively stable businesses, but a few chemical companies drive most of the losses while representing little exposure. The fitted industry coefficient may then be dominated by the chemical companies' experience. Applying exposure-weighted relativities inside that segment may barely reduce the coefficient for the large exposure group, because the original coefficient is already pulled upward by the outlier subgroup.
In that situation it is often better to create a separate GLM factor level,
derive a separate tariff segment, or apply explicit segmentation or
acceptation rules, instead of relying only on add_relativities().
Value
A rating_refinement object containing the stored relativity
specification. The pricing GLM is not fitted again until refit() is
called.
Author(s)
Martin Haringa
See Also
prepare_refinement(), relativities(), split_level(),
add_restriction(), add_shrinkage(), add_rebasing(),
add_smoothing(), refit(), rating_table()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4),
exposure = rep(1, 6),
construction = factor(c("residential", "commercial", "residential",
"commercial", "residential", "commercial")),
construction_detail = factor(c("flat", "shop", "house",
"office", "flat", "shop"))
)
model <- glm(
claims ~ construction + offset(log(exposure)),
family = poisson(),
data = portfolio
)
relativities <- relativities(
split_level(
"residential",
new_levels = c("flat", "house"),
relativities = c(0.95, 1.05)
),
split_level(
"commercial",
new_levels = c("shop", "office"),
relativities = c(1.10, 0.90)
)
)
refined <- prepare_refinement(model, data = portfolio) |>
add_relativities(
model_variable = "construction",
split_variable = "construction_detail",
output_variable = "construction_tariff_segment",
relativities = relativities,
exposure = "exposure"
)
# A subsequent restriction can revise one derived level. The remaining
# tariff-segment levels are fixed at the relativities calculated above.
refined <- refined |>
add_restriction(data.frame(
construction_tariff_segment = "flat",
construction_tariff_segment_restricted = 1.00
))
refined_model <- refit(refined)
rating_table(refined_model, exposure = FALSE)
Add coefficient restrictions to a refinement workflow
Description
Fix selected risk-factor levels at user-supplied relativities before the refined pricing GLM is fitted. This can be appropriate when sampling variation produces an implausible local effect, when an actuarial assumption is supported by additional information, or when a documented tariff constraint must be applied consistently.
Usage
add_restriction(
model,
restrictions,
allow_new_levels = TRUE,
allow_new_risk_factors = FALSE,
replaces = NULL
)
Arguments
model |
Object of class |
restrictions |
Data frame with exactly two columns. The first column
must have the same name as the risk factor to restrict and contains the
levels to adjust. This can also be the |
allow_new_levels |
Logical. If |
allow_new_risk_factors |
Logical. If |
replaces |
|
Details
add_restriction() stores a restriction step on a rating_refinement
object. It does not alter the fitted GLM immediately. The restriction is
evaluated in the recorded step order and applied when refit() is called.
Retain the refinement object when reviewing or revising the specification.
The restrictions data frame identifies the risk factor to restrict by its
first column. This may be a variable from the original GLM or a tariff factor
created by an earlier refinement step. The second column contains the
relativities used for those levels in the refined model.
Actuarial interpretation
The restriction table may contain all levels of the model variable, or only the levels that need a manual adjustment. If only a subset is supplied, the missing levels are automatically filled with their current effective relativities at that point in the refinement workflow. These may be the original fitted GLM relativities or values produced by preceding refinement steps. This makes it possible to change one level explicitly while fixing all other levels at their current values.
Levels that were not observed when the GLM was fitted can also be supplied. Such a level has no coefficient estimate from the model data. Its relativity is therefore an explicit tariff assumption, for example based on expert judgement, external experience or a planned extension of the tariff. Existing levels that are not supplied remain fixed at their fitted relativities.
With allow_new_levels = TRUE, which is the default, these new tariff levels
are retained in the refinement metadata and subsequently shown by
rating_table(). An informational message identifies every newly added
level, its supplied relativity and the fact that it was not observed in the
model data. Set allow_new_levels = FALSE when the restriction table should
be checked strictly against the levels observed by the fitted model, for
example to detect spelling errors in level names.
When a newly supplied level is written as a numeric interval, the function
checks it against the existing interval levels. A warning is issued if the
new interval overlaps the current classification. The level is still
retained because overlapping labels may occasionally be intentional, but
they do not form an unambiguous tariff partition. To replace an interval
classification, first add the complete new classification as a separate
column in the refinement data, use that column as the first column of
restrictions, and identify the old model term with replaces.
A variable that is present in the refinement data but was not included in the
fitted GLM can be added with allow_new_risk_factors = TRUE. In that case all
observed levels must have a supplied relativity. The new factor is applied as
a fixed tariff factor during refit(); its effects are not estimated from the
model data. This can be appropriate when an external classification or expert
assumption must be incorporated, such as a hail zone derived from geographic
information.
allow_new_risk_factors does not create the portfolio variable itself. The
refinement data must already contain a column assigning every observation to
a level. This is required to apply the supplied relativities to individual
records.
Replacing an existing model variable
A new fixed tariff factor can either supplement the fitted GLM or replace an
existing model variable. Supply replaces when the new factor represents an
alternative tariff classification for an effect already present in the
model. During refit(), the named existing term is removed and the supplied
fixed relativities are inserted in its place. With replaces = NULL, the new
factor is added alongside the existing model terms, which preserves the
previous behaviour.
Supplying replaces is itself an explicit request to add the new risk factor,
so allow_new_risk_factors = TRUE does not also need to be supplied. The
replacement relationship is retained in the ordered refinement metadata and
is shown by print(), summary() and audit_refinement(). This makes clear
that the new factor substitutes for an earlier model effect rather than
adding further multiplicative differentiation.
replaces is intentionally limited to a standalone main-effect term in the
current refinement formula. A variable used in an interaction or transformed
expression cannot be removed unambiguously through this argument. Such model
structures should be revised explicitly before the refinement is prepared.
This argument is therefore not a general-purpose facility for deleting model
terms.
Updating an existing restriction
A later call to add_restriction() for the same risk factor and the same
restricted model variable updates the restriction already stored in the
refinement. Relativities supplied in the later call replace the previously
stored values for those levels. Restrictions for levels that are not supplied
again are retained.
The existing and new values are first combined and the resulting restriction
table is then validated as one specification. This is useful when an
actuarial assumption is revised during model refinement: only the affected
levels need to be supplied again, while the remaining tariff assumptions
stay unchanged. The restriction step keeps its original position in the
workflow, so subsequent steps such as add_relativities() use the revised
restricted coefficients.
The second column must retain the same name when an existing restriction is
updated, because that name identifies the restricted model variable used by
refit(). A message reports levels whose previously supplied relativity is
changed.
Restricting a factor created by add_relativities()
An output_variable introduced by an earlier add_relativities() step is
already part of the ordered refinement specification. It is therefore not
treated as a new external risk factor and does not require
allow_new_risk_factors = TRUE. add_restriction() identifies the preceding
relativity step from its stored metadata and replaces the corresponding
derived tariff effect during refit().
When only one level of such a refined variable is supplied, that level receives
the new relativity and every other level is fixed at the relativity produced
by add_relativities(). Mathematically, the resulting restriction therefore
covers all current levels. Only the explicitly supplied level changes. This
is useful when actuarial review supports a local adjustment but the remaining
expert split should not be re-estimated.
Refinement order remains material. A restriction added after
add_relativities() operates on the derived split relativities. A
restriction added before add_relativities() instead changes the coefficient
basis from which the split is derived.
Value
A rating_refinement object containing the stored restriction
specification. The pricing GLM is not fitted again until refit() is
called.
Author(s)
Martin Haringa
See Also
prepare_refinement(), add_smoothing(), add_shrinkage(),
add_rebasing(), add_relativities(), refit(), rating_table()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4),
exposure = rep(1, 6),
postal_area = factor(c("A", "B", "C", "A", "B", "C"))
)
model <- glm(
claims ~ postal_area + offset(log(exposure)),
family = poisson(),
data = portfolio
)
restrictions <- data.frame(
postal_area = c("C", "D"),
relativity = c(1.10, 1.20)
)
refined <- prepare_refinement(model, data = portfolio) |>
add_restriction(restrictions)
# Postal area D was not observed in the portfolio. Its relativity is an
# explicit tariff assumption and becomes available after refitting.
refined_model <- refit(refined)
rating_table(refined_model, exposure = FALSE)
# A factor absent from the fitted GLM can replace an existing model term.
# The portfolio must already assign every observation to a hail zone.
portfolio$hail_zone <- factor(c("low", "high", "low", "high", "low", "high"))
hail_restrictions <- data.frame(
hail_zone = c("low", "high"),
hail_relativity = c(1.00, 1.20)
)
prepare_refinement(model, data = portfolio) |>
add_restriction(
hail_restrictions,
replaces = "postal_area"
)
# During refit(), hail_zone replaces postal_area rather than supplementing it.
# Without `replaces`, a new fixed factor supplements the existing terms.
# A later actuarial review changes only the relativity for the low hail zone.
# The high-zone relativity remains 1.20 and the existing step is updated.
revised_hail_restrictions <- data.frame(
hail_zone = "low",
hail_relativity = 1.10
)
hail_refinement <- prepare_refinement(model, data = portfolio) |>
add_restriction(
hail_restrictions,
allow_new_risk_factors = TRUE
) |>
add_restriction(revised_hail_restrictions)
refit(hail_refinement)
Shrink categorical tariff relativities towards a common level
Description
Reduce differences between the relativities of one categorical risk factor
before the refined GLM is fitted. add_shrinkage() combines each current
relativity with a central level on the logarithmic scale. Extreme
relativities move further in absolute terms, while their ordering is
retained.
Usage
add_shrinkage(model, model_variable, credibility = 0.9, weights = NULL)
Arguments
model |
A |
model_variable |
Character string naming the categorical risk factor to
shrink. This may also identify a tariff factor created by an earlier
|
credibility |
Numeric scalar between 0 and 1. This is the weight given
to the current risk-factor relativity. The remaining weight is assigned to
the common centre. The default |
weights |
|
Details
Shrinkage can be used when the direction of a fitted risk-factor pattern is credible, but the difference between its highest and lowest relativities is considered too large for the available experience or the intended tariff. It is a structured actuarial adjustment rather than a new statistical fit.
For level i, the unnormalised adjusted relativity is
\tilde{r}_i = \exp\{Z \log(r_i) + (1-Z)\log(c)\},
where r_i is the current relativity, Z is credibility, and
c is the weighted geometric centre. A credibility of 1 leaves the
relativities unchanged. A credibility of 0 removes the differences between
levels.
The adjusted relativities are subsequently rescaled so that their weighted
arithmetic mean equals the weighted arithmetic mean before shrinkage. With
portfolio weights such as exposure or claim count, this prevents shrinkage
itself from changing the weighted level of the risk factor. The final GLM
refit may still change the intercept or other fitted quantities; use
audit_refinement() to assess that combined portfolio effect.
Weight selection
weights = NULL first uses explicit GLM weights when these were supplied
during model fitting. Otherwise, a single column in an offset of the form
log(column) is used. This commonly selects claim count for a weighted
severity GLM and exposure for a frequency or risk-premium GLM. If neither
source is unambiguous, the function asks for an explicit choice.
Set weights to a column name to control the basis directly. For example,
exposure is generally appropriate for frequency or risk-premium
relativities, while claim count is generally appropriate for severity
relativities. Set weights = "equal" to give every risk-factor level the
same weight. In that case the equal-level mean is preserved, which does not
necessarily preserve the level of the observed portfolio.
Interpretation
credibility is a user-supplied refinement parameter. It should not be
interpreted as an automatically estimated Buhlmann or Buhlmann-Straub
credibility factor. Its value should be supported by portfolio stability,
validation over time and the intended degree of tariff differentiation.
The selected value and weighting basis are retained in the refinement
specification and shown by summary().
Following a restriction
When shrinkage follows add_restriction(), model_variable remains the
first column of the restriction table: the categorical risk factor whose
levels are being adjusted. The second column is a numeric implementation
column containing the fixed relativities used during refit(); it is not a
separate categorical risk factor. add_shrinkage() resolves that internal
column automatically from the stored restriction metadata.
Value
A rating_refinement object containing an ordered shrinkage step.
The returned object stores the original and adjusted relativities, level
weights, inferred weight source and normalization information. The GLM is
fitted only when refit() is called.
Author(s)
Martin Haringa
See Also
prepare_refinement(), add_smoothing(), add_restriction(),
add_rebasing(), add_relativities(), refit(), audit_refinement()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4, 1, 5),
exposure = c(1, 1, 1, 1, 2, 1, 1, 1),
sector = factor(rep(c("Industry", "Office", "Retail", "Transport"), 2))
)
model <- glm(
claims ~ sector + offset(log(exposure)),
family = poisson(),
data = portfolio
)
refinement <- prepare_refinement(model, data = portfolio) |>
add_shrinkage(
model_variable = "sector",
credibility = 0.9,
weights = "exposure"
)
summary(refinement)
refined_model <- refit(refinement)
rating_table(refined_model)
# Use equal level weights explicitly when portfolio weighting is not wanted.
equal_level_refinement <- prepare_refinement(model, data = portfolio) |>
add_shrinkage(
model_variable = "sector",
credibility = 0.8,
weights = "equal"
)
Smooth grouped tariff relativities in a refinement workflow
Description
Replace independently estimated relativities of an ordered, grouped model variable with a smooth tariff curve. This can reduce sampling variation between adjacent levels of risk factors such as age, vehicle age, insured value or bonus-malus years while retaining the broad effect estimated by the GLM.
Usage
add_smoothing(
model,
model_variable = NULL,
source_variable = NULL,
breaks,
smoothing = "spline",
k = NULL,
degree = NULL,
weights = NULL,
tariff_class = NULL,
rating_variable = NULL,
x_cut = NULL,
x_org = NULL
)
Arguments
model |
Object of class |
model_variable |
Character string. Existing grouped or binned variable in the GLM. This is the model term that will be replaced by a smoothed tariff factor. The column must not contain missing values; remove or impute missing values before adding the smoothing step. |
source_variable |
Character string. Original numeric portfolio variable
underlying |
breaks |
Numeric vector with the tariff segment boundaries to use after
smoothing. These boundaries determine the final tariff segmentation, not
the number of portfolio observations used to estimate the curve. Values
must be finite, strictly increasing and cover every observed value of
|
smoothing |
Character string selecting the smoothing method. Available
values are |
k |
Optional single positive whole number. Basis dimension for smoothing
methods |
degree |
Optional single whole number. Polynomial degree, used only by
|
weights |
Optional character string. Numeric volume column, usually exposure, used to weight the grouped GLM relativities during smoothing. |
tariff_class, rating_variable |
Deprecated. Use |
x_cut, x_org |
Deprecated. Use |
Details
add_smoothing() stores a smoothing specification on a
rating_refinement object. It does not alter the fitted GLM immediately.
The smoothing is evaluated in the recorded step order and applied when
refit() is called.
The original GLM contains model_variable, usually a factor created by
grouping a continuous risk factor. source_variable identifies the original
numeric variable represented by those groups.
The smoother is estimated from the fitted GLM relativities at the midpoint of
each model interval. Consequently, the amount of information available to
the smoother is primarily determined by the number of grouped model levels,
rather than by the number of individual portfolio records. Exposure or
another volume measure can be supplied through weights so that model levels
with more portfolio volume have greater influence on the fitted curve.
The fitted curve is evaluated using breaks and converted back to a grouped
tariff variable. The original model term is replaced by that smoothed tariff
variable during refitting.
Actuarial interpretation
Smoothing introduces a structural assumption: adjacent values of the source variable are expected to have related tariff effects. The selected method, basis dimension and breaks should therefore be assessed against exposure, observed experience, coefficient uncertainty and stability over time. A smooth curve should not be interpreted as evidence that the underlying risk relationship is itself known without uncertainty.
Smoothing methods
The available methods represent different assumptions about the shape of the tariff effect:
"spline"The general-purpose default. Fits an unconstrained penalized cubic regression spline. It is suitable when the tariff effect should be smooth but no monotonicity or curvature restriction is justified.
"poly"Fits a global polynomial through the grouped GLM relativities.
degreedetermines its order. A low degree gives a compact parametric trend; higher degrees can follow more local variation but may oscillate, particularly near the boundaries."increasing"and"decreasing"Fit monotone smooths. These methods constrain the tariff effect to move in one direction, without imposing how quickly its slope changes. They are often the most directly interpretable constrained specifications when actuarial reasoning supports a consistently increasing or decreasing risk effect.
"convex"and"concave"Constrain curvature but not direction. For a convex curve, the slope increases as the source variable increases; for a concave curve, the slope decreases. A convex curve may therefore be U-shaped and a concave curve may be inverted U-shaped. These are advanced choices when curvature itself has a defensible interpretation.
"increasing_convex"and"increasing_concave"Fit increasing curves with an additional curvature constraint. An increasing convex effect rises at an increasing rate, for example when upper-tail risk causes marginal cost to accelerate. An increasing concave effect rises at a decreasing rate and gradually flattens, for example when risk cost rises with insured value but less than proportionally.
"decreasing_convex"and"decreasing_concave"Fit decreasing curves with an additional curvature constraint. A decreasing convex effect becomes less steep and tends to flatten. A decreasing concave effect becomes progressively steeper.
"gam"Fits an unconstrained thin-plate regression spline with
mgcv::gam(). It is mainly intended as a flexible reference when comparing the general spline and shape-constrained specifications. It does not impose the actuarial shape assumptions represented by the constrained methods.
The shape-constrained methods are fitted with scam::scam(). Monotonicity
concerns the direction of the effect, whereas convexity and concavity concern
how its slope changes. In most tariff applications, a directional assumption
is easier to substantiate than a curvature assumption. A constraint
should reflect an actuarial or pricing assumption that is defensible for the
risk factor; it should not be selected solely because it produces a smoother
visual result. The combined monotonicity and curvature methods are advanced
specifications and are most appropriate when both assumptions can be
supported independently.
The former short codes "mpi", "mpd", "cx", "cv", "micx",
"micv", "mdcx" and "mdcv" remain accepted as compatibility aliases.
New code should use the readable method names above. Both forms produce the
same smoothing specification.
Basis dimension and polynomial degree
For "spline", "gam" and the shape-constrained methods, k specifies the
basis dimension. It controls the maximum flexibility available to the smooth,
but it is not the final effective degrees of freedom of the fitted curve.
The estimated smoothing penalty can reduce the effective degrees of freedom
below this maximum.
A smaller k restricts the curve to broad movements. A larger k permits
more local variation, but requires enough distinct grouped covariate values
and may be unstable when only a few tariff levels are available. If k is
NULL, the function uses the smaller of 10 and the number of unique grouped
model points. Spline, GAM and shape-constrained smoothing require at least
three unique grouped values. The function checks this dimension before
fitting and reports the observed number of unique values when the requested
complexity is not feasible.
For "poly", degree has the corresponding complexity role. A polynomial
of degree d requires at least d + 1 unique grouped values. When
degree is omitted, the existing behaviour uses the highest degree supported
by the grouped model points. In practice, an explicit low degree is generally
preferable when a stable global trend is intended.
degree is only accepted for smoothing = "poly". Conversely, k is only
accepted for "spline", "gam" and the shape-constrained methods. This
separation prevents a complexity argument from being supplied but silently
ignored.
The deprecated smooth_coef() wrapper remains available for backwards
compatibility.
Value
An object of class rating_refinement containing the stored
smoothing specification. The pricing GLM is not fitted again until
refit() is called.
Author(s)
Martin Haringa
See Also
prepare_refinement(), edit_smoothing(), add_restriction(),
add_shrinkage(), add_rebasing(), add_relativities(), refit(),
risk_factor_gam()
Examples
## Not run:
library(dplyr)
age_policyholder_frequency <- risk_factor_gam(
data = MTPL,
claim_count = "nclaims",
risk_factor = "age_policyholder",
exposure = "exposure"
)
age_segments_freq <- derive_tariff_segments(
age_policyholder_frequency,
segmentation_penalty = 10,
seed = 1
)
dat <- MTPL |>
add_tariff_segments(age_segments_freq, name = "age_policyholder_freq_cat") |>
mutate(across(where(is.character), as.factor)) |>
mutate(across(where(is.factor), ~ set_reference_level(., exposure)))
freq <- glm(
nclaims ~ bm + age_policyholder_freq_cat,
offset = log(exposure),
family = poisson(),
data = dat
)
sev <- glm(
amount ~ zip,
weights = nclaims,
family = Gamma(link = "log"),
data = dat |> filter(amount > 0)
)
premium_df <- dat |>
add_prediction(freq, sev) |>
mutate(premium = pred_nclaims_freq * pred_amount_sev)
burn_unrestricted <- glm(
premium ~ zip + bm + age_policyholder_freq_cat,
weights = exposure,
family = Gamma(link = "log"),
data = premium_df
)
ref <- prepare_refinement(burn_unrestricted) |>
add_smoothing(
model_variable = "age_policyholder_freq_cat",
source_variable = "age_policyholder",
breaks = c(seq(18, 93, 5), 95),
smoothing = "spline",
k = 6,
weights = "exposure"
)
# When the tariff effect must not decrease, use the readable constrained
# method name. The former value "mpi" remains accepted for compatibility.
increasing_ref <- prepare_refinement(burn_unrestricted) |>
add_smoothing(
model_variable = "age_policyholder_freq_cat",
source_variable = "age_policyholder",
breaks = c(seq(18, 93, 5), 95),
smoothing = "increasing",
k = 6,
weights = "exposure"
)
# Limit the visible range without changing the fitted smoothing curve.
autoplot(ref, x_max = 80, y_max = 1.5)
## End(Not run)
Add derived tariff segments to portfolio data
Description
Adds the tariff segments derived by derive_tariff_segments() as a new factor
column to a portfolio data set. The stored boundaries are applied to the
original continuous risk-factor column, so the result does not depend on the
row order used when the GAM was fitted.
The helper does not re-estimate the GAM or derive new boundaries. It can be used after filtering or reordering the original portfolio and on new data whose risk-factor values remain within the range used to derive the segmentation.
Usage
add_tariff_segments(data, segments, name = NULL, overwrite = FALSE)
Arguments
data |
A data frame to which the tariff segments should be added. |
segments |
Object of class |
name |
Character string. Name of the new output column. If |
overwrite |
Logical. If |
Details
The risk-factor name and optional rounding increment are taken from
segments. The risk-factor column in data must be numeric and contain only
finite, non-missing values. Values outside the original segmentation range
produce an error because their tariff treatment has not been supported by
the fitted GAM. The resulting factor can be used in a GLM or retained as a
candidate grouping for further actuarial review.
Value
A data frame with the derived tariff segment column added.
Author(s)
Martin Haringa
See Also
risk_factor_gam(), derive_tariff_segments()
Examples
## Not run:
age_segments <- risk_factor_gam(
MTPL,
risk_factor = "age_policyholder",
claim_count = "nclaims",
exposure = "exposure"
) |>
derive_tariff_segments()
MTPL |>
add_tariff_segments(age_segments, name = "age_policyholder_segment")
## End(Not run)
Convert an object to a gt table
Description
Generic presentation helper. Methods return a gt table for objects where a
formatted reporting table is more useful than another plot.
Create a formatted gt table from an object returned by
assess_excess_threshold(). The original object remains a regular
data.frame subclass; as_gt() is only used when a presentation table is
needed for a report, tariff note or pricing review.
Format the coefficient-level result from bootstrap_coefficients() as a
gt table. The table retains the requested link or exponentiated scale and
shows how many bootstrap estimates were available for each coefficient.
Create a formatted gt table from an object returned by rating_table().
Risk factors are presented as row groups, while fitted model effects are
shown as relativities or coefficients depending on the scale selected in
rating_table().
Usage
as_gt(x, ...)
## S3 method for class 'threshold_assessment'
as_gt(
x,
claims = TRUE,
loss = FALSE,
premium = TRUE,
locale = "nl-NL",
loss_decimals = 0,
premium_decimals = 0,
ratio_decimals = 1,
color_last_column = TRUE,
title = NULL,
subtitle = NULL,
...
)
## S3 method for class 'bootstrap_coefficients'
as_gt(
x,
scale = c("link", "exponentiated", "relativity"),
confidence = 0.95,
interval = c("percentile", "normal"),
locale = "nl-NL",
estimate_decimals = 3,
success_decimals = 1,
title = NULL,
subtitle = NULL,
...
)
## S3 method for class 'rating_table'
as_gt(
x,
significance = NULL,
show_effect_spanner = NULL,
model_labels = NULL,
locale = "nl-NL",
estimate_decimals = 3,
exposure_decimals = 0,
missing_text = "–",
title = NULL,
subtitle = NULL,
...
)
Arguments
x |
A supported object to convert, such as a |
... |
Arguments passed to methods. |
claims |
Logical. If |
loss |
Logical. If |
premium |
Logical. If |
locale |
Character. Locale used to format model effects and exposure,
for example |
loss_decimals, premium_decimals, ratio_decimals |
Non-negative whole numbers controlling displayed decimals for loss amounts, premium amounts and percentage ratios. |
color_last_column |
Logical. If |
title |
Optional character. Table title. If |
subtitle |
Optional character. Table subtitle. If |
scale |
Character string. |
confidence |
Numeric scalar between 0 and 1 giving the confidence level. |
interval |
Character string. |
estimate_decimals |
Non-negative whole number. Number of decimals shown for fitted coefficients or relativities. |
success_decimals |
Non-negative whole number. Number of decimals for the success-rate percentage. |
significance |
Optional logical. If |
show_effect_spanner |
Optional logical. If |
model_labels |
Optional character vector with display labels for the
fitted models. By default, each model object name is used unchanged. An
unnamed vector is matched to the model columns in their existing order. A
named vector can map model object names to labels, for example
|
exposure_decimals |
Non-negative whole number. Number of decimals shown for the exposure column, when available. |
missing_text |
Single character string used to display missing values.
The default is an en dash ( |
Details
The first column of a rating_table identifies the model risk factor.
as_gt() uses this column as groupname_col and sets
row_group_as_column = TRUE. Levels belonging to the same risk factor are
therefore kept together in a compact format suitable for a tariff note,
model review or technical appendix.
Risk-factor and level order are taken directly from rating_table(). Use
its risk_factor_order, level_order, numeric_level_order,
reference_first and order_model arguments to determine the order before
formatting the table.
With significance = TRUE, significance stars are appended to the fitted
effects and the significance levels are shown below the table. This requires
an object originally created with rating_table(significance = TRUE),
because p-value information is deliberately not retained when significance
is disabled during table construction. Significance stars are a statistical
diagnostic and should be interpreted together with exposure, effect size,
model stability and actuarial relevance.
In the underlying rating_table, estimates and significance indicators are
stored in separate est_* and signif_* columns. as_gt() merges each pair
only for display. The estimates therefore remain numeric in the source
object, including when several models are presented in one table.
Value
A gt_tbl object for supported methods.
Author(s)
Martin Haringa
Examples
portfolio <- data.frame(
policy_id = 1:10,
sector = rep(c("Industry", "Retail"), each = 5),
claim_count = c(
0, 1, 1, 1, 1,
0, 1, 1, 1, 1
),
claim_amount = c(
0, 25000, 120000, 50000, 175000,
0, 40000, 90000, 150000, 300000
),
policy_years = rep(1, 10)
)
thresholds <- assess_excess_threshold(
data = portfolio,
claim_amount = "claim_amount",
thresholds = c(25000, 50000, 100000, 150000),
exposure = "policy_years",
group = "sector",
claim_count = "claim_count"
)
if (requireNamespace("gt", quietly = TRUE)) {
as_gt(thresholds)
}
portfolio <- MTPL
portfolio$zip <- as.factor(portfolio$zip)
frequency_model <- glm(
nclaims ~ bm + zip + offset(log(exposure)),
family = poisson(),
data = portfolio
)
fitted_tariff <- rating_table(
frequency_model,
model_data = portfolio,
exposure = "exposure",
significance = TRUE
)
if (requireNamespace("gt", quietly = TRUE)) {
as_gt(fitted_tariff)
as_gt(fitted_tariff, model_labels = "Frequency model")
as_gt(fitted_tariff, significance = FALSE, locale = "en-US")
}
Present a refinement audit as a gt table
Description
Format the risk-factor and level impact calculated by
audit_refinement() for a technical note or actuarial review.
Usage
## S3 method for class 'refinement_audit'
as_gt(
x,
locale = "nl-NL",
value_decimals = 2,
ratio_decimals = 1,
title = "Refinement impact",
subtitle = NULL,
...
)
Arguments
x |
A |
locale |
Character string used for number formatting. |
value_decimals |
Non-negative whole number for fitted values and absolute changes. |
ratio_decimals |
Non-negative whole number for percentage changes. |
title |
Optional table title. |
subtitle |
Optional table subtitle. If |
... |
Currently unused. |
Value
A gt_tbl object.
See Also
Assess possible excess-loss thresholds
Description
Compare candidate thresholds for capped severity and large-loss pricing work.
assess_excess_threshold() is a diagnostic helper. It does not choose a
threshold automatically. It shows how many claims, how many records contain
claim amounts above candidate thresholds, how much historical claim cost sits
above those thresholds, and how much risk premium would remain after capping
claims at each threshold.
The function is intended for portfolio-level data as well as claim-level
data. Portfolio-level data can include policies without claims, for example
rows where claim_count = 0 and the claim amount is zero. Use this before
redistribute_excess_loss() to understand the effect of the threshold on
the portfolio. The output is useful for tariff notes, pricing reviews and
governance discussions around adjusted severity models.
Usage
assess_excess_threshold(
data,
claim_amount,
thresholds,
exposure = NULL,
group = NULL,
claim_count = NULL
)
Arguments
data |
A |
claim_amount |
Character string. Claim amount column. |
thresholds |
Numeric vector of candidate thresholds. |
exposure |
Optional character string. Exposure column. If supplied,
risk premium before and after capping is calculated. The output column keeps
this original name. If |
group |
Optional character string. Grouping column used to assess
thresholds by segment. The output column keeps this original name. If
|
claim_count |
Optional character string. Claim-count column. If
supplied, |
Details
The output can be used for two common follow-up analyses. First, aggregate the threshold assessment to portfolio level to calculate the average additional risk premium required to finance the excess layer. Second, after selecting a threshold, compare groups to see which parts of the portfolio benefit most from the excess protection.
Value
A data.frame with class "threshold_assessment" and columns:
- group column
The original grouping column, such as
sector, ifgroupis supplied. This is the first column when grouping is used.thresholdThe excess threshold being assessed. Thresholds are shown in the same order as supplied in the
thresholdsargument.- exposure column
The original exposure column, such as
policy_years, ifexposureis supplied. Ifexposure = NULL, this column is namedexposureand counts records.n_claimsTotal number of claims, calculated from
claim_countor inferred fromclaim_amount > 0.n_excess_recordsNumber of records with
claim_amount > threshold. This counts records, not individual claims.total_lossTotal claim amount before applying the threshold.
capped_lossTotal claim amount retained below or at the threshold.
excess_lossTotal claim amount above the threshold.
pure_premium_beforeRisk premium before capping:
total_loss / exposure.pure_premium_afterRisk premium after capping:
capped_loss / exposure.premium_reductionpure_premium_before - pure_premium_after, equivalent toexcess_loss / exposure. This is positive when applying the threshold reduces the retained risk premium.premium_reduction_ratiopremium_reduction / pure_premium_before. This is between 0 and 1 whenpure_premium_before > 0; ifpure_premium_before == 0, it is defined as 0.
Author(s)
Martin Haringa
Examples
portfolio <- data.frame(
policy_id = 1:10,
sector = rep(c("Industry", "Retail"), each = 5),
claim_count = c(
0, 1, 1, 1, 1,
0, 1, 1, 1, 1
),
claim_amount = c(
0, 25000, 120000, 50000, 175000,
0, 40000, 90000, 150000, 300000
),
policy_years = rep(1, 10)
)
thresholds <- assess_excess_threshold(
data = portfolio,
claim_amount = "claim_amount",
thresholds = c(25000, 50000, 100000, 150000),
exposure = "policy_years",
group = "sector",
claim_count = "claim_count"
)
thresholds
if (requireNamespace("gt", quietly = TRUE)) {
as_gt(thresholds)
}
# Calculate the average additional risk premium required to finance
# the excess portion of the claims.
thresholds |>
dplyr::summarise(
policy_years = sum(policy_years),
excess_loss = sum(excess_loss),
capped_loss = sum(capped_loss),
extra_risk_premium = excess_loss / policy_years,
risk_premium_increase = excess_loss / capped_loss,
.by = "threshold"
)
# After selecting a threshold, compare which groups benefit most
# from the excess protection.
selected_threshold <- thresholds |>
dplyr::filter(threshold == 100000) |>
dplyr::select(
sector,
threshold,
policy_years,
n_claims,
n_excess_records,
premium_reduction,
premium_reduction_ratio
) |>
dplyr::arrange(dplyr::desc(premium_reduction_ratio))
selected_threshold
# If claim_count is omitted, records with positive claim amounts are counted.
assess_excess_threshold(
data = portfolio,
claim_amount = "claim_amount",
thresholds = 100000,
exposure = "policy_years",
group = "sector"
)
Audit the effect of a fitted model refinement
Description
Compare an unrestricted GLM with the model returned by refit() on the same
observed portfolio. The audit records the refinement specification and
quantifies how the fitted response or fitted rate changes for the portfolio
and for each final tariff-factor level.
Usage
audit_refinement(
object,
exposure = NULL,
risk_factors = NULL,
scale = c("auto", "response", "per_exposure"),
metric = NULL
)
Arguments
object |
A fitted model returned by |
exposure |
Optional character string naming the exposure column. With
|
risk_factors |
Optional character vector identifying final tariff
factors for the level comparison. If |
scale |
Character string. |
metric |
Optional character string used to describe the audited measure,
for example |
Details
Direct coefficient comparisons are generally not a sufficient refinement
audit. A coefficient can change because the intercept or another model term
changes, while the combined fitted value for a policy remains similar.
audit_refinement() therefore compares predictions from the original and
refined models on common observed model-point combinations.
The model points are obtained with rating_grid(). Portfolio and level
results are weighted by the number of records or, when supplied, by
exposure. With scale = "per_exposure", predictions that include an
exposure offset are divided by total exposure after aggregation. This gives
an exposure-weighted fitted rate rather than an unweighted average over
unique model points.
The resulting measure should be named according to the model being audited.
For a frequency model it is normally a fitted frequency; for a severity
model it is a fitted average severity; and for a direct risk-premium model it
can be labelled "risk_premium". A complete risk-premium comparison requires
either a direct risk-premium model or an explicit combination of frequency
and severity predictions.
Value
An object of class refinement_audit. The object contains package
and model metadata, the ordered refinement steps, portfolio-level results,
results by risk factor and level, and the model points used in the
calculation. Use summary.refinement_audit() for a concise audit report,
as.data.frame() for the level results and as_gt() for a formatted table.
Author(s)
Martin Haringa
See Also
prepare_refinement(), refit(), rating_grid(),
summary.rating_refinement()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4),
exposure = rep(1, 6),
risk_class = factor(c("A", "B", "A", "B", "A", "B"))
)
base_model <- glm(
claims ~ risk_class + offset(log(exposure)),
family = poisson(),
data = portfolio
)
refinement <- prepare_refinement(base_model, data = portfolio) |>
add_restriction(data.frame(
risk_class = "B",
risk_class_restricted = 1.15
))
summary(refinement)
refined_model <- refit(refinement)
audit <- audit_refinement(
refined_model,
exposure = "exposure",
metric = "frequency"
)
summary(audit)
as.data.frame(audit)
if (requireNamespace("gt", quietly = TRUE)) {
as_gt(audit)
}
Plot the resampled performance distribution
Description
Display the empirical distribution of resampled RMSE values from
bootstrap_performance(). The histogram shows the observed resampling
distribution, while the density curve provides a smooth visual summary.
Usage
## S3 method for class 'bootstrap_performance'
autoplot(object, fill = "#E6E6E6", color = NA, ...)
Arguments
object |
An object of class |
fill |
Fill colour of the histogram bars. Default is |
color |
Border colour of the histogram bars. Default is |
... |
Currently unused. |
Details
The dashed orange line marks the RMSE of the original fitted model. The dotted grey lines mark the 2.5 and 97.5 percent empirical quantiles of the resampled values when these can be calculated. Their distance provides a practical indication of sampling variability; it is not a formal prediction interval for future portfolio performance.
Value
A ggplot2::ggplot object.
Author(s)
Martin Haringa
See Also
bootstrap_performance(), rmse()
Examples
## Not run:
mod1 <- glm(nclaims ~ age_policyholder, data = MTPL,
offset = log(exposure), family = poisson())
x <- bootstrap_performance(mod1, MTPL, n_resamples = 100,
show_progress = FALSE)
autoplot(x)
## End(Not run)
Inspect simulation-based residual uniformity
Description
Plot the scaled residuals returned by check_residuals() against their
theoretical uniform quantiles. Systematic departures from the diagonal can
indicate remaining structure, distributional mismatch or influential
observations in a fitted pricing model.
Usage
## S3 method for class 'check_residuals'
autoplot(object, show_message = TRUE, max_points = 1000, ...)
Arguments
object |
An object produced by |
show_message |
Logical. If |
max_points |
Maximum number of QQ-plot points to display. If the
residual check contains more points, an evenly spaced subset is shown.
Use |
... |
Currently unused. |
Details
The subtitle reports the uniformity-test p-value stored in object. This
p-value is a diagnostic signal rather than a stand-alone model acceptance
criterion. The shape and location of deviations should be assessed together
with exposure, fitted values and relevant risk-factor levels.
Reducing max_points affects only the displayed QQ points; it does not
change the residual calculation or the reported test.
Value
A ggplot2::ggplot object.
Author(s)
Martin Haringa
See Also
check_residuals(), check_overdispersion()
Plot observed portfolio experience by risk factor
Description
Visualise the exposure and observed actuarial metrics calculated by
factor_analysis(). The plots support assessment of portfolio composition,
observed frequency, severity, risk premium and loss ratio across risk-factor
levels.
The observed patterns are descriptive. They do not adjust for correlations with other risk factors and should therefore not be interpreted as multivariate tariff relativities.
Usage
## S3 method for class 'factor_analysis'
autoplot(
object,
metrics = NULL,
ncol = 1,
legend_position = c("right", "bottom", "top", "left", "none"),
show_exposure = TRUE,
show_exposure_labels = TRUE,
sort_by_exposure = FALSE,
level_order = NULL,
decimal_mark = ",",
line_color = NULL,
bar_fill = NULL,
abbreviate_labels = TRUE,
label_width = 20,
label_abbreviations = NULL,
flip_bars = FALSE,
show_total = FALSE,
total_color = NULL,
total_name = NULL,
rotate_angle = NULL,
custom_theme = NULL,
remove_underscores = FALSE,
compact_x_axis = TRUE,
show_plots = NULL,
background = NULL,
labels = NULL,
sort = NULL,
sort_manual = NULL,
dec.mark = NULL,
color = NULL,
color_bg = NULL,
coord_flip = NULL,
remove_x_elements = NULL,
...
)
Arguments
object |
A |
metrics |
Numeric or character vector specifying which metrics to plot (default is all available metrics). The numeric positions are:
Character values can be |
ncol |
Positive whole number. Number of columns in the plot composition. |
legend_position |
Character string specifying the legend position.
Supported values are |
show_exposure |
Show exposure as background bars behind line plots (default = TRUE). |
show_exposure_labels |
Show labels with the exposure bars (default = TRUE). |
sort_by_exposure |
Sort risk factor levels into descending order by exposure (default = FALSE). |
level_order |
Custom order for risk factor levels; character vector (default = NULL). |
decimal_mark |
Decimal mark; defaults to |
line_color |
Optional override for line/point color. If NULL (default), colors are taken from the internal palette. If specified, the chosen color is applied to all line-based plots. |
bar_fill |
Optional override for background bar color. If NULL (default), the background color is taken from the internal palette. If specified, the chosen color is applied to all background bars. |
abbreviate_labels |
Logical. If |
label_width |
Positive whole number of at least 2. Maximum number of characters in automatically shortened level labels. |
label_abbreviations |
Optional named character vector with explicit
display labels, for example
|
flip_bars |
Logical. If |
show_total |
Show line for total if |
total_color |
Color for total line (default = |
total_name |
Legend name for total line (default = NULL). |
rotate_angle |
Numeric value for angle of labels on the x-axis (degrees). |
custom_theme |
List with customized theme options. |
remove_underscores |
Logical; remove underscores from labels (default = FALSE). |
compact_x_axis |
Logical. When
This prevents duplicated x-axes in vertically stacked patchwork plots.
Defaults to |
show_plots |
Deprecated. Use |
background |
Deprecated alias for |
labels |
Deprecated alias for |
sort |
Deprecated alias for |
sort_manual |
Deprecated alias for |
dec.mark |
Deprecated alias for |
color |
Deprecated alias for |
color_bg |
Deprecated alias for |
coord_flip |
Deprecated alias for |
remove_x_elements |
Deprecated alias for |
... |
Currently unused. |
Details
For rate-based metrics, exposure can be shown as background bars so that observed level differences can be interpreted together with portfolio volume. Levels with little exposure or few claims can produce volatile observed metrics and should be assessed accordingly.
When the factor analysis contains one or more by variables, separate
observed series are shown. show_total = TRUE adds the aggregate portfolio
series for comparison. Sorting and manual level ordering affect only the
presentation; the underlying summaries are unchanged.
A rate or ratio is not shown for a risk-factor level when its denominator is
zero or missing. For example, average severity is undefined when claim count
is zero, and loss ratio is undefined when premium is zero. autoplot() gives
one combined warning identifying the affected metrics and levels. Other valid
metrics and exposure information remain in the figure.
Value
A patchwork composition containing the requested ggplot panels.
Author(s)
Marc Haine, Martin Haringa
See Also
factor_analysis(), add_portfolio_experience()
Examples
# Plot observed frequency and risk premium
x <- factor_analysis(MTPL2,
x = "area",
severity = "amount",
nclaims = "nclaims",
exposure = "exposure")
autoplot(x, metrics = c("frequency", "risk_premium"))
Inspect a model refinement step
Description
Visualise one stored step of a rating_refinement specification before
fitting the revised GLM with refit(). The plot compares the original
fitted tariff effect with the smoothing, restriction, shrinkage, rebasing or
sublevel relativity specification produced by the selected step.
Usage
## S3 method for class 'rating_refinement'
autoplot(
object,
variable = NULL,
step = NULL,
x_max = NULL,
y_max = NULL,
show_initial_smoothing = FALSE,
show_segments = TRUE,
remove_underscores = FALSE,
rotate_angle = NULL,
custom_theme = NULL,
...
)
Arguments
object |
Object of class |
variable |
Optional character string identifying the model or derived variable whose refinement step should be shown. For one smoothing lineage, the most recent smoothing or edit step is selected. An error is returned when no step matches or when matches belong to different refinements. |
step |
Optional positive integer identifying a step in the stored
refinement sequence. This takes precedence over |
x_max |
Optional single finite numeric value. Maximum value displayed on
the x-axis of a smoothing plot. This changes only the visible plotting
range; it does not remove observations, alter the fitted smoothing curve or
affect |
y_max |
Optional single finite numeric value. Maximum relativity
displayed on the y-axis of a smoothing plot. Like |
show_initial_smoothing |
Logical. For a smoothing or smoothing-edit
plot, whether to overlay the initial curve produced by the corresponding
|
show_segments |
Logical. For a smoothing or smoothing-edit relativity
plot, whether to show the horizontal relativities and boundary points of
the new tariff segments. Set this to |
remove_underscores |
Logical; if |
rotate_angle |
Optional numeric value for the angle of x-axis labels. |
custom_theme |
Optional list passed to |
... |
Additional plotting arguments passed to ggplot2 geoms. |
Details
Refinement steps are evaluated in their stored order up to and including the selected step. The plot is a diagnostic preview: it does not refit the GLM and does not modify the refinement specification.
If step is supplied, that position in the refinement sequence is shown. If
only variable is supplied, the most recent step in one smoothing lineage
is used. For other refinement types, exactly one stored step must match that
variable. When neither is supplied, the object must contain exactly one
refinement step. Otherwise the function asks the user to select a step
explicitly.
Each edit_smoothing() call is stored as a separate workflow step. Selecting
such a step shows the cumulative smoothing after all preceding edits up to
that point. Set show_initial_smoothing = TRUE to add the curve produced by
the corresponding add_smoothing() step before any edits were applied.
Actuarial interpretation
The plot supports review of the proposed tariff structure before estimation. It can be used to assess the local shape and magnitude of a smoothing curve, the effect of fixed relativities, and the differentiation introduced within a broader GLM level. This visual assessment does not by itself establish statistical adequacy; claim volume, exposure, stability over time and model diagnostics should also be considered.
For a sublevel split created by add_relativities(), the original parent
level is shown as a horizontal segment across its child levels. This makes
the parent GLM effect and the proposed within-level differentiation directly
comparable.
Value
A ggplot2 object.
Author(s)
Martin Haringa
See Also
prepare_refinement(), add_smoothing(), edit_smoothing(),
add_restriction(), add_shrinkage(), add_rebasing(),
add_relativities(), refit()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4),
exposure = rep(1, 6),
risk_class = factor(c("A", "B", "C", "A", "B", "C"))
)
model <- glm(
claims ~ risk_class + offset(log(exposure)),
family = poisson(),
data = portfolio
)
refinement <- prepare_refinement(model, data = portfolio) |>
add_restriction(data.frame(
risk_class = "C",
risk_class_restricted = 1.10
))
autoplot(refinement)
Compare fitted risk-factor effects graphically
Description
Plot the coefficients or relativities stored in a rating_table() object by
risk factor. Multiple fitted models can be compared, exposure can be shown as
background bars, and observed portfolio experience attached with
add_portfolio_experience() can be added as a separate line.
Usage
## S3 method for class 'rating_table'
autoplot(
object,
risk_factors = NULL,
metric = NULL,
ncol = 1,
legend_position = c("auto", "right", "bottom", "top", "left", "none"),
show_exposure_labels = TRUE,
decimal_mark = ",",
y_label = "Relativity",
bar_fill = NULL,
model_color = NULL,
use_linetype = FALSE,
abbreviate_labels = TRUE,
label_width = 20,
label_abbreviations = NULL,
rotate_angle = NULL,
custom_theme = NULL,
remove_underscores = FALSE,
labels = NULL,
dec.mark = NULL,
ylab = NULL,
fill = NULL,
color = NULL,
linetype = NULL,
...
)
Arguments
object |
A |
risk_factors |
Optional character vector specifying the risk factors to
plot. If |
metric |
Optional character string. Observed-experience metric to plot
when observed experience has been attached with
|
ncol |
Positive integer specifying the number of columns in the patchwork layout. |
legend_position |
Character string specifying the legend position.
The default, |
show_exposure_labels |
Logical. If |
decimal_mark |
Character string, either |
y_label |
Character string for the primary y-axis. |
bar_fill |
Optional colour for exposure bars. If |
model_color |
Optional single colour overriding the model-line palette. |
use_linetype |
Logical. If |
abbreviate_labels |
Logical. If |
label_width |
Positive whole number of at least 2. Maximum number of characters in automatically shortened level labels. |
label_abbreviations |
Optional named character vector with explicit
display labels, for example
|
rotate_angle |
Optional numeric angle for risk-factor level labels. |
custom_theme |
Optional named list passed to |
remove_underscores |
Logical. If |
labels |
Deprecated alias for |
dec.mark |
Deprecated alias for |
ylab |
Deprecated alias for |
fill |
Deprecated alias for |
color |
Deprecated alias for |
linetype |
Deprecated alias for |
... |
Additional arguments reserved for method compatibility. |
Details
Plot contents
One panel is produced for each selected risk factor. Model effects use the
primary y-axis. When exposure is available, bars are rescaled to the plotting
range and the original exposure scale is shown on the secondary y-axis.
Panel and level order follow the input rating_table() object. This keeps
the reference level and any explicit actuarial review order consistent
between the data frame, as_gt() and the plot.
Observed experience is plotted only after it has been attached with
add_portfolio_experience(). The selected metric is converted to the
relative scale recorded in that object, using either the model reference
level or the portfolio mean.
Actuarial interpretation
The plot supports comparison of fitted tariff effects, portfolio volume and unadjusted observed experience. Differences between the observed and modelled lines may indicate portfolio-mix effects, sparse levels, model smoothing or genuine lack of fit. The chart does not separate these explanations and should be reviewed together with claim counts, residual diagnostics and stability across periods.
When models are compared, the analyst should ensure that response definitions, link functions and relativity scales are sufficiently comparable. Exposure bars provide volume context but are not confidence intervals.
Value
A patchwork object containing one ggplot2 panel per selected risk
factor.
Author(s)
Martin Haringa
See Also
rating_table(), add_portfolio_experience(),
factor_analysis(), as_gt.rating_table()
Examples
portfolio <- MTPL
portfolio$zip <- as.factor(portfolio$zip)
frequency <- glm(
nclaims ~ bm + zip + offset(log(exposure)),
family = poisson(),
data = portfolio
)
effects <- rating_table(
frequency,
model_data = portfolio,
exposure = "exposure"
)
autoplot(effects, risk_factors = "zip", show_exposure_labels = FALSE)
Inspect smooth risk-factor effects and tariff-segment boundaries
Description
Plot the smooth effect estimated by risk_factor_gam() or inspect that same
effect together with candidate boundaries returned by
derive_tariff_segments(). Both methods use the same curve, confidence
interval, observation and axis layers.
Usage
## S3 method for class 'tariff_segments'
autoplot(
object,
confidence = FALSE,
color_gam = "steelblue",
show_observations = FALSE,
color_splits = "grey50",
size_points = 1,
color_points = "black",
rotate_labels = FALSE,
remove_outliers = NULL,
conf_int = NULL,
x_stepsize = NULL,
show_segments = TRUE,
...
)
## S3 method for class 'riskfactor_gam'
autoplot(
object,
confidence = FALSE,
color_gam = "steelblue",
show_observations = FALSE,
x_stepsize = NULL,
size_points = 1,
color_points = "black",
rotate_labels = FALSE,
remove_outliers = NULL,
conf_int = NULL,
...
)
Arguments
object |
An object returned by |
confidence |
Logical. If |
color_gam |
Colour for the fitted GAM line. |
show_observations |
Logical. If |
color_splits |
Colour for segment boundaries. Used only for a
|
size_points |
Numeric point size for observed experience. |
color_points |
Colour for observed experience. |
rotate_labels |
Logical. If |
remove_outliers |
Optional single numeric upper display limit for observed points. The fitted curve remains unchanged. |
conf_int |
Deprecated. Use |
x_stepsize |
Optional positive numeric step size for x-axis tick marks.
If |
show_segments |
Logical. For a |
... |
Additional arguments reserved for method compatibility. |
Details
The fitted line is shown on its natural response scale: claim frequency, average severity or risk premium. Optional observed points represent portfolio experience aggregated at the continuous risk-factor values used for fitting.
For a tariff_segments object, vertical lines show the derived interval
boundaries. These lines support actuarial review of where the continuous
effect changes sufficiently to motivate a categorical tariff treatment.
Set show_segments = FALSE to inspect only the underlying smooth curve.
Confidence intervals describe uncertainty in the fitted curve conditional on the selected GAM specification. They do not include uncertainty from model selection, omitted risk factors or future portfolio changes. Segment boundaries do not by themselves demonstrate that adjacent segments are statistically or commercially distinct. Exposure, claim volume, temporal stability and operational tariff constraints should be considered separately.
remove_outliers affects displayed observed points only. It does not remove
observations from the fitted GAM or alter the prediction curve or segment
boundaries.
Value
A ggplot2 object.
Author(s)
Martin Haringa
See Also
risk_factor_gam(), derive_tariff_segments(),
add_tariff_segments()
Examples
## Not run:
fit <- risk_factor_gam(
MTPL,
risk_factor = "age_policyholder",
claim_count = "nclaims",
exposure = "exposure"
)
# Inspect the continuous effect before deriving tariff segments.
autoplot(fit, confidence = TRUE, show_observations = TRUE)
segments <- derive_tariff_segments(
fit,
segmentation_penalty = 10,
seed = 1
)
# Inspect the same effect with the candidate segment boundaries.
autoplot(segments, confidence = TRUE, show_observations = TRUE)
autoplot(segments, show_segments = FALSE)
## End(Not run)
Plot a fitted truncated severity distribution
Description
Creates a plot of the empirical cumulative distribution function (ECDF) of the observed truncated claim amounts together with the fitted truncated CDF.
The comparison assesses whether the fitted conditional severity distribution represents the shape of the observed claims within the same truncation interval.
Usage
## S3 method for class 'truncated_severity'
autoplot(
object,
ecdf_geom = c("point", "step"),
x_label = NULL,
y_label = NULL,
y_limits = c(0, 1),
x_limits = NULL,
show_title = TRUE,
digits = 2,
truncation_digits = 2,
geom_ecdf = NULL,
xlab = NULL,
ylab = NULL,
ylim = NULL,
xlim = NULL,
print_title = NULL,
print_dig = NULL,
print_trunc = NULL,
...
)
Arguments
object |
An object produced by |
ecdf_geom |
Character string indicating how to display the empirical
CDF. Must be one of |
x_label |
Title of the x axis. Defaults to |
y_label |
Title of the y axis. Defaults to |
y_limits |
Numeric vector of length 2 specifying y-axis limits. |
x_limits |
Optional numeric vector of length 2 specifying x-axis limits. |
show_title |
Logical. If |
digits |
Integer. Number of digits for parameter estimates in the subtitle. |
truncation_digits |
Integer. Number of digits used for truncation bounds. |
geom_ecdf, xlab, ylab, ylim, xlim, print_title, print_dig, print_trunc |
Deprecated argument names kept for backward compatibility. |
... |
Currently unused. |
Details
The plot compares the empirical distribution of the observed, truncated claim severities with the fitted distribution conditional on the same truncation interval. This is a visual check of whether the selected severity distribution is plausible for the part of the portfolio that is actually observed.
Systematic separation between the empirical and fitted curves can indicate that the selected gamma or lognormal distribution does not adequately represent the observed severity shape. The plot does not assess the unobserved parts of the distribution outside the truncation bounds.
Value
A ggplot2 object.
Author(s)
Martin Haringa
See Also
fit_truncated_severity(), rlnormt(), rgammat()
Deprecated alias for set_reference_level()
Description
biggest_reference() is deprecated as of version 0.9.0. Use
set_reference_level() instead.
Usage
biggest_reference(x, weight)
Arguments
x |
A factor. |
weight |
A numeric vector of the same length as |
Value
Assess GLM coefficient stability by portfolio-row bootstrap
Description
Refit a GLM on repeated bootstrap samples of the estimation portfolio and retain the coefficient estimates from every successful refit. The resulting distribution describes how sensitive individual model coefficients are to sampling variation in the observed portfolio.
Usage
bootstrap_coefficients(
object,
n_resamples = 500,
seed = NULL,
show_progress = interactive()
)
Arguments
object |
A fitted |
n_resamples |
Positive whole number. Number of bootstrap samples. Default is 500. |
seed |
Optional single numeric seed for reproducible resampling. |
show_progress |
Logical. If |
Details
Each resample contains the same number of portfolio rows as the original
estimation data and is drawn with replacement. The function recovers these
data from object; a separate data argument is deliberately not required.
Rows omitted during the original model fit are excluded so the resampling
population remains aligned with the fitted GLM.
Original factor levels, the model formula, offsets and model weights are
retained during refitting. A factor level may nevertheless be absent from a
particular bootstrap sample. Its coefficient can then be non-estimable and
is stored as NA for that replicate.
A failed or non-converged GLM refit does not stop the procedure. The failed
replicate is recorded and the function continues. After resampling, an
informative message reports how many requested refits produced usable model
objects. summary.bootstrap_coefficients() reports the number of finite
estimates separately for each coefficient.
Actuarial interpretation
The bootstrap distribution can identify tariff effects that are sensitive to the particular portfolio sample. Wide intervals, material bootstrap bias or a low number of estimable replicates often indicate sparse levels, correlated model terms or limited claim information. These diagnostics should be considered alongside exposure, claim counts, coefficient interpretation and stability across calendar periods.
The row bootstrap represents sampling variation in the observed estimation portfolio. It does not include future trend, parameter uncertainty caused by model selection, structural changes in portfolio composition or dependence between repeated records for the same policy. Where such dependence is material, a cluster-level bootstrap would require a different resampling design.
Value
An object of class "bootstrap_coefficients". It contains the
original coefficients, a coefficient matrix with one row per requested
resample, indicators for successful model fits, recorded failure messages,
and the resampling settings. Use summary.bootstrap_coefficients() for a
coefficient-level data frame and as_gt() for a formatted table.
Author(s)
Martin Haringa
See Also
summary.bootstrap_coefficients(), bootstrap_performance(),
model_performance(), as_gt()
Examples
model <- glm(
nclaims ~ age_policyholder + zip + offset(log(exposure)),
family = poisson(),
data = MTPL
)
boot <- bootstrap_coefficients(
model,
n_resamples = 25,
seed = 123,
show_progress = FALSE
)
summary(boot, scale = "link")
summary(boot, scale = "exponentiated")
summary(boot, scale = "relativity")
if (requireNamespace("gt", quietly = TRUE)) {
as_gt(boot, scale = "relativity")
}
Assess performance stability under repeated resampling
Description
Refit a pricing model on repeated samples and record the resulting response-scale prediction error. The distribution of RMSE values describes how sensitive model performance is to changes in the observed portfolio sample.
Usage
bootstrap_performance(
model,
data,
n_resamples = 50,
sample_fraction = 1,
metric = "rmse",
sampling = c("bootstrap", "split"),
show_progress = TRUE,
rmse_model = NULL,
n = NULL,
frac = NULL
)
Arguments
model |
A fitted model object that can be updated on resampled data. |
data |
Data frame containing the model response and predictors. |
n_resamples |
Positive whole number. Number of resampling replicates. Default is 50. |
sample_fraction |
Fraction of the data used in the training sample. Must
be in |
metric |
Character string. Performance metric to compute. Currently only
|
sampling |
Character string. Sampling scheme. |
show_progress |
Logical. Show a progress bar during resampling.
Default is |
rmse_model |
Optional finite numeric RMSE for the original fitted model.
If |
n, frac |
Deprecated argument names. Use |
Details
Resampling design
With sampling = "bootstrap", training rows are sampled with replacement.
With sampling = "split", they are sampled without replacement. When
sample_fraction < 1, performance is evaluated on records not used for
fitting. When sample_fraction = 1, performance is evaluated on the sampled
training data and should be interpreted as an in-sample stability measure.
Character columns and factor columns are converted to factors with levels taken from the full input data before resampling. For factor variables used in the model, the training sample is augmented when needed so every observed level is represented at least once. This prevents prediction failures when a level is present in the evaluation data but absent from a particular training sample.
Actuarial interpretation
The resampled RMSE distribution is useful for comparing the stability of alternative frequency, severity or risk-premium specifications under repeated portfolio sampling. A narrow distribution indicates that the measured error is relatively insensitive to the sampled records; a wide distribution indicates greater sampling sensitivity.
This is an experience-based diagnostic and does not by itself represent the full uncertainty in future claims, trend, portfolio mix or model specification. Sparse factor levels are retained in training samples where necessary to avoid new-level prediction failures. That protection is useful operationally, but should be considered when interpreting the resampling design.
Value
An object of class "bootstrap_performance", which is a list with
components:
- rmse_bs
Numeric vector with
n_resamplesbootstrap RMSE values.- rmse_mod
Root mean squared error for the original fitted model.
- metric
Metric name.
- sampling
Sampling scheme.
Author(s)
Martin Haringa
See Also
rmse(), model_performance(),
autoplot.bootstrap_performance()
Examples
## Not run:
mod1 <- glm(nclaims ~ age_policyholder, data = MTPL,
offset = log(exposure), family = poisson())
# Use all records
x <- bootstrap_performance(mod1, MTPL, n_resamples = 80,
show_progress = FALSE)
print(x)
autoplot(x)
# Use 80% of records and evaluate on the remaining records
x_frac <- bootstrap_performance(mod1, MTPL, n_resamples = 50,
sample_fraction = .8, sampling = "split",
show_progress = FALSE)
autoplot(x_frac)
## End(Not run)
Deprecated alias for bootstrap_performance()
Description
bootstrap_rmse() is deprecated in favour of bootstrap_performance().
Objects returned by bootstrap_rmse() keep class "bootstrap_rmse" for
backward compatibility and also inherit from "bootstrap_performance".
Usage
bootstrap_rmse(
model,
data,
n = 50,
frac = 1,
metric = "rmse",
sampling = c("bootstrap", "split"),
show_progress = TRUE,
rmse_model = NULL
)
Arguments
model |
A fitted model object that can be updated on resampled data. |
data |
Data frame containing the model response and predictors. |
n |
Deprecated. Use |
frac |
Deprecated. Use |
metric |
Character string. Performance metric to compute. Currently only
|
sampling |
Character string. Sampling scheme. |
show_progress |
Logical. Show a progress bar during resampling.
Default is |
rmse_model |
Optional finite numeric RMSE for the original fitted model.
If |
Value
Calibrate the overall level of a refined pricing model
Description
Adjust the overall prediction level of a fitted model returned by refit()
without re-estimating its relative tariff structure. Calibration is a final
model-level operation: all refinement decisions must be completed before
calling calibrate_model().
Usage
calibrate_model(model, factor)
Arguments
model |
A fitted refined GLM returned by |
factor |
Positive finite numeric scalar. |
Details
For a refined GLM with a log link, calibration adds log(factor) to the
intercept. Consequently, every response-scale prediction is multiplied by
factor, while all non-intercept coefficients and tariff relativities remain
unchanged.
The returned object is a copied, internally consistent fitted model. Its coefficients, linear predictors, fitted values, working residuals, deviance and AIC are updated to the calibrated level. The original refined model is not modified. Calibration metadata store the factor, log shift, original and calibrated intercept, creation time and call.
Refinement and calibration
Model refinement changes or constrains the relative tariff structure and is
evaluated through prepare_refinement(), one or more add_*() operations,
and refit(). Model calibration changes only the final overall level. A
calibrated model cannot be calibrated again or used as the starting point
for further refinement. Retain the rating_refinement specification and
recalibrate a newly refitted model if earlier decisions need to be revised.
Value
A fitted glm that also inherits from calibrated_model. Attributes
calibration_factor, calibration_original_intercept,
calibration_intercept, calibration_log_shift, calibration_call and
calibrated_at record the calibration.
See Also
refit(), rating_table(), add_prediction(),
audit_refinement()
Examples
restrictions <- data.frame(
zip = c(0, 1, 2, 3),
zip_restricted = c(0.90, 1.00, 1.05, 1.10)
)
mod_initial <- glm(
nclaims ~ zip + offset(log(exposure)),
family = poisson(),
data = MTPL
)
mod_refined <- mod_initial |>
prepare_refinement() |>
add_restriction(restrictions) |>
refit(intercept_only = TRUE)
mod_calibrated <- calibrate_model(mod_refined, factor = 1.05)
rating_table(mod_calibrated)
data_final <- mod_refined$data |>
add_prediction(
mod_calibrated,
predictions = "net_risk_premium"
)
Check overdispersion of a Poisson claim frequency model
Description
Tests whether a fitted Poisson GLM shows overdispersion using Pearson's chi-squared statistic.
Usage
check_overdispersion(object)
Arguments
object |
A fitted model of class |
Details
In Poisson claim frequency models, the variance is assumed to be equal to the mean. A dispersion ratio above 1 indicates that the observed variation is larger than expected under that assumption. In pricing work this can be a useful diagnostic signal for omitted heterogeneity, clustering, outliers, or model misspecification. It does not automatically mean that the model is unusable.
A dispersion ratio close to 1 is broadly consistent with the Poisson variance assumption.
A dispersion ratio above 1 suggests overdispersion.
A p-value below 0.05 indicates statistically significant overdispersion.
Value
An object of class "overdispersion_check" and "overdispersion",
which is a list with elements:
- pearson_chisq
Pearson's chi-squared statistic.
- dispersion_ratio
Dispersion ratio, calculated as Pearson's chi-squared statistic divided by residual degrees of freedom.
- residual_df
Residual degrees of freedom.
- p_value
P-value from the chi-squared test.
For backwards compatibility the object also contains the aliases chisq,
ratio, rdf, and p.
Author(s)
Martin Haringa
References
Bolker B. et al. (2017). GLMM FAQ
See also: performance::check_overdispersion().
Examples
x <- glm(nclaims ~ area, offset = log(exposure),
family = poisson(), data = MTPL2)
check_overdispersion(x)
Check simulation-based model residuals
Description
Checks whether a fitted model shows systematic residual deviations from the
distribution implied by the model. The function uses simulation-based
residuals from DHARMa::simulateResiduals(), which are especially useful for
GLMs where classical residual plots can be hard to interpret.
Usage
check_residuals(object, n_simulations = 30)
Arguments
object |
A fitted |
n_simulations |
Number of simulations used to generate residuals. Must be a positive whole number. The default of 30 is intended for a quick check. For a more stable final assessment, 250 to 1,000 simulations will often be more suitable. More simulations increase the calculation time. |
Details
In insurance pricing, residual checks are used to assess whether a model is behaving consistently across the portfolio. For example, a Poisson frequency model may fit the average claim count well but still show structure in the residuals because of omitted rating factors, unmodelled heterogeneity, clustering, outliers, or an unsuitable distributional assumption.
DHARMa simulates new responses from the fitted model and compares the
observed response with those simulations. The resulting scaled residuals are
approximately uniformly distributed on [0, 1] when the model is correctly
specified. This gives a common diagnostic scale for GLMs and related models,
where raw residuals are otherwise difficult to compare across different
fitted values, exposures, or expected claim amounts.
check_residuals() returns the scaled residuals, QQ-plot data, and a
Kolmogorov-Smirnov p-value for a simple uniformity check. The p-value should
be read as a diagnostic signal, not as a pricing decision rule. A low p-value
indicates that the residual distribution differs from what the fitted model
implies and that the model specification may need review.
A low p-value does not always mean that the model has an important pricing problem. In a large portfolio, even a small difference can produce a low p-value. The result should therefore be reviewed together with the QQ plot, the size of the difference and its relevance for the tariff.
Value
An object of class "residual_check" and "check_residuals",
which is a list with:
- qq_data
Data frame with theoretical quantiles (
x) and observed scaled residuals (y).- scaled_residuals
Numeric vector of DHARMa scaled residuals.
- p_value
P-value from a Kolmogorov-Smirnov test against
uniform(0, 1).
For backwards compatibility the object also contains the aliases df and
p.val.
Author(s)
Martin Haringa
References
Dunn, K. P., & Smyth, G. K. (1996). Randomized quantile residuals. JCGS, 5, 1–10.
Gelman, A., & Hill, J. (2006). Data analysis using regression and multilevel/hierarchical models. Cambridge University Press.
Hartig, F. (2020). DHARMa: Residual Diagnostics for Hierarchical (Multi-Level / Mixed) Regression Models. R package version 0.3.0. https://CRAN.R-project.org/package=DHARMa
Examples
## Not run:
m1 <- glm(nclaims ~ area, offset = log(exposure),
family = poisson(), data = MTPL2)
cr <- check_residuals(m1, n_simulations = 250)
autoplot(cr)
## End(Not run)
Deprecated alias for rating_grid()
Description
construct_model_points() is deprecated in favour of rating_grid().
Usage
construct_model_points(
x,
group_by = NULL,
exposure = NULL,
exposure_by = NULL,
aggregate_cols = NULL,
drop_na = FALSE,
group_vars = NULL,
agg_cols = NULL
)
Arguments
x |
A |
group_by |
Optional character vector with the variables that define the
rating-grid points. If |
exposure |
Optional character; name of the exposure column to aggregate. |
exposure_by |
Optional character; name of a column used to split exposure or counts, for example a year variable. |
aggregate_cols |
Optional character vector with additional numeric
columns to aggregate using |
drop_na |
Logical; if |
group_vars, agg_cols |
Deprecated argument names. Use |
Value
See rating_grid().
Deprecated alias for derive_tariff_segments()
Description
construct_tariff_classes() is deprecated as of version 0.9.0. Use
derive_tariff_segments() instead.
Usage
construct_tariff_classes(
object,
complexity = 0,
max_iterations = 10000,
population_size = 200,
seed = 1,
alpha = NULL,
niterations = NULL,
ntrees = NULL
)
Arguments
object |
A |
complexity |
Deprecated. Use |
max_iterations |
Positive integer. Maximum number of evolutionary search iterations. This is an advanced algorithm-control parameter. |
population_size |
Positive integer. Number of candidate trees maintained during the evolutionary search. This is an advanced algorithm-control parameter. |
seed |
Single finite whole number used to reproduce the evolutionary search. |
alpha |
Deprecated. Use |
niterations |
Deprecated. Use |
ntrees |
Deprecated. Use |
Value
Default extrapolation break size based on existing tariff breaks
Description
Uses the median width of existing break intervals as a robust scale-aware default for extrapolation discretisation.
Usage
default_extrapolation_break_size(borders_model, factor = 1)
Arguments
borders_model |
A data.frame with columns |
factor |
Numeric scalar > 0. Multiplier applied to the median break width. |
Value
Numeric scalar (> 0).
Derive candidate tariff segments from a smooth risk-factor effect
Description
Approximate the smooth effect estimated by risk_factor_gam() with intervals
for a continuous risk factor. The resulting boundaries provide a candidate
categorical representation that can be inspected before inclusion in a
pricing GLM or tariff structure.
Usage
derive_tariff_segments(
object,
segmentation_penalty = 0,
seed = 1,
max_iterations = 10000,
population_size = 200,
complexity = NULL,
alpha = NULL,
niterations = NULL,
ntrees = NULL
)
Arguments
object |
A |
segmentation_penalty |
Non-negative numeric penalty on additional tree
splits. Larger values generally favour fewer tariff segments. The default
|
seed |
Single finite whole number used to reproduce the evolutionary search. |
max_iterations |
Positive integer. Maximum number of evolutionary search iterations. This is an advanced algorithm-control parameter. |
population_size |
Positive integer. Number of candidate trees maintained during the evolutionary search. This is an advanced algorithm-control parameter. |
complexity |
Deprecated. Use |
alpha |
Deprecated. Use |
niterations |
Deprecated. Use |
ntrees |
Deprecated. Use |
Details
Method
An evolutionary regression tree from evtree::evtree() is fitted to the
predicted GAM effect over the distinct observed risk-factor values. The tree
therefore approximates the estimated univariate curve; it is not fitted
directly to individual claim outcomes or portfolio loss. Internal tree split
points are translated into interval boundaries. If no internal split is
supported by the fitted search, one interval spanning the observed range is
returned.
The method follows the data-driven binning approach described by Henckaerts
et al. (2018). segmentation_penalty, population_size, max_iterations
and seed control the stochastic search rather than an actuarial
minimum-volume rule. Reusing the same inputs and seed makes the result
reproducible.
Each distinct observed risk-factor value has equal influence when the tree
approximates the fitted curve. Exposure, claim count or another actuarial
weight is deliberately not applied again in this step. The relevant
portfolio information has already influenced the curve through the
statistical specification used by risk_factor_gam(), such as the exposure
offset in a frequency model, claim-count weights in a severity model or
exposure weights in a risk-premium model. Applying a second weight during
segmentation would introduce an additional portfolio-distribution choice
after the GAM has been estimated.
Exposure and claim count remain available through summary(). They are
diagnostics for assessing the support and practical stability of candidate
segments, but they do not influence the estimated boundaries.
Actuarial interpretation
The returned segments approximate the shape of the fitted univariate GAM; they are not automatically a final tariff classification. Before use in a multivariate model, the boundaries should be assessed against exposure and claim volume, stability across periods, operational rounding and the interaction with other risk factors. Particular care is required for boundaries in sparsely populated tails.
summary() reports the number of portfolio records, number of
distinct risk-factor values and available exposure and claim volume within
each proposed segment. These diagnostics support actuarial review but do not
constitute an automatic acceptance rule. Minimum-volume requirements and
operational rounding should be selected with reference to portfolio size,
model purpose and governance standards.
A staged GLM refinement workflow
In practical pricing work, the candidate boundaries are often used to form
an initial set of relatively broad model groups. The actuary reviews
summary() and, where necessary, combines thinly populated segments or
increases segmentation_penalty until the groups have sufficient exposure
and claim information for stable estimation. The resulting factor can then
be included in an unrestricted GLM.
This broad first-stage grouping avoids estimating a separate free GLM
coefficient for every fine tariff interval when observations are unevenly
distributed over the continuous risk factor. After fitting the GLM,
add_smoothing() can use the broad model effect together with the original
continuous variable to construct a regularised pattern over finer breaks.
These finer breaks may reflect operational or commercial tariff boundaries,
while their relativities remain linked through the smoothing specification
rather than being estimated independently for every small segment.
The staged approach therefore separates statistical support from final tariff granularity: broad groups provide the information used by the GLM, while smoothing can translate that information into a finer and more regular tariff structure. Smoothing does not create additional observations, so the resulting classes should still be assessed for stability, extrapolation and commercial suitability.
The first and last boundaries equal the observed range used by the GAM. Applying the segmentation to new data outside that range results in an informative error rather than silent extrapolation.
Use autoplot.tariff_segments() to compare the smooth curve and boundaries.
Use add_tariff_segments() to apply the resulting boundaries to portfolio
data using the original continuous risk factor.
Value
A list of class "tariff_segments" with components:
- gam_prediction
Data frame with the fitted GAM curve.
- risk_factor
Name of the continuous risk factor.
- model_type
Model type:
"frequency","severity", or"pure_premium".- classification_data
Data frame used to derive the segments.
- risk_factor_values
Observed risk factor values in portfolio row order.
- segment_boundaries
Numeric vector with segment boundaries.
- assigned_segments
Factor with the tariff segment assigned to each observed risk factor value.
- segment_summary
Data frame with portfolio counts, distinct risk-factor values and the observed response components for each candidate segment. Use
summary()as the public interface for this table.- segmentation_penalty
Penalty applied to additional tree splits.
For backward compatibility, the old components prediction, x, model,
data, x_obs, splits, class_boundaries, assigned_groups, and
tariff_classes are also returned.
Author(s)
Martin Haringa
References
Antonio, K. and Valdez, E. A. (2012). Statistical concepts of a priori and a posteriori risk classification in insurance. Advances in Statistical Analysis, 96(2), 187–224. doi:10.1007/s10182-011-0152-7
Grubinger, T., Zeileis, A., and Pfeiffer, K.-P. (2014). evtree: Evolutionary learning of globally optimal classification and regression trees in R. Journal of Statistical Software, 61(1), 1–29. doi:10.18637/jss.v061.i01
Henckaerts, R., Antonio, K., Clijsters, M., & Verbelen, R. (2018). A data driven binning strategy for the construction of insurance tariff classes. Scandinavian Actuarial Journal, 2018(8), 681–705. doi:10.1080/03461238.2018.1429300
Wood, S.N. (2011). Fast stable restricted maximum likelihood and marginal likelihood estimation of semiparametric generalized linear models. JRSS B, 73(1), 3–36. doi:10.1111/j.1467-9868.2010.00749.x
See Also
risk_factor_gam(), autoplot.tariff_segments(),
add_tariff_segments(), prepare_refinement(), add_smoothing()
Examples
## Not run:
age_segments <- risk_factor_gam(
MTPL,
risk_factor = "age_policyholder",
claim_count = "nclaims",
exposure = "exposure"
) |>
derive_tariff_segments(
segmentation_penalty = 10,
seed = 1
)
autoplot(age_segments, show_observations = TRUE)
summary(age_segments)
MTPL |>
add_tariff_segments(age_segments, name = "age_policyholder_segment")
## End(Not run)
Edit a smoothing curve in a refinement workflow
Description
Modify a specified interval of a smoothing curve previously added with
add_smoothing(). Use a relative adjustment when the existing shape is
broadly appropriate, or explicit values and control points when the curve
should follow known targets.
Usage
edit_smoothing(
model,
model_variable = NULL,
step = NULL,
from = NULL,
to = NULL,
from_value = NULL,
to_value = NULL,
control_positions = NULL,
control_values = NULL,
adjustment = NULL,
slope_adjustment = 1,
transition = NULL,
allow_extrapolation = FALSE,
extrapolation_step = NULL
)
Arguments
model |
Object of class |
model_variable |
Character string. The |
step |
Optional numeric index of the original smoothing step or one of its later edit steps. In both cases, the new edit is linked to the same original smoothing and appended after the existing workflow steps. |
from, to |
Optional numeric values giving the start and end of the
source-variable interval to modify. For |
from_value, to_value |
Optional numeric values used to override the
smoothed curve value at |
control_positions, control_values |
Optional numeric vectors of equal length. These define additional points that the edited smoothing curve should pass through. |
adjustment |
Optional positive numeric scalar applied multiplicatively
to the current smoothing within the selected interval. |
slope_adjustment |
Positive numeric scalar controlling the change in
slope after |
transition |
Optional character string controlling how |
allow_extrapolation |
Logical. Whether edits may extend beyond the observed source-variable range. |
extrapolation_step |
Optional positive numeric scalar used to set the spacing of extra break points when extrapolation is allowed. |
Details
edit_smoothing() appends a separate, ordered edit step to a
rating_refinement object. It does not alter the fitted GLM immediately.
Repeated calls are cumulative: every new edit starts from the smoothing
produced by preceding edits to the same add_smoothing() step. The selected
cumulative curve is applied when refit() is called.
Use model_variable or step to identify the smoothing to edit. step may
identify either its original add_smoothing() step or a later edit belonging
to that smoothing. The interval from from to to defines the part of the source-variable
range that should be changed. With adjustment, either boundary may be
omitted. Supplying only from edits the curve from that value to the end of
the smoothing range; supplying only to edits it from the beginning of the
range to that value. adjustment multiplies the current smoothing within
the selected range. For example, adjustment = 1.05 requests an increase
of up to 5 percent relative to the existing smoothing.
With two boundaries, the multiplier is anchored at 1 at from and to and
reaches the requested adjustment near the middle. With only from, it is
anchored at 1 at from and moves towards the requested adjustment at the
end of the range. With only to, it starts at the requested adjustment and
reconnects to 1 at to. These one-sided forms are useful for refining a
lower or upper tail without introducing a jump at the supplied boundary.
By default, transition = NULL inherits the smoothing specification from
the add_smoothing() step. The entry and exit are adapted to their opposite
directions and join the unchanged curve continuously. "linear" uses
continuous straight transitions. "step" applies the multiplier
immediately at both boundaries and therefore permits deliberate jumps.
Explicit shape-constrained transition names accepted by add_smoothing()
can also be supplied. When a constrained transition is inherited or selected,
the edited curve is checked for the corresponding monotonicity and curvature.
from_value and to_value instead prescribe curve values at the interval
boundaries. control_positions and control_values add points that the
edited curve should follow inside the interval. Relative adjustments and
explicit target values cannot be combined in one edit_smoothing() call
because they represent different actuarial instructions. They may be used in
separate consecutive edits, which are then evaluated in their stored order.
slope_adjustment changes the remaining increase or decrease after from,
while keeping the curve before that point unchanged. If R(x) is the
current smoothing and a is from, the edited
curve is R(a) + s[R(x) - R(a)] for x > a, where s is
slope_adjustment. A value of 1.10 therefore makes the change after the
anchor 10 percent stronger; 0.90 makes it 10 percent weaker. The curve is
continuous at the anchor.
Each call applies one edit type: a relative adjustment, a
slope_adjustment, or explicit target/control-point values. Apply multiple
changes in consecutive calls so that every actuarial intervention remains a
separate, inspectable refinement step.
Actuarial interpretation
The edited interval is an explicit tariff assumption layered on the statistically fitted smoothing curve. It should be supported by an actuarial rationale and reviewed against exposure, observed experience and the continuity of adjacent segments. The edit does not add information to sparse parts of the portfolio and should not be interpreted as a new model estimate.
Keep the rating_refinement object, call refit() to assess the current
specification, edit that same refinement object, and call refit() again.
The previously fitted GLM remains unchanged. This retains the order and
content of manual adjustments as part of the reproducible refinement
specification.
Value
A rating_refinement object with a separate smoothing-edit step
appended to the ordered specification. The pricing GLM is not fitted again
until refit() is called.
Author(s)
Martin Haringa
See Also
prepare_refinement(), add_smoothing(), add_restriction(),
add_shrinkage(), add_rebasing(), add_relativities(), refit()
Examples
set.seed(42)
driver_age <- rep(seq(20, 59), each = 4)
exposure <- rep(1, length(driver_age))
age_band <- cut(
driver_age,
breaks = c(18, 30, 40, 50, 60),
include.lowest = TRUE
)
expected_claims <- exp(
-1.7 + 0.018 * (driver_age - 20) + 0.0006 * (driver_age - 40)^2
)
portfolio <- data.frame(
claims = rpois(length(driver_age), exposure * expected_claims),
exposure = exposure,
driver_age = driver_age,
age_band = age_band
)
model <- glm(
claims ~ age_band + offset(log(exposure)),
family = poisson(),
data = portfolio
)
refinement <- prepare_refinement(model, data = portfolio) |>
add_smoothing(
model_variable = "age_band",
source_variable = "driver_age",
breaks = c(18, 30, 40, 50, 60),
weights = "exposure"
)
# Fit and inspect the initial smoothing specification.
initial_model <- refit(refinement)
# Edit the retained specification and fit it again.
explicit_refinement <- refinement |>
edit_smoothing(
model_variable = "age_band",
from = 30,
to = 50,
from_value = 1.00,
to_value = 1.10,
control_positions = c(40),
control_values = c(1.05)
)
explicit_model <- refit(explicit_refinement)
# Keep the current shape as the basis and raise the middle of this interval
# by up to 5 percent. The inherited transition remains continuous.
adjusted_refinement <- refinement |>
edit_smoothing(
model_variable = "age_band",
from = 30,
to = 50,
adjustment = 1.05
)
adjusted_model <- refit(adjusted_refinement)
# Keep the curve unchanged through age 40, then strengthen its remaining
# change by 10 percent while retaining continuity at age 40.
steeper_refinement <- refinement |>
edit_smoothing(
model_variable = "age_band",
from = 40,
slope_adjustment = 1.10
)
# A one-sided adjustment applies from age 40 to the end of the range.
upper_tail_refinement <- refinement |>
edit_smoothing(
model_variable = "age_band",
from = 40,
adjustment = 1.05,
transition = "linear"
)
Recover the portfolio data used by a fitted model
Description
Recover the estimation data and pricing metadata stored with a fitted GLM or a model produced by the refinement workflow. The result provides a reproducible basis for rating grids, coefficient tables and portfolio-level model diagnostics.
model_data() is kept as a deprecated compatibility wrapper.
Usage
extract_model_data(x)
Arguments
x |
An object of class |
Details
Data represented by the result
For an ordinary GLM, the function recovers the data stored with the model or its model frame and records the response, model terms, risk factors, weights and offsets. The recovered data represent the observations available to the fitted model. Rows omitted during fitting, for example because of missing model variables, may therefore not be present.
For a refined model, technical columns used to construct smoothing and restriction terms are removed from the returned data. The mappings required to interpret the refined coefficients are retained as attributes.
Actuarial use
The extracted object is intended for downstream calculations that must remain
consistent with the fitted pricing model, such as rating_grid() and
rating_table(). It should not be interpreted as a replacement for the
original raw portfolio extract: preprocessing, filtering and missing-value
handling applied before or during model fitting remain part of the data
provenance.
Value
A data.frame of class "model_data" with additional attributes:
-
response: response variable in the model; -
rf: names of risk factors in the model; -
offweights: weight and offset variables if present; -
terms: model terms object for plain GLMs; -
mgd_rst,mgd_smt: merged restrictions and smooths for refit objects; -
new_nm,old_nm: new and old column names for refit objects.
Author(s)
Martin Haringa
See Also
rating_grid(), rating_table(), prepare_refinement()
Examples
## Not run:
library(insurancerating)
pmodel <- glm(
breaks ~ wool + tension,
data = warpbreaks,
family = poisson(link = "log")
)
extract_model_data(pmodel)
## End(Not run)
Summarise observed portfolio experience by risk factor
Description
Aggregate observed claim, exposure and premium experience for one or more discrete risk factors. The result supports exploratory pricing analysis by showing how portfolio volume and unadjusted actuarial metrics vary across factor levels.
Usage
factor_analysis(
data = NULL,
risk_factors = NULL,
claim_amount = NULL,
claim_count = NULL,
exposure = NULL,
premium = NULL,
group_by = NULL,
df = NULL,
x = NULL,
severity = NULL,
nclaims = NULL,
by = NULL
)
Arguments
data |
A data frame containing portfolio observations. |
risk_factors |
Non-empty character vector naming the discrete risk factors to analyse. |
claim_amount |
Optional character string naming the total claim-amount column. |
claim_count |
Optional character string naming the claim-count column. |
exposure |
Optional character string naming the exposure column. |
premium |
Optional character string naming the premium-amount column. |
group_by |
Optional character vector naming additional grouping variables, such as underwriting year or product segment. |
df, x, severity, nclaims, by |
Deprecated argument names. Use |
Details
Calculated measures
Depending on the supplied columns, the function calculates:
-
frequency = claim_count / exposure; -
average_severity = claim_amount / claim_count; -
risk_premium = claim_amount / exposure; -
loss_ratio = claim_amount / premium; -
average_premium = premium / exposure.
Input amount columns are summed before ratios are calculated. A measure is
omitted when its required inputs were not supplied. A zero or missing
denominator produces NA_real_ rather than an infinite value.
Actuarial interpretation
These are observed, univariate or stratified portfolio measures. They are not adjusted for correlation between rating factors and should not be interpreted as conditional GLM effects. Differences between levels may reflect portfolio mix, small exposure, claim volatility or changes over time. Claim counts, exposure and stability should therefore be reviewed alongside the ratios.
group_by can be used to compare the same risk-factor pattern across
periods or portfolio segments. autoplot.factor_analysis() provides the
corresponding graphical review. Modelled effects can subsequently be
inspected with rating_table().
Column interface
Column names are supplied as character strings. Deprecated univariate()
remains available for compatibility with its former interface.
Value
A data frame with classes "factor_analysis", "univariate" and
"data.frame". It contains the grouping columns, aggregated input columns
and all actuarial measures supported by the supplied inputs. The original
column names are retained for claim amount, claim count, exposure and
premium.
Author(s)
Martin Haringa
See Also
autoplot.factor_analysis(), rating_table(),
add_portfolio_experience()
Examples
area_experience <- factor_analysis(
MTPL2,
risk_factors = "area",
claim_amount = "amount",
claim_count = "nclaims",
exposure = "exposure",
premium = "premium"
)
area_experience
autoplot(area_experience, metrics = c("frequency", "risk_premium"))
Deprecated alias for fisher_classify()
Description
fisher() is deprecated as of version 0.8.0.
Usage
fisher(x, n = 7, diglab = 2)
Arguments
x |
Numeric vector to classify. |
n |
Number of classes. |
diglab |
Deprecated. Use |
Value
See fisher_classify().
Fisher's natural breaks classification
Description
fisher_classify() is deprecated as of version 0.8.0 because Fisher-Jenks
classification is not directly linked to the insurance rating workflow.
Classifies a continuous numeric vector into intervals using Fisher-Jenks natural breaks. Useful for choropleth mapping or other applications where grouped ranges are required.
Usage
fisher_classify(x, n = 7, dig.lab = NULL, diglab = NULL)
Arguments
x |
A numeric vector to be classified. |
n |
Integer. Number of classes to generate (default = 7). |
dig.lab |
Integer. Number of significant digits to use for interval labels (default = 2). |
diglab |
Deprecated. Use |
Details
The "fisher" style uses the algorithm proposed by Fisher (1958), commonly
referred to as the Fisher-Jenks algorithm. This function is a thin wrapper
around classInt::classIntervals().
The argument diglab is deprecated and will be removed in a future version.
Value
A factor indicating the interval to which each element of x
belongs.
Author(s)
Martin Haringa
References
Bivand, R. (2018). classInt: Choose Univariate Class Intervals. R package version 0.2-3. https://CRAN.R-project.org/package=classInt
Fisher, W. D. (1958). On grouping for maximum homogeneity. Journal of the American Statistical Association, 53, pp. 789–798. doi:10.1080/01621459.1958.10501479
Examples
set.seed(1)
x <- rnorm(100)
fisher_classify(x, n = 5)
Deprecated NSE wrapper for risk_factor_gam()
Description
fit_gam() is deprecated as of version 0.8.0.
Please use risk_factor_gam() instead.
In addition, note that column arguments must now be passed as strings (standard evaluation).
Usage
fit_gam(
data,
nclaims,
x,
exposure,
amount = NULL,
pure_premium = NULL,
model = "frequency",
round_x = NULL
)
Arguments
data |
A data frame containing portfolio observations. |
nclaims |
Deprecated NSE argument for claim counts. |
x |
Deprecated NSE argument for the continuous risk factor. |
exposure |
Character string. Exposure column used as an offset or aggregation weight. |
amount |
Deprecated NSE argument for claim amounts. |
pure_premium |
Optional character string. Row-level risk-premium column.
Required for |
model |
Character string. Response context: |
round_x |
Deprecated. Use |
Value
See risk_factor_gam().
Deprecated alias for fit_truncated_severity()
Description
fit_truncated_dist() is deprecated as of version 0.9.0. Use
fit_truncated_severity() instead.
Usage
fit_truncated_dist(
losses = NULL,
distribution = c("gamma", "lognormal"),
lower_truncation = NULL,
upper_truncation = NULL,
start_values = NULL,
print_initial = TRUE,
n_variants = 1,
n_shape_grid = 8,
n_scale_grid = 8,
show_progress = FALSE,
show_summary = TRUE,
y = NULL,
dist = NULL,
left = NULL,
right = NULL,
start = NULL,
trace = NULL,
report = NULL
)
Arguments
losses |
Numeric vector with observed claim severities. |
distribution |
Severity distribution to fit: |
lower_truncation |
Numeric lower truncation point. Claims at or below
this value are assumed not to be present in |
upper_truncation |
Numeric upper truncation point. Claims at or above
this value are assumed not to be present in |
start_values |
Optional named list of starting values. If |
print_initial |
Deprecated logical retained for backward compatibility. |
n_variants |
Controls how many local variations around base starts are used. |
n_shape_grid |
Number of grid points for gamma shape. |
n_scale_grid |
Number of grid points for gamma scale. |
show_progress |
Logical. If |
show_summary |
Logical. If |
y, dist, left, right, start, trace, report |
Deprecated argument names kept for backward compatibility. |
Value
Fit severity distributions to truncated claim data
Description
Estimate an underlying claim severity distribution when the observed claims are truncated.
Usage
fit_truncated_severity(
losses = NULL,
distribution = c("gamma", "lognormal"),
lower_truncation = NULL,
upper_truncation = NULL,
start_values = NULL,
print_initial = TRUE,
n_variants = 1,
n_shape_grid = 8,
n_scale_grid = 8,
show_progress = FALSE,
show_summary = TRUE,
y = NULL,
dist = NULL,
left = NULL,
right = NULL,
start = NULL,
trace = NULL,
report = NULL
)
Arguments
losses |
Numeric vector with observed claim severities. |
distribution |
Severity distribution to fit: |
lower_truncation |
Numeric lower truncation point. Claims at or below
this value are assumed not to be present in |
upper_truncation |
Numeric upper truncation point. Claims at or above
this value are assumed not to be present in |
start_values |
Optional named list of starting values. If |
print_initial |
Deprecated logical retained for backward compatibility. |
n_variants |
Controls how many local variations around base starts are used. |
n_shape_grid |
Number of grid points for gamma shape. |
n_scale_grid |
Number of grid points for gamma scale. |
show_progress |
Logical. If |
show_summary |
Logical. If |
y, dist, left, right, start, trace, report |
Deprecated argument names kept for backward compatibility. |
Details
In insurance pricing, severity models are often fitted on claim amounts that are not observed over the full range of possible losses. Small claims may be absent because of a deductible, reporting threshold, or data extraction rule. Very large claims may be capped, excluded, or modelled separately as large losses. A standard gamma or lognormal fit on the remaining observed claims treats that truncated sample as if it were complete, which can bias the estimated severity distribution.
fit_truncated_severity() fits the distribution conditional on the claim being
observed within the truncation interval. This means the fitted likelihood
uses the density divided by the probability mass between lower_truncation
and upper_truncation. The function is intended for truncation, where
claims outside the interval are absent from the data. This differs from
censoring, where claims outside a limit are still observed but their exact
amount is not known.
Observed losses must lie strictly inside the truncation interval. Values outside the interval indicate that the bounds do not describe the data and therefore produce an error.
Value
An object of class c("truncated_severity", "truncated_dist", "fitdist"). The object
contains the fitted distribution parameters from fitdistrplus::fitdist()
and additional attributes:
- truncated_vec
The observed losses used for fitting.
- lower_truncation, upper_truncation
The truncation bounds.
- fit_attempts
Metadata for each attempted start combination.
- n_attempts, n_success, n_failed
Fit attempt counts.
- best_attempt_index
Index of the selected start combination.
Examples
## Not run:
observed <- MTPL2$amount[MTPL2$amount > 500 & MTPL2$amount < 10000]
fit <- fit_truncated_severity(
losses = observed,
distribution = "gamma",
lower_truncation = 500,
upper_truncation = 10000
)
autoplot(fit)
## End(Not run)
Deprecated alias for outlier_histogram()
Description
histbin() is deprecated as of version 0.8.0.
Please use outlier_histogram() instead.
In addition, note that x must now be passed as string
(standard evaluation).
Usage
histbin(
data,
x,
left = NULL,
right = NULL,
line = FALSE,
bins = 30,
fill = "#E6E6E6",
color = "white",
fill_outliers = "#F28E2B"
)
Arguments
data |
A data.frame containing the portfolio variable to inspect. |
x |
Character; numeric column in |
left, right |
Deprecated aliases for |
line |
Deprecated alias for |
bins |
Integer. Number of bins used for the displayed range. Default = 30. |
fill, color, fill_outliers |
Deprecated aliases for |
Value
See outlier_histogram().
Convert p-values into significance stars
Description
Convert p-values into significance stars
Usage
make_stars(pval)
Arguments
pval |
Numeric vector of p-values. |
Value
Character vector of the same length as pval,
containing "***", "**", "*", ".", or "".
Reduce portfolio periods by merging adjacent date ranges
Description
Combine overlapping or nearly adjacent coverage periods for the same policy, risk or portfolio segment. The result provides a consolidated time basis for exposure calculations, active-policy counts and period-based reporting.
Together with rating_grid(), this function belongs to the portfolio
reduction workflow. Both functions reduce row-level portfolio data while
retaining selected totals. merge_date_ranges() reduces temporally connected
records; rating_grid() reduces records with identical risk-factor values.
Usage
merge_date_ranges(
data = NULL,
...,
period_start = NULL,
period_end = NULL,
group_by = NULL,
aggregate_cols = NULL,
aggregate_fun = "sum",
merge_gap_days = 1,
df = NULL,
begin = NULL,
end = NULL,
agg_cols = NULL,
agg = NULL,
min.gapwidth = NULL
)
Arguments
data |
A |
period_start |
Character string. Name of the column with period start dates. |
period_end |
Character string. Name of the column with period end dates. |
group_by |
Character vector with columns that identify the portfolio entity or rating segment within which date ranges should be merged. |
aggregate_cols |
Character vector with numeric columns to aggregate over merged ranges, for example premium or exposure. |
aggregate_fun |
Function or function name used to combine
|
merge_gap_days |
Non-negative whole number. Ranges with fewer uncovered
days than this value are treated as continuous. The default, |
df, begin, end, ..., agg_cols, agg, min.gapwidth |
Deprecated argument names
kept for backward compatibility. Use |
Details
Portfolio reduction
merge_date_ranges() performs temporal portfolio reduction. It combines
connected periods within the same group_by values and retains the selected
additive amounts. Use rating_grid() for the complementary categorical
reduction of records with identical risk-factor combinations.
Connected periods
Insurance portfolio extracts often contain multiple rows for the same policy or risk because of renewals, endorsements, product changes, or short administrative gaps. Before calculating portfolio in/outflow, active exposure windows, or policy counts, it can be useful to reduce those rows to stable coverage intervals.
merge_date_ranges() merges date ranges within each group_by combination.
Ranges with a gap smaller than merge_gap_days are treated as one continuous
interval. If aggregate_cols is supplied, those columns are aggregated over
the merged interval. The grouping columns should identify records for which
combining coverage periods is actuarially and operationally meaningful;
periods belonging to different risks or contracts should not be pooled.
Missing values are not permitted in group_by, because such records cannot
be assigned reliably to the same policy, risk or segment.
Start and end dates are treated as inclusive. Consequently, two periods for
which the second starts one day after the first ends have zero uncovered
days. They are merged with the default merge_gap_days = 1. A value of 5
merges periods with at most four uncovered days between them.
Aggregation and implementation
Aggregated amounts are combined over the source rows, not prorated over calendar days. Summing premium or exposure is appropriate when each source row contains a distinct additive amount. If overlapping rows already contain amounts for the same covered days, users should resolve that overlap before aggregation to avoid double counting.
Each output row represents one consolidated period within a group_by
combination. This is a temporal reduction of the portfolio; records with the
same risk-factor values are not combined when their periods remain separate.
Internally, interval construction and aggregation use data.table::data.table()
on a local copy. The supplied object is not modified by reference.
For a portfolio that should remain outside R memory, use
merge_date_ranges_db() to perform the same interval reduction in DuckDB.
Value
A regular data.frame with classes "merged_date_ranges" and
"reduce", and attributes:
-
begin— name of the period-start column -
end— name of the period-end column -
cols— grouping columns
Author(s)
Martin Haringa
See Also
merge_date_ranges_db(), rating_grid(), rating_grid_db(),
active_rows_by_date(), split_periods_to_months()
Examples
portfolio <- data.frame(
policy_id = rep(c("P001", "P002"), each = 3),
coverage = rep(c("Fire", "Liability"), each = 3),
period_start = as.Date(c(
"2024-01-01", "2024-02-01", "2024-04-01",
"2024-01-01", "2024-02-03", "2024-03-03"
)),
period_end = as.Date(c(
"2024-01-31", "2024-02-29", "2024-04-30",
"2024-01-31", "2024-03-02", "2024-03-31"
)),
earned_premium = c(100, 110, 120, 80, 90, 95)
)
# Reduce directly adjacent periods within each policy and coverage.
pt1 <- merge_date_ranges(
portfolio,
period_start = "period_start",
period_end = "period_end",
group_by = c("policy_id", "coverage")
)
summary(pt1, period = "months", policy_id, coverage)
# Bridge a short administrative gap and retain additive premium totals.
pt2 <- merge_date_ranges(
portfolio,
period_start = "period_start",
period_end = "period_end",
group_by = c("policy_id", "coverage"),
aggregate_cols = "earned_premium",
# Explicitly bridge administrative gaps of up to four uncovered days.
merge_gap_days = 5
)
summary(pt2, period = "months", policy_id, coverage)
Merge connected portfolio periods in DuckDB
Description
Construct the temporal portfolio reduction performed by
merge_date_ranges() as a lazy DuckDB query. Overlapping or adjacent periods
are merged inside DuckDB, so only the consolidated periods need to be copied
into R.
Usage
merge_date_ranges_db(
data,
period_start,
period_end,
group_by,
aggregate_cols = NULL,
aggregate_fun = c("sum", "mean", "min", "max"),
merge_gap_days = 1
)
Arguments
data |
A lazy DuckDB table created with |
period_start |
Character string naming the period-start column. |
period_end |
Character string naming the period-end column. |
group_by |
Character vector identifying the policy, risk or segment within which periods may be combined. |
aggregate_cols |
Optional character vector naming numeric columns to aggregate over each merged period. |
aggregate_fun |
Character string specifying the SQL aggregation for
|
merge_gap_days |
Non-negative whole number. The interpretation is the
same as in |
Details
Merging connected intervals is a SQL gaps-and-islands calculation. The query uses ordered window functions to compare each start date with the latest preceding end date, assigns an interval identifier, and then aggregates each resulting interval.
Date arithmetic differs between database systems. This implementation is therefore deliberately restricted to DuckDB. It does not download the source table and does not create a permanent database table.
Value
A lazy DuckDB table. Use dbplyr::sql_render() to inspect the SQL or
dplyr::collect() to import the consolidated periods into R.
Author(s)
Martin Haringa
See Also
merge_date_ranges(), rating_grid_db(), rating_grid()
Examples
## Not run:
con <- DBI::dbConnect(duckdb::duckdb())
portfolio_db <- dplyr::tbl(con, "portfolio_periods")
periods_db <- merge_date_ranges_db(
portfolio_db,
period_start = "period_start",
period_end = "period_end",
group_by = c("policy_id", "coverage"),
aggregate_cols = c("earned_exposure", "earned_premium")
)
periods <- dplyr::collect(periods_db)
DBI::dbDisconnect(con, shutdown = TRUE)
## End(Not run)
Deprecated alias for extract_model_data()
Description
model_data() is deprecated in favour of extract_model_data().
Usage
model_data(x)
Arguments
x |
An object of class |
Value
See extract_model_data().
Compare fitted GLMs using common performance measures
Description
Compare one or more fitted GLMs using AIC, BIC and response-scale RMSE. The resulting table provides a concise first comparison of alternative pricing-model specifications fitted to the same portfolio outcome.
Usage
model_performance(...)
Arguments
... |
One or more objects of class |
Details
The following measures are reported:
- AIC
Akaike information criterion, balancing likelihood fit and model complexity.
- BIC
Bayesian information criterion, applying a stronger sample-size-dependent complexity penalty.
- RMSE
Root mean squared error between observed and response-scale predicted values.
Lower values are preferred within each measure, but the measures answer different questions. AIC and BIC depend on the model likelihood, whereas RMSE measures error on the response scale. Comparisons are therefore most meaningful when models use the same response, estimation records, weights and offsets.
The table does not select a pricing model automatically. In actuarial model assessment, statistical fit should be considered together with portfolio calibration, residual behaviour, coefficient stability, exposure by level and the practical interpretability of the resulting tariff structure.
The implementation is adapted from performance::model_performance().
Value
A data frame of class "model_performance", with columns:
- Model
Name of the model object as passed to the function.
- AIC
AIC value.
- BIC
BIC value.
- RMSE
Root mean squared error.
Author(s)
Martin Haringa
See Also
rmse(), bootstrap_performance(), check_overdispersion(),
check_residuals()
Examples
m1 <- glm(nclaims ~ area, offset = log(exposure), family = poisson(),
data = MTPL2)
m2 <- glm(nclaims ~ area + premium, offset = log(exposure), family = poisson(),
data = MTPL2)
model_performance(m1, m2)
Portfolio histogram with tail bins
Description
Visualise the distribution of a numeric portfolio variable while keeping extreme tails readable.
Insurance portfolios often contain skewed variables such as claim amounts,
premium, exposure, insured sums, deductibles, or fitted premiums. A few very
large policies or claim events can stretch a regular histogram so much that
the body of the portfolio becomes hard to inspect. outlier_histogram()
keeps the main range visible and groups values below lower or above upper
into dedicated tail bins.
The plot is useful for actuarial portfolio checks, data quality review, and model preparation: it helps show where most risks are concentrated while still making the presence of extreme observations explicit.
Usage
outlier_histogram(
data,
x,
lower = NULL,
upper = NULL,
density = FALSE,
bins = 30,
bar_fill = "#E6E6E6",
bar_color = "white",
tail_fill = "#F28E2B",
tail_color = "white",
density_color = "#2C7FB8",
left = NULL,
right = NULL,
line = NULL,
fill = NULL,
color = NULL,
fill_outliers = NULL
)
Arguments
data |
A data.frame containing the portfolio variable to inspect. |
x |
Character; numeric column in |
lower |
Optional numeric lower threshold. Values below this threshold are grouped into one left-tail bin. |
upper |
Optional numeric upper threshold. Values above this threshold are grouped into one right-tail bin. |
density |
Logical. If |
bins |
Integer. Number of bins used for the displayed range. Default = 30. |
bar_fill |
Fill color for regular histogram bars. |
bar_color |
Border color for regular histogram bars. |
tail_fill |
Fill color for tail bins. |
tail_color |
Border color for tail bins. |
density_color |
Color for the optional density line. |
left, right |
Deprecated aliases for |
line |
Deprecated alias for |
fill, color, fill_outliers |
Deprecated aliases for |
Details
This function is intended as an exploratory portfolio diagnostic. It does not
remove or winsorize observations in data; it only groups tail values in the
visual display. The labels on the tail bins show the original range captured
by each tail bin.
The method for handling outlier bins is based on https://edwinth.github.io/blog/outlier-bin/.
Value
A ggplot2::ggplot object.
Author(s)
Martin Haringa
Examples
# Inspect the full premium distribution
outlier_histogram(MTPL2, "premium")
# Keep the portfolio body readable while showing both tails
outlier_histogram(MTPL2, "premium", lower = 30, upper = 120, bins = 30)
Deprecated alias for split_periods_to_months()
Description
period_to_months() is deprecated as of version 0.8.0. Use
split_periods_to_months() instead.
Usage
period_to_months(df, begin, end, ...)
Arguments
begin |
Deprecated NSE argument. Use |
end |
Deprecated NSE argument. Use |
... |
Deprecated NSE columns to prorate. Use |
Value
See split_periods_to_months().
Exploratory severity diagnostics by category
Description
Visualise individual claim amounts overall or per risk factor.
Average claim amounts can be misleading because a small number of large
losses may dominate the mean. plot_severity_distribution() shows the full
claim amount distribution, usually on a log scale, together with mean and
median claim amount markers. If risk_factor is supplied, the distribution
is shown per level of that risk factor. If risk_factor = NULL, the function
shows the overall claim amount distribution. This makes heavy tails, clusters
of small claims, spread differences, extreme losses and distributional shape
visible in a way that average severity alone cannot.
The function is intended for exploratory severity diagnostics in pricing
analysis, portfolio diagnostics, tariff notes, exploratory segmentation
analysis and severity model validation. It uses standard evaluation: pass
column names as character strings through claim_amount and risk_factor.
If threshold is supplied, claims above the threshold are highlighted in
"firebrick" and a dotted threshold line is added. Claims at or below the
threshold remain light grey. Direct labels for the mean, median and optional
threshold are added with ggrepel when show_labels = TRUE; ggrepel is a
suggested package and is not imported as a hard dependency.
Usage
plot_severity_distribution(
data,
claim_amount,
risk_factor = NULL,
top_n = 10,
min_claims = 20,
sort = c("median", "mean", "n_claims"),
threshold = NULL,
mean = TRUE,
median = TRUE,
distribution = c("none", "half_violin", "violin"),
point_method = c("quasirandom", "jitter", "none"),
orientation = c("horizontal", "vertical"),
log_scale = TRUE,
boxplot = FALSE,
boxplot_width = 0.06,
show_labels = TRUE,
all_claims_label = "All claims",
mean_label = "Mean",
median_label = "Median",
threshold_label = "Threshold",
x_label = NULL,
y_label = NULL,
point_alpha = 0.16,
point_size = 0.75,
point_width = 0.15
)
Arguments
data |
A |
claim_amount |
Character string. Name of the claim amount column. |
risk_factor |
Optional character string. Name of the risk factor used
to split the severity distribution. If |
top_n |
Positive whole number. Number of categories to keep after filtering and sorting. |
min_claims |
Positive whole number. Categories with fewer than this number of claim observations are removed. |
sort |
Character. Metric used to sort and select categories. One of
|
threshold |
Optional numeric scalar. If supplied, claims above this threshold are highlighted and a dotted threshold line is shown. |
mean |
Logical. If |
median |
Logical. If |
distribution |
Character. Distribution layer. One of |
point_method |
Character. Point placement method. One of
|
orientation |
Character. |
log_scale |
Logical. If |
boxplot |
Logical. If |
boxplot_width |
Numeric scalar. Width of the optional boxplot. Smaller values keep the boxplot as a subtle summary layer behind the individual claim points. |
show_labels |
Logical. If |
all_claims_label |
Character string used as the category label when
|
mean_label |
Character string used for the direct mean marker label.
Default is |
median_label |
Character string used for the direct median marker label.
Default is |
threshold_label |
Character string used for the optional threshold label. |
x_label |
Optional character string. X-axis label. If |
y_label |
Optional character string. Y-axis label. If |
point_alpha |
Numeric alpha for raw claim points. |
point_size |
Numeric point size for raw claim points. |
point_width |
Numeric spread for raw claim points. |
Value
A ggplot object. The plot can be extended with regular ggplot2
syntax, for example + ggplot2::labs(caption = "...") or
+ ggplot2::theme(...).
Author(s)
Martin Haringa
Examples
x <- plot_severity_distribution(
MTPL,
claim_amount = "amount",
risk_factor = "zip",
top_n = 4,
min_claims = 20,
point_method = "jitter",
show_labels = FALSE
)
print(x)
x_threshold <- plot_severity_distribution(
MTPL,
claim_amount = "amount",
risk_factor = NULL,
threshold = 10000,
min_claims = 20,
point_method = "jitter",
show_labels = FALSE
)
Interpret the premium effect of a smoothing curve
Description
Translate an effective smoothing curve in a refinement specification into
concrete modelled-premium comparisons. By default, each selected value is
compared with twice that value. Supplying increment instead compares each
value with a fixed increment above it.
Format an object returned by premium_change(). One refinement state is
shown as a three-column table. Multiple states are shown side by side; when
exactly two are selected, their difference is added in percentage points.
Usage
premium_change(
x,
variable = NULL,
at = NULL,
change = "double",
increment = NULL,
steps = "current",
basis = c("curve", "segments"),
...
)
## S3 method for class 'premium_change'
as_gt(x, locale = "en-US", decimals = 1, title = NULL, subtitle = NULL, ...)
Arguments
x |
For |
variable |
Optional character string identifying the smoothed model variable or its continuous source variable. This may be omitted when the refinement contains exactly one smoothing lineage. |
at |
Optional numeric vector of starting values. Each starting and
comparison value must lie inside the supported smoothing range of every
selected refinement state. Doubling retains the existing requirement that
starting values are positive. If |
change |
Character comparison mode. |
increment |
Optional positive finite numeric increase in the units of
the source variable. When supplied, compares |
steps |
Refinement states to evaluate. Use |
basis |
Character string determining the interpretation basis.
|
... |
Additional arguments are not accepted. |
locale |
Character string passed to |
decimals |
Non-negative integer. Number of decimal places for changes. |
title |
Optional table title. The source-variable name is used by default. |
subtitle |
Optional table subtitle. |
Details
premium_change() is an interpretation helper for smoothing created with
add_smoothing() and subsequently modified with edit_smoothing(). It is
not a smoothing method and does not change the refinement specification.
For a multiplicative relativity curve R(x), doubling reports
R(2x) / R(x) - 1. Fixed-increment mode reports
R(x+h) / R(x) - 1, where h is increment. If total modelled premium
can be written as P(x,z)=C(z)R(x), all other multiplicative model
effects C(z) cancel in this ratio. No particular policy profile is
therefore required for the interpretation.
The effective curve is reconstructed from the stored refinement history.
Consequently, steps = "current" reflects all smoothing edits recorded up
to the current state. Numeric step identifiers refer to positions in the
complete refinement sequence. If another type of refinement occurs after a
smoothing step, the previously effective smoothing is carried forward.
With the default basis = "curve", evaluation uses the continuous effective
smoothing line retained by the refinement system. It therefore describes
the shape and steepness of the estimated or edited curve at exactly x
and the corresponding comparison value; it does not use neighbouring
tariff-segment relativities.
With basis = "segments", both values are assigned to the effective tariff
intervals created by the smoothing. Their current segment relativities are
compared. This describes the premium effect of the implementable segmented
tariff. The result can be zero when both values fall in the same segment and
can change discretely when the comparison crosses a segment boundary.
Values are never extrapolated. When at = NULL, six representative starting
values are selected from the common range for which both the starting and
comparison values are supported in every selected refinement state.
Multiplying an entire curve by a common rebasing constant does not alter the result because that constant cancels in the relativity ratio.
Value
A tibble with class premium_change in long format, containing the
variable, refinement state, starting and comparison values, evaluated
relativities, and premium change as a decimal.
A gt_tbl object.
See Also
add_smoothing(), edit_smoothing(),
autoplot.rating_refinement(), as_gt()
Examples
age <- rep(seq(20, 70, by = 5), each = 5)
portfolio <- data.frame(
claims = rep(c(0, 1, 0, 2, 1), length(age) / 5),
exposure = 1,
age = age
)
portfolio$age_band <- cut(
portfolio$age,
breaks = c(15, 30, 45, 60, 75),
include.lowest = TRUE
)
model <- glm(
claims ~ age_band + offset(log(exposure)),
family = poisson(),
data = portfolio
)
refinement <- prepare_refinement(model, data = portfolio) |>
add_smoothing(
model_variable = "age_band",
source_variable = "age",
breaks = seq(15, 75, by = 5),
smoothing = "poly",
degree = 2,
weights = "exposure"
)
premium_change(refinement, at = c(20, 25, 30))
premium_change(refinement, at = c(20, 25, 30), increment = 5)
premium_change(refinement, at = c(20, 25, 30), basis = "segments")
edited <- refinement |>
edit_smoothing(
model_variable = "age_band",
from = 20,
to = 60,
adjustment = 1.05,
transition = "linear"
)
premium_change(edited, at = c(20, 25, 30), steps = c(1, 2))
Prepare a model refinement workflow
Description
Create an editable refinement specification from a fitted pricing GLM.
Smoothing, coefficient restrictions, shrinkage, rebasing and sublevel
relativities can then be added in a defined order. These steps do not alter the fitted GLM
until refit() is called.
Usage
prepare_refinement(model, data = NULL)
Arguments
model |
Object of class |
data |
Optional data.frame containing exactly the observations retained
in the fitted GLM and all required model variables. If model fitting omitted
rows because of missing values, supply the retained model data rather than
the original unfiltered data. If |
Details
prepare_refinement() creates a persistent refinement specification. This
object contains the original GLM, the corresponding model data and the
ordered smoothing, restriction, shrinkage, rebasing and relativity steps.
Retain this object
during actuarial review so that assumptions can be inspected, revised and
applied again in the same order.
Actuarial interpretation
Preparing a refinement does not change coefficients, fitted values or the tariff structure. It separates the original statistical model from subsequent actuarial adjustments. Each adjustment remains an explicit step rather than being embedded directly in transformed data or overwritten model coefficients. This supports comparison between the unrestricted model and alternative refinement specifications.
refit() applies the stored specification and returns a fitted GLM for model
diagnostics, prediction and tariff reporting. The returned GLM is a result,
not an editable refinement specification. Functions such as
add_smoothing(), edit_smoothing(), add_restriction(), add_shrinkage(),
add_rebasing() and add_relativities() therefore accept a
rating_refinement object and do not
accept an ordinary or refitted GLM directly.
A practical iterative workflow therefore keeps both objects:
refinement <- prepare_refinement(model) |> add_smoothing(...) fitted_model <- refit(refinement) refinement <- refinement |> edit_smoothing(...) fitted_model <- refit(refinement)
prepare_refinement() is normally required only once for such an iteration.
Calling it on a model returned by refit() deliberately starts a new
refinement workflow with the already refined model as its baseline; it does
not recover the earlier smoothing or restriction steps for further editing.
Value
A rating_refinement object containing the original GLM, retained
model data and ordered refinement specification. No GLM is fitted again
until refit() is called.
Author(s)
Martin Haringa
See Also
summary.rating_refinement(), add_smoothing(),
edit_smoothing(), add_restriction(), add_shrinkage(),
add_rebasing(), add_relativities(), refit()
Examples
portfolio <- data.frame(
claims = c(1, 2, 1, 3, 2, 4),
exposure = rep(1, 6),
risk_class = factor(c("A", "B", "A", "B", "A", "B"))
)
model <- glm(
claims ~ risk_class + offset(log(exposure)),
family = poisson(),
data = portfolio
)
refinement <- prepare_refinement(model, data = portfolio) |>
add_restriction(data.frame(
risk_class = "B",
risk_class_restricted = 1.15
))
summary(refinement)
fitted_model <- refit(refinement)
# Retain and revise the specification rather than editing fitted_model.
refinement <- refinement |>
add_restriction(data.frame(
risk_class = "B",
risk_class_restricted = 1.10
))
updated_model <- refit(refinement)
Deprecated alias for rating_table()
Description
rating_factors() is deprecated as of version 0.8.0. Use
rating_table() instead.
Usage
rating_factors(
...,
model_data = NULL,
exposure = TRUE,
exposure_name = NULL,
signif_stars = FALSE,
exponentiate = TRUE,
round_exposure = 0
)
Arguments
... |
One or more fitted |
model_data |
Optional data frame used to fit the models. If |
exposure |
Logical or character string. If |
exposure_name |
Deprecated. Use |
signif_stars |
Deprecated. Use |
exponentiate |
Logical. If |
round_exposure |
Non-negative number of digits used to round exposure. |
Value
See rating_table().
Deprecated single-model rating table helper
Description
Legacy interface. Prefer rating_table() for fitted models in the new
workflow, but this function remains available.
Usage
rating_factors2(
model,
model_data = NULL,
exposure = TRUE,
exposure_name = NULL,
colname = "estimate",
exponentiate = TRUE,
round_exposure = 0
)
Arguments
model |
glm object produced by |
model_data |
Optional data.frame used to create glm object. If |
exposure |
Logical or character. If |
exposure_name |
Optional name for the exposure column in the output. |
colname |
name of coefficient column |
exponentiate |
logical indicating whether or not to exponentiate the coefficient estimates. Defaults to TRUE. |
round_exposure |
number of digits for exposure (defaults to 0) |
Value
A data frame with rating factor coefficients for one model.
Construct observed rating-grid points
Description
Collapse portfolio records with identical risk-factor combinations into observed rating-grid points. Exposure and other numeric measures can be aggregated alongside the combinations for prediction, tariff comparison and portfolio diagnostics.
Together with merge_date_ranges(), this function belongs to the portfolio
reduction workflow. Both functions reduce row-level portfolio data while
retaining selected totals. rating_grid() reduces across identical
risk-factor combinations; merge_date_ranges() reduces temporally connected
records within the same policy, risk or portfolio segment.
The function returns only combinations that are actually observed in the input data. It does not create the full Cartesian product of all unique values. This keeps the output compact and suitable for model diagnostics, portfolio summaries, and prediction analysis.
When x is an object returned by extract_model_data(), the function uses
the extracted model metadata to determine the grouping variables if
group_by is not supplied. When x is a plain data.frame, it is
recommended to supply group_by explicitly.
Usage
rating_grid(
x,
group_by = NULL,
exposure = NULL,
exposure_by = NULL,
aggregate_cols = NULL,
drop_na = FALSE,
group_vars = NULL,
agg_cols = NULL
)
Arguments
x |
A |
group_by |
Optional character vector with the variables that define the
rating-grid points. If |
exposure |
Optional character; name of the exposure column to aggregate. |
exposure_by |
Optional character; name of a column used to split exposure or counts, for example a year variable. |
aggregate_cols |
Optional character vector with additional numeric
columns to aggregate using |
drop_na |
Logical; if |
group_vars, agg_cols |
Deprecated argument names. Use |
Details
Portfolio reduction
rating_grid() performs categorical portfolio reduction. It combines rows
with the same observed group_by values and retains the corresponding totals.
Use merge_date_ranges() for the complementary temporal reduction of
connected coverage periods.
Observed combinations
The grid represents the combinations present in the supplied portfolio or model data. It deliberately does not construct combinations that were not observed. This avoids creating artificial model points and is particularly relevant when risk factors are structurally related, such as product, coverage and distribution channel.
Each output row therefore represents one observed combination of the
variables in group_by. Exposure and aggregate_cols are summed over the
source records belonging to that combination. Such a row is a model point:
one observed covariate combination together with its aggregated additive
portfolio quantities. This is a categorical reduction of the portfolio; no
date intervals are combined.
Estimating a GLM on aggregated data
For a standard Poisson frequency GLM, aggregation before model fitting can
preserve the coefficient estimates exactly. This applies when records are
grouped by every predictor used in the model, claim counts are summed, earned
exposure is summed, and the aggregated model uses offset(log(exposure)).
Within such a group all records have the same linear predictor. Their
contribution to the coefficient estimation therefore depends on total claims
and total exposure, which are retained by the rating grid.
This equivalence is conditional, not a general property of every GLM. Aggregating over a model variable changes the model, and row-level weights, interactions, offsets or non-additive quantities must be retained correctly. Binomial, severity, quasi-likelihood and dispersion analyses require their own sufficient totals and weights. Row-level residuals and influence diagnostics are also no longer available after aggregation, even when the fitted Poisson coefficients are unchanged.
In practice, a rating grid is particularly useful before estimating a frequency model on a large portfolio. It can reduce repeated policy records to a much smaller table, lowering memory use and fitting time. Keep the unaggregated data when policy-level predictions, sampling, validation or diagnostics are required, and verify on a representative sample that the selected aggregation retains all inputs needed by the intended model.
Estimating a severity GLM on aggregated data
Claim count and claim amount are additive portfolio measures and can be
supplied through aggregate_cols. Frequency is then calculated as total
claim count divided by total exposure. Average severity is total claim amount
divided by total claim count.
A Gamma severity GLM fitted to grouped average severities can produce the same
coefficient estimates as a model fitted to the underlying individual claims.
This requires grouping by every predictor in the severity model, calculating
average_severity = claim_amount / claim_count, using claim_count as the
model weight, and applying the same family and link. Within each grid row all
underlying claims then have the same linear predictor, while the weight
retains the number of claims represented by the average.
This equivalence concerns the coefficient estimates, not the complete model output. Aggregation removes claim-level residuals and outlier information. Deviance, residual degrees of freedom, estimated dispersion, standard errors and significance tests can therefore differ from a claim-level fit. Claim- level data should remain available for severity-distribution checks, influential-claim analysis and model validation. The equivalence is also lost if a severity predictor varies within a grid row or if the totals and weights do not represent the underlying claims correctly.
If exposure_by is supplied, exposure or row counts are split across levels
of that variable and returned in wide format, for example
"exposure_2020" or "count_2020".
For objects returned by extract_model_data(), refinement mappings are joined
by their original factor column. They are not cross-joined onto every row.
Aggregation, reshaping and refinement joins are performed internally with
data.table::data.table() to support large pricing portfolios. A local copy
is used, so the supplied object is not modified by reference. The output is
a regular data.frame, irrespective of the class of the input data.
When the row-level portfolio does not fit comfortably in R memory, use
rating_grid_db() to perform the grouped reduction in a database and collect
only the resulting grid.
Value
A data.frame with one row per observed rating-grid point.
Author(s)
Martin Haringa
See Also
rating_grid_db(), merge_date_ranges(),
merge_date_ranges_db(), extract_model_data(), rating_table()
Examples
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
))
Reduce a database portfolio to observed rating-grid points
Description
Construct the same type of observed rating-factor combinations as
rating_grid(), while leaving the calculation in the database. The function
returns a lazy query and does not copy the source portfolio into R.
Each output row is a model point: one observed covariate combination together
with aggregated exposure and other additive quantities requested by the user.
This is useful when the row-level portfolio is too large for available R
memory. The database performs the grouping and aggregation; the reduced
result can subsequently be imported with dplyr::collect().
Usage
rating_grid_db(
x,
group_by,
exposure = NULL,
aggregate_cols = NULL,
drop_na = FALSE,
exposure_by = NULL
)
Arguments
x |
A lazy database table created with |
group_by |
Character vector containing the columns that define an observed rating-grid point. |
exposure |
Optional character string naming an exposure column to sum.
If |
aggregate_cols |
Optional character vector naming additional columns to sum within each combination. |
drop_na |
Logical. If |
exposure_by |
Reserved for consistency with |
Details
rating_grid_db() performs only operations that translate naturally to SQL:
grouping, row counting and sums. Column names are supplied as strings. No SQL
is executed until the lazy query is printed, collected or otherwise used by
the database backend.
Database tables have no inherent row order. Apply dplyr::arrange() after
the final database operation when a specific presentation order is required.
The database table should already contain the rating variables required for
the intended model or portfolio analysis. Unlike rating_grid(), this
function does not inspect a fitted R model or refinement metadata because
those objects are held in R rather than in the database.
Value
A lazy database table. Use dbplyr::sql_render() to inspect the SQL
or dplyr::collect() to import the reduced result into R.
Author(s)
Martin Haringa
See Also
rating_grid(), merge_date_ranges_db(), merge_date_ranges()
Examples
## Not run:
con <- DBI::dbConnect(duckdb::duckdb())
portfolio_db <- dplyr::tbl(con, "portfolio")
grid_db <- rating_grid_db(
portfolio_db,
group_by = c("sector", "region"),
exposure = "earned_exposure",
aggregate_cols = "earned_premium"
)
dbplyr::sql_render(grid_db)
grid <- dplyr::collect(grid_db)
DBI::dbDisconnect(con, shutdown = TRUE)
## End(Not run)
Present fitted pricing-model effects as a rating table
Description
Extract coefficients from one or more fitted GLMs and organise them by risk factor and level. Reference levels are made explicit, coefficients can be expressed as multiplicative relativities, and portfolio exposure can be attached to support actuarial review.
Usage
rating_table(
...,
model_data = NULL,
exposure = TRUE,
exposure_output = NULL,
estimate_name = NULL,
exponentiate = TRUE,
significance = FALSE,
reference_first = TRUE,
level_order = c("estimate_descending", "estimate_ascending", "model", "alphabetical"),
level_order_by_risk_factor = NULL,
numeric_level_order = c("ascending", "as_specified"),
risk_factor_order = c("model", "alphabetical"),
order_model = NULL,
round_exposure = 0,
exposure_name = NULL,
signif_stars = NULL
)
Arguments
... |
One or more fitted |
model_data |
Optional data frame used to fit the models. If |
exposure |
Logical or character string. If |
exposure_output |
Optional character string naming the exposure column
in the output.
If |
estimate_name |
Optional character vector with the exact output column
name for each model estimate. Supply one value for one model, an unnamed
vector in model order, or a named vector whose names identify the supplied
model objects. If |
exponentiate |
Logical. If |
significance |
Logical. If |
reference_first |
Logical. If |
level_order |
Character string controlling the default order of nominal
factor levels. |
level_order_by_risk_factor |
Optional named character vector providing
an ordering override for individual risk factors. Names identify risk
factors and values must be |
numeric_level_order |
Character string controlling levels that are all
recognisable as numbers or numeric intervals. |
risk_factor_order |
Character string controlling risk-factor order.
|
order_model |
Optional character string naming the supplied model whose
level order, reference levels and estimates are used for sorting. This is
mainly relevant when several models are compared. If |
round_exposure |
Non-negative number of digits used to round exposure. |
exposure_name |
Deprecated. Use |
signif_stars |
Deprecated. Use |
Details
Coefficients and relativities
The table contains one row per model term level. For factor variables,
the reference level is added explicitly with relativity 1 when
exponentiate = TRUE, or coefficient 0 when exponentiate = FALSE.
Numeric model terms are retained on the scale supplied by the fitted model
structure.
By default, estimate columns are named from the supplied model expressions,
for example est_frequency for an object named frequency.
estimate_name can replace these with exact user-supplied names. With
several models, use an unnamed vector in model order or a named vector such
as c(frequency = "freq_relativity", severity = "sev_relativity").
Effects are joined by risk factor and level.
Actuarial interpretation
With a log-link GLM, exponentiated coefficients represent conditional
multiplicative effects relative to the model reference level. They should be
interpreted together with the model specification and should not be confused
with the unadjusted observed measures returned by factor_analysis().
Exposure by level provides context for the amount of portfolio information supporting each fitted effect. Significance indicators describe evidence conditional on the fitted model; they do not measure practical materiality, temporal stability or suitability for direct tariff implementation.
Comparing multiple models is useful for assessing changes between unrestricted and refined specifications, or between alternative model formulations. Comparable response definitions and coefficient scales remain the responsibility of the analyst.
Row order and reference levels
By default, risk factors follow the model formula. Numeric levels and intervals are shown from low to high, explicitly ordered factors retain their factor-level sequence, and remaining nominal factors are shown from highest to lowest fitted effect. This separates structural order from an ordering used to compare tariff differentiation.
reference_first applies only when a nominal factor uses model or
alphabetical order. It does not move the reference level ahead of a numeric,
ordinal or estimate-based sequence. The reference remains recorded in the
rating-table metadata, including a reference selected with add_rebasing().
Alternative level ordering is useful for specific review tasks. Alphabetical
order supports lookup and export, while model order can retain a deliberately
specified factor sequence. Use level_order_by_risk_factor when nominal and
ordinal factors require different treatment in the same table. With several
models, order_model defines which fitted specification provides
estimate-based ordering. as_gt() and autoplot.rating_table() retain the
row order established here.
Only a factor stored with ordered = TRUE is identified automatically as an
ordinal scale. A regular factor may also have deliberately arranged levels,
but that intention cannot be distinguished reliably from an arbitrary model
order. Use level_order_by_risk_factor = c(variable = "model") to preserve
that sequence explicitly.
Numeric labels and intervals receive separate treatment because alphabetical
ordering can give an incorrect tariff sequence. With the default
numeric_level_order = "ascending", a risk factor is sorted numerically only
when every displayed level is either a complete number or a valid interval
with two numeric boundaries. Mixed labels such as "Industry 1" remain
categorical. Set numeric_level_order = "as_specified" when the fitted model
order or another level_order should be retained deliberately.
Significance indicators
When significance = TRUE, every model receives its own signif_* column.
For example, models named frequency and severity produce
est_frequency, signif_frequency, est_severity and
signif_severity. Keeping estimates and indicators separate preserves the
numeric type of the fitted effects for subsequent calculations, filtering
and export.
as_gt() combines each estimate with its corresponding significance
indicator for presentation and adds the significance thresholds as a source
note below the table. Reference levels generally have no separate
coefficient test and therefore have no significance indicator.
rating_table() accepts fitted models only. A rating_refinement
specification must first be fitted with refit().
Value
A data frame with classes "rating_table", legacy "riskfactor"
and "data.frame". It can be inspected and manipulated directly with
ordinary data-frame operations. For backward compatibility, x$df returns
the same table without the package-specific class and metadata. The table
contains:
- risk_factor
Model term or risk-factor name.
- level
Factor level or term representation.
- Estimate column
Coefficient or exponentiated relativity for each supplied model. Its default
est_*name is derived from the model expression and can be replaced withestimate_name.signif_*Optional significance indicator for each model.
- Exposure column
Optional aggregated exposure, retaining the requested output name.
Author(s)
Martin Haringa
See Also
as_gt() for grouped tabular presentation,
autoplot.rating_table() for graphical comparison,
factor_analysis() for observed portfolio experience, and refit() for
fitting a refinement specification.
Examples
df <- MTPL
df$zip <- as.factor(df$zip)
freq <- glm(
nclaims ~ bm + zip + offset(log(exposure)),
family = poisson(),
data = df
)
fitted_effects <- rating_table(
freq,
model_data = df,
exposure = "exposure"
)
fitted_effects
head(fitted_effects)
# Give the estimate column an explicit name
rating_table(
freq,
model_data = df,
exposure = "exposure",
estimate_name = "frequency_relativity"
)
# For several models, names can be supplied in model order or by model name
freq_alternative <- update(freq, . ~ . - bm)
rating_table(
freq,
freq_alternative,
model_data = df,
exposure = "exposure",
estimate_name = c(
freq = "current_relativity",
freq_alternative = "alternative_relativity"
)
)
# The historical accessor remains available for existing code
identical(fitted_effects$df, as.data.frame(fitted_effects))
if (requireNamespace("gt", quietly = TRUE)) {
as_gt(fitted_effects)
}
# Keep coefficients on the model scale instead of exponentiating
rating_table(
freq,
model_data = df,
exposure = "exposure",
exponentiate = FALSE
)
# Significance is supplementary to exposure and stability assessment
rating_table(
freq,
model_data = df,
exposure = "exposure",
significance = TRUE
)
# Compare two fitted models side by side
freq_simple <- glm(
nclaims ~ bm + offset(log(exposure)),
family = poisson(),
data = df
)
rating_table(
freq_simple,
freq,
model_data = df,
exposure = FALSE
)
# Order all levels by fitted relativity
rating_table(
freq,
model_data = df,
exposure = "exposure",
level_order = "estimate_descending"
)
Redistribute large losses for severity or risk-premium modelling
Description
Large claims can have a disproportionate influence on observed severity and
on estimated risk-factor effects. redistribute_excess_loss() decomposes
each selected claim amount into a retained component up to a specified
threshold and an excess component above that threshold. The excess component
is subsequently allocated using portfolio-wide, risk-factor-level or
partially pooled experience. The allocation preserves the total excess loss,
subject to numerical tolerance.
The allocated excess can be incorporated in the pricing analysis in two ways:
-
output = "redistributed_claim"adds the allocated excess loss to the retained claim amount. The resulting variable contains both components and can be used as the response in a single severity model. -
output = "excess_loading"keeps retained claim severity and excess loss as separate quantities. The function returns an excess loading per unit ofredistribution_weight. This loading can be added to the risk premium based on predicted frequency and retained severity.
Both output forms use the same threshold, credibility and allocation calculations. They therefore differ only in how the allocated excess loss is represented in subsequent modelling; the total amount allocated is the same.
The default is output = "redistributed_claim".
Usage
redistribute_excess_loss(
data,
claim_amount,
threshold,
claim_count = NULL,
redistribution_weight = NULL,
receives_redistribution = NULL,
redistribute_excess = NULL,
risk_factor = NULL,
redistribution_method = c("portfolio", "risk_factor", "partial"),
credibility = NULL,
credibility_basis = c("claims", "excess_records"),
credibility_threshold = 50,
credibility_scale = 1,
calculation_details = TRUE,
output = c("redistributed_claim", "excess_loading")
)
Arguments
data |
A data.frame containing portfolio-level or claim-level observations. |
claim_amount |
Character string naming a finite, non-negative numeric column with observed claim amounts or aggregate claim loss per row. |
threshold |
Positive numeric scalar defining the boundary between retained and excess loss. For selected rows, the amount above this value is allocated. |
claim_count |
Optional character string naming a non-negative,
whole-number claim-count column. Claim count identifies claim-bearing rows
and is the denominator of the adjusted average claim amount. It is also the
default redistribution weight. If |
redistribution_weight |
Optional character string naming a finite,
non-negative numeric column. The column determines the relative allocation
shares and the unit of the resulting loading. If |
receives_redistribution |
Optional character string. Logical column
indicating which rows may receive allocated excess loss. Rows with |
redistribute_excess |
Optional character string. Logical column
indicating which rows contribute their excess component to the allocation.
Rows with |
risk_factor |
Optional character string naming the risk-factor column for
|
redistribution_method |
Character string specifying the experience level
used in the allocation: |
credibility |
Optional numeric scalar in |
credibility_basis |
Character string specifying the experience measure
used in automatic credibility: |
credibility_threshold |
Positive numeric scalar representing the amount
of credibility-basis experience at which automatic credibility equals
0.5, before applying |
credibility_scale |
Non-negative numeric scalar multiplying automatic
credibility before truncation to |
calculation_details |
Logical. If |
output |
Character string specifying the representation of allocated
excess. |
Details
For each row selected through redistribute_excess, the observed claim
amount is decomposed as:
claim\_amount = capped\_claim\_amount + excess\_claim\_amount
If a row is not selected through redistribute_excess, its full observed
amount is retained. Any amount above the threshold remains available as a
diagnostic quantity but is excluded from the amount to be allocated.
The excess amount is allocated over eligible rows in proportion to
redistribution_weight. If no weight column is supplied, claim count is
used. For redistributed-claim output, only rows with a positive claim count
are eligible. For excess-loading output, eligibility is determined by a
positive redistribution weight, so policy rows without observed claims may
receive a loading when exposure is used.
For output = "redistributed_claim", the redistributed claim amount is:
adjusted\_claim\_amount = capped\_claim\_amount + redistributed\_excess
This redistributed claim amount is returned as <claim_amount>_adjusted.
The corresponding average per claim is suitable as the response in a
claim-count-weighted severity GLM. Because this response includes allocated
excess loss, the same excess component should not subsequently be added to
the estimated risk premium.
For output = "excess_loading", capped claim severity remains separate and:
excess\_loading_i =
\frac{allocated\_excess\_loss_i}{redistribution\_weight_i}
The loading can be added to the risk premium derived from predicted
frequency and retained severity. When earned exposure is used as
redistribution_weight, the loading is expressed per unit of earned
exposure. The total allocated excess loss is preserved, subject to numerical
tolerance.
Interpretation of the output forms
Redistributed-claim output combines retained and allocated loss in one model response. It requires one severity model and assigns the complete historical loss burden to that response. Its interpretation is most direct when the modelled risk-factor levels contain sufficient claim experience and the estimated effects are stable across observation periods.
The allocated component is not an observed loss for the receiving row. For example, an observed claim of 10,000 may receive an allocation of 20,000, resulting in a redistributed amount of 30,000. If the row belongs to a risk-factor level with few claims, the fitted model may attribute a material part of this allocated portfolio experience to that individual level. This can increase the sampling variability of its estimated effect.
Excess-loading output estimates retained severity from observed loss up to the threshold and represents allocated excess as a separate risk-premium component:
retained\ risk\ premium = predicted\ frequency \cdot
predicted\ retained\ severity
total\ risk\ premium = retained\ risk\ premium + excess\ loading
The two output forms therefore imply different model interpretations rather than different total loss amounts. The selection should reflect claim volume, the stability of risk-factor effects and the intended construction of the technical risk premium.
Sparse risk-factor levels
Before fitting a redistributed-claim severity model, claim volume should be assessed by risk-factor level. Levels with limited information may be combined using an economically or actuarially meaningful hierarchy. Coefficient stability across periods and agreement between observed and predicted severity provide additional diagnostics. Excess-loading output is an alternative when separate level estimates remain weakly supported.
For example, a model-preparation rule may map sectors with fewer than 20
claims to "Other". The value 20 is illustrative and should not be treated
as a general minimum. An appropriate threshold depends on portfolio size,
heterogeneity and validation results. Grouping is therefore outside the
scope of this function.
Reproducing the redistribution
The optional calculation columns allow the allocation to be reproduced. For partial redistribution, the loading before total-preservation scaling is:
blended\_loading = Z_g \cdot risk\_factor\_loading_g +
(1 - Z_g) \cdot portfolio\_loading
where Z_g denotes the credibility assigned to risk-factor level g.
Blending may change the total amount implied by the unscaled loadings. A
common scaling factor is therefore applied:
preservation\_factor =
\frac{total\ excess\ to\ redistribute}
{\sum_i blended\_loading_i \cdot redistribution\_weight_i}
The final amount received by row i is:
redistributed\_excess_i = blended\_loading_i \cdot
preservation\_factor \cdot redistribution\_weight_i
For example, let the sector loading be 30 per unit of weight, the portfolio
loading 20 and sector credibility 0.40. The blended loading equals
0.40 * 30 + 0.60 * 20 = 24. With a scaling factor of 1.10, the final
loading equals 26.4. A receiving row with redistribution weight 2 is then
allocated 26.4 * 2 = 52.8. In redistributed-claim output, 52.8 is added to
the retained claim amount; in excess-loading output, 26.4 is retained as the
loading per unit of weight.
With portfolio redistribution, credibility is zero and the blend equals the portfolio loading. With risk-factor redistribution, credibility is one and the blend equals the risk-factor loading.
Eligibility and redistribution weights
receives_redistribution identifies the rows to which excess loss may be
allocated. Rows with value FALSE receive zero. Their observed excess loss
is nevertheless included in the total amount to be allocated unless excluded
through redistribute_excess. For redistributed-claim output, a receiving
row must also have a positive claim count. For excess-loading output, a
receiving row must have a positive redistribution weight.
redistribute_excess controls which large losses contribute their excess
part to the redistribution. If it is NULL, every row with
claim_amount > threshold contributes. For a row marked FALSE, the claim
is not capped: its full observed amount is retained and its excess does not
enter the allocation pool. This permits specific loss types, such as events
treated outside the regular large-loss procedure, to remain unchanged.
redistribution_weight controls both the relative shares among receiving
rows and the unit of the resulting loading. Claim count produces an amount
per claim, expected claim count an amount per expected claim, earned exposure
an amount per exposure unit, and insured amount an amount per unit insured.
If it is NULL, claim count is used. For an excess loading that is added to
an annual risk premium, earned exposure is usually the corresponding unit.
Rows with zero redistribution weight remain in the output but receive zero.
For example, claim_count * insured_amount assigns a larger share to claim
observations with a higher insured amount. The excess threshold and an
insured-amount criterion for receiving allocations are separate model
specifications.
Redistribution methods
The redistribution_method argument determines the level at which excess
experience is estimated:
-
"portfolio"derives one loading from the complete allocation portfolio. Risk-factor-level excess experience is not used. -
"risk_factor"derives a separate loading for each risk-factor level. Each level is based only on its own excess experience and allocation weight. -
"partial"blends the risk-factor loading with the portfolio loading using credibility.
For partial redistribution, credibility is either supplied directly through
credibility or calculated as:
Z_g = \frac{n_g}{n_g + credibility\_threshold}
With credibility_basis = "claims", n_g is the number of claims in the
risk-factor level. With credibility_basis = "excess_records", it is the
number of records containing a positive excess amount. The resulting value
is multiplied by credibility_scale and bounded between zero and one.
Credibility and redistribution weight have distinct roles. Credibility
determines the contribution of risk-factor-level experience to a partial
loading. Redistribution weight determines the row-level allocation shares
and the unit of the final loading. Thus, with
credibility_basis = "claims" and earned exposure as
redistribution_weight, claim volume determines credibility while the
resulting loading is expressed per unit of exposure.
Aggregated portfolio rows
When a row contains multiple claims, claim_amount is treated as the total
loss for that row and threshold is applied to that row total. The function
cannot identify which individual claims exceeded the threshold from an
aggregated row. Claim-level input is required when the threshold is intended
to apply separately to each claim.
Value
The input data.frame with additional columns and class
"excess_redistribution". The object uses standard data.frame printing.
summary.excess_redistribution() aggregates contributed, allocated and
shifted loss. Both output forms add:
<claim_amount>_cappedObserved claim amount capped at
thresholdwhen its excess is allocated. Rows excluded throughredistribute_excessretain their full observed amount.<claim_amount>_excessObserved amount above
threshold, whether or not that amount is selected for allocation.<claim_amount>_is_excessLogical indicator that the observed claim amount exceeds
threshold.
With output = "redistributed_claim", the result additionally contains:
<claim_amount>_redistributed_excessRow-level allocated excess loss. Rows without claims receive zero.
<claim_amount>_adjustedRetained claim amount plus row-level allocated excess loss.
<claim_amount>_adjusted_averageRedistributed claim amount divided by claim count. Rows without claims contain zero.
With output = "excess_loading", the result additionally contains:
allocated_excess_lossAbsolute excess-loss amount allocated to the row.
excess_loadingAllocated excess loss per unit of
redistribution_weight.
With calculation_details = TRUE, the result also contains
<risk_factor>_excess_loading, <risk_factor>_credibility,
portfolio_excess_loading, blended_excess_loading,
redistribution_scaling_factor and final_redistribution_loading.
For receiving rows, final_redistribution_loading equals
blended_excess_loading multiplied by
redistribution_scaling_factor.
The selected output and effective redistribution-weight label are stored in
the "output" and "redistribution_weight_label" attributes.
Author(s)
Martin Haringa
See Also
summary.excess_redistribution()
Examples
portfolio <- data.frame(
policy_id = 1:10,
sector = c(rep("Industry", 5), rep("Retail", 4), "Office"),
claim_count = c(0, 1, 1, 1, 1, 0, 1, 1, 1, 1),
claim_amount = c(
0, 25000, 120000, 50000, 175000,
0, 40000, 90000, 150000, 300000
),
policy_years = rep(1, 10)
)
# Output form 1: include allocated excess in the severity response.
adjusted <- redistribute_excess_loss(
portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
risk_factor = "sector",
redistribution_method = "partial",
output = "redistributed_claim"
)
summary(adjusted)
# Inspect the row-level calculation. The allocated amount in the final column
# equals final_redistribution_loading times redistribution weight.
adjusted[c(
"sector_excess_loading", "sector_credibility",
"portfolio_excess_loading", "blended_excess_loading",
"redistribution_scaling_factor", "final_redistribution_loading",
"claim_amount_redistributed_excess"
)]
# Omit row-level calculation columns while retaining them in summary().
compact_adjusted <- redistribute_excess_loss(
portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
risk_factor = "sector",
redistribution_method = "partial",
calculation_details = FALSE
)
summary(compact_adjusted)
# Combine levels with limited claim experience before model estimation.
# Three claims is used for this small example; it is not a general minimum.
adjusted$sector_claim_count <- ave(
adjusted$claim_count, adjusted$sector, FUN = sum
)
adjusted$sector_model <- ifelse(
adjusted$sector_claim_count >= 3,
adjusted$sector,
"Other"
)
# Fit a severity model to redistributed average claim amount. For aggregated
# rows, claim count represents the number of observations underlying each
# average. With one row per claim, this additional weight is unnecessary.
severity_data <- adjusted[adjusted$claim_count > 0, ]
stats::glm(
claim_amount_adjusted_average ~ sector_model,
weights = claim_count,
family = stats::Gamma(link = "log"),
data = severity_data
)
# Output form 2: estimate retained severity and excess loading separately.
# Using policy years expresses excess_loading per policy year.
loading_result <- redistribute_excess_loss(
portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
redistribution_weight = "policy_years",
risk_factor = "sector",
redistribution_method = "partial",
output = "excess_loading"
)
frequency_model <- stats::glm(
claim_count ~ sector + offset(log(policy_years)),
family = stats::poisson(link = "log"),
data = loading_result
)
retained_severity_model <- stats::glm(
claim_amount_capped ~ sector,
weights = claim_count,
family = stats::Gamma(link = "log"),
data = loading_result[loading_result$claim_count > 0, ]
)
loading_result$predicted_claim_frequency <- stats::predict(
frequency_model,
newdata = loading_result,
type = "response"
) / loading_result$policy_years
loading_result$predicted_retained_severity <- stats::predict(
retained_severity_model,
newdata = loading_result,
type = "response"
)
loading_result$predicted_retained_risk_premium <-
loading_result$predicted_claim_frequency *
loading_result$predicted_retained_severity
loading_result$predicted_total_risk_premium <-
loading_result$predicted_retained_risk_premium +
loading_result$excess_loading
# Portfolio redistribution estimates one loading across all sectors. Sector-
# specific excess experience does not enter the allocation loading.
portfolio_adjusted <- redistribute_excess_loss(
portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
redistribution_method = "portfolio"
)
summary(portfolio_adjusted, by = "sector")
# Risk-factor redistribution estimates a separate loading for each sector.
# The estimate for a sector uses only that sector's excess loss and weight.
sector_adjusted <- redistribute_excess_loss(
portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
risk_factor = "sector",
redistribution_method = "risk_factor"
)
# Allocate in proportion to claim count times insured amount, restricted to
# policies with an insured amount of at least 100,000.
weighted_portfolio <- transform(
portfolio,
insured_amount = rep(c(50000, 250000), each = 5)
)
weighted_portfolio$receives_redistribution <-
weighted_portfolio$insured_amount >= 100000
weighted_portfolio$redistribution_weight <-
weighted_portfolio$claim_count * weighted_portfolio$insured_amount
weighted_adjusted <- redistribute_excess_loss(
weighted_portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
redistribution_weight = "redistribution_weight",
receives_redistribution = "receives_redistribution"
)
# Exclude catastrophe events and unsettled claims from the allocation pool.
# Their full observed claim amounts remain retained in the model data.
portfolio$is_catastrophe <- c(
FALSE, FALSE, FALSE, FALSE, TRUE,
FALSE, FALSE, FALSE, FALSE, FALSE
)
portfolio$claim_status <- c(
"settled", "settled", "settled", "settled", "settled",
"settled", "settled", "open", "settled", "settled"
)
portfolio$redistribute_excess <-
!portfolio$is_catastrophe &
portfolio$claim_status == "settled"
selected_adjusted <- redistribute_excess_loss(
portfolio,
claim_amount = "claim_amount",
threshold = 100000,
claim_count = "claim_count",
redistribute_excess = "redistribute_excess"
)
Deprecated alias for merge_date_ranges()
Description
reduce() is deprecated as of version 0.8.0. Use
merge_date_ranges() instead.
Usage
reduce(df, begin, end, ..., agg_cols = NULL, agg = "sum", min.gapwidth = 5)
Arguments
begin |
Deprecated NSE argument. Use |
end |
Deprecated NSE argument. Use |
... |
Deprecated NSE grouping columns. Use |
agg_cols |
Deprecated NSE argument. Use |
agg |
Deprecated. Use |
min.gapwidth |
Deprecated. Use |
Value
See merge_date_ranges().
Objects exported from other packages
Description
These objects are imported from other packages. Follow the links below to see their documentation.
- ggplot2
Fit a prepared refinement specification
Description
Apply the ordered steps stored in a rating_refinement object and fit the
resulting pricing GLM. This evaluates the current refinement specification;
it may be called repeatedly while smoothing, restrictions, shrinkage,
rebasing or sublevel relativities are being reviewed.
Usage
refit(object, intercept_only = FALSE, ...)
Arguments
object |
Object of class |
intercept_only |
Logical. If |
... |
Additional arguments passed to |
Details
refit() applies the stored steps in their recorded order, constructs the
required tariff variables and offsets, updates the model formula and calls
stats::glm() with the original model family. Additional fitting arguments
can be supplied through ....
Actuarial interpretation
The refitted model represents the combined effect of the original GLM structure and the explicit actuarial assumptions stored in the refinement. Its coefficients and predictions should be assessed against exposure, observed experience, model diagnostics and the unrestricted model. A refit does not establish that a manual restriction or curve edit is statistically estimated; it applies that assumption as specified.
Intercept-only recalibration
With intercept_only = FALSE, the refined GLM is fitted with the remaining
free model terms that are still present after applying the refinement steps.
With intercept_only = TRUE, remaining original model effects are fixed as
offsets based on their existing fitted relativities. Only the intercept is
then estimated. Consequently, relative differences between those fixed
effects remain unchanged while the overall expected premium level is
recalibrated to the supplied model data.
In practical actuarial work, intercept_only = TRUE is generally suitable
for a controlled actuarial or commercial refinement of an accepted tariff
structure. Examples include a small manual restriction, a limited curve
adjustment or a final calibration in which the relativities of unaffected
risk factors should remain unchanged.
Use intercept_only = FALSE when the refinement forms part of substantive
model development. The remaining free model terms are then estimated again,
allowing the GLM to account for dependence between risk factors and find a
new joint statistical optimum conditional on the fixed refinement steps.
Coefficients of risk factors that were not directly refined may therefore
also change.
Model result and further refinement
Printing the returned model first shows the original and refitted formulas,
the model family, whether an intercept-only refit was used, and a concise
description of every restriction, smoothing, shrinkage or relativity step. This is
followed by the regular glm output with the model call, coefficients,
degrees of freedom, deviance and AIC. The object continues to inherit from
glm, so standard methods such as stats::predict.glm() and
summary.glm() remain available.
The returned GLM is a fitted result, not an editable refinement
specification. Retain the original rating_refinement object when further
changes may be required. Passing the refitted GLM to prepare_refinement()
starts a new workflow from that model and does not reconstruct the earlier
sequence of refinement steps.
Value
A fitted object inheriting from glm. Compatibility classes
refitrestricted, refitsmooth, or both are added when relevant. The
object stores refinement metadata used by rating_table() and
rating_grid() to identify fixed relativities, smoothed variables and
derived tariff factors.
Author(s)
Martin Haringa
See Also
prepare_refinement(), add_smoothing(), edit_smoothing(),
add_restriction(), add_shrinkage(), add_rebasing(),
add_relativities(), rating_table(), rating_grid(),
audit_refinement()
Examples
zip_df <- data.frame(
zip = c(0, 1, 2, 3),
zip_adj = c(0.8, 0.9, 1.0, 1.2)
)
model <- glm(
nclaims ~ zip + offset(log(exposure)),
family = poisson(),
data = MTPL
)
refinement <- prepare_refinement(model) |>
add_restriction(zip_df)
refined_model <- refit(refinement, intercept_only = TRUE)
Deprecated refit wrapper
Description
refit_glm() is deprecated as of version 0.9.0. Use refit() instead.
Usage
refit_glm(x, intercept_only = FALSE, ...)
Arguments
x |
Object of class |
intercept_only |
Logical. |
... |
Other arguments. |
Value
Object of class glm.
Define sublevel relativity specifications
Description
Use split_level() to describe how one existing GLM factor level is divided
into more detailed portfolio levels with specified multiplicative
relativities. Use relativities() to combine one or more of these definitions
into the specification supplied to add_relativities().
Usage
split_level(level, new_levels, relativities)
relativities(...)
Arguments
level |
Character string. Existing level of the risk factor to split. |
new_levels |
Character vector. Levels of the more detailed portfolio
variable within |
relativities |
Numeric vector. Multiplicative relativities corresponding
to |
... |
One or more objects created by |
Details
level identifies the existing parent level in model_variable.
new_levels identifies the corresponding levels of split_variable.
relativities gives their relative tariff effects before any optional
exposure normalisation by add_relativities().
Each call to split_level() represents one parent level. Several parent
levels can be refined in one step by passing their definitions to
relativities(). Parent levels must be unique within the combined
specification. Levels of the original model variable that are not included
remain unsplit.
These helpers assemble and validate explicit tariff assumptions. They do not
estimate, normalise or apply the supplied relativities and do not alter a
fitted GLM. Exposure normalisation, when requested, is performed by
add_relativities().
Value
split_level() returns a named list of length one. Its name is
level; its value is a data frame with columns new_level and
relativity. relativities() returns the combined named list expected by
the relativities argument of add_relativities().
Author(s)
Martin Haringa
See Also
Examples
construction_split <- split_level(
level = "residential",
new_levels = c("flat", "house"),
relativities = c(0.95, 1.05)
)
relativities(
construction_split,
split_level(
"commercial",
new_levels = c("shop", "office"),
relativities = c(1.10, 0.90)
)
)
Deprecated restriction helper
Description
restrict_coef() is deprecated as of version 0.9.0. Use
add_restriction() instead.
prepare_refinement(model) |> add_restriction(...) |> refit()
Usage
restrict_coef(
model,
restrictions,
allow_new_levels = TRUE,
allow_new_risk_factors = TRUE
)
Arguments
model |
A fitted model object. |
restrictions |
data.frame with exactly two columns. |
allow_new_levels |
Logical. If |
allow_new_risk_factors |
Logical. Whether a fixed tariff factor that is
available in the model data but absent from the fitted model may be added.
The default is |
Value
A rating_refinement object containing the restriction step. Call
refit() to apply the restriction and return the refined GLM. New code
should use prepare_refinement() followed by add_restriction() directly.
See Also
add_restriction(), prepare_refinement(), refit()
Simulate severities from a truncated gamma distribution
Description
Generate random claim severities from a gamma distribution conditional on
the result falling inside the interval (lower, upper).
Usage
rgammat(n, shape, scale, lower, upper)
Arguments
n |
Integer. Number of observations to generate. |
shape |
Numeric. Shape parameter of the gamma distribution. |
scale |
Numeric. Scale parameter of the gamma distribution. |
lower |
Numeric. Lower truncation bound. |
upper |
Numeric. Upper truncation bound. |
Details
Random values are generated by sampling from a uniform distribution on the
interval [F(lower), F(upper)], where F is the CDF of the gamma
distribution, and then applying the inverse CDF.
The resulting sample follows the specified conditional distribution; values outside the truncation interval are not generated.
In severity analysis, this can be used for simulation and model checking when the available claims are observed only between a lower reporting threshold and an upper modelling limit. Truncation should not be confused with censoring or capping: the function assumes that values outside the interval are absent rather than recorded at a boundary.
Value
A numeric vector of length n containing random draws from the
truncated gamma distribution.
Author(s)
Martin Haringa
See Also
fit_truncated_severity(), rlnormt()
Estimate a smooth effect for a continuous risk factor
Description
Estimate the relationship between a continuous risk factor and claim frequency, average severity or risk premium with a generalized additive model (GAM). The fitted curve is intended for exploratory risk-factor analysis before selecting a functional form, applying refinement or deriving categorical tariff segments.
Usage
risk_factor_gam(
data,
risk_factor = NULL,
claim_count = NULL,
exposure = NULL,
claim_amount = NULL,
pure_premium = NULL,
model = "frequency",
round_risk_factor = NULL,
x = NULL,
nclaims = NULL,
amount = NULL,
round_x = NULL
)
Arguments
data |
A data frame containing portfolio observations. |
risk_factor |
Character string. Numeric continuous risk-factor column
in |
claim_count |
Character string. Claim-count column. Required for
|
exposure |
Character string. Exposure column used as an offset or aggregation weight. |
claim_amount |
Optional character string. Total claim-amount column.
Required for |
pure_premium |
Optional character string. Row-level risk-premium column.
Required for |
model |
Character string. Response context: |
round_risk_factor |
Optional positive numeric value. The continuous risk factor is rounded to multiples of this value before aggregation and model fitting. This can reduce computation and local volatility when the variable has many distinct values, but it also removes detail. |
x, nclaims, amount, round_x |
Deprecated argument names. Use |
Details
Statistical specification
-
"frequency"fits a Poisson GAM to aggregated claim counts withlog(exposure)as offset. -
"severity"fits a Gamma GAM with log link to average claim amount. The response is total claim amount divided by claim count and claim count is used as model weight. -
"pure_premium"fits a Gamma GAM with log link to exposure-weighted risk premium.
Observations are first aggregated by the risk-factor value after optional rounding. Predictions and pointwise confidence intervals are then evaluated over the observed range.
Actuarial interpretation
The fitted curve describes the marginal pattern in the selected portfolio data. It can reveal non-linearity, broad turning points and areas with sparse support, but it is not by itself a final tariff structure. Correlation with other risk factors, exposure concentration, claim volume, tail observations and stability across periods should be considered before using the pattern in a multivariate GLM.
autoplot.riskfactor_gam() can be used to inspect the curve and observed
experience. derive_tariff_segments() can subsequently translate the smooth
pattern into candidate intervals. Alternatively, add_smoothing() supports
smoothing within the structured refinement workflow.
Column interface and compatibility
Column names are supplied as character strings. Deprecated fit_gam() and
riskfactor_gam() interfaces remain available for compatibility.
Value
A list of class "risk_factor_gam" with compatibility classes
"riskfactor_gam" and "fitgam". It contains:
- prediction
Prediction grid with fitted values and pointwise confidence limits.
- x
Name of the continuous risk factor.
- model
Response context:
"frequency","severity"or"pure_premium".- data
Aggregated observed experience and fitted values at observed risk-factor values.
- x_obs
Risk-factor values in the original portfolio row order, after optional rounding.
- round_risk_factor
Rounding increment used for the risk factor, or
NULLwhen no rounding was applied.
Author(s)
Martin Haringa
References
Antonio, K. and Valdez, E. A. (2012). Statistical concepts of a priori and a posteriori risk classification in insurance. Advances in Statistical Analysis, 96(2):187–224.
Henckaerts, R., Antonio, K., Clijsters, M. and Verbelen, R. (2018). A data driven binning strategy for the construction of insurance tariff classes. Scandinavian Actuarial Journal, 2018:8, 681–705.
Wood, S.N. (2011). Fast stable restricted maximum likelihood and marginal likelihood estimation of semiparametric generalized linear models. Journal of the Royal Statistical Society (B) 73(1):3–36.
See Also
autoplot.riskfactor_gam(), derive_tariff_segments(),
add_smoothing()
Examples
age_frequency <- risk_factor_gam(
MTPL,
risk_factor = "age_policyholder",
claim_count = "nclaims",
exposure = "exposure",
model = "frequency"
)
autoplot(age_frequency, show_observations = TRUE)
Deprecated alias for risk_factor_gam()
Description
riskfactor_gam() is deprecated in favour of risk_factor_gam().
Usage
riskfactor_gam(
data,
nclaims = NULL,
x = NULL,
exposure = NULL,
amount = NULL,
pure_premium = NULL,
model = "frequency",
round_x = NULL,
risk_factor = NULL,
claim_count = NULL,
claim_amount = NULL,
round_risk_factor = NULL
)
Arguments
data |
A data frame containing portfolio observations. |
nclaims |
Deprecated. Use |
x |
Deprecated. Use |
exposure |
Character string. Exposure column used as an offset or aggregation weight. |
amount |
Deprecated. Use |
pure_premium |
Optional character string. Row-level risk-premium column.
Required for |
model |
Character string. Response context: |
round_x |
Deprecated. Use |
risk_factor |
Character string. Numeric continuous risk-factor column
in |
claim_count |
Character string. Claim-count column. Required for
|
claim_amount |
Optional character string. Total claim-amount column.
Required for |
round_risk_factor |
Optional positive numeric value. The continuous risk factor is rounded to multiples of this value before aggregation and model fitting. This can reduce computation and local volatility when the variable has many distinct values, but it also removes detail. |
Value
See risk_factor_gam().
Simulate severities from a truncated lognormal distribution
Description
Generate random claim severities from a lognormal distribution conditional
on the result falling inside the interval (lower, upper).
Usage
rlnormt(n, meanlog, sdlog, lower, upper)
Arguments
n |
Integer. Number of observations to generate. |
meanlog |
Numeric. Mean of the underlying normal distribution. |
sdlog |
Numeric. Standard deviation of the underlying normal distribution. |
lower |
Numeric. Lower truncation bound. |
upper |
Numeric. Upper truncation bound. |
Details
Random values are generated by sampling from a uniform distribution on the
interval [F(lower), F(upper)], where F is the CDF of the
lognormal distribution, and then applying the inverse CDF.
The resulting sample follows the specified conditional distribution; values outside the truncation interval are not generated.
In severity analysis, this can be used for simulation and model checking when the available claims are observed only between a lower reporting threshold and an upper modelling limit. Truncation should not be confused with censoring or capping: the function assumes that values outside the interval are absent rather than recorded at a boundary.
Value
A numeric vector of length n containing random draws from the
truncated lognormal distribution.
Author(s)
Martin Haringa
See Also
fit_truncated_severity(), rgammat()
Calculate response-scale prediction error
Description
Calculate the root mean squared error (RMSE) between observed outcomes and response-scale predictions from a fitted model. RMSE summarises the typical absolute prediction error in the same unit as the model response.
Usage
rmse(x, data = NULL)
Arguments
x |
A fitted model object, for example a |
data |
Optional data frame on which the observed response and predictions
are evaluated. If |
Details
RMSE is defined as
\sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i-\hat{y}_i)^2}.
In pricing work, RMSE can be used to compare alternative specifications for the same response, portfolio and exposure treatment. Lower values indicate smaller response-scale errors. Because errors are squared, individual large deviations receive relatively high weight. This can be relevant for severity models, but it also makes RMSE sensitive to large claims.
RMSE values should not be compared across responses with different units or scales. A value calculated on the estimation data is an in-sample diagnostic, not an estimate of future predictive performance. Use resampling or separate validation data when out-of-sample performance is required, and interpret RMSE together with calibration, residual and distributional diagnostics.
Value
A numeric value: the root mean squared error.
Author(s)
Martin Haringa
See Also
model_performance(), bootstrap_performance(),
check_residuals()
Examples
x <- glm(nclaims ~ area, offset = log(exposure),
family = poisson(), data = MTPL2)
rmse(x, MTPL2)
Deprecated alias for active_rows_by_date()
Description
rows_per_date() is deprecated as of version 0.9.0. Use
active_rows_by_date() instead.
Usage
rows_per_date(
df,
dates,
df_begin,
df_end,
dates_date,
...,
nomatch = NULL,
mult = "all"
)
Arguments
df |
Deprecated. Use |
dates |
A |
df_begin |
Deprecated NSE argument. Use |
df_end |
Deprecated NSE argument. Use |
dates_date |
Deprecated NSE argument. Use |
... |
Deprecated NSE join columns. Use |
nomatch, mult |
Deprecated technical argument names. Use |
Value
Scale secondary axis for background plotting
Description
Internal helper to rescale a secondary variable (s_axis) so it aligns
with the scale of a first variable (f_axis). Adds two new columns
to the data frame: s_axis_scale and s_axis_print.
Usage
scale_second_axis(background, df, dfby, f_axis, s_axis, by)
Value
The input data frame with two additional columns:
-
s_axis_scale– scaled version ofs_axis -
s_axis_print– rounded version ofs_axis
Set the reference level of a factor
Description
Relevels a factor so that the selected category becomes the reference
(first) level. By default, the reference level is chosen as the level with
the largest total weight, for example the largest exposure in an insurance
portfolio. Use method = "manual" with reference_level when a specific
business category should be the reference level.
Choosing a reference level does not change fitted values or the overall model fit. It changes the coefficient parameterisation and therefore the level against which the remaining factor relativities are expressed.
Usage
set_reference_level(
x,
weight = NULL,
method = "largest_weight",
reference_level = NULL
)
Arguments
x |
A factor (unordered). Character vectors should be converted to factor before use. |
weight |
A numeric vector of the same length as |
method |
Character. Method used to choose the reference level.
Supported methods are |
reference_level |
Character string with the level to use as reference
when |
Details
method = "largest_weight" is useful when the reference category should
represent a substantial and relatively stable part of the portfolio. The
supplied weight is commonly earned exposure, but another actuarially
meaningful volume measure may be used.
method = "manual" is appropriate when the reference category is determined
by tariff interpretation, governance or an established pricing convention.
The selected category must already be an observed factor level.
Value
A factor of the same length as x, with the selected reference
level set as the first level.
Author(s)
Martin Haringa
References
Kaas, Rob & Goovaerts, Marc & Dhaene, Jan & Denuit, Michel. (2008). Modern Actuarial Risk Theory: Using R. doi:10.1007/978-3-540-70998-5
See Also
add_rebasing() for rescaling current tariff relativities within a
prepared refinement workflow after a model has been fitted.
Examples
portfolio <- data.frame(
region = factor(c("North", "North", "South", "West")),
exposure = c(120, 80, 60, 40)
)
set_reference_level(portfolio$region, portfolio$exposure)
set_reference_level(
portfolio$region,
method = "manual",
reference_level = "South"
)
# Apply the largest-weight reference rule to every factor in a data frame
library(dplyr)
df <- chickwts |>
mutate(across(where(is.character), as.factor)) |>
mutate(across(where(is.factor), ~set_reference_level(., weight)))
Deprecated smoothing helper
Description
smooth_coef() is deprecated as of version 0.9.0. Use
add_smoothing() instead.
prepare_refinement(model) |> add_smoothing(...) |> refit()
Usage
smooth_coef(
model,
x_cut,
x_org,
degree = NULL,
breaks = NULL,
smoothing = "spline",
k = NULL,
weights = NULL
)
Arguments
model |
A fitted model object. |
x_cut |
Deprecated model variable used in the GLM. |
x_org |
Deprecated source variable used to fit the smoothing curve. |
degree |
Deprecated polynomial degree. |
breaks |
Deprecated smoothing break points. |
smoothing |
Deprecated smoothing type. |
k |
Deprecated spline basis dimension. |
weights |
Deprecated weights column. |
Value
A legacy smooth object. New code should use prepare_refinement(),
add_smoothing(), and refit().
See Also
add_smoothing(), prepare_refinement(), refit()
Split portfolio periods into calendar months
Description
Split policy periods that cross calendar-month boundaries into separate monthly records. Numeric amounts such as earned exposure and earned premium can be allocated over those records while preserving the amount of each original portfolio row.
Usage
split_periods_to_months(
data = NULL,
period_start = NULL,
period_end = NULL,
prorate_cols = NULL,
df = NULL,
begin = NULL,
end = NULL,
cols = NULL
)
Arguments
data |
A |
period_start |
Character string. Name of the column with policy period start dates. |
period_end |
Character string. Name of the column with policy period end dates. |
prorate_cols |
Character vector with names of numeric columns to prorate over the monthly rows, for example exposure or premium. |
df, begin, end, cols |
Deprecated argument names kept for backward
compatibility. Use |
Details
Pricing, reserving and monitoring analyses often require exposure and premium by calendar month, whereas policy administration data generally contains periods with arbitrary start and end dates. The function converts those periods into a monthly representation before aggregation, modelling or reporting.
This is a temporal expansion rather than a portfolio reduction. See
merge_date_ranges() for consolidating connected periods and
active_rows_by_date() for matching dated events to active periods.
Prorated columns are distributed according to the part of the policy period represented by each monthly row. Full months receive weight 1 and partial months use a 30-day convention. The monthly weights are normalised within each source row. Consequently, monthly exposure and premium sum to their original values, including for periods that contain partial months.
Column names are supplied as character strings, for example
period_start = "begin_date". The deprecated period_to_months() interface
used unquoted column names and is retained only for backward compatibility.
Expansion and proration are performed internally with
data.table::data.table() on a local copy. The supplied object is not
modified by reference, and the returned object is always a regular
data.frame.
Value
A regular data.frame with one row for each calendar month covered
by an original portfolio record. The original columns are retained and an
id column identifies the source row. Columns supplied through
prorate_cols contain their allocated monthly amounts.
Author(s)
Martin Haringa
See Also
active_rows_by_date(), merge_date_ranges(), rating_grid()
Examples
portfolio <- data.frame(
policy_id = c("P001", "P002", "P003"),
sector = c("Industry", "Retail", "Services"),
coverage_start = as.Date(c("2025-01-15", "2025-02-01", "2025-03-20")),
coverage_end = as.Date(c("2025-03-31", "2025-02-28", "2025-05-10")),
earned_exposure = c(0.21, 0.08, 0.14),
earned_premium = c(420, 160, 280)
)
# Allocate each policy period and its amounts over calendar months.
monthly_portfolio <- split_periods_to_months(
portfolio,
period_start = "coverage_start",
period_end = "coverage_end",
prorate_cols = c("earned_exposure", "earned_premium")
)
monthly_portfolio
# The allocated monthly amounts reconcile to the source portfolio.
aggregate(
cbind(earned_exposure, earned_premium) ~ id,
data = monthly_portfolio,
FUN = sum
)
# Deprecated interface with unquoted column names
## Not run:
period_to_months(
portfolio,
coverage_start,
coverage_end,
earned_exposure,
earned_premium
)
## End(Not run)
Deprecated low-level relativity constructor
Description
split_relativities() is deprecated. Use split_level() to define a named
parent-level split and combine multiple splits with relativities().
Usage
split_relativities(new_levels, relativities)
Arguments
new_levels |
Character vector. Names of the new sublevels. |
relativities |
Numeric vector. Relativities corresponding to each
sublevel. Must have the same length as |
Value
A data frame with columns new_level and relativity. New code
should use split_level(), which also records the parent model level
required by add_relativities().
Author(s)
Martin Haringa
See Also
split_level(), relativities(), add_relativities()
Examples
split_level(
level = "construction",
new_levels = c("residential", "commercial", "civil"),
relativities = c(1.00, 1.10, 1.25)
)
Summarise bootstrap coefficient stability
Description
Summarise the coefficient distributions returned by
bootstrap_coefficients() on the GLM link scale or after exponentiation.
Usage
## S3 method for class 'bootstrap_coefficients'
summary(
object,
scale = c("link", "exponentiated", "relativity"),
confidence = 0.95,
interval = c("percentile", "normal"),
...
)
Arguments
object |
A |
scale |
Character string. |
confidence |
Numeric scalar between 0 and 1 giving the confidence level. |
interval |
Character string. |
... |
Additional arguments are not used. |
Value
A data frame with one row per original coefficient and columns:
- term
Coefficient name.
- estimate
Estimate from the original GLM.
- bootstrap_mean
Mean of the finite bootstrap estimates.
- bias
Bootstrap mean minus the original estimate.
- bootstrap_se
Standard deviation of the bootstrap estimates.
- lower, upper
Requested bootstrap interval.
- n_successful
Number of finite bootstrap estimates for the term.
- n_requested
Number of requested bootstrap samples.
- success_rate
n_successful / n_requested.
Author(s)
Martin Haringa
See Also
bootstrap_coefficients(), as_gt()
Summarise redistributed large-loss experience
Description
Audit how much excess loss was contributed and received across portfolio
segments after redistribute_excess_loss(). The summary shows whether a
segment receives more large-loss cost than it contributes, or transfers part
of its observed excess burden to other segments. The same audit is available
for redistributed claims and separate excess loadings.
Usage
## S3 method for class 'excess_redistribution'
summary(object, by = NULL, ...)
Arguments
object |
An object returned by |
by |
Optional character string. Column used to group the audit. If
|
... |
Unused. |
Details
redistributed_excess_contributed is the excess amount removed from selected
large losses in a segment. redistributed_excess_received is the amount
assigned back to claim-bearing rows in that segment. Their difference is:
net\_loss\_shift = received - contributed
A positive value means that the segment receives more redistributed loss
than it contributed. A negative value means that it transfers loss to other
segments. Across the full portfolio, net_loss_shift sums to zero, subject
to numerical tolerance.
When by = NULL, the summary uses the risk_factor supplied to
redistribute_excess_loss(). If no risk factor was supplied, one portfolio
row is returned. Supply by to inspect a portfolio redistribution by another
portfolio characteristic, for example summary(x, by = "sector").
The summary also exposes the loading calculation. For partial
redistribution, <risk_factor>_excess_loading is blended with
portfolio_excess_loading using <risk_factor>_credibility. The resulting
blended_excess_loading is multiplied by
redistribution_scaling_factor to obtain
final_redistribution_loading. Multiplying the final loading by the total
receiving redistribution_weight gives
redistributed_excess_received.
If by differs from the risk factor used in the redistribution, loading and
credibility columns are receiving-weighted averages within each audit group.
For separate-loading output, adjusted_loss is shown only as a reconciliation
of retained plus allocated loss; it does not mean that the allocated amount
was added to the row-level severity response.
Value
A data.frame with one row per audit group. The grouping column keeps
its original name and type when by is used. The remaining columns are:
n_recordsNumber of portfolio records in the group.
claim_countNumber of claims in the group.
redistribution_weightTotal redistribution weight of rows that receive redistributed loss.
n_excess_recordsNumber of records with an observed amount above the threshold.
n_redistributed_excess_recordsNumber of records whose excess amount was actually contributed to the redistribution pool.
observed_lossObserved claim cost before redistribution.
retained_lossClaim cost retained at or below the threshold, including full claims excluded through
redistribute_excess.observed_excess_lossObserved claim cost above the threshold, including excess from rows excluded through
redistribute_excess.redistributed_excess_contributedExcess removed from selected large losses and contributed to the redistribution pool.
<risk_factor>_excess_loadingExcess loading estimated from the risk-factor-level experience, per unit of redistribution weight.
<risk_factor>_credibilityWeight assigned to the risk-factor loading. It is zero for portfolio redistribution, one for risk-factor redistribution and between zero and one for partial redistribution.
portfolio_excess_loadingPortfolio-wide excess loading per unit of redistribution weight.
blended_excess_loadingRisk-factor loading times credibility plus portfolio loading times one minus credibility.
redistribution_scaling_factorFactor that preserves the total amount being redistributed after blending.
final_redistribution_loadingBlended loading multiplied by the redistribution scaling factor.
allocated_excess_lossAbsolute excess-loss amount allocated to the audit group.
average_excess_loadingAllocated excess loss per unit of total receiving redistribution weight.
redistributed_excess_receivedExcess assigned to receiving claim-bearing rows.
net_loss_shiftReceived minus contributed redistributed excess.
adjusted_lossClaim cost after redistribution.
observed_average_claimObserved loss divided by claim count.
adjusted_average_claimAdjusted loss divided by claim count.
Author(s)
Martin Haringa
See Also
Summarise a prepared refinement specification
Description
Describe the original GLM and the ordered actuarial adjustments stored in a
rating_refinement object before refit() is called. The summary records
what will be applied; it does not compare fitted predictions because the
refined GLM has not yet been estimated.
Usage
## S3 method for class 'rating_refinement'
summary(object, ...)
Arguments
object |
A |
... |
Currently unused. |
Value
An object of class summary.rating_refinement containing model and
package metadata together with a data frame describing the refinement steps
in their evaluation order.
See Also
prepare_refinement(), refit(), audit_refinement()
Summarise a refinement audit
Description
Return and print the provenance, ordered refinement steps, total portfolio
effect and the largest absolute level changes from audit_refinement().
Usage
## S3 method for class 'refinement_audit'
summary(object, top_n = 10, ...)
Arguments
object |
A |
top_n |
Non-negative whole number controlling how many level changes are included in the printed summary. |
... |
Currently unused. |
Value
An object of class summary.refinement_audit containing the audit
metadata, formulas, steps, portfolio result and selected level impacts.
See Also
Summarise candidate tariff segments
Description
Return the portfolio diagnostics stored when derive_tariff_segments()
created the candidate segmentation. The summary can be used to assess
whether the proposed intervals contain sufficient exposure and claim
information before they are used in a GLM or tariff structure.
Usage
## S3 method for class 'tariff_segments'
summary(object, ...)
Arguments
object |
A |
... |
Additional arguments reserved for method compatibility. |
Value
A data frame with one row per candidate segment and the columns:
- segment
Candidate tariff interval.
- portfolio_records
Number of portfolio rows assigned to the interval.
- risk_factor_values
Number of distinct observed risk-factor values represented by the interval.
- exposure
Total exposure represented in a frequency or risk-premium GAM.
- claim_count
Total observed claim count for a frequency or severity GAM.
- frequency
Observed claim frequency, calculated as
claim_count / exposure, for a frequency GAM.- claim_amount
Total observed claim amount for a severity GAM.
- average_severity
Observed average severity, calculated as
claim_amount / claim_count, for a severity GAM.- risk_premium_amount
Total exposure-weighted risk-premium amount for a risk-premium GAM.
- risk_premium
Observed risk premium, calculated as
risk_premium_amount / exposure, for a risk-premium GAM.
The response columns are model dependent. The returned table therefore
contains the numerator, denominator and observed y-axis measure relevant to
the model used by risk_factor_gam().
Author(s)
Martin Haringa
See Also
derive_tariff_segments(), add_tariff_segments()
Deprecated alias for factor_analysis()
Description
univariate() is deprecated as of version 0.8.0. Use
factor_analysis() instead.
Usage
univariate(
df,
x,
severity = NULL,
nclaims = NULL,
exposure = NULL,
premium = NULL,
by = NULL
)
Arguments
df |
A |
x |
Column name or expression with the risk factor. |
severity |
Column name or expression with claim amounts. |
nclaims |
Column name or expression with claim counts. |
exposure |
Column name or expression with exposures. |
premium |
Column name or expression with premiums. |
by |
Optional grouping column name or expression. |
Value
See factor_analysis().
Create new offset-term and new formula
Description
Create new offset-term and new formula
Usage
update_formula_add(offset_term, fm_no_offset, add_term)
Arguments
offset_term |
String obtained from get_offset() |
fm_no_offset |
Obtained from remove_offset_formula() |
add_term |
Name of restricted risk factor to add |
Deprecated alias for refit_glm()
Description
update_glm() is deprecated as of version 0.8.0. Use refit() for the new
refinement workflow.
Usage
update_glm(x, intercept_only = FALSE, ...)
Arguments
x |
Object of class |
intercept_only |
Logical. |
... |
Other arguments. |
Value
See refit_glm().