0% found this document useful (0 votes)
65 views71 pages

CS2B Summary Sheets 2021

This document discusses Poisson processes and Markov chains. It provides instructions for: 1. Calculating probabilities, quantiles, and simulations related to arrivals and inter-arrival times using Poisson distribution functions in R like dpois(), ppois(), qpois(), and rpois(). 2. Simulating compound Poisson processes and analyzing sample results. 3. Working with claims data in R, including reading in data, selecting rows/columns, and formatting dates. 4. Performing sample Poisson process calculations like fitting a process to data and calculating future arrival probabilities. 5. Using vector, matrix, and markovchain package operations in R like creating matrices, summing rows, matrix multiplication, and

Uploaded by

Vaibhav Sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
65 views71 pages

CS2B Summary Sheets 2021

This document discusses Poisson processes and Markov chains. It provides instructions for: 1. Calculating probabilities, quantiles, and simulations related to arrivals and inter-arrival times using Poisson distribution functions in R like dpois(), ppois(), qpois(), and rpois(). 2. Simulating compound Poisson processes and analyzing sample results. 3. Working with claims data in R, including reading in data, selecting rows/columns, and formatting dates. 4. Performing sample Poisson process calculations like fitting a process to data and calculating future arrival probabilities. 5. Using vector, matrix, and markovchain package operations in R like creating matrices, summing rows, matrix multiplication, and

Uploaded by

Vaibhav Sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 71

Poisson processes

1 Theoretical Poisson process calculations


This summary assumes that we are considering a Poisson process with rate <lambda>.

Probability function (PF) of arrivals


To calculate the probability of a certain number of arrivals, <n>, between two times, <s> and
<t>, we can use the dpois() function with the following structure:

dpois(<n>, <lambda> * (<t> - <s>))

Alternatively, for a vector of arrival counts <vector>:

dpois(<vector>, <lambda> * (<t> - <s>))

To plot the PF of a Poisson distribution with parameter <rate> from 0 to <n>:

barplot(dpois(0:<n>, <rate>),
main = "PMF of Pois(<rate>)",
xlab = "n",
ylab = "P(N = n)",
names.arg = 0:<n>)

Cumulative distribution function (CDF) of arrivals


To calculate the probability of less than or equal to a certain number of arrivals, <n>, between
two times, <s> and <t>, we can use the ppois() function with the following structure:

ppois(<n>, <lambda> * (<t> - <s>))

For the same process and times, to calculate the probability of more than or equal to a certain
number of arrivals, <n>:

1 - ppois(<n> - 1, <lambda> * (<t> - <s>))


ppois(<n> - 1, <lambda> * (<t> - <s>), lower = FALSE)

Quantiles of arrivals
We can calculate quantiles of the distribution of arrivals between two times, <s> and <t>, by
using the qpois() function with the following structure:

qpois(<percentage point>, <lambda> * (<t> - <s>))

Simulating the number of arrivals


We can generate a sample of size <n> of the number of arrivals between two times, <s> and <t>,
by using the rpois() function with the following structure:

rpois(<n>, <lambda> * (<t> - <s>))


Page 4 CS2B: Poisson processes – Summary

Probability density function (PDF) of inter-arrival times


To calculate the PDF of a certain inter-arrival time, <t>, we can use the dexp() function with the
following structure:

dexp(<t>, <lambda>)

To plot the PDF of the inter-arrival time distribution from 0 to <t> using an increment of <h>:

plot(seq(0,<t>,<h>), dexp(seq(0,<t>,<h>), <lambda>), type = "l",


main = "Density of Exp(<lambda>) distribution",
xlab = "x",
ylab = "f(x)")

Cumulative distribution function (CDF) of inter-arrival times


To calculate the probability of the next arrival being less than or equal to <t> time units we can
use the pexp() function with the following structure:

pexp(<t>, <lambda>)

To calculate the probability of the next arrival being greater than or equal to <t> time units we
can use the pexp() function with the following structure:

1 - pexp(<t>, <lambda>)
pexp(<t>, <lambda>, lower = FALSE)

Quantiles of inter-arrival times


We can calculate quantiles of the inter-arrival times by using the qexp() function with the
following structure:

qexp(<percentage point>, <lambda>)

Simulating inter-arrival times


We can generate a sample of size <n> of the inter-arrival times by using the rexp() function with
the following structure:

rexp(<n>, <lambda>)

© IFE The Actuarial Education Company


CS2B: Poisson processes – Summary Page 5

2 Compound Poisson processes

Simulating compound Poisson processes


To generate <sims> simulations of a compound Poisson process with Poisson parameter
<lambda> over a time period of <t> time units and the value associated with each arrival is given
by Xi where X i  Bin   n  ,  p   :

set.seed(<seed>)
(arrivals = rpois(<sims>, <lambda> * <t>))

total = numeric(<sims>)

for(i in 1:length(arrivals)){
value = rbinom(arrivals[i], <n>, <p>)
total[i] = sum(value * <y>)
}

Note that you may need to adjust this for different distributions for the value associated with
each arrival. More detail on compound Poisson processes can be found in Chapter 19.

Analysing the sample:

hist(total)
mean(total)
sd(total)

Estimating the probability that the total value is greater than <x>:

length(total[total > <x>]) / length(total)

The Actuarial Education Company © IFE


Page 6 CS2B: Poisson processes – Summary

3 Working with data

Setting your working directory


To find your working directory:

getwd()

To set your working directory:

setwd("<folder location>")

Reading in and viewing the data


To read in a csv file containing claims data, <data.csv>:

claims = read.csv("<data.csv>")

To see our table of data:

claims
head(claims)

Indexing the data


To see the sixth row:

claims[6,]

To see the entry in the second column of the sixth row:

claims[6,2]

Selecting a particular column, for example claim.type:

claims$claim.type

Selecting rows that meet a condition, for example all house insurance claims:

claims[claims$claim.type == "House", ]

© IFE The Actuarial Education Company


CS2B: Poisson processes – Summary Page 7

Working with dates


To update the entries in a column claim.date in the format "dd/mm/yyyy" so they can be read
as dates by R:

claims$claim.date <- as.Date(claims$claim.date, format("%d/%m/%Y"))

Some other common date formats are set out below:

Date format R format


dd/mm/yyyy %d/%m/%Y
dd-mm-yyyy %d-%m-%Y
ddmmyyyy %d%m%Y
dd/mm/yy %d/%m/%y
dd-mm-yy %d-%m-%y
ddmmyy %d%m%y

To find all claims between two dates <start> and <end>:

claims.2015 = claims[claims$claim.date >= <start> &


claims$claim.date <= <end>, ]

The Actuarial Education Company © IFE


Page 8 CS2B: Poisson processes – Summary

4 Sample Poisson process calculations

Fitting a Poisson process to the data


Estimating the parameter based on claims in the objects <claims> and an observation period of
<t> time units:

<L> = nrow(<claims>)/<t>

Future arrivals
Calculating the probability of exactly <n> arrivals over a period of <t> time units:

dpois(<n>, <L> * <t>)

CDF-type probability calculations, quantile calculations and simulations can be performed with
ppois(), qpois() and rpois() respectively.

Calculating the probability of the next arrival being less than or equal to <t> time units:

pexp(<t>, <L>)

Subdividing the data


To subdivide the data into types:

motor <- claims[claims$claim.type == "Motor",]


house <- claims[claims$claim.type == "House",]

Estimating the parameter for each type, over an observation period of <t> time units:

LM = nrow(motor) / <t>
LH = nrow(house) / <t>

Thinning a Poisson process


To ‘thin’ a Poisson parameter:

nrow(motor) / nrow(claims) * <L>


nrow(house) / nrow(claims) * <L>

© IFE The Actuarial Education Company


Markov chains
1 Vector and matrix operations

Creating vectors
To create row vectors of data:

data = c( , , , … , )

Creating matrices
Creating a matrix:

P = matrix(<vector of entries> ,nrow = , ncol = , byrow = TRUE)


rownames(P) = c(<states>)
colnames(P) = c(<states>)

The identity matrix


The n  n identity matrix is created by typing:

diag(<n>)

Summing rows
To sum rows:

rowSums(<matrix>)

Matrix multiplication
Use the %*% operator:

<matrix 1> %*% <matrix 2>

Multiplying row vectors together


To multiply the individual elements of row vectors:

<vector 1> * <vector 2>

Dot product of two vectors


To add up these numbers:

sum(<vector 1> * <vector 2>)

Alternatively:

<vector 1> %*% <vector 2>

The Actuarial Education Company © IFE


Page 4 CS2B - 2 Markov chains – Summary

2 Markov chain objects

Installing and loading the markovchain package


Install and load the package:

install.packages("markovchain")
library("markovchain")

Creating a markovchain object


Create a markovchain object using the new() function with a transition matrix <matrix>, a set
of states <states> and a name ‘name’:

mc <- new("markovchain", transitionMatrix = <matrix>,


states = <states>, name = "name")

Components of markovchain objects


Extract with ‘@’ or, for the transition matrix, use [] after the markovchain object:

<mc>[] or <mc>@transitionMatrix Extract the transition matrix

<mc>@states Extract the states

Properties of Markov chains


To determine if the Markov chain is irreducible and what the period of the chain is:

is.irreducible(<mc>)
period(<mc>)

© IFE The Actuarial Education Company


CS2B - 2 Markov chains – Summary Page 5

3 Markov chain calculations

Powers of a transition matrix


Basic R functions

Write a function with a for loop, set M as the identity matrix, and for each j in the loop,
multiply M by P . Return M as the output of the function:

Pn = function(P,n) {
M = diag(nrow(P))
for (j in 1:n) {
M = M %*% P
}
M
}

Package functionality

Use the power operator to calculate powers of the transition matrix using markovchain objects.
For example, to calculate the <n>-step transition probabilities for the markovchain object <mc>:

<mc> ^ <n>

Expected distribution after n steps


Basic R functions

Use the %*% operator to matrix multiply the starting distribution by the relevant power of the
transition matrix:

<start dist> %*% Pn(<matrix>, <n>)

We could also construct a function that takes as inputs the starting position, the transition matrix
and the number of steps, n , and returns the expected distribution after that many steps.

A possible function that calculates this, without using the matrix power function, is as follows:

exp.dist = function(start, P, n){


pos = start
for (i in 1:n){
pos = pos %*% P
}
pos
}

Package functionality

To calculate the expected distribution after <n> steps with a given starting distribution, <d>, we
can use the following command structure:

<d> * <mc> ^ <n>

The Actuarial Education Company © IFE


Page 6 CS2B - 2 Markov chains – Summary

The long-term behaviour of Markov chains


Basic R functions

Raise the transition matrix to a very large power to find the long-term behaviour of the chain:

Pn(<matrix>, <power>)

Alternatively, create a row vector of any starting position and multiply it by the matrix many
times, using a for loop:

position = <start dist>


for (j in 1:40) {
position = position %*% <matrix>
}
Position

Package functionality

To find the stationary distribution of a markovchain object <mc>:

steadyStates(<mc>)

Premium income in an NCD system


Basic R functions

To calculate the expected total income over <n> years for any given starting distribution <d>,
discount vector <discount> and premium <premium>, we can use the following commands:

