Actuarial Standard of Practice No. 41, Actuarial Communications states that “In the actuarial report, the actuary should state the actuarial findings, and identify the methods, procedures, assumptions, and data used by the actuary with sufficient clarity that another actuary qualified in the same practice area could make an objective appraisal of the reasonableness of the actuary’s work as presented in the actuarial report.”
Say what you will about Excel, we must acknowledge that it is excellent at producing exhibits and it is “universally known / understood.” That, in a sense, is why it has been the preferred tool for the production of actuarial reports that comply with ASOP 41.
R and associated tools (Sweave, knitr, etc) were designed around principles of reproducible research. If you are not familiar with this concept, it is discussed on CRAN as follows:
The goal of reproducible research is to tie specific instructions to data analysis and experimental data so that scholarship can be recreated, better understood and verified.
R largely facilitates reproducible research using literate programming; a document that is a combination of content and data analysis code. The Sweave function (in the base R utils package) and the knitr package can be used to blend the subject matter and R code so that a single document defines the content and the algorithms.
That is, they are intended to allow another user to reproduce - and more importantly test - the results of the original researcher. That should naturally allow for a report produced in R to comply with ASOP 41 - but does it?
Let us, for the moment, leave aside other required documentation (such as the responsible actuary, scope of assignment, etc.) and actuarial issues (such as the consideration of additional methods, exposures, etc.). What if this were the “exhibits and technical narrative” of my actuarial report:
I prepared this analysis using R. I first loaded the packages I needed as follows:
library(ChainLadder)
## Loading required package: systemfit
## Loading required package: Matrix
## Loading required package: car
## Loading required package: lmtest
## Loading required package: zoo
##
## Attaching package: 'zoo'
##
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
##
## Loading required package: statmod
##
## ChainLadder version 0.2.0
##
## Type ?ChainLadder to access overall documentation and
## vignette('ChainLadder') for the package vignette.
##
## Type demo(ChainLadder) to get an idea of the functionality of this package.
## See demo(package='ChainLadder') for a list of more demos.
##
## More information is available on the ChainLadder project web-site:
## http://code.google.com/p/chainladder/
##
## To suppress this message use the statement:
## suppressPackageStartupMessages(library(ChainLadder))
library(scales)
This is my paid claims data triangle:
auto$PersonalAutoPaid
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
## [1,] 101125 209921 266618 305107 327850 340669 348430 351193 353353
## [2,] 102541 203213 260677 303182 328932 340948 347333 349813 350523
## [3,] 114932 227704 298120 345542 367760 377999 383611 385224 NA
## [4,] 114452 227761 301072 340669 359979 369248 373325 NA NA
## [5,] 115597 243611 315215 354490 372376 382738 NA NA NA
## [6,] 127760 259416 326975 365780 386725 NA NA NA NA
## [7,] 135616 262294 327086 367357 NA NA NA NA NA
## [8,] 127177 244249 317972 NA NA NA NA NA NA
## [9,] 128631 246803 NA NA NA NA NA NA NA
## [10,] 126288 NA NA NA NA NA NA NA NA
## [,10]
## [1,] 353584
## [2,] NA
## [3,] NA
## [4,] NA
## [5,] NA
## [6,] NA
## [7,] NA
## [8,] NA
## [9,] NA
## [10,] NA
This is my incurred claims data triangle:
auto$PersonalAutoIncurred
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
## [1,] 325423 336426 346061 347726 350995 353598 354797 355025 354986
## [2,] 323627 339267 344507 349295 351038 351583 352050 352231 352193
## [3,] 358410 386330 385684 384699 387678 387954 388540 389436 NA
## [4,] 405319 396641 391833 384819 380914 380163 379706 NA NA
## [5,] 434065 429311 422181 409322 394154 392802 NA NA NA
## [6,] 417178 422307 413486 406711 406503 NA NA NA NA
## [7,] 398929 398787 398020 400540 NA NA NA NA NA
## [8,] 378754 361097 369328 NA NA NA NA NA NA
## [9,] 351081 335507 NA NA NA NA NA NA NA
## [10,] 329236 NA NA NA NA NA NA NA NA
## [,10]
## [1,] 355363
## [2,] NA
## [3,] NA
## [4,] NA
## [5,] NA
## [6,] NA
## [7,] NA
## [8,] NA
## [9,] NA
## [10,] NA
I decided to use the Munich Chain Ladder method. I implemented that method like this:
a <- MunichChainLadder(auto$PersonalAutoPaid, auto$PersonalAutoIncurred)
and my results were this:
summary(a)
## $ByOrigin
## Latest Paid Latest Incurred Latest P/I Ratio Ult. Paid Ult. Incurred
## 1 353584 355363 0.9949939 353584.0 355363.0
## 2 350523 352193 0.9952583 350983.1 352566.6
## 3 385224 389436 0.9891844 390707.8 389800.8
## 4 373325 379706 0.9831949 381382.4 380488.4
## 5 382738 392802 0.9743789 394381.1 394213.4
## 6 386725 406503 0.9513460 408595.7 408483.7
## 7 367357 400540 0.9171543 402019.5 402220.0
## 8 317972 369328 0.8609475 371457.5 372148.2
## 9 246803 335507 0.7356121 341785.5 342901.1
## 10 126288 329236 0.3835790 338886.7 339940.7
## Ult. P/I Ratio
## 1 0.9949939
## 2 0.9955087
## 3 1.0023269
## 4 1.0023496
## 5 1.0004253
## 6 1.0002742
## 7 0.9995014
## 8 0.9981440
## 9 0.9967466
## 10 0.9968997
##
## $Totals
## Paid Incurred P/I Ratio
## Latest: 3290539 3710614 0.8867910
## Ultimate: 3733783 3738126 0.9988383
That resulted in the following estimates of unpaid claims:
paste0("Estimated Ultimate Claims = ", comma_format(digits = 0)(summary(a)$Totals[2,1]))
## [1] "Estimated Ultimate Claims = 3,733,783"
paste0("Paid Claims = ", comma_format(digits = 0)(summary(a)$Totals[1,1]))
## [1] "Paid Claims = 3,290,539"
paste0("Estimated Unpaid Claims = ", comma_format(digits = 0)(summary(a)$Totals[2,1] - summary(a)$Totals[1,1]))
## [1] "Estimated Unpaid Claims = 443,244"
paste0("Estimated Ultimate Claims = ", comma_format(digits = 0)(summary(a)$Totals[2,2]))
## [1] "Estimated Ultimate Claims = 3,738,126"
paste0("Paid Claims = ", comma_format(digits = 0)(summary(a)$Totals[1,1]))
## [1] "Paid Claims = 3,290,539"
paste0("Estimated Unpaid Claims = ", comma_format(digits = 0)(summary(a)$Totals[2,2] - summary(a)$Totals[1,1]))
## [1] "Estimated Unpaid Claims = 447,587"
I selected this estimate:
paste0("Estimated Unpaid Claims = ", comma_format(digits = 0)(mean(summary(a)$Totals[2,2] - summary(a)$Totals[1,1],
summary(a)$Totals[2,1] - summary(a)$Totals[1,1])))
## [1] "Estimated Unpaid Claims = 447,587"
These are the tools I used.
citation()[[1]]$textVersion
## [1] "R Core Team (2015). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. URL http://www.R-project.org/."
citation("ChainLadder")[[1]]$textVersion
## [1] "Markus Gesmann, Daniel Murphy, Wayne Zhang, Alessandro Carrato, Giuseppe Crupi and Mario Wuthrich (2015). ChainLadder: Statistical Methods and Models for Claims Reserving in General\nInsurance. R package version 0.2.0. http://CRAN.R-project.org/package=ChainLadder"
citation("scales")[[1]]$textVersion
## [1] "Hadley Wickham (2014). scales: Scale functions for graphics.. R package version 0.2.4. http://CRAN.R-project.org/package=scales"
Do you think I complied with ASOP 41? I submit that my report:
state(s) the actuarial findings
identif(ies) the methods, procedures, assumptions, and data used by the me
However:
Does it do so with sufficient clarity that another actuary qualified in the same practice area could make an objective appraisal of the reasonableness of the actuary’s work as presented in the actuarial report.?
Does it matter that I have documented the report in an HTML file?
Do I really need to echo the R code? Or is that my intellectual property (much as we might consider the formulas in an excel workbook?
I would be interested in your thoughts.