## Author: Markus Gesmann
## Copyright: Markus Gesmann, markus.gesmann@gmail.com
## Date:10/11/2007
## Date:08/09/2008
## Date:22/03/2009
## Date:25/05/2010 Dan
MackChainLadder <- function(
Triangle,
weights=1,
alpha=1,
est.sigma="log-linear",
tail=FALSE,
tail.se=NULL,
tail.sigma=NULL,
mse.method = "Mack")
{
## idea: have a list for tail factor
## tail=list(f=FALSE, f.se=NULL, sigma=NULL, F.se=NULL)
##
# 2013-02-25 Parameter risk recursive formula may have a third term per
# Murphy and BBMW
if (! mse.method %in% c("Mack", "Independence")) stop("mse.method must be 'Mack' or 'Independence'")
Triangle <- checkTriangle(Triangle)
m <- dim(Triangle)[1]
n <- dim(Triangle)[2]
## Create chain ladder models
## Mack uses alpha between 0 and 2 to distinguish
## alpha = 0 straight averages
## alpha = 1 historical chain ladder age-to-age factors
## alpha = 2 ordinary regression with intercept 0
## However, in Zehnwirth & Barnett they use the notation of delta, whereby delta = 2 - alpha
## the delta is than used in a linear modelling context.
delta <- 2-alpha
CL <- chainladder(Triangle, weights=weights, delta=delta)
alpha <- 2 - CL$delta
# Estimate expected values and standard errors in four steps:
# 1) Squaring the Triangle: Expected values and f/F SE's from the data in the triangle
# 2) Expected values and f/F SE's from the tail factor specifications
# 3) Process Risk and Parameter Risk estimates of the squared triangle (incl tail column)
# 3) Expected values and SE's for the totals-across-origin-periods of the predicted values
## 1) Squaring the Triangle
## EXPECTED VALUES: Predict the chain ladder models
FullTriangle <- predict.ChainLadder(list(Models=CL[["Models"]], Triangle=Triangle))
## f/F SE's
StdErr <- Mack.S.E(CL[["Models"]], FullTriangle, est.sigma = est.sigma,
weights = CL[["weights"]], alpha = alpha)
## 2) Tail
## Check for tail factor
if(is.logical(tail)){
if(tail){
tail <- tailfactor(StdErr$f)
tail.factor <- tail$tail.factor
StdErr$f <- c(StdErr$f, tail.factor = tail.factor)
}else{
# tail.factor <- tail
# MunichChainLadder needs an 'f' vector as long as the triangle is wide
# and adding on a harmless tail doesn't seem to cause any
# Mack difficulties
# Default will not be named in the output
tail.factor <- 1.000
StdErr$f <- c(StdErr$f, tail.factor)
}
}else{
# if(is.numeric(tail))
# Documentation says tail must be logic or numeric
tail.factor <- as.numeric(tail)
StdErr$f <- c(StdErr$f, tail.factor = tail.factor)
}
# Then finally, ...
if (tail.factor > 1) {
## EXPECTED VALUES
FullTriangle <- tail_E(FullTriangle, tail.factor)
## STANDARD ERRORS
## Estimate the standard error of f and F in the tail
## If tail.se and/or tail.sigma provided, return those values
StdErr <- tail_SE(FullTriangle, StdErr, Total.SE, tail.factor,
tail.se = tail.se, tail.sigma = tail.sigma,
alpha = alpha)
}
## 3) Calculate process and parameter risks of the predicted loss amounts
StdErr <- c(StdErr, MackRecursive.S.E(FullTriangle, StdErr$f, StdErr$f.se, StdErr$F.se, mse.method = mse.method))
## 4) Total-across-origin-periods by development period
## EXPECTED VALUES
## Not complicated. Not required at this time.
## STANDARD ERRORS
## Calculate process and parameter risk for the sum of the predicted loss amounts
Total.SE <- TotalMack.S.E(FullTriangle, StdErr$f, StdErr$f.se, StdErr$F.se, StdErr$FullTriangle.procrisk, mse.method = mse.method)
## Collect the output
output <- list()
output[["call"]] <- match.call(expand.dots = FALSE)
output[["Triangle"]] <- Triangle
output[["FullTriangle"]] <- FullTriangle
output[["Models"]] <- CL[["Models"]]
output[["f"]] <- StdErr$f
output[["f.se"]] <- StdErr$f.se
output[["F.se"]] <- StdErr$F.se
output[["sigma"]] <- StdErr$sigma
output[["Mack.ProcessRisk"]] <- StdErr$FullTriangle.procrisk # new dmm
output[["Mack.ParameterRisk"]] <- StdErr$FullTriangle.paramrisk # new dmm
output[["Mack.S.E"]] <- sqrt(StdErr$FullTriangle.procrisk^2 + StdErr$FullTriangle.paramrisk^2)
output[["weights"]] <- CL$weights
output[["alpha"]] <- alpha
## total.procrisk <- apply(StdErr$FullTriangle.procrisk, 2, function(x) sqrt(sum(x^2)))
output[["Total.Mack.S.E"]] <- Total.SE[1] # [1] removes attributes
output[["Total.ProcessRisk"]] <- attr(Total.SE, "processrisk")
output[["Total.ParameterRisk"]] <- attr(Total.SE, "paramrisk")
output[["tail"]] <- tail
class(output) <- c("MackChainLadder", "TriangleModel", "list")
return(output)
}
##############################################################################
## Calculation of the mean squared error and standard error
## mean squared error = stochastic error (process variance) + estimation error
## standard error = sqrt(mean squared error)
approx.equal <- function (x, y, tol=.Machine$double.eps^0.5) abs(x-y)<tol
Mack.S.E <- function(MackModel, FullTriangle, est.sigma="log-linear", weights, alpha) {
n <- ncol(FullTriangle)
m <- nrow(FullTriangle)
f <- rep(1, n - 1)
f.se <- rep(0, n - 1)
sigma <- rep(0, n - 1)
## Extract estimated slopes, std. error and sigmas
## 2015-10-9 Replace lm's warning with more appropriate message
smmry <- suppressWarnings(lapply(MackModel, summary))
f <- sapply(smmry, function(x) x$coef["x","Estimate"])
f.se <- sapply(smmry, function(x) x$coef["x","Std. Error"])
sigma <- sapply(smmry, function(x) x$sigma)
df <- sapply(smmry, function(x) x$df[2L])
tolerance <- .Machine$double.eps
perfect.fit <- (df > 0) & (f.se < tolerance)
w <- which(perfect.fit)
if (length(w)) {
warn <- "Information: essentially no variation in development data for period(s):\n"
nms <- colnames(FullTriangle)
periods <- paste0("'", paste(nms[w], nms[w+1], sep = "-"), "'")
warn <- c(warn, paste(periods, collapse = ", "))
# Print warning message
warning(warn)
}
#
isna <- is.na(sigma)
## Think about weights!!!
if(est.sigma[1] %in% "log-linear"){
if (sum(!isna) == 1) {
warning(paste("Too few (1) link ratios for fitting 'loglinear' model to estimate sigma_n.",
"est.sigma will be overwritten to 'Mack'.\n",
"Mack's estimation method will be used instead."))
est.sigma <- "Mack"
}
else {
## estimate sigma[n-1] via log-linear regression
sig.model <- suppressWarnings(estimate.sigma(sigma))
sigma <- sig.model$sigma
p.value.of.model <- tryCatch(summary(sig.model$model)$coefficient[2,4],
error = function(e) e)
if (inherits(p.value.of.model, "error") |
is.infinite(p.value.of.model) |
is.nan(p.value.of.model)
) {
warning(paste("'loglinear' model to estimate sigma_n doesn't appear appropriate.\n",
"est.sigma will be overwritten to 'Mack'.\n",
"Mack's estimation method will be used instead."))
est.sigma <- "Mack"
}
else
if(p.value.of.model > 0.05){
warning(paste("'loglinear' model to estimate sigma_n doesn't appear appropriate.",
"\np-value > 5.\n",
"est.sigma will be overwritten to 'Mack'.\n",
"Mack's estimation method will be used instead."))
est.sigma <- "Mack"
}
else{
f.se[isna] <- sigma[isna]/sqrt(weights[1,isna]*FullTriangle[1,isna]^alpha[isna])
}
}
}
if(est.sigma[1] %in% "Mack"){
for(i in which(isna)){ # usually i = n - 1
ratio <- (sigma[i - 1]^4/sigma[i - 2]^2) # Bug fix: sigma[i - 2]^2 could be zero
if(is.nan(ratio) | is.infinite(ratio)){
sigma[i] <- sqrt(abs(min(sigma[i - 2]^2, sigma[i - 1]^2)))
}else{
sigma[i] <- sqrt(abs(min(ratio, min(sigma[i - 2]^2, sigma[i - 1]^2))))
}
f.se[i] <- sigma[i]/sqrt(weights[1,i]*FullTriangle[1,i]^alpha[i])
}
}
if(is.numeric(est.sigma)){
# Markus, I'm not sure what your intention is in this next loop when the lengths of est.sigma and sigma differ
for(i in seq(along=est.sigma)){
l <- length(est.sigma)
# BUG?! If length(est.sigma) > n, then n-i could be negative
sigma[n-i] <- est.sigma[l-i+1]
f.se[n-i] <- sigma[n-i]/sqrt(weights[1,n-i]*FullTriangle[1,n-i]^alpha[n-i])
}
}
W <- weights
W[is.na(W)] <- 1
F.se <- t(sigma/t(sqrt(W[,-n]*t(t(FullTriangle[,-n])^alpha[-n]))))
return(list(sigma = sigma,
f = f,
f.se = f.se,
F.se = F.se)
)
}
################################################################
MackRecursive.S.E <- function(FullTriangle, f, f.se, F.se, mse.method = "Mack"){
n <- ncol(FullTriangle)
m <- nrow(FullTriangle)
FullTriangle.procrisk <- FullTriangle[, 1:n] * 0
FullTriangle.paramrisk <- FullTriangle[, 1:n] * 0
## Recursive Formula
colindex <- 1:(n-1)
for (k in colindex) {
for (i in (m-k+1):m) {
FullTriangle.procrisk[i,k+1] <- sqrt(
FullTriangle[i,k]^2*(F.se[i,k]^2)
+ FullTriangle.procrisk[i,k]^2*f[k]^2
)
FullTriangle.paramrisk[i,k+1] <- sqrt(
FullTriangle[i,k]^2*(f.se[k]^2)
+ FullTriangle.paramrisk[i,k]^2*f[k]^2
# 2013-02-25 Parameter risk recursive formula may have a third term
+ ifelse(mse.method == "Mack", 0, FullTriangle.paramrisk[i, k]^2 * (f.se[k]^2))
)
}
}
return(list(FullTriangle.procrisk=FullTriangle.procrisk,
FullTriangle.paramrisk=FullTriangle.paramrisk))
}
################################################################################
## Total reserve SE
TotalMack.S.E <- function(FullTriangle, f, f.se, F.se, FullTriangle.procrisk, mse.method = "Mack") {
# 2013-02-25
# Parameter Risk and Process Risk components of Total Risk broken out
# The current program design expects a scalar, total.seR, from this
# function equal to the total risk of the total reserve.
# So as not to break existing code, the Parameter Risk and
# Process Risk vectors (one element per column) are attached as attributes
C <- FullTriangle
n <- ncol(C)
m <- nrow(C)
total.paramrisk <- numeric(n)
# Assumption is that origin years are independent, therefore
# process variance is additive
total.procrisk <- sqrt(colSums(FullTriangle.procrisk^2, na.rm = TRUE))
# For parameter risk recursion, the sum of future loss expected values
# plus the diagonal value, by development age, comes in handy.
# Currently, code relies on the fact that nrows(triangle) >= ncols
# Actually, function 'checkTriangle' makes sure Triangle is square
M <- sapply(1:ncol(FullTriangle), function(k) sum(FullTriangle[(m + 1 - k):m, k], na.rm = TRUE))
for(k in c(1:(n-1))){
# total.seR[k+1] <- sqrt(total.seR[k]^2 * f[k]^2 +
# sum(C[c((m+1-k):m),k]^2 *
# (F.se[c((m+1-k):n),k]^2),na.rm=TRUE)
# + sum(C[c((m+1-k):m),k],na.rm=TRUE)^2 * f.se[k]^2 )
total.paramrisk[k + 1] <- sqrt(
sum(M[k], na.rm = TRUE)^2 * f.se[k]^2 +
total.paramrisk[k]^2 * f[k]^2 +
ifelse(mse.method == "Mack", 0, total.paramrisk[k]^2 * f.se[k]^2))
}
# The scalar returned is the total risk of total reserves in the rightmost column
total.seR <- sqrt(total.procrisk^2 + total.paramrisk^2)[n]
attr(total.seR, "processrisk") <- total.procrisk
attr(total.seR, "paramrisk") <- total.paramrisk
return(total.seR)
}
##############################################################################
estimate.sigma <- function(sigma){
if(!all(is.na(sigma))){
n <- length(sigma)
dev <- 1:n
my.dev <- dev[!is.na(sigma) & sigma > 0]
my.model <- lm(log(sigma[my.dev]) ~ my.dev)
sigma[is.na(sigma)] <- exp(predict(my.model, newdata=data.frame(my.dev=dev[is.na(sigma)])))
}
return(list(sigma=sigma, model=my.model))
}
########################################################################
## Estimate expected value when tail
tail_E <- function(FullTriangle, tail.factor){
n <- ncol(FullTriangle)
m <- nrow(FullTriangle)
FullTriangle <- cbind(FullTriangle, FullTriangle[,n] * tail.factor)
dimnames(FullTriangle) <- list(origin=dimnames(FullTriangle)[[1]],
dev=c(dimnames(FullTriangle)[[2]][1:n], "Inf"))
return(FullTriangle)
}
########################################################################
## Estimate standard error for tail
tail_SE <- function(FullTriangle, StdErr, Total.SE, tail.factor,
tail.se = NULL, tail.sigma = NULL, alpha) {
n <- ncol(FullTriangle)
m <- nrow(FullTriangle)
## Idea: linear model for f, estimate dev for tail factor
## linear model for f.se and sigma and put dev from above in
## 2013-02-28: The tail factor is given and stored in StdEff$f[n-1];
## want to relate the f.se's and sigma's from link ratios estimated
## from the interior of the triangle to the given tail.
## The link ratios from the interior of the triangle are stored in
## StdEff$f[1:(n-2)].
stopifnot(n > 2) # Must have at least two columns in the original triangle
# to impute estimates for the tail-driven,
# previously c-bind-ed Ultimate column
start <- 1
.f <- StdErr$f[start:(n - 2)]
.dev <- c(start:(n - 2))
## mf <- lm(log(.f[.f>1]-1) ~ .dev[.f>1])
mf <- lm(log(.f-1) ~ .dev)
tail.pos <- (log(StdErr$f[n - 1] - 1) - coef(mf)[1]) / coef(mf)[2]
if(is.null(tail.se)){
.fse <- StdErr$f.se[start:(n-2)]
mse <- lm(log(.fse) ~ .dev)
tail.se <- exp(predict(mse, newdata=data.frame(.dev=tail.pos)))
}
StdErr$f.se <- c(StdErr$f.se, tail.se = tail.se)
if(is.null(tail.sigma)){
.sigma <- StdErr$sigma[start:(n-2)]
msig <- lm(log(.sigma) ~ .dev)
tail.sigma <- exp(predict(msig, newdata = data.frame(.dev = tail.pos)))
}
StdErr$sigma <- c(StdErr$sigma, tail.sigma = as.numeric(tail.sigma))
## estimate the standard error of the tail factor ratios
se.F.tail <- tail.sigma / sqrt(FullTriangle[, n - 1]^alpha[n-2])
StdErr$F.se <- cbind(StdErr$F.se, se.F.tail)
return(StdErr)
}
##############################################################################
## Summary
##
summary.MackChainLadder <- function(object,...){
## Summarise my results
Latest <- getLatestCumulative(object$Triangle)
ex.origin.period <- Latest!=0
Ultimate <- object[["FullTriangle"]][,ncol(object[["FullTriangle"]])]
Dev.To.Date <- Latest/Ultimate
IBNR <- Ultimate-Latest
Mack.S.E <- object[["Mack.S.E"]][,ncol(object[["Mack.S.E"]])]
CV <- Mack.S.E/(Ultimate-Latest)
ByOrigin <- data.frame(Latest, Dev.To.Date, Ultimate, IBNR, Mack.S.E, CV)
names(ByOrigin)[6]="CV(IBNR)"
ByOrigin <- ByOrigin[ex.origin.period,]
Totals <- c(sum(Latest,na.rm=TRUE),
sum(Latest,na.rm=TRUE)/sum(Ultimate,na.rm=TRUE),
sum(Ultimate,na.rm=TRUE),
sum(IBNR,na.rm=TRUE), object[["Total.Mack.S.E"]],
object[["Total.Mack.S.E"]]/sum(IBNR,na.rm=TRUE)
)
# Totals <- c(Totals, round(x[["Total.Mack.S.E"]]/sum(res$IBNR,na.rm=TRUE),2))
Totals <- as.data.frame(Totals)
colnames(Totals)=c("Totals")
rownames(Totals) <- c("Latest:","Dev:","Ultimate:",
"IBNR:","Mack S.E.:",
"CV(IBNR):")
output <- list(ByOrigin=ByOrigin, Totals=Totals)
return(output)
}
#getLatestCumulative <- function(cumulative.tri)
# apply(cumulative.tri, 1L, function(x) ifelse(length(w <- which(!is.na(x))) > 0L, x[tail(w, 1L)], x[1L]))
##############################################################################
## print
##
print.MackChainLadder <- function(x,...){
summary.x <- summary(x)
print(x$call)
cat("\n")
print(format(summary.x$ByOrigin, big.mark = ",", digits = 3),...)
Totals <- summary.x$Totals
rownames(Totals)[5] <- "Mack.S.E"
Totals[1:6,] <- formatC(Totals[1:6,], big.mark=",",digits=2,format="f")
cat("\n")
print(Totals, quote=FALSE)
invisible(x)
}
################################################################################
## plot
##
plot.MackChainLadder <- function(
x, mfrow=NULL, title=NULL,
lattice=FALSE, which=1:6, ...){
.myResult <- summary(x)$ByOrigin
.FullTriangle <- x[["FullTriangle"]]
.Triangle <- x[["Triangle"]]
if(is.null(mfrow)){
mfrow <- c(ifelse(length(which) < 2,1,
ifelse(length(which) < 3, 2,
ceiling(length(which)/2))),
ifelse(length(which)>2,2,1))
}
if(!lattice){
if(is.null(title)) myoma <- c(0,0,0,0) else myoma <- c(0,0,2,0)
op=par(mfrow=mfrow, oma=myoma, mar=c(4.5,4.5,2,2))
plotdata <- t(as.matrix(.myResult[,c("Latest","IBNR")]))
n <- ncol(plotdata)
if(1 %in% which){
if(getRversion() < "2.9.0") { ## work around missing feature
bp <- barplot(plotdata,
legend.text=c("Latest","Forecast"),
## args.legend=list(x="topleft"), only avilable from R version >= 2.9.0
names.arg=rownames(.myResult),
main="Mack Chain Ladder Results",
xlab="Origin period",
ylab="Amount",#paste(Currency,myUnit),
ylim=c(0, max(apply(.myResult[c("Ultimate", "Mack.S.E")],1,sum),na.rm=TRUE)))
}else{
bp <- barplot(plotdata,
legend.text=c("Latest","Forecast"),
args.legend=list(x="topleft"),
names.arg=rownames(.myResult),
main="Mack Chain Ladder Results",
xlab="Origin period",
ylab="Amount",#paste(Currency,myUnit),
ylim=c(0, max(apply(.myResult[c("Ultimate", "Mack.S.E")],1,sum),na.rm=TRUE)))
}
## add error ticks
## require("Hmisc")
.errbar(x=bp, y=.myResult$Ultimate,
yplus=(.myResult$Ultimate + .myResult$Mack.S.E),
yminus=(.myResult$Ultimate - .myResult$Mack.S.E),
cap=0.05,
add=TRUE)
}
if(2 %in% which){
matplot(t(.FullTriangle), type="l",
main="Chain ladder developments by origin period",
xlab="Development period", ylab="Amount", #paste(Currency, myUnit)
)
matplot(t(.Triangle), add=TRUE)
}
Residuals=residuals(x)
if(3 %in% which){
plot(standard.residuals ~ fitted.value, data=Residuals,
ylab="Standardised residuals", xlab="Fitted")
lines(lowess(Residuals$fitted.value, Residuals$standard.residuals), col="red")
abline(h=0, col="grey")
}
if(4 %in% which){
plot(standard.residuals ~ origin.period, data=Residuals,
ylab="Standardised residuals", xlab="Origin period")
lines(lowess(Residuals$origin.period, Residuals$standard.residuals), col="red")
abline(h=0, col="grey")
}
if(5 %in% which){
plot(standard.residuals ~ cal.period, data=Residuals,
ylab="Standardised residuals", xlab="Calendar period")
lines(lowess(Residuals$cal.period, Residuals$standard.residuals), col="red")
abline(h=0, col="grey")
}
if(6 %in% which){
plot(standard.residuals ~ dev.period, data=Residuals,
ylab="Standardised residuals", xlab="Development period")
lines(lowess(Residuals$dev.period, Residuals$standard.residuals), col="red")
abline(h=0, col="grey")
}
title( title , outer=TRUE)
par(op)
}else{
## require(grid)
## Set legend
fl <-
grid.layout(nrow = 2, ncol = 4,
heights = unit(rep(1, 2), "lines"),
widths =
unit(c(2, 1, 2, 1),
c("cm", "strwidth", "cm",
"strwidth"),
data = list(NULL, "Chain ladder dev.", NULL,
"Mack's S.E.")))
foo <- frameGrob(layout = fl)
foo <- placeGrob(foo,
linesGrob(c(0.2, 0.8), c(.5, .5),
gp = gpar(col=1, lty=1)),
row = 1, col = 1)
foo <- placeGrob(foo,
linesGrob(c(0.2, 0.8), c(.5, .5),
gp = gpar(col=1, lty=3)),
row = 1, col = 3)
foo <- placeGrob(foo,
textGrob(label = "Chain ladder dev."),
row = 1, col = 2)
foo <- placeGrob(foo,
textGrob(label = "Mack's S.E."),
row = 1, col = 4)
long <- expand.grid(origin=as.numeric(dimnames(.FullTriangle)$origin),
dev=as.numeric(dimnames(.FullTriangle)$dev))
long$value <- as.vector(.FullTriangle)
long$valuePlusMack.S.E <- long$value + as.vector(x$Mack.S.E)
long$valueMinusMack.S.E <- long$value - as.vector(x$Mack.S.E)
sublong <- long[!is.na(long$value),]
xyplot(valuePlusMack.S.E + valueMinusMack.S.E + value ~ dev |
factor(origin), data=sublong, t="l", lty=c(3,3,1), as.table=TRUE,
main="Chain ladder developments by origin period",
xlab="Development period",
ylab="Amount",col=1,
legend = list(top = list(fun = foo)),...)
}
}
################################################################################
## residuals
##
residuals.MackChainLadder <- function(object,...){
m <- nrow(object[["Triangle"]])
n <- ncol(object[["Triangle"]])
myresiduals <- unlist(lapply(object[["Models"]], resid,...))
standard.residuals <- rep(NA, length(myresiduals))
## Identify if we have standardized residuals for all items,
## e.g. this is not the case if some weights have been set to zero
x=lapply(lapply(object$Model, resid), function(x) as.numeric(names(x)))
y=lapply(lapply(object$Model, rstandard), function(x) as.numeric(names(x)))
names(y)=1:length(y)
names(x)=1:length(x)
rst <- unlist(lapply(names(x), function(z) x[[z]] %in% y[[z]]))
## rst holds the indices with standardised residuals for weights > 0
standard.residuals[rst] <- unlist(lapply(object[["Models"]], rstandard,...))
fitted.value <- unlist(lapply(object[["Models"]], fitted))
origin.period <- as.numeric(unlist(lapply(lapply(object[["Models"]],residuals),names)))
dev.period <-rep(1:(n-1), sapply(lapply(object[["Models"]],residuals),length))
cal.period <- origin.period + dev.period - 1
myResiduals <- data.frame(origin.period,
dev.period,
cal.period,
residuals=myresiduals,
standard.residuals,
fitted.value)
return(na.omit(myResiduals))
}
.errbar <- function (
x, y, yplus, yminus, cap = 0.015,
main = NULL, sub = NULL,
xlab = as.character(substitute(x)),
ylab = if (is.factor(x) || is.character(x)) "" else as.character(substitute(y)),
add = FALSE, lty = 1, type = "p", ylim = NULL, lwd = 1, pch = 16,
errbar.col = par("fg"), Type = rep(1, length(y)), ...)
{
# Based on code by Frank Harrell, Hmisc package, licence: GPL >= 2
if (is.null(ylim))
ylim <- range(y[Type == 1], yplus[Type == 1], yminus[Type ==
1], na.rm = TRUE)
if (is.factor(x) || is.character(x)) {
x <- as.character(x)
n <- length(x)
t1 <- Type == 1
t2 <- Type == 2
n1 <- sum(t1)
n2 <- sum(t2)
omai <- par("mai")
mai <- omai
mai[2] <- max(strwidth(x, "inches")) + 0.25
par(mai = mai)
on.exit(par(mai = omai))
plot(NA, NA, xlab = ylab, ylab = "", xlim = ylim, ylim = c(1,
n + 1), axes = FALSE, ...)
axis(1)
w <- if (any(t2))
n1 + (1:n2) + 1
else numeric(0)
axis(2, at = c(seq.int(length.out = n1), w), labels = c(x[t1],
x[t2]), las = 1, adj = 1)
points(y[t1], seq.int(length.out = n1), pch = pch, type = type,
...)
segments(yplus[t1], seq.int(length.out = n1), yminus[t1],
seq.int(length.out = n1), lwd = lwd, lty = lty, col = errbar.col)
if (any(Type == 2)) {
abline(h = n1 + 1, lty = 2, ...)
offset <- mean(y[t1]) - mean(y[t2])
if (min(yminus[t2]) < 0 & max(yplus[t2]) > 0)
lines(c(0, 0) + offset, c(n1 + 1, par("usr")[4]),
lty = 2, ...)
points(y[t2] + offset, w, pch = pch, type = type,
...)
segments(yminus[t2] + offset, w, yplus[t2] + offset,
w, lwd = lwd, lty = lty, col = errbar.col)
at <- pretty(range(y[t2], yplus[t2], yminus[t2]))
axis(side = 3, at = at + offset, labels = format(round(at,
6)))
}
return(invisible())
}
if (add)
points(x, y, pch = pch, type = type, ...)
else plot(x, y, ylim = ylim, xlab = xlab, ylab = ylab, pch = pch,
type = type, ...)
xcoord <- par()$usr[1:2]
smidge <- cap * (xcoord[2] - xcoord[1])/2
segments(x, yminus, x, yplus, lty = lty, lwd = lwd, col = errbar.col)
if (par()$xlog) {
xstart <- x * 10^(-smidge)
xend <- x * 10^(smidge)
}
else {
xstart <- x - smidge
xend <- x + smidge
}
segments(xstart, yminus, xend, yminus, lwd = lwd, lty = lty,
col = errbar.col)
segments(xstart, yplus, xend, yplus, lwd = lwd, lty = lty,
col = errbar.col)
return(invisible())
}
quantile.MackChainLadder <- function(x, probs=c(0.75, 0.95), na.rm = FALSE,
names = TRUE, type = 7,...){
if(! ("MackChainLadder" %in% class(x))){
stop("x is not a MackChainLadder output")
}
Latest <- getLatestCumulative(x$Triangle)
Ultimate <- x[["FullTriangle"]][,ncol(x[["FullTriangle"]])]
Dev.To.Date <- Latest/Ultimate
IBNR <- Ultimate-Latest
Mack.S.E <- x[["Mack.S.E"]][,ncol(x[["Mack.S.E"]])]
CV <- Mack.S.E/IBNR
Skewn <- Asymetrie(x)
## Cornish-Fisher
quantile <- qnorm(probs)
## Cornish-Fisher: by origin period
CF <- (apply(t(quantile), 2, function(q)
q + 1/6 * (q^2 - 1) * Skewn$Skewnes))
QuantilePY <- (1 + CF * CV) * IBNR
## Cornish-Fisher: totals across origin periods
CF <- quantile + 1/6*(quantile^2-1) * Skewn$OverSkew/x[["Total.Mack.S.E"]]^3
TotQuantile <- (1 + CF*x[["Total.Mack.S.E"]]/sum(IBNR))*sum(IBNR)
TotSkew <- Skewn$OverSkew/x[["Total.Mack.S.E"]]^3
ByOrigin <- data.frame(Skewness=Skewn$Skewnes,
(QuantilePY))
names(ByOrigin) <- c("Skewness", paste("IBNR ", probs*100, "%", sep=""))
origin <- dimnames(x$Triangle)[[1]]
if(length(origin)==nrow(ByOrigin)){
rownames(ByOrigin) <- origin
}
Totals <- as.data.frame(c(TotSkew, TotQuantile))
colnames(Totals)=c("Totals")
rownames(Totals) <- c("Skewness", paste("IBNR ", probs*100, "%:", sep=""))
output <- list(ByOrigin=ByOrigin, Totals=Totals)
return(output)
}
##############################################################################
## Calculation of the skewness (Eric Dal Moro - added 31 July 2018)
##
Asymetrie<- function(x) {
if(! ("MackChainLadder" %in% class(x))){
stop("x is not a MackChainLadder output")
}
Triangle <- x$Triangle
FullTriangle <- x$FullTriangle
n <- dim(Triangle)[2]
Skewnes <- c(n-1)
Skewnesi <- c(n)
Sk3ki <- c(n)
Inter <- c(n-1)
OverSkew <- c(1)
Variance <- c(n-1)
Correlation <- matrix(nrow=n-2,ncol=n-2)
MackModel <- x$Models
Sigma2<-x$sigma^2
f <- x$f
f.se <- x$f.se
sigma <- x$sigma
## Calculation of the difference between individual chain-ladder coefficients and the chain-ladder coef
CLRatio <- function(i, Triangle, f){
y=Triangle[,i+1]/Triangle[,i] - f[i]
}
myModel <- sapply(c(1:(n-1)), CLRatio, Triangle, f)
Interm1<-function(i, yData){
Interm1 <- sum(yData[c(1:(n-i)),i]^1.5)
}
Interm2<-function(i, yData){
Interm2 <- sum(yData[c(1:(n-i)),i])
}
#Calculation of Sk3k
Skew<- function(i, yModel, yData, Interme1, Interme2){
# yModel <- yModel[!is.na(yModel)]
y=1/(n-i-Interme1[i]^2/Interme2[i]^3)*sum(yData[c(1:(n-i)),i]^1.5*(yModel[c(1:(n-i)),i]^3))
}
Interme1 <- sapply(c(1:(n-1)), Interm1, Triangle)
Interme2 <- sapply(c(1:(n-1)), Interm2, Triangle)
Interme2<- as.numeric(Interme2)
Sk3k <- sapply(c(1:(n-1)), Skew, myModel, Triangle, Interme1, Interme2)
for (k in c(1:(n-1)))
{
if ((is.infinite(Sk3k[k])) | (is.nan(Sk3k[k])))
{Sk3k[k]=0}
}
# Calculation of Skewness per accident year
for (k in c(1:(n-1))) {
Variance[k] <- Triangle[n+1-k,k]^2*Sigma2[k]*(1/Interme2[k]+1/Triangle[n+1-k,k])
if (k<n-1) { Skewnes[k] <- Triangle[n+1-k,k]^1.5*Sk3k[k]+Triangle[n+1-k,k]^3*Sk3k[k]*Interme1[k]/Interme2[k]^3 }
for (j in c((k+1):(n-1))) {
intermediaire <- 0
intermediaire1 <- 0
for (v in c(1:(n-j))) {
intermediaire <- intermediaire + FullTriangle[v,j]
}
for (v in c(1:(n-j))) {
intermediaire1 <- intermediaire1 + FullTriangle[v,j]^1.5
}
if (k<n-1) {
Skewnes[k] <- Skewnes[k]*f[j]^3+FullTriangle[n+1-k,j]^1.5*Sk3k[j]*(1+Variance[k]/FullTriangle[n+1-k,j]^2)^(3/8)+3*Sigma2[j]*f[j]*Variance[k]+FullTriangle[n+1-k,j]^3*intermediaire1/intermediaire^3*Sk3k[j]
}
if (k<n-1) {
Variance[k] <- Variance[k]*f[j]^2+FullTriangle[n+1-k,j]^2*Sigma2[j]*(1/intermediaire+1/FullTriangle[n+1-k,j])
}
}
}
#Calculation of Mack correlation between accident years (Mack article 1993)
for (k in c(1:(n-1))) {
Inter[n-k]<-Sigma2[k]/f[k]^2/Interme2[k]
}
for (k in c(2:(n-2))) {
Inter[k]<-Inter[k-1]+Inter[k]
}
for (k in c(1:(n-2))) {
for (l in c((k+1):(n-1))) {
Correlation[k,l-1]<-Inter[k]*FullTriangle[k+1,n]*FullTriangle[l+1,n]/Variance[n-k]^0.5/Variance[n-l]^0.5
}
}
#Calculation of overall Skewness across all accident years
OverSkew<-sum(Skewnes)
for (o in c(1:(n-2))) {
for (p in c((o+1):(n-1))) {
OverSkew=OverSkew + 3*Correlation[o,p-1]*(Variance[n-o]*Variance[n-p])^0.5*(Variance[n-o]/FullTriangle[o+1,n]+Variance[n-p]/FullTriangle[p+1,n])*(2+Correlation[o,p-1]*(Variance[n-o]*Variance[n-p])^0.5/(FullTriangle[p+1,n]*FullTriangle[o+1,n]))
OverSkew=OverSkew + 3*Correlation[o,p-1]^2*(Variance[n-o]*Variance[n-p])*(FullTriangle[o+1,n]+FullTriangle[p+1,n])/(FullTriangle[o+1,n]*FullTriangle[p+1,n])
}
}
for (o in c(1:(n-3))) {
for (p in c((o+1):(n-2))) {
for (q in c((p+1):((n-1)))) {
OverSkew=OverSkew + 6*Correlation[o,p-1]*Correlation[p,q-1]*Correlation[o,q-1]*(Variance[n-o]*Variance[n-p]*Variance[n-q])^0.5*((Variance[n-o]*Variance[n-p]*Variance[n-q])^0.5/(FullTriangle[o+1,n]*FullTriangle[p+1,n]*FullTriangle[q+1,n])+Variance[n-o]^0.5/(Correlation[p,q-1]*FullTriangle[o+1,n])+Variance[n-p]^0.5/(Correlation[o,q-1]*FullTriangle[p+1,n])+Variance[n-q]^0.5/(Correlation[o,p-1]*FullTriangle[q+1,n]))
}
}
}
Skewnesi[1]<-0
Skewnesi[2]<-0
Sk3ki[1]<-0
Sk3ki[2]<-0
for (k in c(3:n)) {
Skewnesi[k]<-0
if (Variance[n-k+1]>0) {
Skewnesi[k]<-Skewnes[n-k+1]/Variance[n-k+1]^1.5
}
Sk3ki[k]<-Sk3k[n-k+1]
}
output <- list()
output[["Skewnes"]]<-Skewnesi
output[["Correlation"]]<-Correlation
output[["OverSkew"]]<-OverSkew
output[["Sk3k"]]<-Sk3ki
return(output)
}
## End addition Eric Dal Moro 31 July 2018
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.