Paper B Applied Computer Lab
Run live R statistical code in your browser for CS1, and master dynamic Excel financial models for CM1 with line-by-line mechanics and full mark-scheme commentaries.
Paper B Marking Rubrics & Examination Strategy
IFoA Assessment BreakdownUnlike Paper A where pure algebra dominates, Paper B examiners split marks between functional computational execution and qualitative actuarial decision making:
1. Code & Calculation Rigour ~40% of marks
Correct R/Excel syntax, appropriate statistical distributions, avoidance of hardcoded numbers, and dynamic cell/vector references.
2. Written Commentary & Advice ~60% of marks
Explicit hypothesis statements (\(H_0\) vs \(H_1\)), quoted test statistics and p-values, clear business conclusions, and insurance recommendations in Word.
3. Fatal Examiner Traps Critical Mark Loss
Missing log(exposure) in GLM offsets, omitting type="response" in predictions, or hardcoding values instead of formula links in Excel.
Loading in-browser R runtime via WebAssembly...\nYou can write and run full R statistical scripts with 0 backend servers!
CS1 Exam Modules: Line-by-Line Mechanics & Marking Schemes
Poisson Claim Frequency GLMs with Exposure Offset
In general insurance pricing, annual claim count \(Y_i \sim \text{Poisson}(\mu_i)\) where \(\mu_i = e_i \cdot \lambda_i\). Taking logs gives \(\ln \mu_i = \ln e_i + \beta_0 + \sum \beta_j x_{ij}\). Here, \(\ln e_i\) has a fixed regression coefficient of \(1.0\) and is passed into R using the offset() argument.
# 1. Fit Poisson GLM with log link and log-exposure offset
model <- glm(claims ~ age_group + vehicle_type,
family = poisson(link = "log"),
offset = log(exposure),
data = motor_data)
# 2. Extract multiplicative baseline and rating relativities
relativities <- exp(coef(model))
# 3. Perform Analysis of Deviance (Drop1 Likelihood Ratio Test)
drop_table <- drop1(model, test = "Chisq")
🔍 Line-by-Line Code Breakdown
family = poisson(link = "log"): Specifies a Poisson error distribution with canonical log link function \(\eta_i = \ln \mu_i\), ensuring predicted frequencies remain strictly non-negative.offset = log(exposure): Accounts for differing policy durations. Because \(\ln(\text{claims}) = \ln(\text{exposure}) + \mathbf{x}^T\boldsymbol{\beta}\), the exposure term enters the linear predictor with a fixed coefficient of 1. (Trap: Passingoffset = exposurewithoutlog()is an instant 3-mark penalty!).exp(coef(model)): Exponentiates the additive regression parameters \(\beta_j\) to convert them into multiplicative rating factors relative to the base policyholder class.drop1(model, test = "Chisq"): Sequentially drops each covariate, computes the change in scaled deviance \(\Delta D = D_{\text{reduced}} - D_{\text{full}}\), and compares against a \(\chi^2_{\Delta df}\) distribution to test statistical significance.
Simulating Aggregate Portfolio Claims (\(S = \sum X_i\)) & 99.5% 1-in-200 Year VaR
Under Solvency II, insurers must hold capital against the 99.5% Value at Risk (VaR) of aggregate losses over a 1-year horizon. We model compound Poisson-Gamma risk by drawing frequency \(N \sim \text{Poisson}(\lambda)\) and severities \(X_i \sim \text{Gamma}(\alpha, \beta)\).
set.seed(42)
n_sims <- 10000
N <- rpois(n_sims, lambda = 25)
S <- numeric(n_sims)
for(i in 1:n_sims) {
if(N[i] > 0) S[i] <- sum(rgamma(N[i], shape = 2.5, rate = 0.002))
}
var_995 <- quantile(S, 0.995)
cat("99.5% Solvency VaR: £", round(var_995, 2))
🔍 Line-by-Line Code Breakdown
set.seed(42): Initializes the pseudo-random number generator so simulated output matches the examiner's marking key exactly.N <- rpois(n_sims, lambda = 25): Generates 10,000 independent draws of annual claim counts for the portfolio.if(N[i] > 0) sum(rgamma(...)): For years where claims occur (\(N > 0\)), simulates \(N_i\) individual claim sizes and computes compound total loss \(S_i = \sum_{j=1}^{N_i} X_j\). If \(N_i = 0\), aggregate loss defaults to 0.quantile(S, 0.995): Computes the 99.5th empirical sample percentile, representing the 1-in-200 year extreme loss threshold.
Bootstrap Resampling for Skewed Claim Severity Medians
When insurance claim datasets contain extreme outlier losses, the sample mean is heavily distorted and the Central Limit Theorem fails under small samples. Non-parametric bootstrap resampling with replacement provides distribution-free confidence intervals for the population median.
set.seed(123)
raw_claims <- c(320, 450, 480, 510, 620, 780, 850, 1400, 2900, 8500)
B <- 5000
boot_stats <- replicate(B, median(sample(raw_claims, replace = TRUE)))
ci_pct <- quantile(boot_stats, probs = c(0.025, 0.975))
cat("95% Percentile Bootstrap CI: [£", round(ci_pct[1], 2), ", £", round(ci_pct[2], 2), "]")
🔍 Line-by-Line Code Breakdown
sample(raw_claims, replace = TRUE): Resamples \(n\) observations from the empirical data with replacement, mimicking draws from the underlying population.replicate(B, median(...)): Repeats the resampling and median calculation \(B = 5,000\) times to construct the empirical bootstrap sampling distribution of the median.quantile(boot_stats, probs = c(0.025, 0.975)): Takes the 2.5% and 97.5% percentiles of the bootstrap distribution to form the 95% non-parametric confidence interval without making normality assumptions.
Empirical Bayes Credibility Parameter Estimation (EBCT 1)
Estimating the credibility factor \(Z = \frac{n}{n + \frac{s^2}{a}}\) where \(s^2 = \mathbb{E}[s^2(\theta)]\) (Expected Value of Process Variance, EPV) and \(a = \text{Var}(m(\theta))\) (Variance of Hypothetical Means, VHM) from historical risk group claim experience.
# Sample risk matrix (N risk groups across n years) risk_data <- matrix(c(12, 15, 14, 18, 8, 10, 9, 11, 24, 28, 22, 26, 5, 7, 6, 8), nrow = 4, byrow = TRUE) N <- nrow(risk_data); n <- ncol(risk_data) m_i <- rowMeans(risk_data) m_overall <- mean(m_i) s2_i <- apply(risk_data, 1, var) epv <- mean(s2_i) s2_means <- var(m_i) vhm <- max(0, s2_means - epv / n) Z <- n / (n + epv / vhm)