discount = <discount>
Use this function when we have time
income = <premium> * (1 – discount)
inhomogeneous model :-
position = <d>
exp.prem <- function(age,start,n){
prem.inc = 0 also use this in for loop : -

for (j in 1:<n>) { age <- age + 1


prem.inc = prem.inc + sum(position * income)
position = position %*% P
}

prem.inc

Package functionality

To calculate the total expected premium income over the next <n> years with income
vector <income>, we can use the expectedRewards() function with the following structure:

expectedRewards(<mc>, <n> - 1, <income>)

If we instead want to consider the expected income for a specific starting distribution, <d>, we
can use the following structure:

<d> %*% expectedRewards(<mc>, <n> - 1, <income>)

© IFE The Actuarial Education Company


CS2B - 2 Markov chains – Summary Page 7

Sampling from a Markov chain


To generate a random path from the markovchain object <mc> with <n> transitions starting
from a particular state ‘Start state’ and including the start state in the sample:

rmarkovchain(<n>, <mc>, t0 = "Start state", include.t0 = TRUE)

Leave out the t0 argument for a start state to be chosen at random. Leave out the include.t0
argument to exclude the start state in the output (or set it to FALSE).

Fitting a Markov chain


To estimate transition probabilities based on an ordered sample path of states visited:

markovchainFit(<vector of states visited>)

Time-inhomogeneous Markov chains


If transition probabilities are functions of time, or age, then to create the one-step transition
probability matrix we can first construct the probabilities as functions in R. For example:

prob1 = function(x){
0.015*x
}
prob2 = function(x){
0.02*x
}

We can then use these to calculate the one-step transition matrix as a function. For example:

Px = function(x) {
matrix(c(1 - prob1(x), prob1(x),
1 - prob2(x), prob2(x)),
nrow = 2, ncol = 2, byrow = TRUE)
}

Alternatively, we could code the probabilities directly into the matrix function. For example:

Px = function(x) {
matrix(c(1 - 0.015*x, 0.015*x,
1 - 0.02*x, 0.02*x),
nrow = 2, ncol = 2, byrow = TRUE)
}

Time-inhomogeneous Markov chains in NCD systems


To track the progress of drivers in an NCD system with the one-step transition matrix at age <x>
given by Px(<x>), a starting distribution of <d>, starting age <a> and for a duration of <b> years
(where the duration is at least 1):

position = <d>
for (j in <a>:(<a> + <b> - 1)) {
position = position %*% Px(j)
}
position

The Actuarial Education Company © IFE


Embedded jump chains
1 Summary
The summary assumes we have a sample jump chain path in the vector <journey>.

Length of journey and summary


To find the length of the sample journey:

length(<journey>)

To summarise the number of times each state was visited:

table(<journey>)

Extracting sample doubles


To create a journey of pairs from a journey of singles with a loop:

doubles = character(length(<journey>) - 1)
for (i in 1:(length(doubles))){
doubles[i] = paste(<journey>[i], <journey>[i+1], sep = "")
}

Alternatively, all in one go:

doubles = paste(<journey>[-n], <journey>[-1], sep = "")

Extracting sample triplets


To create a journey of triplets from a journey of singles:

triplets = character(length(<journey>) - 2)
for (i in 1:(length(triplets))){
triplets[i] =
paste(<journey>[i], <journey>[i+1], <journey>[i+2], sep = "")
}

Alternatively, all in one go:

triplets = paste(doubles[-length(doubles)],
<journey>[-(1:2)], sep = "")

Calculating ni

To calculate the number of times the transition i to something was observed:

(ni = table(<journey>[-length(<journey>)]))

Calculating nij

To calculate the number of times the transition i  j was observed:

(nij = table(doubles))
Page 4 CS2B: Embedded jump chains – Chapters 2 and 4 – Summary

ˆij
Calculating p

To calculate estimates of the transition probabilities with a loop:

pij = numeric(length(nij))
names(pij) = names(nij)
for(AB in names(nij)){
pij[AB] = nij[AB] / ni[substr(AB, 1, 1)]
}

Alternatively, all in one go:

(pij = nij / ni[substr(names(nij), 1, 1)])

Alternatively, using the markovchain package:

library(markovchain)
markovchainFit(<journey>)$estimate

Calculating nijk (the observed triplets)

To calculate the number of times the transition i  j  k was observed:

(nijk = table(triplets))

triplets
ABA ABC ACA ACB BAB BAC BCA BCB CAB CAC CBA CBC
47 36 66 157 59 147 34 98 24 76 159 96

Calculating tnij

(tnij = table(doubles[-length(doubles)]))

Calculating expected triplets


To create the matrix structure of observed triplets versus expected triplets, assuming all possible
triplets were observed:

trip.table = as.matrix(nijk)
trip.table = cbind(trip.table, NA)
colnames(trip.table) = c("Observed", "Expected")

To calculate expected triplets using a loop:

for(ABC in rownames(trip.table)){
AB = substr(ABC, 1, 2)
BC = substr(ABC, 2, 3)
tnAB = tnij[AB]
pBC = pij[BC]
trip.table[ABC, "Expected"] = tnAB * pBC
}

© IFE The Actuarial Education Company


CS2B: Embedded jump chains – Chapters 2 and 4 – Summary Page 5

Alternatively, all in one go:

trip.table[, "Expected"] = tnij[substr(rownames(trip.table),1,2)] *


pij[substr(rownames(trip.table), 2, 3)]

Performing a triplets test

To perform a  2 test at the 5% significance level, assuming all possible triplets were observed:

O = trip.table[,"Observed"]
E = trip.table[,"Expected"]
sum((O-E)^2/E)

r = length(names(nijk))
q = length(names(nij))
s = length(unique(<journey>))
DOF = r - 2*q + s
qchisq(0.95, DOF)

The Actuarial Education Company © IFE


Markov jump processes
1 Summary

Creating a generator matrix


Set up a generic 3-state generator matrix, making sure that the rows sum to 0, eg:

states = paste("State", 1:3)


A = matrix(c(-1,0.4,0.6,
0.5,-1.5,1,
0.8,1.2,-2),
nrow = 3, ncol = 3, byrow = TRUE,
dimnames = list(states, states))

rowSums(A)

State 1 State 2 State 3


0 0 0

Alternatively, it may be helpful to set up some or all of the rates as variables:

mu12 = 0.4
mu13 = 0.6
mu21 = 0.5
mu23 = 1
mu31 = 0.8
mu32 = 1.2

mu11 = - mu12 - mu13


mu22 = - mu21 - mu23
mu33 = - mu32 - mu31

A = matrix(c(mu11, mu12, mu13,


mu21, mu22, mu23,
mu31, mu32, mu33),
nrow = 3, ncol = 3, byrow = TRUE,
dimnames = list(states, states))

Estimating transition probabilities using the approximate method


P  h   I  hA  o  h 

where:
 A is the generator matrix
 I is the identity matrix, and

 o  h is a polynomial of h2 and higher powers of h .

For example, to approximate P 1 12  using the 3-state generator matrix A:

h = 1/12
Ph = diag(3)+A*h
Ph
Page 4 CS2B: Markov jump processes – Summary

Creating a function for matrix powers:

Pn = function(P, n) {
M = diag(nrow(P))
for (j in 1:n) {
M = M %*% P
}
M
}

Then, for example, approximating P  20  using increments of 1 / 12 :

Pn(Ph, 240)

State 1 State 2 State 3


[1,] 0.3896104 0.3290043 0.2813853
[2,] 0.3896104 0.3290043 0.2813853
[3,] 0.3896104 0.3290043 0.2813853

Alternatively, we can calculate powers of matrices using the markovchain package:

library(markovchain)
mc <- new("markovchain", transitionMatrix = Ph,
states = states, name = "my.mc")
mc ^ 240

Functions to estimate transition probabilities


Creating a function for P  h for a given generator matrix, A , and time increment, h :

Ph = function(h, A){
diag(nrow(A)) + h * A
}

Creating a function to calculate P  t  for a given value of t , time increment, h , and generator
matrix, A , by using our Ph() and Pn() functions:

Pt = function(t, h, A){
n = t / h
Pn(Ph(h, A), n)
}
Alternatively, we can calculate using the markovchain package:
Pt = function(t, h, A){
mc <- new("markovchain", transitionMatrix = Ph(h,A),
states = states, name = "my.mc")
mc^(t/h)
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
state <- 0:8
gen.matrixv <- function(lambda, mu){
mat <- matrix(0, nrow = 9, ncol = 9)
for (k in 1:9){
if (k > 1){
A function that outputs the generator mat[k, k - 1] <- (k - 1) * mu
matrix for the jump process as a }
matrix in R. The function should take if (k < 9){
the two rates as inputs, λ and μ : mat[k, k + 1] <- lambda
}
(number of states = 0 to 8) }
mat <- mat + diag(-rowSums(mat))
dimnames(mat) <- list(state, state)
mat
}
CS2B: Markov jump processes – Summary Page 5

Simulating a time-homogeneous MJP


Approximate method

Creating a markovchain object with transition matrix constructed from the probabilities over
the period h and generator matrix A:

(mc1 = new("markovchain", transitionMatrix = Ph(h, A)))

Generating a random path with n transitions using rmarkovchain():

sims = rmarkovchain(n, mc1, include.t0 = TRUE)

To force the path to start at a particular state, "Start state", we can include
t0 = "Start state" in the function.

Exact method

Simulating the order of states

Constructing the transition matrix from generator matrix A:

(P = generatorToTransitionMatrix(A))

Creating the markovchain object:

(mc2 = new("markovchain", transitionMatrix = P))

Generating a random path with n transitions using rmarkovchain():

sims = rmarkovchain(n, mc2, include.t0 = TRUE)

Calculating the i :

ljs = abs(diag(A))

Extracting the relevant i for each simulated state:

ljs[sims]

Simulating n waiting times using the corresponding exponential parameters:

waiting.times = rexp(n, ljs[sims])

The Actuarial Education Company © IFE


Page 6 CS2B: Markov jump processes – Summary

Non-constant generator matrix


Setting up a function to calculate the generator matrix as a function of t , assuming we have a
vector of state names, states:

A = function(t){
sigma12 = 0.4*t states = paste("State", 1:3)
sigma13 = 0.6*t A = function(t){
sigma11 = -t matrix(c( -t, 0.4*t, 0.6*t,
sigma21 = 0.5*t 0.5*t, -1.5*t, t,
sigma23 = t 0.8*t, 1.2*t, -2*t),
sigma22 = -1.5*t nrow = 3, ncol = 3, byrow = TRUE,
sigma31 = 0.8*t dimnames = list(states, states))
sigma32 = 1.2*t }
sigma33 = -2*t

matrix(c(sigma11, sigma12, sigma13,


sigma21, sigma22, sigma23,
sigma31, sigma32, sigma33),
nrow = 3, ncol = 3, byrow = TRUE,
dimnames = list(states, states))
}

Transition probability matrix in time-inhomogenous Markov jump process


Probabilities over longer time periods can be estimated by successively multiplying the
probabilities over shorter time periods (increments of length h ) as follows (assuming we have a
vector of state names, states, already set up):

t s
1
h
P  s , t    P  s  hj , s  hj  h 
j 0

states <- c("State 1","State 2","State 3")

Pst = function(s,t,h){
M = diag(length(states))
dimnames(M) = list(states,states)
n = (t - s)/h
for (j in 1:n){
M = M %*% Ph(h, A(s))
s <- s+h
}
M
}

© IFE The Actuarial Education Company


Survival models

1 Survival model calculations

1.1 Calculating  x
To calculate the force of mortality when mortality follows Makeham’s law with constants <A>,
x
<B> and <C>, ie  x  A  Bc :

A = <A>
B = <B>
C = <C>

mu = function(x){
A + B * C ^ x
}

1.2 Calculating t px exactly

Creating a function to calculate survival probabilities that have a given formula, eg


t 
c x ct 1 :
t px  s g

tpx = function(x, t){


s = exp(-A)
g = exp(-B/ log(C))

s ^ t * g ^ (C ^ x * (C ^ t - 1))
}

Calculating the probability that a life aged <x> lives for at least another <t> years:

tpx(<x>, <t>)

Calculating the survival probability that a life aged <x> survives for at least another <t> years for
each <t> in a vector of times, <time vector>:

tpx(<x>, <time vector>)

1.3 Calculating t px with integration


x t
  ds
Calculating t px  e x s , the probability that a life aged <x> survives at least another <t>
years, using a function for the force of mortality, <mu>:

exp(-integrate(<mu>, <x>, <x> + <t>)$value)

x t
  ds
Constructing a function to calculate t px  e x s , using a function for the force of mortality,
<mu>:

tpx = function(x, t){


sapply(t, function(t) {exp(-integrate(<mu>, x, x + t)$value)})
}
Page 4 CS2B - 6 Survival models – Summary

1.4 Calculating t qx

To calculate t qx , the probability that a life aged <x> does not survive at further <t> years,
assuming we have a function that calculates t px , tpx:

1 - tpx(<x>, <t>)

1.5 Calculating mx
1
To calculate mx  qx 0 t px dt for a life aged <x>, assuming we have a function for t px , tpx:

(1 - tpx(<x>, 1)) / integrate(tpx, 0, 1, x = <x>)$value

1.6 Calculating e x using integration



Calculating ex for a life aged <x> where an appropriate upper limit is <upper limit>:

integrate(tpx, 0, <upper limit>, x = <x>)

An upper limit of  , in R Inf, may be an appropriate choice depending on the circumstances.

1.7 Calculating ex using summation


Calculating ex for a life aged <x> where an appropriate upper limit is <upper limit>:

sum(tpx(<x>, 1:<upper limit>))

1.8 Plotting a function with one input


To plot a set of <x values> and a <function> evaluated at those values:

plot(<x values>, <function>(<x values>))

To plot a <function> from <start> to <end>:

plot(<function>, <start>, <end>)

Or:

curve(<function>(x), <start>, <end>)

1.9 Plotting a function with multiple inputs


To plot a <function> with fixed arguments <fixed arguments> from <start> to <end>:

curve(<function>(x, <fixed arguments>), <start>, <end>)

Alternatively, to plot a <function> with fixed arguments <fixed arguments> over the input
range <x values> for the varying argument, <arg>:

plot(<x values>, <function>(<fixed arguments>, <arg> = <x values>))

© IFE The Actuarial Education Company


CS2B - 6 Survival models – Summary Page 5

2 Survival distribution calculations

2.1 Exponential distribution


Throughout this section, it is assumed that the future lifetime random variable, Tx , follows an
exponential distribution with parameter <mu>.

Probability density function (PDF) of survival times

To calculate the pdf of Tx at <t>, ie fTx (t) :

dexp(<t>, <mu>)

Cumulative distribution function (CDF) of survival times

To calculate the CDF of Tx at <t>, ie P Tx  t  :

pexp(<t>, <mu>)

Quantiles of survival times

To calculate the 100 <u>th percentile of Tx , ie the value of t such that: P Tx  t   <u>:

qexp(<u>, <mu>)

Simulating survival times

To simulate <n> values of Tx :

rexp(<n>, <mu>)

2.2 Weibull distribution


Throughout this section and the next, it is assumed that the future lifetime random variable, Tx ,
follows a Weibull distribution with parameters <alpha> and <beta> using the parameterisation
in the Course Notes and shape and scale parameters <shape> and <scale> using the
parameterisation in R where:

shape  

 1
scale  

Probability density function (PDF) of survival times

To calculate the pdf of Tx at <t>, ie fTx (t) :

dweibull(<t>, <shape>, <scale>)

The Actuarial Education Company © IFE


Page 6 CS2B - 6 Survival models – Summary

Cumulative distribution function (CDF) of survival times

To calculate the CDF of Tx at <t>, ie P Tx  t  :

pweibull(<t>, <shape>, <scale>)

Quantiles of survival times

To calculate the 100 <u>th percentile of Tx , ie the value of t such that: P Tx  t   <u>:

qweibull(<u>, <shape>, <scale>)

Simulating survival times

To simulate <n> values of Tx :

rweibull(<n>, <shape>, <scale>)

2.3 Alternative functions for the Weibull distribution


Defining functions for the Weibull distribution using the parameterisation used in the Course
Notes (the parameters used here are c and  , denoted c and g, instead of  and  ):

rweibull2 <- function(n,c,g){


(-log(1 - runif(n))/c)^(1/g)
}
dweibull2 <- function(x,c,g){
c*g*x^(g - 1)*exp(-c*x^g)
}
pweibull2 <- function(q,c,g){
1 - exp(-c*q^g)
}
qweibull2 <- function(p,c,g){
(-log(1 - p)/c)^(1/g)
}

Probability density function (PDF) of survival times

To calculate the pdf of Tx at <t>, ie fTx (t ) :

dweibull2(<t>, <alpha>, <beta>)

Cumulative distribution function (CDF) of survival times

To calculate the CDF of Tx at <t>, ie P Tx  t  :

pweibull2(<t>, <alpha>, <beta>)

Quantiles of survival times

To calculate the 100 <u>th percentile of Tx , ie the value of t such that: P Tx  t   <u>:

qweibull2(<u>, <alpha>, <beta>)

© IFE The Actuarial Education Company


CS2B - 6 Survival models – Summary Page 7

Simulating survival times

To simulate <n> values of Tx :

rweibull2(<n>, <alpha>, <beta>)

2.4 Expected lifetime calculations


Survival probability functions

To calculate t px under the exponential model:

tpx_exp = function(t, mu){


exp(-mu*t)
}

To calculate t px under the Weibull model:

tpx_weibull = function(t, c, g){


exp(-c*t^g)
}


ex

To calculate the expected future lifetime under the exponential model with parameter <mu> using
integration:

integrate(tpx_exp, 0, Inf, mu = <mu>)

or, without the custom t px function:

integrate(pexp, 0, Inf, rate = <mu>, lower.tail = FALSE)

or, to calculate it exactly:

1/<mu>

To calculate the expected future lifetime under the Weibull model:

integrate(tpx_weibull, 0, Inf, c = <alpha>, g = <beta>)

or:

integrate(pweibull, 0, Inf, shape = <shape>, scale = <scale>,


lower.tail = FALSE)

or, to calculate it exactly:

gamma(1 + 1 / <beta>) * 1 / <alpha>^(1/<beta>)

The Actuarial Education Company © IFE


Page 8 CS2B - 6 Survival models – Summary

ex

To calculate the curtate expected future lifetime under the exponential model using an
appropriate upper limit, <upper limit>:

sum(tpx_exp(1:<upper limit>, mu = <mu>))

or, without the custom t px function:

sum(1 - pexp(1:<upper limit>, <mu>))

or, to calculate it exactly:

exp(-<mu>) / (1 - exp(-<mu>))

To calculate the curtate expected future lifetime under the Weibull model using an appropriate
upper limit, <upper limit>:

sum(tpx_weibull(1:<upper limit>, c = <alpha>, g = <beta>))

or, without the custom t px function:

sum(1 – pweibull2(1:<upper limit>, <alpha>, <beta>))

© IFE The Actuarial Education Company


CS2B - 6 Survival models – Summary Page 9

3 R code – flexsurv package


In R, the Gompertz distribution uses the parameters shape and rate. These are
related to the parameters B and c above as follows:
3.1 Gompertz distribution shape = lnc
rate = B
For the purposes of the examples in this section, we assume that mortality for lives aged
<start age> and older follow Gompertz’ law such that:

 x  Bc x  start age for x   start age 

In this case, it is Tstart age  that follows the Gompertz distribution with parameters B and c or,
in R, parameters shape  lnc and rate  B . The distribution of Tstart age a follows the

Gompertz distribution with parameters shape  lnc and rate  B  c a .

Throughout the rest of this section, <shape> and <rate> are used to denote appropriately
calculated shape and rate parameters for Tx for the case where Tstart age  follows the
Gompertz with parameters <B> and <C>.

3.2 Probability density function (PDF) of survival times


Calculating the PDF at time <t>, ie fTx (t ) .

dgompertz(<t>, <shape>, <rate>)

3.3 Cumulative distribution function (CDF) of survival times


Calculating the CDF at time <t>, ie P Tx  t  .

pgompertz(<t>, <shape>, <rate>)

3.4 Quantiles of survival times


To calculate the 100 <u>th percentile of Tx , ie the value of t such that: P Tx  t   <u>:

qgompertz(<u>, <shape>, <rate>)

3.5 Simulating survival times


To simulate <n> values of Tx :

rgompertz(<n>, <shape>, <rate>)

The Actuarial Education Company © IFE


Page 10 CS2B - 6 Survival models – Summary

3.6 Hazard rate


To calculate the hazard rate at age <x> for a population where mortality is assumed to follow
Gompertz’ law from age <start age> (for <x> > - <start age>):

hgompertz(<x> - <start age>, <shape>, <rate>)

Alternatively:

<B> * <C> ^ (<x> - <start age>)

3.7 Integrated hazard


To calculate the integrated hazard from ages <x> to <x> + <t> with appropriately calculated
<shape> and <rate> parameters:

Hgompertz(<t>, <shape>, <rate>)

To calculate the t px , the probability that a life aged <x> survives at least another <t> years using
the integrated hazard:

1 - exp(-Hgompertz(<t>, <shape>, <rate>))

3.8 Expected lifetimes


Survival probability functions

To calculate t px under the Gompertz model:

tpx = function(x, t){


g = exp(-<B>/ log(<C>))
g ^ (<C> ^ (x - <start age>) * (<C> ^ t - 1))
}


ex

To calculate the expected future lifetime under the Gompertz model for a life aged <x>:

integrate(tpx, 0, Inf, x = <x>)

or, without the custom t px function:

integrate(pgompertz, 0, Inf, shape = <shape>, rate = <rate>,


lower.tail = FALSE)

© IFE The Actuarial Education Company


CS2B - 6 Survival models – Summary Page 11

ex

To calculate the curtate expected future lifetime using an appropriate upper limit,
<upper limit>:

sum(tpx(<x>, 1:<upper limit>)

or, without the custom t px function:

sum(pgompertz(1:<upper limit>, shape = <shape>, rate = <rate>,


lower.tail = FALSE))

When doing the Goodness of Fit Test :-


1) expected values do not sum to 1
chisq.test(obs,p=exp,rescale.p = TRUE)
2) expected values sum to 1
chisq.test(obs,p=exp)

The Actuarial Education Company © IFE


Estimating the lifetime distribution
1 Survival function estimation in base R

1.1 Loading data


Loading a csv file called <data.csv>:

surv.data = read.csv("<data.csv>")

1.2 Event times


Calculating the times of events based on a vector of times that observation of each life started,
<start times>, and the times they experienced an event <end times>:
event.times = <end time> - <start times>

1.3 Calculating t j and d j

Calculating t j and d j together from a data set, <surv data>, assuming it contains a column of
event times, <event times>, and a column of observation end reasons (say ‘Death’ or
‘Censoring’), <end reason>:

(deaths = as.data.frame(table(
<surv data>$<event times>[<surv data>$<end reason> == "Death"])))

colnames(deaths) = c("tj", "dj")

deaths$tj = as.numeric(as.vector(deaths$tj))

or:

(deaths = aggregate(<event times> ~ c(<event times>), FUN = length(),


data = <surv data>[<surv data>$<end reason> == "Death"]))

colnames(deaths) = c("tj", "dj")

1.4 Calculating nj

Calculating n j from a table (with no lives being left truncated) containing t j and d j , <deaths>,
and a data set, <surv data>, containing a column of event times, <event times>:

<deaths>$nj = sapply(1:nrow(<deaths>), function(j) sum(<surv


data>$<event times> >= <deaths>$tj[j]))

or:

<deaths>$nj = sapply(<deaths>$tj, function(tj)


sum(<surv data>$<event times> >= tj))

or:

<deaths>$nj = numeric(nrow(<deaths>))
for (i in 1:nrow(<deaths>)){
<deaths>$nj[i] = sum((<surv data>$<event times> >= <deaths>$tj[i])
}

This is used when there is more than one deaths or censoring at one one time :-
deaths$nj <- sapply(deaths$tj,function(j){
sum(surv.data$Events[surv.data$Time>=j])})
Page 4 CS2B-7: Estimating the lifetime distribution – Summary

1.5 Calculating ˆj

Calculating n j from a table containing d j and n j , <deaths>:

<deaths>$lamj = <deaths>$dj / <deaths>$nj

1.6 Calculating SˆKM  t 

Calculating SˆKM  t  from a table, <deaths>, containing ̂ j in a column called lamj:

<deaths>$skm = cumprod(1 - <deaths>$lamj)

1.7 Calculating SˆNA  t 

Calculating ̂  t  from a table, <deaths>, containing ̂ j in a column called lamj:

<deaths>$Lj = cumsum(<deaths>$lamj)

Then calculating SˆNA  t  :

<deaths>$sna = exp(-<deaths>$Lj)

1.8 Plotting SˆKM  t  and SˆNA  t 

Last observation is a censoring

This part assumes that the last observation is a censoring at time <last obs>.

Creating a vector of appropriate times from a table containing t j , <deaths>:

times = c(0, <deaths>$tj, <last obs>)

Creating an appropriate vector of the survival function estimate from a table, <deaths>,
containing Sˆ  t  in the column <s est>:

s.est = c(1, <deaths>$<s est>, <deaths>$<s est>[nrow(<deaths>)])

Last observation is a death

This part assumes that the last observation is a death at time <last obs>.

Creating a vector of appropriate times from a table containing t j , <deaths>:

times = c(0, <deaths>$tj)

Creating an appropriate vector of the survival function estimate from a table, <deaths>,
containing Sˆ  t  in the column <s est>:

s.est = c(1, <deaths>$<s est>)

© IFE The Actuarial Education Company


CS2B-7: Estimating the lifetime distribution – Summary Page 5

Plotting the survival estimate

Plotting a chart of Ŝ  t  against t using the vectors above:

plot(times, s.est, type = "s")

Plotting multiple graphs on the same plot

To tell R that the next plot() command should be on the same chart:

par(new = TRUE)

Alternatively, using the lines() function to add another survival curve estimate at times
<times> with values <surv est>:

lines(<times>, <surv est>, type = "s", col = "blue")

To add a legend at <location> with labels <labels> and colours <cols>:

legend(<location>, legend = <labels>, col = <cols>, lwd = 1)

1.9 Calculating SKM  t  confidence intervals

Calculating standard error estimates

Calculating standard error estimates for the survival function from a table containing SˆKM  t  in a
column skm as well as n j and d j , <deaths>:

temp = <deaths>$dj / (<deaths>$nj * (<deaths>$nj - <deaths>$dj))

<deaths>$km.se = sqrt((<deaths>$skm)^2 * cumsum(temp))

Confidence intervals

Calculating the relevant percentage point for a 100 1    % level symmetric confidence interval:

z = qnorm(1 - <alpha> / 2)

Calculating the confidence intervals:

KM_CI = data.frame(tj = <deaths>$tj,


lower_95 = <deaths>$skm - z*<deaths>$km.se,
estimate = <deaths>$skm,
upper_95 = <deaths>$skm + z*<deaths>$km.se)

Adjusting the intervals to be bounded by 0 and 1:

KM_CI$lower_95 = pmax(KM_CI$lower_95, 0)
KM_CI$upper_95 = pmin(KM_CI$upper_95, 1)

The Actuarial Education Company © IFE


Page 6 CS2B-7: Estimating the lifetime distribution – Summary

1.10 Calculating SNA  t  confidence intervals

Estimating the standard error for the integrated hazard

Calculating standard error estimates for the integrated hazard from a table containing n j and d j ,
<deaths>:

temp = <deaths>$dj * (<deaths>$nj - <deaths>$dj) / <deaths>$nj^3


<deaths>$Lj.se = sqrt(cumsum(temp))

Calculating the relevant percentage point for a 100 1    % level symmetric confidence interval:

z = qnorm(1 - <alpha> / 2)

Assuming that the table <deaths> also has a column containing ̂  t  called Lj, calculating the
approximate confidence intervals for the integrated hazard:

NA_CI = data.frame(tj = <deaths>$tj,


lower.LJ.95 = <deaths>$Lj - z*<deaths>$Lj.se,
est.LJ = <deaths>$Lj,
upper.LJ.95 = <deaths>$Lj + z*<deaths>$Lj.se)

Adjusting the intervals for the integrated hazard to be bounded below by 0:

NA_CI$lower.LJ.95 = pmax(0, NA_CI$lower.LJ.95)

Assuming that the table <deaths> also has a column containing SˆNA  t  called sna, calculating
the approximate confidence intervals for the survival function, using appropriate bounds:

NA_CI$lower.sna.95 = pmax(0, exp(-NA_CI$upper.LJ.95))


NA_CI$est.sna = <deaths>$sna
NA_CI$upper.sna.95 = pmin(1, exp(-NA_CI$lower.LJ.95))

© IFE The Actuarial Education Company


CS2B-7: Estimating the lifetime distribution – Summary Page 7

2 Survival function estimation with the survival package

2.1 Installing and loading the survival package


install.packages("survival")
library(survival)

2.2 Creating survival objects


Creating event codes

To create a vector of event codes (1’s and 0’s) from a vector of reasons that observation ended
for each life, <reasons>, that contains, for each life, either an indicator of death, <death>, or
right censoring, <censoring>:

event.codes = ifelse(<reasons> == <death>, 1, 0)

Creating a survival object

To create a survival object from a vector of event times, <event times>, and a vector of
event codes, <event codes>, that, for each life, contains either the value ‘1’ (indicating that the
event was a death) or ‘0’ (indicating that the life was right censored):

Surv(<event times>, <event codes>)

2.3 Introduction to the survfit() function


The relevant code is presented in the sections that follow.

2.4 Calculating SˆKM  t 

To calculate the Kaplan-Meier estimate of the survival function given a survival object,
<survival object>, without any grouping:

fitKM = survfit(formula = <survival object> ~ 1, conf.type =


"plain")

To see a summary of the fit:

summary(fitKM)

To extract the estimate and lower and upper parts of the approximate confidence intervals:

fitKM.summary = summary(fitKM)
fitKM.summary$surv
fitKM.summary$lower
fitKM.summary$upper

The Actuarial Education Company © IFE


Page 8 CS2B-7: Estimating the lifetime distribution – Summary

2.5 Calculating SˆNA  t 

To calculate the Nelson-Aalen estimate of the survival function given a survival object,
<survival object>, without any grouping:

fitNA = survfit(formula = <survival object> ~ 1, conf.type =


"plain", stype = 2)

To see a summary of the fit:

summary(fitNA)

To extract the estimate and lower and upper parts of the approximate confidence intervals:

fitNA.summary = summary(fitNA)
fitNA.summary$surv
fitNA.summary$lower
fitNA.summary$upper

2.6 Plotting the survival function estimates


Plotting a survival function estimate along with estimated confidence intervals of a fit constructed
using the survfit() function, <fit>:

plot(<fit>)

Adding a survival function estimate, constructed using the survfit() function and stored in
<fit>, to an existing graph:

lines(<fit>)

2.7 Estimating the survival function based on a grouping


To calculate the Kaplan-Meier estimate of the survival function given a survival object,
<survival object>, with groups for each life in the vector <groups>:

fitKM = survfit(formula = <survival object> ~ <groups>, conf.type =


"plain")

To calculate the Nelson-Aalen estimate of the survival function given a survival object,
<survival object>, with groups for each life in the vector <groups>:

fitNA = survfit(formula = <survival object> ~ <groups>, conf.type =


"plain", stype = 2)

© IFE The Actuarial Education Company


Proportional hazards models

1 R code – proportional hazard models in base R

1.1 Partial likelihood and log-likelihood


Creating R functions for the likelihood and partial log-likelihood:

part.lik = function(b){
<formula for L>
}

part.log.lik = function(b){
<formula for log(L)>
}

1.2 Plotting the partial likelihood and log-likelihood


We can plot <function> from <start> to <end> using the plot() function:

plot(<function>, <start>, <end>)

1.3 Maximising the partial likelihood and log-likelihood graphically


Plot the functions over successively narrower ranges to obtain the estimate of the parameter.

1.4 Non-linear minimisation


Minimising a <function> using initial values of the parameters <start values>:

(MLE = nlm(<function>, p = <start values>))

In the context of Chapter 8, <function> may be a function that calculates the negative partial
log-likelihood.

Remember to look for a code of 1 or 2 in the output to determine whether the algorithm appears
to have converged on a solution.

Extracting the maximum likelihood estimate:

MLE$estimate
Page 4 CS2B-8: Proportional hazards models – Summary

2 R code – proportional hazard models with the survival package

2.1 Installing and loading the survival package


install.packages("survival")
library(survival)

2.2 Creating survival objects


To create a survival object from a vector of event times, <event times>, and a vector of
event codes, <event codes>, that, for each life, contains either the value ‘1’ (indicating that the
event was a death) or ‘0’ (indicating that the life was right censored):

Surv(<event times>, <event codes>)

2.3 Loading the data


Loading a csv file called <data.csv>:

surv.data = read.csv("<data.csv>")

2.4 Fitting a Cox model


Fitting a Cox model with <survival object> and a set of <explanatory variables>:

coxph(formula = <survival object> ~ <explanatory variables>,


ties = "breslow")

If there are multiple explanatory variables to include, say n , then the structure of
<explanatory variables> is <var 1> + <var 2> + … + <var n>.

Testing significance against the null model

Extracting the p -value from the output:

<fit>$logtest["pvalue"]

Testing significant improvement of one model over another

Performing a likelihood ratio test to assess whether <fit2> is a significant improvement over
<fit1> where <fit2> contains the same parameters as <fit1> as well as some additional
parameters:

anova(<fit1>, <fit2>, test = "Chi")

© IFE The Actuarial Education Company


Exposed To Risk

Using a function

Alternatively, we can set up a function that calculates the exposed to risk for each individual and
then apply this function to the whole data table.

We first set up a function that operates on x , the vector of four data values in an individual
record.

etrfn <- function(x) {


d0 = 2013.000 #start of investigation;
d1 = x[2] #date of birth;
d70 = d1 + 70 #70th birthday;
d2 = x[3] #date of entry;
startdate = max(d70,d2,d0);

d99 = 2017.999 #end of investigation;


d71 = d1 + 71 #71st birthday;
d3 = x[4] #date of death;

#Replace NA’s with end of investigation date;


d4 = ifelse(is.na(d3),d99,d3)

enddate = min(d71,d4,d99);

#Check enddate is not before startdate;


enddate2 = max(enddate,startdate)

enddate2 - startdate }

etr = sum(apply(data,1,etrfn))
etr

When to calculate the number of lives who died at age 70 last birthday during the investigation period.

start_date = 1 January 2013


end_date = 31 December 2017.

deaths <- data$LIFE[!is.na(data$DEATH)


& data$ENTRY>start_date & data$DEATH<end_date
& (data$BIRTH+71)<end_date & (data$BIRTH+71)>data$DEATH
& (data$BIRTH+70)>start_date & (data$BIRTH+70)<data$DEATH]
length(deaths)

When to calculate the number of lives aged 70 last birthday whose policies were in force at the start of the
investigation period.

age70last <- data$LIFE[(is.na(data$DEATH) | data$DEATH>2013.000)


& data$ENTRY<=2013.000
& (data$BIRTH+70)>2012.000
& (data$BIRTH+71)<=2014.000]
length(age70last)
Graduation

Serial correlations test


The differences between the three methods are as follows:

 The acf() function uses equation (10.2) from Chapter 10 of the Course Notes but omits
m j
the factor of on the denominator, ie it is using:
m
m j
 (zi  z )(zi  j  z )
r j  i 1
m
 (zi  z )2
i 1

 The formula on page 34 of the Tables is the same as equation (10.2) from Chapter 10 of
the Course Notes:
m j
 (zi  z )(zi  j  z )
r j  i 1 m
m j

(z  z )2
m i 1 i

m
This can be calculated in R as the estimate from the acf() function multiplied by .
m j

 The cor() function uses equation (10.1) from Chapter 10 of the Course Notes:
m j
 (zi  z (1) )(zi  j  z (2) )
i 1
rj 
m j m j
 (zi  z (1) )2  (zi  j  z (2) )2
i 1 i 1

The Actuarial Education Company © IFE


Graduation Using Spline Method
The code for this method is given below :

n = 6; xx = c(0,5,15,20,30,40)

a0 = 0.00077; a1 = -0.00012
b = c(0.70,-0.88,-0.91,1.75)*1e-6

phi <- function(x,j) max((x-xx[j])^3,0)

xvalues = 1:40
yvalues = NULL

for(x in xvalues){
term = a0 + a1*x;
for(j in 1:(n-2)){
c1 = (xx[n] - xx[j])/(xx[n] - xx[n-1])
c2 = (xx[n-1] - xx[j])/(xx[n] - xx[n-1])
Phixj = phi(x,j) - c1*phi(x,n-1) - c2*phi(x,n)
term=term+b[j]*Phixj}
yvalues = c(yvalues,term)}

The relevant formulae for this type of spline function are:

n2
f (x)  a0  a1 x   b j  j (x)
j 1

 xn  x j   xn1  x j 
where  j (x)   j (x)    n1 (x)    n (x)
 xn  xn1   xn  xn1 

 (x  x )3 if x  x j
 j
and  j (x)  
 0 otherwise
CS2B-13 and 14: Time series – Summary Page 1

The Actuarial Education Company © IFE


Time series

1 Summary

Simulating time series


To simulate n1 values from a stationary ARIMA(p, q) , with zero mean, and where sd1 is the
standard deviation of the white noise terms:

set.seed(123)
Xt <- arima.sim(n=n1,list(ar=c(ar1,ar2,…),ma=c(ma1,ma2,…)),sd=sd1)

To add a deterministic trend onto a time series Xt with integer times, we create a new time series
Yt  a  ct  Xt :

n <- length(Xt)
Yt <- a + c*seq(1,n) + Xt

To create an ARIMA(p, d , q) time series where d  1 (ie a time series Yt such that Xt  1  B  Yt ):

Yt <- constant+cumsum(Xt)
Yt <- ts(Yt)

Importing time series data


Xt <- read.table("Xt.csv")
Xt <- ts(Xt,start=c(year,period), frequency=number of observations
per year)

Plotting time series


Plotting the data

To plot time series data Xt:

ts.plot(Xt,main="Time series Xt",ylab="Value",col="blue")


points(Xt,col="red",cex=0.7)

Plotting the sample ACF and sample PACF

To plot the correlogram and partial correlogram of the data Xt, where k relates to the number of
ACFs (or PACFs) to include in the graph:

par(mfrow=c(2,1))
acf(Xt,lag.max=k,main="Time series Xt",ylab="Sample ACF")
pacf(Xt,lag.max=k,main="Time series Xt",ylab="Sample PACF")
par(mfrow=c(1,1))

Note: acf plots start from lag 0 and include k+1 values in the graph. pacf plots start from lag 1
and include k values in the graph.
Page 4 CS2B-13 and 14: Time series – Summary

Plotting the theoretical ACF and theoretical PACF

To plot the theoretical ACF and PACF of an ARMA(p, q) :

modelacf <- ARMAacf(ar=c(ar1,ar2,…),ma=c(ma1,ma2,…),lag.max=k)


modelacf <- ARMAacf(ar=c(ar1,ar2,…),ma=c(ma1,ma2,…),lag.max=k,
pacf= TRUE)
par(mfrow=c(2,1))
barplot(modelacf,main="ACF of ARMA(p,q)",col="red",xlab="Lag")
barplot(modelpacf,main="PACF of ARMA(p,q)",col="blue",xlab="Lag",
names.arg=seq(1,k))
par(mfrow=c(1,1))

Note that, as before, the theoretical ACF is calculated for lags 0  lag  k , whereas the theoretical
PACF is calculated for lags 1  lag  k .

Summarising time series data


The number of observations per unit of time is:

frequency(Xt)

The time of the first observation is:

start(Xt)

The time of the last observation is:

end(Xt)

The sample ACFs and PACFs, for lags 0  lag  k or 1  lag  k respectively, are:

a <- acf(Xt,plot=FALSE,lag.max=k); a The ith entry here means the


p <- pacf(Xt,plot=FALSE,lag.max=k); p
(i-1)th lag of acf
To extract the ith entry from these data, use a$acf[i] or p$acf[i].

Statistical test for stationarity


For the PP test, we have:
H 0 : the time series has a unit root (and hence can be differenced).

H 1 : the time series is stationary.

PP.test(Xt)

(This assumes there are no other underlying reasons for non-stationarity, besides the unit root.)

Removing trends
d
To difference time series data d times, we find Dt  1  B Xt :

Dt <- diff(Xt,lag=1,differences=d)

© IFE The Actuarial Education Company


CS2B-13 and 14: Time series – Summary Page 5

To remove a linear trend with regression, we find Yt  Xt  a  bt :

fit = lm(Xt ~ time(Xt)


Yt = Xt – fit$fitted.values

To check how frequently the seasonal variation occurs, plot the series or the sample ACF:

plot(Xt)
points(Xt, pch = 20)
abline(v = <vector of values>)
acf(Xt)

 
To apply seasonal differencing with a lag of n, we find St  1  Bn Xt :

St <- diff(Xt,lag=n,differences=1)

To apply the method of seasonal means, we first create a data frame containing the values of Xt
and their associated time periods. For example, for a series with monthly data:

Xt.df = data.frame(year = <vector>, month = <vector>, values = Xt)

We then calculate the relevant averages, for example for each month:

Xbars = aggreagete(values ~ month, data = Xt.df, FUN = mean)

We then calculate a series by subtracting the means:

=
Wt Xt – xbars$values

Separating out components of a time series


The decompose function

To plot a simple graph of each component:

plot(decompose(Xt,type="additive"))

(Use "additive" if the seasonality / variability appears to be constant over time. Otherwise use
"multiplicative".)

To extract data from this decomposition:

decomp <- decompose(Xt,type="additive")


trend <- decomp$trend
seasonal <- decomp$seasonal
random <- decomp$random

The stl function

Alternatively, if data is additive and does not span an integer time period, use the stl function:

plot(stl(Xt,s.window="periodic"),main="Components of time series:


Xt")
The top line represents the time series data, in this case ldeaths.

The second line (labelled ‘trend’) is calculated using the method of moving averages.

The third line is calculated by subtracting the trend and then applying the method of seasonal means.

The final line represents the random variation in the data. It is calculated by subtracting the seasonal and
trend components from the observed data.

(In fact, the calculation is somewhat more complicated than the description implies. However, a deeper
understanding of the method is not required.)
Page 6 CS2B-13 and 14: Time series – Summary

To extract data from this decomposition:

stl <- stl(Xt,s.window="periodic")


trend <- stl$time.series[,"trend"]
seasonal <- stl$time.series[,"seasonal"]
remainder <- stl$time.series[,"remainder"]

Plotting components on the same graph

ts.plot(Xt,ylab="",main="Components of time series: Xt", col="dark


grey")
points(Xt,cex=0.5,col="dark grey")
lines(trend,col="red")
lines(seasonal+trend,col="blue")

Combining plots
To print graphs in an a a b array, use par(mfrow=c(a,b)).

For more complicated graphs use the layout function, eg:

m <- matrix(2,2,data=c(1,2,1,3))
layout(m)

Always reset the graphical parameters afterwards, using par(mfrow=c(1,1)).

Fitting a model
To fit an ARIMA(p, d , q) to data Xt:

fit <- arima(Xt,order=c(p,d,q)), fit

Equivalently, difference the data d times and then fit an ARIMA(p,0, q) .

d <- diff(Xt,differences=d)
arima(d,order=c(p,0,q))

To find what order of autoregressive model AR(p) is the best fit, and to find the fitted mean ̂ :

ar(Xt)
ar(Xt)$x.mean

Analysing goodness of fit


To extract residuals from a fitted model fit:

fit <- arima(Xt,order=c(p,d,q))


e <- fit$residuals

To plot the (standardised) residuals and the ACF of the residuals of the fitted model fit:

tsdiag(fit)

To carry out a Ljung-Box test on the residuals e, based on m autocorrelation coefficients:


H0 : the residuals are independent

Box.test(e,lag=m,type="Ljung",fitdf=p+q)

To extract the Akaike Information Criterion (AIC) for a fitted model fit:

fit$aic
Page 7 CS2B-13 and 14: Time series – Summary

Calculating the AIC for many models

answer<-numeric(4)
for (p in 0:2) for (d in 0:2) for (q in 0:2) {
aic<-arima(Xt,order=c(p,d,q))$aic
row<-c(p,d,q,aic)
answer<-rbind(answer,row)
}
answer <- answer[-1,]
answer

Turning points test

e <- a$residuals
n <- length(e)
turns <- numeric(n)

Our loop runs from i = 2,...,(n-1), not from i=1,...,n, because the first and last residuals can not be turning points

for(i in 2:(n-1)){
if(e[i-1]<e[i] && e[i]>e[i+1]){
turns[i] <- 1
}
else{next(i)}
}

for(i in 2:(n-1)){
if(e[i-1]>e[i] && e[i]<e[i+1]){
turns[i] <- 1
}
else{next(i)}
}

turns
nt <- sum(turns)

E <- (2/3)*(n-2)
V <- (16*n - 29)/90

E
nt

ts <- (nt+0.5-E)/sqrt(V)
Since the value of ts is greater than -1.96 , so we have insufficient evidence to reject Null Hypothesis
So residuals follow the white noise Process
Page 8 CS2B-13 and 14: Time series – Summary

Forecasting
Stationary data

To forecast the next n values from a fitted time series fit:

p <- predict(fit,n.ahead=n); p

To extract the ith individual forecast:


p$pred[i]

To plot the forecast on the same graph as the original data Xt, extend the x axis to include the
start time of the data and the end time the forecast:

ts.plot(Xt,col="blue",xlim=c(start time,end time),main="Data and


forecast",ylab="Data")
lines(p$pred,col="red")

Non-stationary data

If the model was fitted to differenced data Dt, where the original data was Xt (ie Dt  1  B  Xt )
we have to add back the trend:

p <- predict(fit,n.ahead=n)$pred
p.with.trend <- tail(Xt,1)+cumsum(p)
p.with.trend <- ts(p.with.trend,start=c(start year,period),
frequency = number of observations per year)

To plot the forecast on the same graph as the original data Xt, extend the x axis as before, and
also extend the y axis to include all values of both the forecast and the data:

ts.plot(Xt,col="blue",xlim= c(start time,end time),main="Data and


forecast",ylab="Data",ylim=c(minimum,maximum))
lines(p.with.trend,col="red")

Exponential smoothing
To forecast n future values and 95% confidence intervals of the forecasts, for data Xt, and
smoothing parameter a:

HW <- HoltWinters(Xt,alpha=a,beta=FALSE,gamma=FALSE)
predict(HW,n.ahead=n,level=0.95,prediction.interval=TRUE)

To find the optimal smoothing parameter:

HW2 <- HoltWinters(Xt,beta=FALSE,gamma=FALSE)


HW2$alpha

The Actuarial Education Company © IFE


Page 9 CS2B-13 and 14: Time series – Summary

Multivariate time series


Testing stationarity of a VAR(1)

a b 
To calculate the eigenvalues of the 2  2 coefficient matrix A   :
c d

A <- matrix(2,2,data=c(a,c,b,d))
eigen(A)$values

Finding a cointegrating vector  ,  

To find a vector  ,   for two time series Xt and Yt, such that  X t   Yt is stationary:

First write a function to carry out the PP.test for the cointegrating vector:

find.coint <- function(coint) {


comb <- coint[1]*Xt + coint[2]*Yt
test <- PP.test(comb)$p.value
test
}

Then specify (arbitrary) starting values for the iteration, and minimise the value of the function
find.coint:

v <- c(1,1)
fit <- nlm(find.coint,v); fit

So the cointegrating vector is:

c(fit$estimate[1],fit$estimate[2])

Writing a general function to test cointegration

is.cointegrated<-function(x,y) {
xp<- PP.test(x)$p.value>=0.05 && PP.test(diff(x))$p.value<0.05
yp<- PP.test(y)$p.value>=0.05 && PP.test(diff(y))$p.value<0.05
find.coint<-function(coint) {
comb<-coint[1]*x+coint[2]*y
test<-PP.test(comb)$p.value
test
}
v<-c(1,1)
fit<-nlm(find.coint,v)
a<-fit$estimate[1]
b<-fit$estimate[2]
v.fit<-c(a,b)
v.fit.check<-round(a,6)==0 && round(b,6)==0
comb<-a*x+b*y
combp<- PP.test(comb)$p.value<0.05
if (xp==TRUE && yp==TRUE && combp==TRUE && v.fit.check==FALSE) {
print("the vectors are cointegrated with cointegrating vector");
print(v.fit)
} else {
print("x and y are not cointegrated")
}
}
CS2B-15: Loss distributions – Summary Page 1

Loss distributions
Summary

The Actuarial Education Company © IFE


CS2B-15: Loss distributions – Summary Page 3

1 Summary

In-built distribution functions

Calculation
X  exp() X  Ga m m a ( ,  ) X  log N( , 2 ) X  W(c,  )
required
f (x) dexp(x,  ) dgamma(x,  ,  ) dlnorm(x,  ,  ) dweibull(x,  , c
(1  ) )

F (q ) pexp(q,  ) pgamma(x,  ,  ) plnorm(x,  ,  ) pweibull(x,  , c


(1  ) )

find q for (1  ) )


qexp(p,  ) qgamma(p,  ,  ) qlnorm(p,  ,  ) qweibull(p,  , c
P( X  q)  p

simulate n
rgamma(n,  ,  ) rlnorm(n,  ,  ) (1  ) )
random rexp(n,  ) rweibull(n,  , c
variates

(1  )
In R, the second parameter of the Weibull distribution is c .

2
The lognormal (and normal) distributions use  instead of  .

To find the log-likelihood of a vector v, use lnL <- sum(dXXX(v,parameters,log=TRUE)).

Use the set.seed function to be able to reproduce a simulation.

The two-parameter Pareto distribution


rpareto <- function(n,a,lambda){
lambda*((1 - runif(n))^(-1/a)-1)
}

dpareto <- function(x,a,lambda){


a*(lambda^a)/((lambda+x)^(a+1))
}

ppareto <- function(q,a,lambda){


1 - (lambda/(lambda+q))^a
}

qpareto <- function(p,a,lambda){


lambda * ((1-p)^(-1/a)-1)
}

The Weibull distribution


rweibull2 <- function(n,c,g){
(-log(1-runif(n))/c)^(1/g)
}
dweibull2 <- function(x,c,g){
c*g*x^(g-1)*exp(-c*x^g)
}
pweibull2 <- function(q,c,g){
1-exp(-c*q^g)
}
qweibull2 <- function(p,c,g){
(-log(1-p)/c)^(1/g)
}
Page 4 CS2B-15: Loss distributions – Summary

The Burr distribution


rburr <- function(n,a,lambda,g){
(lambda *((1-runif(n))^(-1/a)-1))^(1/g)
}

dburr <- function(x,a,lambda,g){


(a*g*lambda^a)*x^(g-1)/((lambda+x^g)^(a+1))
}

pburr <- function(q,a,lambda,g){


1-(lambda/(lambda+q^g))^a
}

qburr <- function(p,a,lambda,g){


(lambda*((1-p)^(-1/a)-1))^(1/g)
}

The three-parameter Pareto distribution:


To calculate f (x) :

d3pareto <- function(x,a,lambda,k){


gamma(a+k)*(lambda^a)*x^(k-1)/(gamma(a)*gamma(k)*(lambda+x)^(a+k))
}

To calculate F (q) , first specify parameter values a, lambda and k, then:

d3pareto.v2 <- function(x){


gamma(a+k)*(lambda^a)*x^(k-1)/(gamma(a)*gamma(k)*(lambda+x)^(a+k))
}
integrate(d3pareto,0,q)$value

Summarising data
head(x)
summary(x)
min(x); max(x)
quantile(x,seq(0,0.5,by=0.1))

Empirical probabilities
To estimate P ( X  c )

length(x[x>c])/length(x)

Locating specific observations


where <- which(x==max(x))
x[where]

Key metrics
mean(x)
var(x)
quantile(x,0.5)

© IFE The Actuarial Education Company


CS2B-15: Loss distributions – Summary Page 5

To find the empirical mode of a vector x:

band <- x%/%1


x2 <- cbind(x,band)
data <- aggregate(x~band,x2,length)
which(data[,2]==max(data[,2]))

Maximum likelihood estimation


First write the negative log-likelihood function:

fMLE <- function(params) {


f<- <probability density function>
lnf<-log(f)
sum(-lnf)
}

Then specify starting values a, b, c etc for the iteration, and find the maximum likelihood:

p <- c(a,b,c, …); MLE <- nlm(fMLE,p); MLE

Alternatively, writing a function that takes both the sample data and parameters as inputs:

fMLE <- function(data, params) {


f<- <probability density function>
lnf<-log(f)
sum(-lnf)
}

Then specify starting values a, b, c etc for the iteration, and find the maximum likelihood:

p <- c(a,b,c, …); MLE <- nlm(fMLE,p, data = sample); MLE

Alternatively, using the fitdistr() function in the MASS package:

fitdistr(data, distribution, list of starting values)

The <starting values> are not required when formulae exist. This is the case for the

normal, lognormal, geometric, exponential and Poisson distributions.

Histogram
hist(data, freq=FALSE)
x <- seq(0,max(data))
parameter1 <- nlm$estimate[1]
parameter2 <- nlm$estimate[2] … etc
y <- dXXX(x, parameter1, parameter2, …)
lines(x,y)

Empirical density function


plot(density(data,from= …,to= …),xlab="…",main="…",col="blue")
x <- seq(from= …,to= …)
parameter1 <- nlm$estimate[1]
parameter2 <- nlm$estimate[2] … etc
y <- dXXX(x, parameter1, parameter2, …)
lines(x,y,col="red")

The Actuarial Education Company © IFE


Page 6 CS2B-15: Loss distributions – Summary

Q-Q plots
set.seed(XXX)
x <- rXXX(n, parameter1, parameter2, …)

qqplot(x,data,xlab = "Quantiles from fitted distribution",ylab =


"Sample quantiles",main = "QQ plot of data")
abline(0,1,col="red")

The fitrdistr() function does not support the Pareto distribution. The full list of supported
distributions, given in the required format to input into the function, is:

"beta" "lognormal"
"cauchy" "logistic"
"chi-squared" "negative binomial"
"exponential" "normal"
"gamma" "Poisson"
"geometric" "t"
"log-normal" "weibull"

© IFE The Actuarial Education Company


Extreme Value Theory

1 Summary

Block maxima
To find the maximum in each year:

maxima <- aggregate(data~year,data,max)

To find the maxima, grouped by month and year:

maxima <- aggregate(data~year:month,data,max)

To group into bespoke intervals (eg n months per interval):

(data - constant)%/%n+1

(but be prepared to adjust the constant and the +1, depending on the data provided).

Threshold exceedances
For threshold level u, the threshold exceedances te are:

u <- constant
te <- data[data>u]-u;

Maximum likelihood estimation


Write the negative log-likelihood function:

fMLE <- function(params) {


f <- <probability density function>
lnf <- log(f)
sum(-lnf)
}

Specify starting values a, b, c, for the iteration and find the maximum likelihood estimates:

p <- c(a,b,c)
nlm(fMLE,p)

Plot fitted PDF on same graph as histogram or empirical density function


hist(data,freq=FALSE) or plot(density(data))
x <- seq(min(data),max(data),by=constant)
y <- probability density function
lines(x,y)
GEV Distribution pdf (when gamma is not equal to 0)
dGEV <- function(x,alpha,beta,gamma){
1/beta*(1+gamma*(x-alpha)/beta)^-(1+1/gamma)*
exp(-((1+gamma*(x-alpha)/beta)^(-1/gamma)))
}

pGEV <- function(x,alpha,beta,gamma){


exp(-(1+gamma*(x-alpha)/beta)^(-1/gamma))
}

rGEV <- function(n,alpha,beta,gamma){


alpha + (beta/gamma)*((-log(runif(n)))^(-gamma)-1)
}

To find the MLE of GEV distruibution


fMLE <- function(params) {
f<- 1/params[2]*(1+params[3]*(maxima[,2]-params[1])/params[2])^
(-1-1/params[3])*exp(-(1+params[3]*(maxima[,2]-params[1])/params[2])^
(-1/params[3]))
lnf<-log(f)
sum(-lnf)
}

GPD Distribution pdf (when gamma is not equal to 0)


dGPD <- function(x,beta,gamma){
(1/beta)*(1 + x/(gamma*beta))^(-gamma-1)
}

pGPD <- function(x,beta,gamma){


1 - (1+x/(gamma*beta))^(-gamma)
}

rGPD <- function(n,beta,gamma){


gamma*beta*((1-runif(n))^(-1/gamma)-1)
}

To find the MLE of GPD distribution


fMLE <- function(params) {
f<- 1/params[1]*((1+te/(params[2]*params[1]))^-(params[2]+1))
lnf<-log(f)
sum(-lnf)
}
Page 4 CS2B-16: Extreme Value Theory – Summary

Q-Q plots
GEV distribution

First simulate 1,000 values from the fitted GEV distribution (for   0 ):

rGEV <- function(n,a,b,g) {


rp <- b/g*((-log(runif(n)))^(-g)-1)+a
rp
}
set.seed(123)
x <- rGEV(1000,alpha,beta,gamma)

Then create the Q-Q plot:

qqplot(x,maxima,xlab = "Quantiles from fitted distribution",ylab =


"Sample quantiles",main = "QQ plot of maxima")
abline(0,1,col="red")

GPD distribution

First simulate 1,000 values from the fitted GPD distribution (for   0 ):

rGPD <- function(n,g,b) {


rp <- g*b*((1-runif(n))^(-1/g)-1)
rp
}
set.seed(123)
x<-rGPD(1000, gamma, beta)

Then create the Q-Q plot:

qqplot(x,exceedances,xlab = "Quantiles from fitted


distribution",ylab = "Sample quantiles",main =
"QQ plot of exceedances")
abline(0,1,col="red")

Limiting density ratios


x <-seq(min,max,by=constant)
d1 <- dXXX(x,parameters of first distribution)
d2 <- dYYY(x,parameters of second distribution)
r <- d1/d2

Hazard rate
x <- seq(min,max,by=constant)
H <- dXXX(x,parameters of distribution)/(1-pXXX(x,parameters of
distribution))

© IFE The Actuarial Education Company


CS2B-16: Extreme Value Theory – Summary Page 5

Mean residual life


Specify a vector y (such that y has a high maximum value):

y <- c(min:max)

Write the survival function. For distributions built into R, this is:

Sy <- function(y) {
1 - pXXX(y,parameters of distribution)
}

Calculate the mean residual life e(x) for given value(s) of x:

ex <- integrate(Sy,x,Inf)$value/Sy(x)

The Actuarial Education Company © IFE


Copulas

1 Summary

Simulation
Setting the seed for generating random numbers

set.seed(17)

The random numbers generated in R are really just a single very long sequence of numbers.
The set.seed function sets the starting point to use for the next random number ‘generated’.
An integer from 1 to 100 is usually a good choice.

Generating random numbers

To generate a single random number from the U(0,1) distribution:

runif(1)

To generate a vector of length n of independent random numbers from the U(0,1) distribution:

runif(n)

To generate a vector of length n of independent random numbers from the N( , 2 ) distribution:

rnorm(n, mean = mu, sd = sigma) or rnorm(n, mu, simga)

To generate a vector of length n of independent random numbers from the Gamma( ,  )


distribution:

rgamma(n, shape = alpha, rate = beta) or rgamma(n, alpha, beta)

See the summary for Chapter 15 for other distributions.

Probability functions
Finding the percentiles points of a distribution

qnorm(0.975)

This is for the N(0,1) distribution and will give the familiar value 1.96 (or rather, the slightly more
accurate value of 1.959964). Other distributions work in the same way, eg to calculate the
median of an Gamma(1,1) distribution:

qgamma(0.5, 1, 1)

See also the summary for Chapter 15 for other distributions.


Page 4 CS2B-17: Copulas – Summary

Correlations
Calculating correlation coefficients

cor(xvalues, yvalues, method = "pearson")

The default method is Pearson, but you can change the method to "spearman" or "kendall".
Note that these arguments must be written with a small letter.

If your data values are stored in a two-column matrix, you can use:

cmatrix = cor(matrix)

This returns the correlation matrix, rather than a single value. The correlation coefficient is the
‘off-diagonal’ value in this matrix:

cmatrix[2,1] or cmatrix[1,2]

1 Copula Package
gumbelCopula(param = <alpha> , dim = 2) OR gum.cop = archmCopula("gumbel", 3, 2)
frankCopula(param = <alpha>, dim = 2)
claytonCopula(param = <alpha>, dim = 2)
archmCopula(family, param = <alpha>, dim = 2)

normalCopula(param = <rho>, dim = 2)


tCopula(param = <rho>, dim = 2,df = <df>)
ellipCopula(family, param = <rho>, dim = 2)

Using copula objects


dCopula(<marginal CDF values>, <copula object>)
pCopula(<marginal CDF values, <copula object>)
rCopula(<number of simulations>, <copula object>)

Fitting a copula
fitCopula(<copula>, <data>, method ="...")
Methods:-
mpl = max pseudo-likelihood (using pobs)
ml = max likelihood (using true values)
itau = invert Kendall's tau estimator (using pobs or true values)
irho = invert spearman's rho (using pobs or true values)

summary(<fitCopula>)

Marginals
my.joint.dist = mvdc(gum.cop, margins = c("norm", "gamma"),
paramMargins = list(list(mean = 3, sd = 2), list(shape = 2, rate = 4)))

set.seed(100)
joint.sims = rMvdc(1000, my.joint.dist)
Reinsurance

1 Summary

Excess of loss reinsurance


To find the amounts paid by the insurer and reinsurer:

y <- pmin(x,M)
z <- pmax(0,x - M)

The reinsurer’s conditional claim amounts are:

w <- z[z>0]

When an inflation factor of k applies, these become:

ky <- pmin(k*x,M)
kz <- pmax(0,k*x - M)
w <- kz[kz>0]

The proportion of claims that involve the reinsurer is:

length(x[x > M])/length(x)

To calculate the percentage reduction in the number of claims affecting the insurer, if a policy
excess is involved:

y <- pmax(0,x - M)
length(y[y>0])/length(x)

To find the retention level M required for the reinsurer to break even:

z.profit <- function(M) {


z <- pmax(0,x - M)
total.reinsurance.payments <- sum(z)
profit <- premium - total.reinsurance.payments
abs(profit)
}
nlm(z.profit,constant)

To find the retention level that minimises the variance of the insurer’s claims (if the reinsurer’s
maximum payment on a claim is Zmax):

f <- function(M) {
var(x - pmin(pmax(x - M,0),Zmax))
}
nlm(f,constant)

Proportional reinsurance
To find the amounts paid by the insurer and reinsurer, for retained proportion a:

y <- a*x
z <- (1 - a)*x
Page 4 CS2B-18: Reinsurance – Summary

When an inflation factor of k applies, these become:

ky <- a*k*x
kz <- (1 - a)*k*x

Estimating parameters from censored data


The log-likelihood function for the amounts paid by the reinsurer on all claims z is:

nz
ln L  nm  ln P  X  M     ln fX (zi  M)
i 1

Initial calculations:

nm <- length(z[z==0])
zcensored <- z[z>0]
M <- constant

Write the negative log-likelihood function (where pXXX and dXXX are the distribution function
and density function, respectively):

flnL <- function(parameters) {


- nm*pXXX(M,parameters[1],parameters[2],…,log.p=TRUE) -
sum(dXXX(zcensored+M,parameters[1],parameters[2],…,log=TRUE))
}

Find the maximum likelihood estimates, using starting values a, b, c, etc:

p <- c(a,b,c)
nlm <- nlm(flnL,p); nlm

© IFE The Actuarial Education Company


Risk models
1 Summary

Collective risk model – without reinsurance


To simulate 10,000 observations from a compound Poisson distribution with Poisson
parameter l:

set.seed(123)
n <- rpois(10000,l)
s <- numeric(10000)
for(i in 1:10000)
{x <- rXXX(n[i],parameters of claim size distribution)
s[i] <- sum(x)}

To simulate 10,000 observations from a compound binomial distribution with parameters m


and p:

set.seed(123)
n <- rbinom(10000,size=m,prob=p)
s <- numeric(10000)
for(i in 1: 10000) {
x <- rXXX(n[i], parameters of claim size distribution)
s[i] <- sum(x)
}

To simulate 10,000 observations from a compound negative binomial distribution with


parameters k and p:

set.seed(123)
n <- rnbinom(10000,size=k,prob=p)
s <- numeric(10000)
for(i in 1: 10000) {
x <- rXXX(n[i], parameters of claim size distribution)
s[i] <- sum(x)
}

Collective risk model – with reinsurance


Proportional reinsurance with retention a%

To simulate 10,000 observations from the insurer’s aggregate claims distribution SI :

set.seed(123)
n <- rNNN(10000, parameters of claim number distribution)
sI <- numeric(10000)
for(i in 1:10000) {
x <- rXXX(n[i], parameters of claim size distribution)
y <- a/100*x
sI[i] <- sum(y)
}
Page 4 CS2B-19 and 20: Risk models – Summary

To simulate 10,000 observations from the reinsurer’s aggregate claims distribution SR :

set.seed(123)
n <- rNNN(10000, parameters of claim number distribution)
sR <- numeric(10000)
for(i in 1:10000) {
x <- rXXX(n[i], parameters of claim size distribution)
z <- (1-a/100)*x
sR[i] <- sum(z)
}

Excess of loss reinsurance with retention limit M

To simulate 10,000 observations from the insurer’s aggregate claims distribution SI :

set.seed(123)
n <- rNNN(10000, parameters of claim number distribution)
sI <- numeric(10000)
for(i in 1:10000) {
x <- rXXX(n[i], parameters of claim size distribution)
y <- pmin(x,M)
sI[i] <- sum(y)
}

To simulate 10,000 observations from the reinsurer’s aggregate claims distribution SR :

set.seed(123)
n <- rNNN(10000, parameters of claim number distribution)
sR <- numeric(10000)
for(i in 1:10000) {
x <- rXXX(n[i], parameters of claims size distribution)
z <- pmax(0,x-M)
sR[i] <- sum(z)
}

Aggregate excess of loss reinsurance with retention limit M

Simulate the aggregate claims S before reinsurance, and then calculate:

sI <- pmin(s,M)

or:

sR <- pmax(0,s-M)

© IFE The Actuarial Education Company


CS2B-19 and 20: Risk models – Summary Page 5

Individual risk model – method 1


To simulate 10,000 observations from an individual risk model with two types of policy and total
number of policies n:

set.seed(123)
S.sim <- numeric(10000)
for (j in 1:10000) {

x <- c(rXXX1(number of Type I policies, parameters of


Type I claim size distribution),
rXXX2(number of Type II policies, parameters of
Type II claim size distribution))

death <- c(rbinom(number of Type I policies, parameters of


Type I claim size distribution),
rbinom(number of Type II policies, parameters of
Type II claim size distribution))

sim <- x*death


sim<-sum(sim)
S.sim[j] <- sim
}

Proportional or individual excess of loss reinsurance

When reinsurance applies, insert the calculation of y (or z) immediately before the line:
death <- …

and, in the line sim <- x*death, replace x with y (or z).

Aggregate excess of loss reinsurance with retention limit M

Simulate the aggregate claims S.sim before reinsurance, and then calculate:

sI <- pmin(S.sim,M) or sR <- pmax(0,S.sim-M)

Individual risk model – method 2


To simulate 10,000 observations from an individual risk model where:
 the number of policies in the portfolio is n
 a vector q represents the probability of a claim on each policy
 the dataframe size represents the parameters of the claim size random variable on each
policy
 each claim follows the same family of distribution XXX):

set.seed(123)
S.sim <- numeric(10000)
for (i in 1:10000) {
death <- rbinom(n,1,q)
x <- rXXX (n, parameters = size[,1], size[,2], … )
sim <- x*death
S.sim[i] <- sum(sim)
}

The Actuarial Education Company © IFE


Page 6 CS2B-19 and 20: Risk models – Summary

Proportional or individual excess of loss reinsurance

When reinsurance applies, insert the calculation of y (or z) immediately before the line:

sim <- x*death

and, in the line sim <- x*death, replace x with y (or z).

Aggregate excess of loss reinsurance with retention limit M

Simulate the aggregate claims S.sim before reinsurance, and then calculate:

sI <- pmin(S.sim,M) or sR <- pmax(0,S.sim-M)

Parameter uncertainty in a heterogeneous portfolio


The code in this section is based on the compound Poisson distribution with a continuously
distributed unknown parameter. You should be able to adapt it for other compound distributions
and for a discretely distributed unknown parameter.

Simulating the number of claims of an individual policy

The code to generate 10,000 simulations from the mixture distribution of the number of claims
for a compound Poisson distribution with unknown continuously distributed parameter is:

sims <- 10000


set.seed(123)
lambda <- rXXX(sims, parameters of Poisson parameter distribution)
N <- rpois(sims, lambda)

Simulating the aggregate claims from a randomly chosen policy

The code to generate 10,000 simulations from the mixture distribution of a randomly chosen
policy with a compound Poisson distribution with unknown continuously distributed parameter is:

sims <- 10000


S <- numeric(sims)

set.seed(123)

lambda <- rXXX(sims, parameters of Poisson parameter distribution)

N <- rpois(sims, lambda)

for (i in 1:sims){
S[i] <- sum(rXXX(N[i], parameters of claim distribution))
}

© IFE The Actuarial Education Company


CS2B-19 and 20: Risk models – Summary Page 7

Simulating the aggregate claims from the portfolio

The code to generate 10,000 simulations of the aggregate portfolio claims with 100 such policies
is:

sims <- 10000


policies <- 100
S <- numeric(policies)

Results <- matrix(nrow = sims, ncol = policies)

set.seed(123)

for (i in 1:sims){

lambda <- rXXX(policies, parameters of Poisson parameter


distribution)

N <- rpois(policies, lambda)

for (j in 1:policies){
S[j] <- sum(rXXX(N[j], parameters of claim distribution))
}

Results[i, ] <- S

Total <- rowSums(Results)

Parameter uncertainty in a homogeneous portfolio


The code in this section is based on the compound Poisson distribution with a discretely
distributed unknown parameter. You should be able to adapt it for other compound distributions
and for a continuously distributed unknown parameter.

Simulating the aggregate claims from a randomly chosen policy

The code to generate 10,000 simulations from the mixture distribution of a randomly chosen
policy with a compound Poisson distribution with unknown discretely distributed parameter is:

sims <- 10000


S <- numeric(sims)

set.seed(123)

lambda <- sample(x = c(possible values of Poisson parameter


variable),
replace = TRUE, size = sims,
prob = c(probabilities for each possible value of
Poisson parameter variable))

N <- rpois(sims, lambda)

for (i in 1:sims){
S[i] <- sum(rXXX(N[i], parameters of claim distribution))
}

The Actuarial Education Company © IFE


Page 8 CS2B-19 and 20: Risk models – Summary

Simulating the aggregate claims from the portfolio

The code to generate 10,000 simulations of the aggregate portfolio claims with 100 policies, all
with the same Poisson parameter is:

sims <- 10000


policies <- 100
S <- numeric(policies)

Results <- matrix(nrow = sims, ncol = policies)

set.seed(123)

for (i in 1:sims){

lambda <- sample(x = c(possible values of Poisson parameter


variable),
replace = TRUE, size = 1,
prob = c(probabilities for each possible value of
Poisson parameter variable))

N <- rpois(policies, lambda)

for (j in 1:policies) {
S[j] <- sum(rXXX(N[i], parameters of claim distribution))
}

Results[i, ] <- S

Total <- rowSums(Results)

Remember that the size of the sample when generating the observations for the Poisson
parameter is 1 for each simulation (as it is the same across policies). This has been highlighted in
red in the code above.

© IFE The Actuarial Education Company


CS2B-21: Machine learning – Summary Page 1

Machine learning
Summary

The Actuarial Education Company © IFE


1 Unsupervised learning summary
Standardising data

We can calculate z-scores for a data set using the scale() function:

scale(data)

k-means algorithm

R has a built-in function kmeans() that carries out the calculations for the k-means algorithm. To
use it, you just need to specify the data and the number of clusters, k , to search for.

model1 = kmeans(data,k)

Alternatively, you can specify a starting set of cluster centres. The number of centres that you
provide will be the number of clusters that the algorithm returns:

model1 = kmeans(data,centres)

It’s best to assign the output to a variable (we’ve called it model1 here) so that you can access the
components of the results. For example, you can then access the coordinates of the centroids the
algorithm has found:

model1$centers

Calculating cluster centres

To calculate the centroid of cluster i for the points in the data set data using the clustering
vector clusters:

colMeans(data[clusters == i,])

To calculate the centroids for all clusters at once for the points in the data set data using the
clustering vector clusters:

centres = sapply(1:2, function(i) {colMeans(data[clusters == i,])})

Alternatively, the aggregate() function gives this same output but as rows:

aggregate(data, list(clusters), FUN = mean)

Plotting clusters different colours

To plot the points in the data set data and colour them according to the values in the vector
clusters:
plot(data, col = clusters)
When you are using the aggregate function to calculate the centres :-
apply(data, 1, function(row) { sqrt(sum((row - centres[1,2:n])^2))} )
where n is the last column number in the "centres"

Calculating distances

To calculate the distance between each row of the data set data and the cluster centre centre:

apply(data, 1, function(row) {sqrt(sum((row - centre)^2))} )

You may need to select relevant elements of each row if there are additional columns over and
above the variables used in the clustering.

Assigning clusters

To assign a cluster based on a set of distance columns dist_columns in the data set data:

apply(data[, c(dist_columns)], 1, which.min)

Total within-group sum of squares

Calculating the total within-group sum of squares when applying k-means for 1 to k clusters on
the data set data:

tot.withinss = numeric(k)

for(i in 1:k){
tot.withinss[i] = kmeans(data, i)$tot.withinss
}

One way to select an appropriate number of clusters is to identify a number after


which there is no longer a significant improvement in the total within-group sum of
squares

dist_1 <- sqrt((data$Horror-centers[1,2])^2+

(data$Romcom-centers[1,3])^2+

(data$Action-centers[1,4])^2+

(data$Comedy-centers[1,5])^2+

(data$Fantasy-centers[1,6])^2)

dist_2 <- sqrt((data$Horror-centers[2,2])^2+

(data$Romcom-centers[2,3])^2+

(data$Action-centers[2,4])^2+

(data$Comedy-centers[2,5])^2+

(data$Fantasy-centers[2,6])^2)

dist_3 <- sqrt((data$Horror-centers[3,2])^2+

(data$Romcom-centers[3,3])^2+

(data$Action-centers[3,4])^2+

(data$Comedy-centers[3,5])^2+

(data$Fantasy-centers[3,6])^2)

data <- cbind(data,dist_1,dist_2,dist_3)


CS2B-21: Machine learning – Summary Page 5

2 Supervised learning summary


Constructing a function to evaluate a decision tree

To construct a decision tree by testing numerous conditions on each data point we can use nested
ifelse statements. For example, consider the following decision tree that uses height and
weight to divide athletes up into sports:

An example function that evaluates this tree is:

decision = function(height, weight){


ifelse(height > 195, "B",
ifelse(weight < 70, ifelse(height < 160, "H", "C"),
ifelse(weight > 85, "R", "T")))
}

We have used the first letter of each sport to represent the categories. Running this on some
example data:

height =
c(155,150,170,180,197,170,175,168,170,205,193,199,185,170,207,205,1
90,180)

weight =
c(65,69,64,66,71,73,80,85,88,85,90,95,96,96,100,102,101,103)

data = data.frame(height, weight)

decision(data$height, data$weight)

[1] "H" "H" "C" "C" "B" "T" "T" "T" "R" "B" "R" "B" "R"
[14] "R" "B" "B" "R" "R"

Comparing predicted values to observed values

To compare predictions in the vector preds with actual values in the vector obs:

table(preds,obs)

The Actuarial Education Company © IFE


Page 6 CS2B-21: Machine learning – Summary

Calculating the maximum probability for naïve Bayes predictions

To calculate the index of the maximum probability for each row in a data set probs containing
probabilities in columns:

apply(probs, 1, which.min)

The code for pdf of uniform distribution taking care of x can take only integer values :
ddunif <- function(x,a,b){
if(sum(x != floor(x))!=0){
stop("Error:A value in x is not an integer")
}
ifelse(x<a | x>b ,0,1/(b-a+1))
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
cond.prob.given.T1 = function(F1, F2){
ddunif(F1, 40, 100) * ddunif(F2, 40, 100)
}
cond.prob.given.T2 = function(F1, F2){
ddunif(F1, 10, 70) * ddunif(F2, 30, 90)
}
cond.prob.given.T3 = function(F1, F2){
ddunif(F1, 0, 80) * ddunif(F2, 0, 60)
}

prior1 = 0.5
prior2 = 0.3
prior3 = 0.2

prob.type.given.F = function(F1, F2){

T1.prob = cond.prob.given.T1(F1, F2) * prior1


T2.prob = cond.prob.given.T2(F1, F2) * prior2
T3.prob = cond.prob.given.T3(F1, F2) * prior3

sum = T1.prob + T2.prob + T3.prob

T1.prob = T1.prob / sum


T2.prob = T2.prob / sum
T3.prob = T3.prob / sum

data.frame(T1.prob, T2.prob, T3.prob)


}

(probs = prob.type.given.F(sample.data$F1, sample.data$F2))

© IFE The Actuarial Education Company

You might also like